diff --git a/src/account.cpp b/src/account.cpp index 7f69976d3142b8b1bb5e9ec2553fcf69679bdfa4..429346e152357c874d8f6a96eb1e8f8f2506a119 100644 --- a/src/account.cpp +++ b/src/account.cpp @@ -376,15 +376,6 @@ Account::getVolatileAccountDetails() const {DRing::Account::VolatileProperties::ACTIVE, active_ ? TRUE_STR : FALSE_STR}}; } -void -Account::onIsComposing(const std::string& conversationId, const std::string& peer, bool isComposing) -{ - emitSignal<DRing::ConfigurationSignal::ComposingStatusChanged>(accountID_, - conversationId, - peer, - isComposing ? 1 : 0); -} - bool Account::hasActiveCodec(MediaType mediaType) const { diff --git a/src/account.h b/src/account.h index 6b596c93348ed3b3a67d6ba5c2c669ec2ab474a4..f7b2e5be477fec42cf2d7c034a13e556269dbb79 100644 --- a/src/account.h +++ b/src/account.h @@ -60,7 +60,7 @@ class Value; } namespace jami { -static constexpr uint64_t DRING_ID_MAX_VAL = 9007199254740992; +static constexpr uint64_t JAMI_ID_MAX_VAL = 9007199254740992; namespace upnp { class Controller; @@ -175,16 +175,8 @@ public: return 0; } - virtual void sendInstantMessage(const std::string& /*convId*/, - const std::map<std::string, std::string>& /*msg*/) - {} - virtual void setIsComposing(const std::string& /*conversationUri*/, bool /*isWriting*/) {}; - virtual void onIsComposing(const std::string& /*conversationId*/, - const std::string& /*peer*/, - bool /*isWriting*/); - virtual bool setMessageDisplayed(const std::string& /*conversationUri*/, const std::string& /*messageId*/, int /*status*/) @@ -323,20 +315,11 @@ public: */ virtual void connectivityChanged() {}; - virtual void onNewGitCommit(const std::string& /*peer*/, - const std::string& /*deviceId*/, - const std::string& /*conversationId*/, - const std::string& /*commitId*/) {}; - - virtual void onMessageDisplayed(const std::string& /*peer*/, - const std::string& /*conversationId*/, - const std::string& /*interactionId*/) {}; - - // Invites - virtual void onConversationRequest(const std::string& /*from*/, const Json::Value&) {}; - virtual void onNeedConversationRequest(const std::string& /*from*/, - const std::string& /*conversationId*/) {}; - virtual void checkIfRemoveForCompat(const std::string& /*peerUri*/) {}; + virtual bool handleMessage(const std::string& /*from*/, + const std::pair<std::string, std::string>& /*message*/) + { + return false; + }; /** * Helper function used to load the default codec order from the codec factory diff --git a/src/call.cpp b/src/call.cpp index a151c0bff9c567922795add5a10992b65c442736..8d8ef5e175d9b72938a1568e13b63326fff00d6a 100644 --- a/src/call.cpp +++ b/src/call.cpp @@ -125,8 +125,8 @@ Call::Call(const std::shared_ptr<Account>& account, duration_start_ = clock::now(); else if (cnx_state == ConnectionState::DISCONNECTED && call_state == CallState::OVER) { if (auto jamiAccount = std::dynamic_pointer_cast<JamiAccount>(getAccount().lock())) { - jamiAccount->addCallHistoryMessage(getPeerNumber(), getCallDuration().count()); - + jamiAccount->convModule()->addCallHistoryMessage(getPeerNumber(), + getCallDuration().count()); monitor(); } } diff --git a/src/call_factory.cpp b/src/call_factory.cpp index a846e42336ef787dc159711fcae4df3a46e6f8fd..f2e532823f7dfe51a9319e768268739a025d1979 100644 --- a/src/call_factory.cpp +++ b/src/call_factory.cpp @@ -34,7 +34,7 @@ CallFactory::getNewCallID() const std::string random_id; do { random_id = std::to_string( - std::uniform_int_distribution<uint64_t>(1, DRING_ID_MAX_VAL)(rand_)); + std::uniform_int_distribution<uint64_t>(1, JAMI_ID_MAX_VAL)(rand_)); } while (hasCall(random_id)); return random_id; } diff --git a/src/client/conversation_interface.cpp b/src/client/conversation_interface.cpp index b420d703fe6ed94402e34df893481fb0d0179747..a686d74a438fb52d1daa463831fae1f72ef4c83d 100644 --- a/src/client/conversation_interface.cpp +++ b/src/client/conversation_interface.cpp @@ -31,6 +31,7 @@ #include "logger.h" #include "manager.h" #include "jamidht/jamiaccount.h" +#include "jamidht/conversation_module.h" namespace DRing { @@ -38,7 +39,8 @@ std::string startConversation(const std::string& accountId) { if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) - return acc->startConversation(); + if (auto convModule = acc->convModule()) + return convModule->startConversation(); return {}; } @@ -46,21 +48,24 @@ void acceptConversationRequest(const std::string& accountId, const std::string& conversationId) { if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) - acc->acceptConversationRequest(conversationId); + if (auto convModule = acc->convModule()) + convModule->acceptConversationRequest(conversationId); } void declineConversationRequest(const std::string& accountId, const std::string& conversationId) { if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) - acc->declineConversationRequest(conversationId); + if (auto convModule = acc->convModule()) + convModule->declineConversationRequest(conversationId); } bool removeConversation(const std::string& accountId, const std::string& conversationId) { if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) - return acc->removeConversation(conversationId); + if (auto convModule = acc->convModule()) + return convModule->removeConversation(conversationId); return false; } @@ -68,7 +73,8 @@ std::vector<std::string> getConversations(const std::string& accountId) { if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) - return acc->getConversations(); + if (auto convModule = acc->convModule()) + return convModule->getConversations(); return {}; } @@ -76,7 +82,8 @@ std::vector<std::map<std::string, std::string>> getConversationRequests(const std::string& accountId) { if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) - return acc->getConversationRequests(); + if (auto convModule = acc->convModule()) + return convModule->getConversationRequests(); return {}; } @@ -86,14 +93,16 @@ updateConversationInfos(const std::string& accountId, const std::map<std::string, std::string>& infos) { if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) - acc->updateConversationInfos(conversationId, infos); + if (auto convModule = acc->convModule()) + convModule->updateConversationInfos(conversationId, infos); } std::map<std::string, std::string> conversationInfos(const std::string& accountId, const std::string& conversationId) { if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) - return acc->conversationInfos(conversationId); + if (auto convModule = acc->convModule()) + return convModule->conversationInfos(conversationId); return {}; } @@ -104,7 +113,8 @@ addConversationMember(const std::string& accountId, const std::string& contactUri) { if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) - acc->addConversationMember(conversationId, contactUri); + if (auto convModule = acc->convModule()) + convModule->addConversationMember(conversationId, contactUri); } void @@ -113,14 +123,16 @@ removeConversationMember(const std::string& accountId, const std::string& contactUri) { if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) - acc->removeConversationMember(conversationId, contactUri); + if (auto convModule = acc->convModule()) + convModule->removeConversationMember(conversationId, contactUri); } std::vector<std::map<std::string, std::string>> getConversationMembers(const std::string& accountId, const std::string& conversationId) { if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) - return acc->getConversationMembers(conversationId); + if (auto convModule = acc->convModule()) + return convModule->getConversationMembers(conversationId); return {}; } @@ -132,7 +144,8 @@ sendMessage(const std::string& accountId, const std::string& parent) { if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) - acc->sendMessage(conversationId, message, parent); + if (auto convModule = acc->convModule()) + convModule->sendMessage(conversationId, message, parent); } uint32_t @@ -142,7 +155,8 @@ loadConversationMessages(const std::string& accountId, size_t n) { if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) - return acc->loadConversationMessages(conversationId, fromMessage, n); + if (auto convModule = acc->convModule()) + return convModule->loadConversationMessages(conversationId, fromMessage, n); return 0; } @@ -154,7 +168,8 @@ countInteractions(const std::string& accountId, const std::string& authorUri) { if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) - return acc->countInteractions(conversationId, toId, fromId, authorUri); + if (auto convModule = acc->convModule()) + return convModule->countInteractions(conversationId, toId, fromId, authorUri); return 0; } diff --git a/src/client/datatransfer.cpp b/src/client/datatransfer.cpp index 7383d6e718d75eb77af52fc445c0ba79df6ee3a1..a8e6b7934b690c67ffcb82a15de8b032f0345213 100644 --- a/src/client/datatransfer.cpp +++ b/src/client/datatransfer.cpp @@ -83,7 +83,8 @@ downloadFile(const std::string& accountId, const std::string& path) noexcept { if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId)) - return acc->downloadFile(conversationId, interactionId, fileId, path); + if (auto convModule = acc->convModule()) + return convModule->downloadFile(conversationId, interactionId, fileId, path); return {}; } diff --git a/src/data_transfer.cpp b/src/data_transfer.cpp index d41eabfc59374686791d43cade18396cb1695db5..f0fa11f8b65c5d4dcfc806d621ff934f3e9d18ae 100644 --- a/src/data_transfer.cpp +++ b/src/data_transfer.cpp @@ -52,7 +52,7 @@ DRing::DataTransferId generateUID() { thread_local dht::crypto::random_device rd; - return std::uniform_int_distribution<DRing::DataTransferId> {1, DRING_ID_MAX_VAL}(rd); + return std::uniform_int_distribution<DRing::DataTransferId> {1, JAMI_ID_MAX_VAL}(rd); } constexpr const uint32_t MAX_BUFFER_SIZE {65534}; /* Channeled max packet size */ diff --git a/src/im/message_engine.cpp b/src/im/message_engine.cpp index 086797a34d01c918bb9d68aa79ecf2a846952234..42585f3e4887694828e84adba7b588e37b7c9fec 100644 --- a/src/im/message_engine.cpp +++ b/src/im/message_engine.cpp @@ -52,7 +52,7 @@ MessageEngine::sendMessage(const std::string& to, const std::map<std::string, st std::lock_guard<std::mutex> lock(messagesMutex_); auto& peerMessages = messages_[to]; do { - token = std::uniform_int_distribution<MessageToken> {1, DRING_ID_MAX_VAL}(account_.rand); + token = std::uniform_int_distribution<MessageToken> {1, JAMI_ID_MAX_VAL}(account_.rand); } while (peerMessages.find(token) != peerMessages.end()); auto m = peerMessages.emplace(token, Message {}); m.first->second.to = to; diff --git a/src/jamidht/CMakeLists.txt b/src/jamidht/CMakeLists.txt index 67e9b4c3eabd77e587fc676ba02bdb7d0659fc5a..47209bd934c0badb2b9adc5ec3e5597ab8084b7e 100644 --- a/src/jamidht/CMakeLists.txt +++ b/src/jamidht/CMakeLists.txt @@ -32,6 +32,11 @@ list (APPEND Source_Files__jamidht "${CMAKE_CURRENT_SOURCE_DIR}/jamiaccount.h" "${CMAKE_CURRENT_SOURCE_DIR}/multiplexed_socket.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/multiplexed_socket.h" + "${CMAKE_CURRENT_SOURCE_DIR}/channel_handler.h" + "${CMAKE_CURRENT_SOURCE_DIR}/conversation_channel_handler.h" + "${CMAKE_CURRENT_SOURCE_DIR}/conversation_channel_handler.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/conversation_module.h" + "${CMAKE_CURRENT_SOURCE_DIR}/conversation_module.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/namedirectory.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/namedirectory.h" "${CMAKE_CURRENT_SOURCE_DIR}/p2p.cpp" diff --git a/src/jamidht/Makefile.am b/src/jamidht/Makefile.am index 48c636170347db17a9f911568ff1e6dde1aa9001..b0ee274bd173f18f35ce8498ff503aeb8f5d9ac9 100644 --- a/src/jamidht/Makefile.am +++ b/src/jamidht/Makefile.am @@ -25,6 +25,11 @@ libringacc_la_SOURCES = \ conversationrepository.cpp \ gitserver.h \ gitserver.cpp \ + channel_handler.h \ + conversation_channel_handler.h \ + conversation_channel_handler.cpp \ + conversation_module.h \ + conversation_module.cpp \ multiplexed_socket.h \ multiplexed_socket.cpp \ accountarchive.cpp \ diff --git a/src/jamidht/account_manager.cpp b/src/jamidht/account_manager.cpp index 8796c1f3c4ab3a0fdaf202516e764b11669bed8f..cf3c1178a0160f2d1c45f5e245aca8a1ee2acf48 100644 --- a/src/jamidht/account_manager.cpp +++ b/src/jamidht/account_manager.cpp @@ -461,107 +461,6 @@ AccountManager::getContacts() const return ret; } -void -AccountManager::setConversations(const std::map<std::string, ConvInfo>& newConv) -{ - if (info_) { - info_->conversations = newConv; - saveConvInfos(); - } -} - -void -AccountManager::setConversationMembers(const std::string& convId, - const std::vector<std::string>& members) -{ - if (info_) { - auto convIt = info_->conversations.find(convId); - if (convIt != info_->conversations.end()) { - convIt->second.members = members; - saveConvInfos(); - } - } -} - -void -AccountManager::saveConvInfos() const -{ - if (!info_) - return; - std::ofstream file(info_->contacts->path() + DIR_SEPARATOR_STR "convInfo", - std::ios::trunc | std::ios::binary); - msgpack::pack(file, info_->conversations); -} - -void -AccountManager::addConversation(const ConvInfo& info) -{ - if (info_) { - info_->conversations[info.id] = info; - saveConvInfos(); - } -} - -void -AccountManager::setConversationsRequests(const std::map<std::string, ConversationRequest>& newConvReq) -{ - if (info_) { - std::lock_guard<std::mutex> lk(conversationsRequestsMtx); - info_->conversationsRequests = newConvReq; - saveConvRequests(); - } -} - -void -AccountManager::saveConvRequests() const -{ - if (!info_) - return; - std::ofstream file(info_->contacts->path() + DIR_SEPARATOR_STR "convRequests", - std::ios::trunc | std::ios::binary); - msgpack::pack(file, info_->conversationsRequests); -} - -std::optional<ConversationRequest> -AccountManager::getRequest(const std::string& id) const -{ - if (info_) { - std::lock_guard<std::mutex> lk(conversationsRequestsMtx); - auto it = info_->conversationsRequests.find(id); - if (it != info_->conversationsRequests.end()) - return it->second; - } - return std::nullopt; -} - -bool -AccountManager::addConversationRequest(const std::string& id, const ConversationRequest& req) -{ - if (info_) { - std::lock_guard<std::mutex> lk(conversationsRequestsMtx); - auto it = info_->conversationsRequests.find(id); - if (it != info_->conversationsRequests.end()) { - // Check if updated - if (req == it->second) - return false; - } - info_->conversationsRequests[id] = req; - saveConvRequests(); - return true; - } - return false; -} - -void -AccountManager::rmConversationRequest(const std::string& id) -{ - if (info_) { - std::lock_guard<std::mutex> lk(conversationsRequestsMtx); - info_->conversationsRequests.erase(id); - saveConvRequests(); - } -} - /** Obtain details about one account contact in serializable form. */ std::map<std::string, std::string> AccountManager::getContactDetails(const std::string& uri) const @@ -583,12 +482,14 @@ AccountManager::findCertificate( if (cb) cb(cert); } else { - dht_->findCertificate(h, [cb = std::move(cb)](const std::shared_ptr<dht::crypto::Certificate>& crt) { - if (crt) - tls::CertificateStore::instance().pinCertificate(crt); - if (cb) - cb(crt); - }); + dht_->findCertificate(h, + [cb = std::move(cb)]( + const std::shared_ptr<dht::crypto::Certificate>& crt) { + if (crt) + tls::CertificateStore::instance().pinCertificate(crt); + if (cb) + cb(crt); + }); } return true; } diff --git a/src/jamidht/account_manager.h b/src/jamidht/account_manager.h index 1fe1fc379f606f6167dd849fa7a0e9affebc607e..a74847ab547e7302080c937d3ed109e4a5389525 100644 --- a/src/jamidht/account_manager.h +++ b/src/jamidht/account_manager.h @@ -47,8 +47,6 @@ struct AccountInfo { dht::crypto::Identity identity; std::unique_ptr<ContactList> contacts; - std::map<std::string, ConvInfo> conversations; - std::map<std::string, ConversationRequest> conversationsRequests; std::string accountId; std::string deviceId; std::shared_ptr<dht::crypto::PublicKey> devicePk; @@ -223,18 +221,6 @@ public: void removeContactConversation(const std::string& uri); // for non swarm contacts std::vector<std::map<std::string, std::string>> getContacts() const; - // Conversations - void saveConvInfos() const; - void saveConvRequests() const; - void setConversations(const std::map<std::string, ConvInfo>& newConv); - void setConversationMembers(const std::string& convId, const std::vector<std::string>& members); - void addConversation(const ConvInfo& info); - void setConversationsRequests(const std::map<std::string, ConversationRequest>& newConvReq); - std::optional<ConversationRequest> getRequest(const std::string& id) const; - bool addConversationRequest(const std::string& id, const ConversationRequest& req); - void rmConversationRequest(const std::string& id); - mutable std::mutex conversationsRequestsMtx; - /** Obtain details about one account contact in serializable form. */ std::map<std::string, std::string> getContactDetails(const std::string& uri) const; diff --git a/src/jamidht/archive_account_manager.cpp b/src/jamidht/archive_account_manager.cpp index 27cb4b8cae9f61ddb4a43240df6240aabe2280c4..6f44f57c9a86b1a440c0467bf857932f41d7f1ca 100644 --- a/src/jamidht/archive_account_manager.cpp +++ b/src/jamidht/archive_account_manager.cpp @@ -23,6 +23,7 @@ #include "base64.h" #include "jami/account_const.h" #include "account_schema.h" +#include "jamidht/conversation_module.h" #include <opendht/dhtrunner.h> #include <opendht/thread_pool.h> @@ -349,13 +350,11 @@ ArchiveAccountManager::onArchiveLoaded(AuthContext& ctx, AccountArchive&& a) info->contacts = std::make_unique<ContactList>(a.id.second, path_, onChange_); info->contacts->setContacts(a.contacts); info->contacts->foundAccountDevice(deviceCertificate, ctx.deviceName, clock::now()); - info->conversations = a.conversations; - info->conversationsRequests = a.conversationsRequests; info->ethAccount = ethAccount; info->announce = std::move(receipt.second); + ConversationModule::saveConvInfosToPath(path_, a.conversations); + ConversationModule::saveConvRequestsToPath(path_, a.conversationsRequests); info_ = std::move(info); - saveConvInfos(); - saveConvRequests(); JAMI_WARN("[Auth] created new device: %s", info_->deviceId.c_str()); ctx.onSuccess(*info_, @@ -584,8 +583,9 @@ ArchiveAccountManager::updateArchive(AccountArchive& archive) const archive.config[it.first] = it.second; } archive.contacts = info_->contacts->getContacts(); - archive.conversations = info_->conversations; - archive.conversationsRequests = info_->conversationsRequests; + // Note we do not know accountID_ here, use path + archive.conversations = ConversationModule::convInfosFromPath(path_); + archive.conversationsRequests = ConversationModule::convRequestsFromPath(path_); } void diff --git a/src/jamidht/channel_handler.h b/src/jamidht/channel_handler.h new file mode 100644 index 0000000000000000000000000000000000000000..a0c448f059298e42bdfeb30fb6830fddb4b6da3f --- /dev/null +++ b/src/jamidht/channel_handler.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2021 Savoir-faire Linux Inc. + * + * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include "jamidht/multiplexed_socket.h" + +namespace jami { + +using ConnectCb = std::function<void(std::shared_ptr<ChannelSocket>, const DeviceId&)>; + +/** + * A Channel handler is used to make the link between JamiAccount and ConnectionManager + * Its role is to manage channels for a protocol (git/sip/etc) + */ +class ChannelHandlerInterface +{ +public: + virtual ~ChannelHandlerInterface() {}; + + /** + * Ask for a new channel + * @param deviceId The device to connect + * @param name The name of the channel + * @param cb The callback to call when connected (can be immediate if already connected) + */ + virtual void connect(const DeviceId& deviceId, const std::string& name, ConnectCb&& cb) + = 0; + + /** + * Determine if we accept or not the request. Called when ConnectionManager receives a request + * @param deviceId Device who asked + * @param name The name of the channel + * @return if we accept or not + */ + virtual bool onRequest(const DeviceId& deviceId, const std::string& name) = 0; + + /** + * Called when ConnectionManager has a new channel ready + * @param deviceId Related device + * @param name The name of the channel + * @param channel Channel to handle + */ + virtual void onReady(const DeviceId& deviceId, + const std::string& name, + std::shared_ptr<ChannelSocket> channel) + = 0; +}; + +} // namespace jami \ No newline at end of file diff --git a/src/jamidht/connectionmanager.cpp b/src/jamidht/connectionmanager.cpp index 24a10502c0904b7845aa298052306a5492683d7d..51b64b4aed2aff7a8bb4971b2f18ba1f9af87997 100644 --- a/src/jamidht/connectionmanager.cpp +++ b/src/jamidht/connectionmanager.cpp @@ -440,7 +440,7 @@ ConnectionManager::Impl::connectDevice(const std::shared_ptr<dht::crypto::Certif dht::Value::Id vid; auto tentatives = 0; do { - vid = ValueIdDist(1, DRING_ID_MAX_VAL)(sthis->account.rand); + vid = ValueIdDist(1, JAMI_ID_MAX_VAL)(sthis->account.rand); --tentatives; } while (sthis->getPendingCallbacks(deviceId, vid).size() != 0 && tentatives != MAX_TENTATIVES); diff --git a/src/jamidht/conversation_channel_handler.cpp b/src/jamidht/conversation_channel_handler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4d7edbd081ed61fcbfc2eab6aff4ccfb9bc3cd17 --- /dev/null +++ b/src/jamidht/conversation_channel_handler.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2021 Savoir-faire Linux Inc. + * + * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "jamidht/conversation_channel_handler.h" + +namespace jami { + +ConversationChannelHandler::ConversationChannelHandler(std::weak_ptr<JamiAccount>&& acc, + ConnectionManager& cm) + : ChannelHandlerInterface() + , account_(acc) + , connectionManager_(cm) +{} + +ConversationChannelHandler::~ConversationChannelHandler() {} + +void +ConversationChannelHandler::connect(const DeviceId& deviceId, + const std::string& channelName, + ConnectCb&& cb) +{ + connectionManager_.connectDevice(deviceId, + "git://" + deviceId.toString() + "/" + channelName, + [cb = std::move(cb)](std::shared_ptr<ChannelSocket> socket, + const DeviceId& dev) { + if (cb) + cb(socket, dev); + }); +} + +bool +ConversationChannelHandler::onRequest(const DeviceId&, const std::string& name) +{ + // Pre-check before acceptance. Sometimes, another device can start a conversation + // which is still not synced. So, here we decline channel's request in this case + // to avoid the other device to want to sync with us if we are not ready. + auto sep = name.find_last_of('/'); + auto conversationId = name.substr(sep + 1); + auto remoteDevice = name.substr(6, sep - 6); + if (auto acc = account_.lock()) + if (auto convModule = acc->convModule()) { + auto res = !convModule->isBannedDevice(conversationId, remoteDevice); + return res; + } + return false; +} + +void +ConversationChannelHandler::onReady(const DeviceId&, + const std::string&, + std::shared_ptr<ChannelSocket>) +{} + +} // namespace jami \ No newline at end of file diff --git a/src/jamidht/conversation_channel_handler.h b/src/jamidht/conversation_channel_handler.h new file mode 100644 index 0000000000000000000000000000000000000000..455bc0790a8d760b8786ec00fe0f6dbca5459104 --- /dev/null +++ b/src/jamidht/conversation_channel_handler.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2021 Savoir-faire Linux Inc. + * + * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include "jamidht/channel_handler.h" +#include "jamidht/connectionmanager.h" +#include "jamidht/jamiaccount.h" + +namespace jami { + +/** + * Manages Conversation's channels + */ +class ConversationChannelHandler : public ChannelHandlerInterface +{ +public: + ConversationChannelHandler(std::weak_ptr<JamiAccount>&& acc, ConnectionManager& cm); + ~ConversationChannelHandler(); + + /** + * Ask for a new git channel + * @param deviceId The device to connect + * @param name The name of the channel + * @param cb The callback to call when connected (can be immediate if already connected) + */ + void connect(const DeviceId& deviceId, const std::string& name, ConnectCb&& cb) override; + + /** + * Determine if we accept or not the git request + * @param deviceId device who asked + * @param name name asked + * @return if the channel is for a valid conversation and device not banned + */ + bool onRequest(const DeviceId& deviceId, const std::string& name) override; + + /** + * TODO, this needs to extract gitservers from JamiAccount + */ + void onReady(const DeviceId& deviceId, + const std::string& name, + std::shared_ptr<ChannelSocket> channel) override; + +private: + std::weak_ptr<JamiAccount> account_; + ConnectionManager& connectionManager_; +}; + +} // namespace jami \ No newline at end of file diff --git a/src/jamidht/conversation_module.cpp b/src/jamidht/conversation_module.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a863e3076f05297815bfb54dfc30cf48722eceae --- /dev/null +++ b/src/jamidht/conversation_module.cpp @@ -0,0 +1,1479 @@ +/* + * Copyright (C) 2021 Savoir-faire Linux Inc. + * + * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "conversation_module.h" + +#include <fstream> + +#include <opendht/thread_pool.h> + +#include "account_const.h" +#include "client/ring_signal.h" +#include "fileutils.h" +#include "jamidht/account_manager.h" +#include "jamidht/jamiaccount.h" +#include "manager.h" +#include "vcard.h" + +namespace jami { + +struct PendingConversationFetch +{ + bool ready {false}; + bool cloning {false}; + std::string deviceId {}; + std::set<std::string> connectingTo {}; +}; + +class ConversationModule::Impl : public std::enable_shared_from_this<Impl> +{ +public: + Impl(std::weak_ptr<JamiAccount>&& account, + NeedsSyncingCb&& needsSyncingCb, + SengMsgCb&& sendMsgCb, + NeedSocketCb&& onNeedSocket); + + // Retrieving recent commits + /** + * Clone a conversation (initial) from device + * @param deviceId + * @param convId + */ + void cloneConversation(const std::string& deviceId, + const std::string& peer, + const std::string& convId); + + /** + * Pull remote device + * @param peer Contact URI + * @param deviceId Contact's device + * @param conversationId + * @param commitId (optional) + */ + void fetchNewCommits(const std::string& peer, + const std::string& deviceId, + const std::string& conversationId, + const std::string& commitId = ""); + /** + * Handle events to receive new commits + */ + void checkConversationsEvents(); + bool handlePendingConversations(); + + // Requests + std::optional<ConversationRequest> getRequest(const std::string& id) const; + + // Conversations + /** + * Remove a repository and all files + * @param convId + * @param sync If we send an update to other account's devices + * @param force True if ignore the removing flag + */ + void removeRepository(const std::string& convId, bool sync, bool force = false); + + /** + * Send a message notification to all members + * @param conversation + * @param commit + * @param sync If we send an update to other account's devices + */ + void sendMessageNotification(const std::string& conversationId, + const std::string& commitId, + bool sync); + void sendMessageNotification(const Conversation& conversation, + const std::string& commitId, + bool sync); + + /** + * @return if a convId is a valid conversation (repository cloned & usable) + */ + bool isConversation(const std::string& convId) const + { + std::lock_guard<std::mutex> lk(conversationsMtx_); + return conversations_.find(convId) != conversations_.end(); + } + + std::weak_ptr<JamiAccount> account_; + NeedsSyncingCb needsSyncingCb_; + SengMsgCb sendMsgCb_; + NeedSocketCb onNeedSocket_; + + std::string accountId_ {}; + std::string deviceId_ {}; + std::string username_ {}; + + // Requests + mutable std::mutex conversationsRequestsMtx_; + std::map<std::string, ConversationRequest> conversationsRequests_; + + // Conversations + mutable std::mutex conversationsMtx_ {}; + std::map<std::string, std::shared_ptr<Conversation>> conversations_; + std::mutex pendingConversationsFetchMtx_ {}; + std::map<std::string, PendingConversationFetch> pendingConversationsFetch_; + + bool startFetch(const std::string& convId, const std::string& deviceId) + { + std::lock_guard<std::mutex> lk(pendingConversationsFetchMtx_); + auto it = pendingConversationsFetch_.find(convId); + if (it == pendingConversationsFetch_.end()) { + PendingConversationFetch pf; + pf.connectingTo.insert(deviceId); + pendingConversationsFetch_[convId] = std::move(pf); + return true; + } + auto& pf = it->second; + if (pf.ready) + return false; // Already doing stuff + if (pf.connectingTo.find(deviceId) != pf.connectingTo.end()) + return false; // Already connecting to this device + pf.connectingTo.insert(deviceId); + return true; + } + + // The following informations are stored on the disk + mutable std::mutex convInfosMtx_; // Note, should be locked after conversationsMtx_ if needed + std::map<std::string, ConvInfo> convInfos_; + // The following methods modify what is stored on the disk + /** + * @note convInfosMtx_ should be locked + */ + void saveConvInfos() const { ConversationModule::saveConvInfos(accountId_, convInfos_); } + /** + * @note conversationsRequestsMtx_ should be locked + */ + void saveConvRequests() const + { + ConversationModule::saveConvRequests(accountId_, conversationsRequests_); + } + bool addConversationRequest(const std::string& id, const ConversationRequest& req) + { + std::lock_guard<std::mutex> lk(conversationsRequestsMtx_); + auto it = conversationsRequests_.find(id); + if (it != conversationsRequests_.end()) { + // Check if updated + if (req == it->second) + return false; + } + conversationsRequests_[id] = req; + saveConvRequests(); + return true; + } + void rmConversationRequest(const std::string& id) + { + std::lock_guard<std::mutex> lk(conversationsRequestsMtx_); + conversationsRequests_.erase(id); + saveConvRequests(); + } + + // Receiving new commits + std::shared_ptr<RepeatedTask> conversationsEventHandler {}; + + std::weak_ptr<Impl> weak() { return std::static_pointer_cast<Impl>(shared_from_this()); } +}; + +ConversationModule::Impl::Impl(std::weak_ptr<JamiAccount>&& account, + NeedsSyncingCb&& needsSyncingCb, + SengMsgCb&& sendMsgCb, + NeedSocketCb&& onNeedSocket) + : account_(account) + , needsSyncingCb_(needsSyncingCb) + , sendMsgCb_(sendMsgCb) + , onNeedSocket_(onNeedSocket) +{ + if (auto shared = account.lock()) { + accountId_ = shared->getAccountID(); + deviceId_ = shared->currentDeviceId(); + if (const auto* accm = shared->accountManager()) + if (const auto* info = accm->getInfo()) + username_ = info->accountId; + } + conversationsRequests_ = convRequests(accountId_); +} + +void +ConversationModule::Impl::cloneConversation(const std::string& deviceId, + const std::string&, + const std::string& convId) +{ + JAMI_DBG("[Account %s] Clone conversation on device %s", accountId_.c_str(), deviceId.c_str()); + + if (!isConversation(convId)) { + // Note: here we don't return and connect to all members + // the first that will successfully connect will be used for + // cloning. + // This avoid the case when we try to clone from convInfos + sync message + // at the same time. + if (!startFetch(convId, deviceId)) { + JAMI_WARN("[Account %s] Already fetching %s", accountId_.c_str(), convId.c_str()); + return; + } + onNeedSocket_(convId, deviceId, [=](const auto& channel) { + auto acc = account_.lock(); + std::unique_lock<std::mutex> lk(pendingConversationsFetchMtx_); + auto& pending = pendingConversationsFetch_[convId]; + if (channel && !pending.ready) { + pending.ready = true; + pending.deviceId = channel->deviceId().toString(); + lk.unlock(); + acc->addGitSocket(channel->deviceId(), convId, channel); + checkConversationsEvents(); + return true; + } + return false; + }); + + JAMI_INFO("[Account %s] New conversation detected: %s. Ask device %s to clone it", + accountId_.c_str(), + convId.c_str(), + deviceId.c_str()); + } else { + JAMI_INFO("[Account %s] Already have conversation %s", accountId_.c_str(), convId.c_str()); + } +} + +void +ConversationModule::Impl::fetchNewCommits(const std::string& peer, + const std::string& deviceId, + const std::string& conversationId, + const std::string& commitId) +{ + JAMI_DBG("[Account %s] fetch commits for peer %s on device %s", + accountId_.c_str(), + peer.c_str(), + deviceId.c_str()); + + std::unique_lock<std::mutex> lk(conversationsMtx_); + auto conversation = conversations_.find(conversationId); + if (conversation != conversations_.end() && conversation->second) { + if (!conversation->second->isMember(peer, true)) { + JAMI_WARN("[Account %s] %s is not a member of %s", + accountId_.c_str(), + peer.c_str(), + conversationId.c_str()); + return; + } + if (conversation->second->isBanned(deviceId)) { + JAMI_WARN("[Account %s] %s is a banned device in conversation %s", + accountId_.c_str(), + deviceId.c_str(), + conversationId.c_str()); + return; + } + + // Retrieve current last message + auto lastMessageId = conversation->second->lastCommitId(); + if (lastMessageId.empty()) { + JAMI_ERR("[Account %s] No message detected. This is a bug", accountId_.c_str()); + return; + } + + if (!startFetch(conversationId, deviceId)) { + JAMI_WARN("[Account %s] Already fetching %s", + accountId_.c_str(), + conversationId.c_str()); + return; + } + onNeedSocket_(conversationId, + deviceId, + [this, + conversationId = std::move(conversationId), + peer = std::move(peer), + deviceId = std::move(deviceId), + commitId = std::move(commitId)](const auto& channel) { + auto conversation = conversations_.find(conversationId); + auto acc = account_.lock(); + if (!channel || !acc || conversation == conversations_.end() + || !conversation->second) { + std::lock_guard<std::mutex> lk(pendingConversationsFetchMtx_); + pendingConversationsFetch_.erase(conversationId); + return false; + } + acc->addGitSocket(channel->deviceId(), conversationId, channel); + conversation->second->sync( + peer, + deviceId, + [this, + conversationId = std::move(conversationId), + peer = std::move(peer), + deviceId = std::move(deviceId), + commitId = std::move(commitId)](bool ok) { + if (!ok) { + JAMI_WARN("[Account %s] Could not fetch new commit from " + "%s for %s, other " + "peer may be disconnected", + accountId_.c_str(), + deviceId.c_str(), + conversationId.c_str()); + JAMI_INFO("[Account %s] Relaunch sync with %s for %s", + accountId_.c_str(), + deviceId.c_str(), + conversationId.c_str()); + } + std::lock_guard<std::mutex> lk(pendingConversationsFetchMtx_); + pendingConversationsFetch_.erase(conversationId); + }, + commitId); + return true; + }); + } else { + if (getRequest(conversationId) != std::nullopt) + return; + { + // Check if the conversation is cloning + std::lock_guard<std::mutex> lk(pendingConversationsFetchMtx_); + if (pendingConversationsFetch_.find(conversationId) != pendingConversationsFetch_.end()) + return; + } + bool clone = false; + { + std::lock_guard<std::mutex> lkCi(convInfosMtx_); + auto convIt = convInfos_.find(conversationId); + clone = convIt != convInfos_.end(); + } + if (clone) { + cloneConversation(deviceId, peer, conversationId); + return; + } + lk.unlock(); + JAMI_WARN("[Account %s] Could not find conversation %s, ask for an invite", + accountId_.c_str(), + conversationId.c_str()); + sendMsgCb_(peer, + std::move(std::map<std::string, std::string> { + {"application/invite", conversationId}})); + } +} + +void +ConversationModule::Impl::checkConversationsEvents() +{ + bool hasHandler = conversationsEventHandler and not conversationsEventHandler->isCancelled(); + std::lock_guard<std::mutex> lk(pendingConversationsFetchMtx_); + if (not pendingConversationsFetch_.empty() and not hasHandler) { + conversationsEventHandler = Manager::instance().scheduler().scheduleAtFixedRate( + [w = weak()] { + if (auto this_ = w.lock()) + return this_->handlePendingConversations(); + return false; + }, + std::chrono::milliseconds(10)); + } else if (pendingConversationsFetch_.empty() and hasHandler) { + conversationsEventHandler->cancel(); + conversationsEventHandler.reset(); + } +} + +bool +ConversationModule::Impl::handlePendingConversations() +{ + std::lock_guard<std::mutex> lk(pendingConversationsFetchMtx_); + for (auto it = pendingConversationsFetch_.begin(); it != pendingConversationsFetch_.end();) { + if (it->second.ready && !it->second.cloning) { + it->second.cloning = true; + dht::ThreadPool::io().run([w = weak(), + conversationId = it->first, + deviceId = it->second.deviceId]() { + auto sthis = w.lock(); + if (!sthis) + return; + // Clone and store conversation + auto erasePending = [&] { + std::lock_guard<std::mutex> lk(sthis->pendingConversationsFetchMtx_); + sthis->pendingConversationsFetch_.erase(conversationId); + }; + try { + auto conversation = std::make_shared<Conversation>(sthis->account_, + deviceId, + conversationId); + if (!conversation->isMember(sthis->username_, true)) { + JAMI_ERR("Conversation cloned but doesn't seems to be a valid member"); + conversation->erase(); + erasePending(); + return; + } + if (conversation) { + auto removeRepo = false; + { + std::lock_guard<std::mutex> lk(sthis->conversationsMtx_); + // Note: a removeContact while cloning. In this case, the conversation + // must not be announced and removed. + auto itConv = sthis->convInfos_.find(conversationId); + if (itConv != sthis->convInfos_.end() && itConv->second.removed) + removeRepo = true; + sthis->conversations_.emplace(conversationId, conversation); + } + if (removeRepo) { + sthis->removeRepository(conversationId, false, true); + erasePending(); + return; + } + auto commitId = conversation->join(); + if (!commitId.empty()) + sthis->sendMessageNotification(conversationId, commitId, false); + // Inform user that the conversation is ready + emitSignal<DRing::ConversationSignal::ConversationReady>(sthis->accountId_, + conversationId); + sthis->needsSyncingCb_(); + } + } catch (const std::exception& e) { + emitSignal<DRing::ConversationSignal::OnConversationError>(sthis->accountId_, + conversationId, + EFETCH, + e.what()); + JAMI_WARN("Something went wrong when cloning conversation: %s", e.what()); + } + erasePending(); + }); + } + ++it; + } + return !pendingConversationsFetch_.empty(); +} + +std::optional<ConversationRequest> +ConversationModule::Impl::getRequest(const std::string& id) const +{ + std::lock_guard<std::mutex> lk(conversationsRequestsMtx_); + auto it = conversationsRequests_.find(id); + if (it != conversationsRequests_.end()) + return it->second; + return std::nullopt; +} + +void +ConversationModule::Impl::removeRepository(const std::string& conversationId, bool sync, bool force) +{ + std::unique_lock<std::mutex> lk(conversationsMtx_); + auto it = conversations_.find(conversationId); + if (it != conversations_.end() && it->second && (force || it->second->isRemoving())) { + try { + if (it->second->mode() == ConversationMode::ONE_TO_ONE) { + auto account = account_.lock(); + for (const auto& member : it->second->getInitialMembers()) { + if (member != account->getUsername()) { + account->accountManager()->removeContactConversation(member); + } + } + } + } catch (const std::exception& e) { + JAMI_ERR() << e.what(); + } + JAMI_DBG() << "Remove conversation: " << conversationId; + it->second->erase(); + conversations_.erase(it); + lk.unlock(); + + if (!sync) + return; + std::lock_guard<std::mutex> lkCi(convInfosMtx_); + auto convIt = convInfos_.find(conversationId); + if (convIt != convInfos_.end()) { + convIt->second.erased = std::time(nullptr); + needsSyncingCb_(); + } + saveConvInfos(); + } +} + +void +ConversationModule::Impl::sendMessageNotification(const std::string& conversationId, + const std::string& commitId, + bool sync) +{ + std::lock_guard<std::mutex> lk(conversationsMtx_); + auto it = conversations_.find(conversationId); + if (it != conversations_.end() && it->second) { + sendMessageNotification(*it->second, commitId, sync); + } +} + +void +ConversationModule::Impl::sendMessageNotification(const Conversation& conversation, + const std::string& commitId, + bool sync) +{ + Json::Value message; + message["id"] = conversation.id(); + message["commit"] = commitId; + message["deviceId"] = deviceId_; + Json::StreamWriterBuilder builder; + const auto text = Json::writeString(builder, message); + for (const auto& members : conversation.getMembers()) { + auto uri = members.at("uri"); + // Do not send to ourself, it's synced via convInfos + if (!sync && username_.find(uri) != std::string::npos) + continue; + // Announce to all members that a new message is sent + sendMsgCb_(uri, + std::move(std::map<std::string, std::string> { + {"application/im-gitmessage-id", text}})); + } +} + +//////////////////////////////////////////////////////////////// + +void +ConversationModule::saveConvRequests( + const std::string& accountId, + const std::map<std::string, ConversationRequest>& conversationsRequests) +{ + auto path = fileutils::get_data_dir() + DIR_SEPARATOR_STR + accountId; + saveConvRequestsToPath(path, conversationsRequests); +} + +void +ConversationModule::saveConvRequestsToPath( + const std::string& path, const std::map<std::string, ConversationRequest>& conversationsRequests) +{ + std::ofstream file(path + DIR_SEPARATOR_STR + "convRequests", + std::ios::trunc | std::ios::binary); + msgpack::pack(file, conversationsRequests); +} + +void +ConversationModule::saveConvInfos(const std::string& accountId, + const std::map<std::string, ConvInfo>& conversations) +{ + auto path = fileutils::get_data_dir() + DIR_SEPARATOR_STR + accountId; + saveConvInfosToPath(path, conversations); +} + +void +ConversationModule::saveConvInfosToPath(const std::string& path, + const std::map<std::string, ConvInfo>& conversations) +{ + std::ofstream file(path + DIR_SEPARATOR_STR + "convInfo", std::ios::trunc | std::ios::binary); + msgpack::pack(file, conversations); +} + +//////////////////////////////////////////////////////////////// + +ConversationModule::ConversationModule(std::weak_ptr<JamiAccount>&& account, + NeedsSyncingCb&& needsSyncingCb, + SengMsgCb&& sendMsgCb, + NeedSocketCb&& onNeedSocket) + : pimpl_ {std::make_unique<Impl>(std::move(account), + std::move(needsSyncingCb), + std::move(sendMsgCb), + std::move(onNeedSocket))} +{ + loadConversations(); +} + +void +ConversationModule::loadConversations() +{ + JAMI_INFO("[Account %s] Start loading conversations…", pimpl_->accountId_.c_str()); + auto conversationsRepositories = fileutils::readDirectory( + fileutils::get_data_dir() + DIR_SEPARATOR_STR + pimpl_->accountId_ + DIR_SEPARATOR_STR + + "conversations"); + std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_); + pimpl_->conversations_.clear(); + for (const auto& repository : conversationsRepositories) { + try { + auto conv = std::make_shared<Conversation>(pimpl_->account_, repository); + pimpl_->conversations_.emplace(repository, std::move(conv)); + } catch (const std::logic_error& e) { + JAMI_WARN("[Account %s] Conversations not loaded : %s", + pimpl_->accountId_.c_str(), + e.what()); + } + } + pimpl_->convInfos_ = convInfos(pimpl_->accountId_); + + // Set removed flag + for (auto& [key, info] : pimpl_->convInfos_) { + auto itConv = pimpl_->conversations_.find(info.id); + if (itConv != pimpl_->conversations_.end() && info.removed) + itConv->second->setRemovingFlag(); + } + + JAMI_INFO("[Account %s] Conversations loaded!", pimpl_->accountId_.c_str()); +} + +std::vector<std::string> +ConversationModule::getConversations() const +{ + std::vector<std::string> result; + std::lock_guard<std::mutex> lk(pimpl_->convInfosMtx_); + result.reserve(pimpl_->convInfos_.size()); + for (const auto& [key, conv] : pimpl_->convInfos_) { + if (conv.removed) + continue; + result.emplace_back(key); + } + return result; +} + +std::string +ConversationModule::getOneToOneConversation(const std::string& uri) const noexcept +{ + auto acc = pimpl_->account_.lock(); + if (!acc) + return {}; + auto details = acc->getContactDetails(uri); + auto it = details.find(DRing::Account::TrustRequest::CONVERSATIONID); + if (it != details.end()) + return it->second; + return {}; +} + +std::vector<std::map<std::string, std::string>> +ConversationModule::getConversationRequests() const +{ + std::vector<std::map<std::string, std::string>> requests; + std::lock_guard<std::mutex> lk(pimpl_->conversationsRequestsMtx_); + requests.reserve(pimpl_->conversationsRequests_.size()); + for (const auto& [id, request] : pimpl_->conversationsRequests_) { + if (request.declined) + continue; // Do not add declined requests + requests.emplace_back(request.toMap()); + } + return requests; +} + +void +ConversationModule::onTrustRequest(const std::string& uri, + const std::string& conversationId, + const std::vector<uint8_t>& payload, + time_t received) +{ + { + std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_); + auto itConv = pimpl_->conversations_.find(conversationId); + if (itConv != pimpl_->conversations_.end()) { + JAMI_INFO("[Account %s] Received a request for a conversation " + "already handled. Ignore", + pimpl_->accountId_.c_str()); + return; + } + } + if (pimpl_->getRequest(conversationId) != std::nullopt) { + JAMI_INFO("[Account %s] Received a request for a conversation " + "already existing. Ignore", + pimpl_->accountId_.c_str()); + return; + } + emitSignal<DRing::ConfigurationSignal::IncomingTrustRequest>(pimpl_->accountId_, + conversationId, + uri, + payload, + received); + ConversationRequest req; + req.from = uri; + req.conversationId = conversationId; + req.received = std::time(nullptr); + auto details = vCard::utils::toMap( + std::string_view(reinterpret_cast<const char*>(payload.data()), payload.size())); + req.metadatas = ConversationRepository::infosFromVCard(details); + auto reqMap = req.toMap(); + pimpl_->addConversationRequest(conversationId, std::move(req)); + emitSignal<DRing::ConversationSignal::ConversationRequestReceived>(pimpl_->accountId_, + conversationId, + reqMap); +} + +void +ConversationModule::onConversationRequest(const std::string& from, const Json::Value& value) +{ + ConversationRequest req(value); + JAMI_INFO("[Account %s] Receive a new conversation request for conversation %s from %s", + pimpl_->accountId_.c_str(), + req.conversationId.c_str(), + from.c_str()); + auto convId = req.conversationId; + req.from = from; + + if (pimpl_->getRequest(convId) != std::nullopt) { + JAMI_INFO("[Account %s] Received a request for a conversation already existing. " + "Ignore", + pimpl_->accountId_.c_str()); + return; + } + req.received = std::time(nullptr); + auto reqMap = req.toMap(); + pimpl_->addConversationRequest(convId, std::move(req)); + // Note: no need to sync here because other connected devices should receive + // the same conversation request. Will sync when the conversation will be added + + emitSignal<DRing::ConversationSignal::ConversationRequestReceived>(pimpl_->accountId_, + convId, + reqMap); +} + +void +ConversationModule::onNeedConversationRequest(const std::string& from, + const std::string& conversationId) +{ + // Check if the conversation exists + std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_); + auto itConv = pimpl_->conversations_.find(conversationId); + if (itConv != pimpl_->conversations_.end() && !itConv->second->isRemoving()) { + if (!itConv->second->isMember(from, true)) { + JAMI_WARN("%s is asking a new invite for %s, but not a member", + from.c_str(), + conversationId.c_str()); + return; + } + + // Send new invite + auto invite = itConv->second->generateInvitation(); + lk.unlock(); + JAMI_DBG("%s is asking a new invite for %s", from.c_str(), conversationId.c_str()); + pimpl_->sendMsgCb_(from, std::move(invite)); + } +} + +void +ConversationModule::acceptConversationRequest(const std::string& conversationId) +{ + auto acc = pimpl_->account_.lock(); + // For all conversation members, try to open a git channel with this conversation ID + auto request = pimpl_->getRequest(conversationId); + if (!acc || request == std::nullopt) { + JAMI_WARN("[Account %s] Request not found for conversation %s", + pimpl_->accountId_.c_str(), + conversationId.c_str()); + return; + } + auto memberHash = dht::InfoHash(request->from); + if (!memberHash) { + JAMI_WARN("Invalid member detected: %s", request->from.c_str()); + return; + } + acc->forEachDevice(memberHash, + [w = pimpl_->weak(), request = *request, conversationId]( + const std::shared_ptr<dht::crypto::PublicKey>& pk) { + auto sthis = w.lock(); + auto deviceId = pk->getLongId().toString(); + if (!sthis or deviceId == sthis->deviceId_) + return; + + if (!sthis->startFetch(conversationId, deviceId)) { + JAMI_WARN("[Account %s] Already fetching %s", + sthis->accountId_.c_str(), + conversationId.c_str()); + return; + } + sthis->onNeedSocket_( + conversationId, pk->getLongId().toString(), [=](const auto& channel) { + auto acc = sthis->account_.lock(); + std::unique_lock<std::mutex> lk( + sthis->pendingConversationsFetchMtx_); + auto& pending = sthis->pendingConversationsFetch_[conversationId]; + if (channel && !pending.ready) { + pending.ready = true; + pending.deviceId = channel->deviceId().toString(); + lk.unlock(); + // Save the git socket + acc->addGitSocket(channel->deviceId(), + conversationId, + channel); + sthis->checkConversationsEvents(); + return true; + } + return false; + }); + }); + pimpl_->rmConversationRequest(conversationId); + ConvInfo info; + info.id = conversationId; + info.created = std::time(nullptr); + info.members.emplace_back(pimpl_->username_); + info.members.emplace_back(request->from); + addConvInfo(info); +} + +void +ConversationModule::declineConversationRequest(const std::string& conversationId) +{ + std::lock_guard<std::mutex> lk(pimpl_->conversationsRequestsMtx_); + auto it = pimpl_->conversationsRequests_.find(conversationId); + if (it != pimpl_->conversationsRequests_.end()) { + it->second.declined = std::time(nullptr); + pimpl_->saveConvRequests(); + } + emitSignal<DRing::ConversationSignal::ConversationRequestDeclined>(pimpl_->accountId_, + conversationId); + pimpl_->needsSyncingCb_(); +} + +std::string +ConversationModule::startConversation(ConversationMode mode, const std::string& otherMember) +{ + // Create the conversation object + std::shared_ptr<Conversation> conversation; + try { + conversation = std::make_shared<Conversation>(pimpl_->account_, mode, otherMember); + } catch (const std::exception& e) { + JAMI_ERR("[Account %s] Error while generating a conversation %s", + pimpl_->accountId_.c_str(), + e.what()); + return {}; + } + auto convId = conversation->id(); + { + std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_); + pimpl_->conversations_[convId] = std::move(conversation); + } + + // Update convInfo + ConvInfo info; + info.id = convId; + info.created = std::time(nullptr); + info.members.emplace_back(pimpl_->username_); + if (!otherMember.empty()) + info.members.emplace_back(otherMember); + addConvInfo(info); + + pimpl_->needsSyncingCb_(); + + emitSignal<DRing::ConversationSignal::ConversationReady>(pimpl_->accountId_, convId); + return convId; +} + +// Message send/load +void +ConversationModule::sendMessage(const std::string& conversationId, + const std::string& message, + const std::string& parent, + const std::string& type, + bool announce, + const OnDoneCb& cb) +{ + Json::Value json; + json["body"] = message; + json["type"] = type; + sendMessage(conversationId, json, parent, announce, cb); +} + +void +ConversationModule::sendMessage(const std::string& conversationId, + const Json::Value& value, + const std::string& parent, + bool announce, + const OnDoneCb& cb) +{ + std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_); + auto conversation = pimpl_->conversations_.find(conversationId); + if (conversation != pimpl_->conversations_.end() && conversation->second) { + conversation->second->sendMessage( + value, + parent, + [this, conversationId, announce, cb = std::move(cb)](bool ok, + const std::string& commitId) { + if (cb) + cb(ok, commitId); + if (!announce) + return; + if (ok) + pimpl_->sendMessageNotification(conversationId, commitId, true); + else + JAMI_ERR("Failed to send message to conversation %s", conversationId.c_str()); + }); + } +} + +void +ConversationModule::addCallHistoryMessage(const std::string& uri, uint64_t duration_ms) +{ + auto finalUri = uri.substr(0, uri.find("@ring.dht")); + finalUri = finalUri.substr(0, uri.find("@jami.dht")); + auto convId = getOneToOneConversation(finalUri); + if (!convId.empty()) { + Json::Value value; + value["to"] = finalUri; + value["type"] = "application/call-history+json"; + value["duration"] = std::to_string(duration_ms); + sendMessage(convId, value); + } +} + +void +ConversationModule::onMessageDisplayed(const std::string& peer, + const std::string& conversationId, + const std::string& interactionId) +{ + std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_); + auto conversation = pimpl_->conversations_.find(conversationId); + if (conversation != pimpl_->conversations_.end() && conversation->second) { + conversation->second->setMessageDisplayed(peer, interactionId); + } +} + +uint32_t +ConversationModule::loadConversationMessages(const std::string& conversationId, + const std::string& fromMessage, + size_t n) +{ + std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_); + auto acc = pimpl_->account_.lock(); + auto conversation = pimpl_->conversations_.find(conversationId); + if (acc && conversation != pimpl_->conversations_.end() && conversation->second) { + const uint32_t id = std::uniform_int_distribution<uint32_t> {}(acc->rand); + conversation->second->loadMessages( + [accountId = pimpl_->accountId_, conversationId, id](auto&& messages) { + emitSignal<DRing::ConversationSignal::ConversationLoaded>(id, + accountId, + conversationId, + messages); + }, + fromMessage, + n); + return id; + } + return 0; +} + +std::shared_ptr<TransferManager> +ConversationModule::dataTransfer(const std::string& id) const +{ + std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_); + auto conversation = pimpl_->conversations_.find(id); + if (conversation != pimpl_->conversations_.end() && conversation->second) + return conversation->second->dataTransfer(); + return {}; +} + +bool +ConversationModule::onFileChannelRequest(const std::string& conversationId, + const std::string& member, + const std::string& fileId, + bool verifyShaSum) const +{ + std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_); + auto conversation = pimpl_->conversations_.find(conversationId); + if (conversation != pimpl_->conversations_.end() && conversation->second) + return conversation->second->onFileChannelRequest(member, fileId, verifyShaSum); + return false; +} + +bool +ConversationModule::downloadFile(const std::string& conversationId, + const std::string& interactionId, + const std::string& fileId, + const std::string& path, + size_t start, + size_t end) +{ + std::string sha3sum = {}; + std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_); + auto conversation = pimpl_->conversations_.find(conversationId); + if (conversation == pimpl_->conversations_.end() || !conversation->second) + return false; + + return conversation->second->downloadFile(interactionId, fileId, path, "", "", start, end); +} + +void +ConversationModule::syncConversations(const std::string& peer, const std::string& deviceId) +{ + // Sync conversations where peer is member + std::set<std::string> toFetch; + std::set<std::string> toClone; + { + std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_); + std::lock_guard<std::mutex> lkCI(pimpl_->convInfosMtx_); + for (const auto& [key, ci] : pimpl_->convInfos_) { + auto it = pimpl_->conversations_.find(key); + if (it != pimpl_->conversations_.end() && it->second) { + if (!it->second->isRemoving() && it->second->isMember(peer, false)) + toFetch.emplace(key); + } else if (!ci.removed + && std::find(ci.members.begin(), ci.members.end(), peer) + != ci.members.end()) { + // In this case the conversation was never cloned (can be after an import) + toClone.emplace(key); + } + } + } + for (const auto& cid : toFetch) + pimpl_->fetchNewCommits(peer, deviceId, cid); + for (const auto& cid : toClone) + pimpl_->cloneConversation(deviceId, peer, cid); +} + +void +ConversationModule::onSyncData(const SyncMsg& msg, + const std::string& peerId, + const std::string& deviceId) +{ + for (const auto& [key, convInfo] : msg.c) { + auto convId = convInfo.id; + auto removed = convInfo.removed; + pimpl_->rmConversationRequest(convId); + if (not removed) { + // If multi devices, it can detect a conversation that was already + // removed, so just check if the convinfo contains a removed conv + auto itConv = pimpl_->convInfos_.find(convId); + if (itConv != pimpl_->convInfos_.end() && itConv->second.removed) + continue; + pimpl_->cloneConversation(deviceId, peerId, convId); + } else { + { + std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_); + auto itConv = pimpl_->conversations_.find(convId); + if (itConv != pimpl_->conversations_.end() && !itConv->second->isRemoving()) { + emitSignal<DRing::ConversationSignal::ConversationRemoved>(pimpl_->accountId_, + convId); + itConv->second->setRemovingFlag(); + } + } + std::unique_lock<std::mutex> lk(pimpl_->convInfosMtx_); + auto& ci = pimpl_->convInfos_; + auto itConv = ci.find(convId); + if (itConv != ci.end()) { + itConv->second.removed = std::time(nullptr); + if (convInfo.erased) { + itConv->second.erased = std::time(nullptr); + pimpl_->saveConvInfos(); + lk.unlock(); + pimpl_->removeRepository(convId, false); + } + break; + } + } + } + + for (const auto& [convId, req] : msg.cr) { + if (pimpl_->isConversation(convId)) { + // Already accepted request + pimpl_->rmConversationRequest(convId); + continue; + } + + // New request + if (!pimpl_->addConversationRequest(convId, req)) + continue; + + if (req.declined != 0) { + // Request declined + JAMI_INFO("[Account %s] Declined request detected for conversation %s (device %s)", + pimpl_->accountId_.c_str(), + convId.c_str(), + deviceId.c_str()); + emitSignal<DRing::ConversationSignal::ConversationRequestDeclined>(pimpl_->accountId_, + convId); + continue; + } + + JAMI_INFO("[Account %s] New request detected for conversation %s (device %s)", + pimpl_->accountId_.c_str(), + convId.c_str(), + deviceId.c_str()); + + emitSignal<DRing::ConversationSignal::ConversationRequestReceived>(pimpl_->accountId_, + convId, + req.toMap()); + } +} + +bool +ConversationModule::needsSyncingWith(const std::string& memberUri, const std::string& deviceId) const +{ + std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_); + for (const auto& [_, conv] : pimpl_->conversations_) + if (conv->isMember(memberUri, false) && conv->needsFetch(deviceId)) + return true; + return false; +} + +void +ConversationModule::setFetched(const std::string& conversationId, const std::string& deviceId) +{ + auto remove = false; + { + std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_); + auto it = pimpl_->conversations_.find(conversationId); + if (it != pimpl_->conversations_.end() && it->second) { + remove = it->second->isRemoving(); + it->second->hasFetched(deviceId); + } + } + if (remove) + pimpl_->removeRepository(conversationId, true); +} + +void +ConversationModule::onNewCommit(const std::string& peer, + const std::string& deviceId, + const std::string& conversationId, + const std::string& commitId) +{ + std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_); + auto itConv = pimpl_->convInfos_.find(conversationId); + if (itConv != pimpl_->convInfos_.end() && itConv->second.removed) + return; // ignore new commits for removed conversation + JAMI_DBG("[Account %s] on new commit notification from %s, for %s, commit %s", + pimpl_->accountId_.c_str(), + peer.c_str(), + conversationId.c_str(), + commitId.c_str()); + lk.unlock(); + pimpl_->fetchNewCommits(peer, deviceId, conversationId, commitId); +} + +void +ConversationModule::addConversationMember(const std::string& conversationId, + const std::string& contactUri, + bool sendRequest) +{ + std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_); + // Add a new member in the conversation + auto it = pimpl_->conversations_.find(conversationId); + if (it == pimpl_->conversations_.end()) { + JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str()); + return; + } + + if (it->second->isMember(contactUri, true)) { + JAMI_DBG("%s is already a member of %s, resend invite", + contactUri.c_str(), + conversationId.c_str()); + // Note: This should not be necessary, but if for whatever reason the other side didn't join + // we should not forbid new invites + auto invite = it->second->generateInvitation(); + lk.unlock(); + pimpl_->sendMsgCb_(contactUri, std::move(invite)); + return; + } + + it->second + ->addMember(contactUri, + [this, conversationId, sendRequest, contactUri](bool ok, + const std::string& commitId) { + if (ok) { + std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_); + auto it = pimpl_->conversations_.find(conversationId); + if (it != pimpl_->conversations_.end() && it->second) { + pimpl_->sendMessageNotification(*it->second, + commitId, + true); // For the other members + if (sendRequest) { + auto invite = it->second->generateInvitation(); + lk.unlock(); + pimpl_->sendMsgCb_(contactUri, std::move(invite)); + } + } + } + }); +} + +void +ConversationModule::removeConversationMember(const std::string& conversationId, + const std::string& contactUri, + bool isDevice) +{ + std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_); + auto it = pimpl_->conversations_.find(conversationId); + if (it != pimpl_->conversations_.end() && it->second) { + it->second->removeMember(contactUri, + isDevice, + [this, conversationId](bool ok, const std::string& commitId) { + if (ok) { + pimpl_->sendMessageNotification(conversationId, + commitId, + true); + } + }); + } +} + +std::vector<std::map<std::string, std::string>> +ConversationModule::getConversationMembers(const std::string& conversationId) const +{ + std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_); + auto conversation = pimpl_->conversations_.find(conversationId); + if (conversation != pimpl_->conversations_.end() && conversation->second) + return conversation->second->getMembers(true, true); + + lk.unlock(); + std::lock_guard<std::mutex> lkCI(pimpl_->convInfosMtx_); + auto convIt = pimpl_->convInfos_.find(conversationId); + if (convIt != pimpl_->convInfos_.end()) { + std::vector<std::map<std::string, std::string>> result; + result.reserve(convIt->second.members.size()); + for (const auto& uri : convIt->second.members) { + result.emplace_back(std::map<std::string, std::string> {{"uri", uri}}); + } + return result; + } + return {}; +} + +uint32_t +ConversationModule::countInteractions(const std::string& convId, + const std::string& toId, + const std::string& fromId, + const std::string& authorUri) const +{ + std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_); + auto conversation = pimpl_->conversations_.find(convId); + if (conversation != pimpl_->conversations_.end() && conversation->second) { + return conversation->second->countInteractions(toId, fromId, authorUri); + } + return 0; +} + +void +ConversationModule::updateConversationInfos(const std::string& conversationId, + const std::map<std::string, std::string>& infos, + bool sync) +{ + std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_); + // Add a new member in the conversation + auto it = pimpl_->conversations_.find(conversationId); + if (it == pimpl_->conversations_.end()) { + JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str()); + return; + } + + it->second->updateInfos(infos, + [this, conversationId, sync](bool ok, const std::string& commitId) { + if (ok && sync) { + pimpl_->sendMessageNotification(conversationId, commitId, true); + } else if (sync) + JAMI_WARN("Couldn't update infos on %s", conversationId.c_str()); + }); +} + +std::map<std::string, std::string> +ConversationModule::conversationInfos(const std::string& conversationId) const +{ + std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_); + // Add a new member in the conversation + auto it = pimpl_->conversations_.find(conversationId); + if (it == pimpl_->conversations_.end() or not it->second) { + std::lock_guard<std::mutex> lkCi(pimpl_->convInfosMtx_); + auto itConv = pimpl_->convInfos_.find(conversationId); + if (itConv == pimpl_->convInfos_.end()) { + JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str()); + return {}; + } + return {{"syncing", "true"}}; + } + + return it->second->infos(); +} + +std::vector<uint8_t> +ConversationModule::conversationVCard(const std::string& conversationId) const +{ + std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_); + // Add a new member in the conversation + auto it = pimpl_->conversations_.find(conversationId); + if (it == pimpl_->conversations_.end() || !it->second) { + JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str()); + return {}; + } + + return it->second->vCard(); +} + +bool +ConversationModule::isBannedDevice(const std::string& convId, const std::string& deviceId) const +{ + std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_); + auto conversation = pimpl_->conversations_.find(convId); + return conversation == pimpl_->conversations_.end() || !conversation->second + || conversation->second->isBanned(deviceId); +} + +void +ConversationModule::removeContact(const std::string& uri, bool ban) +{ + // Remove related conversation + auto isSelf = uri == pimpl_->username_; + bool updateConvInfos = false; + std::vector<std::string> toRm; + { + std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_); + std::lock_guard<std::mutex> lkCi(pimpl_->convInfosMtx_); + for (auto& [convId, conv] : pimpl_->convInfos_) { + auto itConv = pimpl_->conversations_.find(convId); + if (itConv != pimpl_->conversations_.end() && itConv->second) { + try { + // Note it's important to check getUsername(), else + // removing self can remove all conversations + if (itConv->second->mode() == ConversationMode::ONE_TO_ONE) { + auto initMembers = itConv->second->getInitialMembers(); + if ((isSelf && initMembers.size() == 1) + || std::find(initMembers.begin(), initMembers.end(), uri) + != initMembers.end()) + toRm.emplace_back(convId); + } + } catch (const std::exception& e) { + JAMI_WARN("%s", e.what()); + } + } else if (std::find(conv.members.begin(), conv.members.end(), uri) + != conv.members.end()) { + // It's syncing with uri, mark as removed! + conv.removed = std::time(nullptr); + updateConvInfos = true; + } + } + if (updateConvInfos) + pimpl_->saveConvInfos(); + } + // Note, if we ban the device, we don't send the leave cause the other peer will just + // never got the notifications, so just erase the datas + for (const auto& id : toRm) + if (ban) + pimpl_->removeRepository(id, false, true); + else + removeConversation(id); +} + +bool +ConversationModule::removeConversation(const std::string& conversationId) +{ + std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_); + auto it = pimpl_->conversations_.find(conversationId); + if (it == pimpl_->conversations_.end()) { + JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str()); + return false; + } + auto members = it->second->getMembers(); + auto hasMembers = !(members.size() == 1 + && pimpl_->username_.find(members[0]["uri"]) != std::string::npos); + // Update convInfos + std::unique_lock<std::mutex> lockCi(pimpl_->convInfosMtx_); + auto itConv = pimpl_->convInfos_.find(conversationId); + if (itConv != pimpl_->convInfos_.end()) { + itConv->second.removed = std::time(nullptr); + // Sync now, because it can take some time to really removes the datas + if (hasMembers) + pimpl_->needsSyncingCb_(); + } + pimpl_->saveConvInfos(); + lockCi.unlock(); + auto commitId = it->second->leave(); + emitSignal<DRing::ConversationSignal::ConversationRemoved>(pimpl_->accountId_, conversationId); + if (hasMembers) { + JAMI_DBG() << "Wait that someone sync that user left conversation " << conversationId; + // Commit that we left + if (!commitId.empty()) { + // Do not sync as it's synched by convInfos + pimpl_->sendMessageNotification(*it->second, commitId, false); + } else { + JAMI_ERR("Failed to send message to conversation %s", conversationId.c_str()); + } + // In this case, we wait that another peer sync the conversation + // to definitely remove it from the device. This is to inform the + // peer that we left the conversation and never want to receive + // any messages + return true; + } + lk.unlock(); + // Else we are the last member, so we can remove + pimpl_->removeRepository(conversationId, true); + return true; +} + +void +ConversationModule::checkIfRemoveForCompat(const std::string& peerUri) +{ + auto convId = getOneToOneConversation(peerUri); + if (convId.empty()) + return; + std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_); + auto it = pimpl_->conversations_.find(convId); + if (it == pimpl_->conversations_.end()) { + JAMI_ERR("Conversation %s doesn't exist", convId.c_str()); + return; + } + // We will only removes the conversation if the member is invited + // the contact can have mutiple devices with only some with swarm + // support, in this case, just go with recent versions. + if (it->second->isMember(peerUri)) + return; + lk.unlock(); + removeConversation(convId); +} + +std::map<std::string, ConvInfo> +ConversationModule::convInfos(const std::string& accountId) +{ + auto path = fileutils::get_data_dir() + DIR_SEPARATOR_STR + accountId; + return convInfosFromPath(path); +} + +std::map<std::string, ConvInfo> +ConversationModule::convInfosFromPath(const std::string& path) +{ + std::map<std::string, ConvInfo> convInfos; + try { + // read file + auto file = fileutils::loadFile("convInfo", path); + // load values + msgpack::object_handle oh = msgpack::unpack((const char*) file.data(), file.size()); + oh.get().convert(convInfos); + } catch (const std::exception& e) { + JAMI_WARN("[convInfo] error loading convInfo: %s", e.what()); + } + return convInfos; +} + +std::map<std::string, ConversationRequest> +ConversationModule::convRequests(const std::string& accountId) +{ + auto path = fileutils::get_data_dir() + DIR_SEPARATOR_STR + accountId; + return convRequestsFromPath(path); +} + +std::map<std::string, ConversationRequest> +ConversationModule::convRequestsFromPath(const std::string& path) +{ + std::map<std::string, ConversationRequest> convRequests; + try { + // read file + auto file = fileutils::loadFile("convRequests", path); + // load values + msgpack::object_handle oh = msgpack::unpack((const char*) file.data(), file.size()); + oh.get().convert(convRequests); + } catch (const std::exception& e) { + JAMI_WARN("[convInfo] error loading convInfo: %s", e.what()); + } + return convRequests; +} + +void +ConversationModule::addConvInfo(const ConvInfo& info) +{ + std::lock_guard<std::mutex> lk(pimpl_->convInfosMtx_); + pimpl_->convInfos_[info.id] = info; + pimpl_->saveConvInfos(); +} + +void +ConversationModule::setConversationMembers(const std::string& convId, + const std::vector<std::string>& members) +{ + std::lock_guard<std::mutex> lk(pimpl_->convInfosMtx_); + auto convIt = pimpl_->convInfos_.find(convId); + if (convIt != pimpl_->convInfos_.end()) { + convIt->second.members = members; + pimpl_->saveConvInfos(); + } +} + +} // namespace jami \ No newline at end of file diff --git a/src/jamidht/conversation_module.h b/src/jamidht/conversation_module.h new file mode 100644 index 0000000000000000000000000000000000000000..ff8209520eb4aad9430275c09a292500b01cfbcd --- /dev/null +++ b/src/jamidht/conversation_module.h @@ -0,0 +1,343 @@ +/* + * Copyright (C) 2021 Savoir-faire Linux Inc. + * + * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include "scheduled_executor.h" +#include "jamidht/conversation.h" +#include "jamidht/conversationrepository.h" +#include "jamidht/jami_contact.h" + +#include <mutex> +#include <msgpack.hpp> + +namespace jami { + +struct SyncMsg +{ + jami::DeviceSync ds; + std::map<std::string, jami::ConvInfo> c; + std::map<std::string, jami::ConversationRequest> cr; + MSGPACK_DEFINE(ds, c, cr) +}; + +using ChannelCb = std::function<bool(const std::shared_ptr<ChannelSocket>&)>; +using NeedSocketCb = std::function<void(const std::string&, const std::string&, ChannelCb&&)>; +using SengMsgCb = std::function<void(const std::string&, std::map<std::string, std::string>&&)>; +using NeedsSyncingCb = std::function<void()>; + +class ConversationModule +{ +public: + ConversationModule(std::weak_ptr<JamiAccount>&& account, + NeedsSyncingCb&& needsSyncingCb, + SengMsgCb&& sendMsgCb, + NeedSocketCb&& onNeedSocket); + ~ConversationModule() = default; + + /** + * Refresh informations about conversations + */ + void loadConversations(); + + /** + * Return all conversation's id (including syncing ones) + */ + std::vector<std::string> getConversations() const; + + /** + * Get related conversation with member + * @param uri The member to search for + * @return the conversation id if found else empty + */ + std::string getOneToOneConversation(const std::string& uri) const noexcept; + + /** + * Return conversation's requests + */ + std::vector<std::map<std::string, std::string>> getConversationRequests() const; + + /** + * Called when detecting a new trust request with linked one to one + * @param uri Sender's URI + * @param conversationId Related conversation's id + * @param payload VCard + * @param received Received time + */ + void onTrustRequest(const std::string& uri, + const std::string& conversationId, + const std::vector<uint8_t>& payload, + time_t received); + + /** + * Called when receiving a new conversation's request + * @param from Sender + * @param value Conversation's request + */ + void onConversationRequest(const std::string& from, const Json::Value& value); + + /** + * Called when a peer needs an invite for a conversation (generally after that they received + * a commit notification for a conversation they don't have yet) + * @param from + * @param conversationId + */ + void onNeedConversationRequest(const std::string& from, const std::string& conversationId); + + /** + * Accepts a conversation's request + * @param convId + */ + void acceptConversationRequest(const std::string& conversationId); + + /** + * Decline a conversation's request + * @param convId + */ + void declineConversationRequest(const std::string& conversationId); + + /** + * Starts a new conversation + * @param mode Wanted mode + * @param otherMember If needed (one to one) + * @return conversation's id + */ + std::string startConversation(ConversationMode mode = ConversationMode::INVITES_ONLY, + const std::string& otherMember = ""); + + // Message send/load + void sendMessage(const std::string& conversationId, + const Json::Value& value, + const std::string& parent = "", + bool announce = true, + const OnDoneCb& cb = {}); + + void sendMessage(const std::string& conversationId, + const std::string& message, + const std::string& parent = "", + const std::string& type = "text/plain", + bool announce = true, + const OnDoneCb& cb = {}); + + /** + * Add to the related conversation the call history message + * @param uri Peer number + * @param duration_ms The call duration in ms + */ + void addCallHistoryMessage(const std::string& uri, uint64_t duration_ms); + + // Received that a peer displayed a message + void onMessageDisplayed(const std::string& peer, + const std::string& conversationId, + const std::string& interactionId); + + /** + * Load conversation's messages + * @param conversationId Conversation to load + * @param fromMessage + * @param n Max interactions to load + * @return id of the operation + */ + uint32_t loadConversationMessages(const std::string& conversationId, + const std::string& fromMessage = "", + size_t n = 0); + + // File transfer + /** + * Returns related transfer manager + * @param id Conversation's id + * @return nullptr if not found, else the manager + */ + std::shared_ptr<TransferManager> dataTransfer(const std::string& id) const; + + /** + * Choose if we can accept channel request + * @param member Member to check + * @param fileId File transfer to check (needs to be waiting) + * @param verifyShaSum For debug only + * @return if we accept the channel request + */ + bool onFileChannelRequest(const std::string& conversationId, + const std::string& member, + const std::string& fileId, + bool verifyShaSum = true) const; + + /** + * Ask conversation's members to send a file to this device + * @param conversationId Related conversation + * @param interactionId Related interaction + * @param fileId Related fileId + * @param path where to download the file + */ + bool downloadFile(const std::string& conversationId, + const std::string& interactionId, + const std::string& fileId, + const std::string& path, + size_t start = 0, + size_t end = 0); + + // Sync + /** + * Sync conversations with detected peer + */ + void syncConversations(const std::string& peer, const std::string& deviceId); + + /** + * Detect new conversations and request from other devices + * @param msg Received data + * @param peerId Sender + * @param deviceId + */ + void onSyncData(const SyncMsg& msg, const std::string& peerId, const std::string& deviceId); + + /** + * Check if we need to share infos with a contact + * @param memberUri + * @param deviceId + */ + bool needsSyncingWith(const std::string& memberUri, const std::string& deviceId) const; + + /** + * Notify that a peer fetched a commit + * @note: this definitely remove the repository when needed (when we left and someone fetched the information) + * @param conversationId Related conv + * @param deviceId Device who synced + */ + void setFetched(const std::string& conversationId, const std::string& deviceId); + + /** + * Launch fetch on new commit + * @param peer Who sent the notification + * @param deviceId Who sent the notification + * @param conversationId Related conversation + * @param commitId Commit to retrieve + */ + void onNewCommit(const std::string& peer, + const std::string& deviceId, + const std::string& conversationId, + const std::string& commitId); + + // Conversation's member + /** + * Adds a new member to a conversation (this will triggers a member event + new message on success) + * @param conversationId + * @param contactUri + * @param sendRequest If we need to inform the peer (used for tests) + */ + void addConversationMember(const std::string& conversationId, + const std::string& contactUri, + bool sendRequest = true); + /** + * Remove a member from a conversation (this will trigger a member event + new message on success) + * @param conversationId + * @param contactUri + * @param isDevice + */ + void removeConversationMember(const std::string& conversationId, + const std::string& contactUri, + bool isDevice = false); + /** + * Get members + * @param conversationId + * @return a map of members with their role and details + */ + std::vector<std::map<std::string, std::string>> getConversationMembers( + const std::string& conversationId) const; + /** + * Retrieve the number of interactions from interactionId to HEAD + * @param convId + * @param interactionId "" for getting the whole history + * @param authorUri Stop when detect author + * @return number of interactions since interactionId + */ + uint32_t countInteractions(const std::string& convId, + const std::string& toId, + const std::string& fromId, + const std::string& authorUri) const; + + // Conversation's infos management + /** + * Update metadata from conversations (like title, avatar, etc) + * @param conversationId + * @param infos + * @param sync If we need to sync with others (used for tests) + */ + void updateConversationInfos(const std::string& conversationId, + const std::map<std::string, std::string>& infos, + bool sync = true); + std::map<std::string, std::string> conversationInfos(const std::string& conversationId) const; + // Get the map into a VCard format for storing + std::vector<uint8_t> conversationVCard(const std::string& conversationId) const; + + /** + * Return if a device is banned from a conversation + * @param convId + * @param deviceId + */ + bool isBannedDevice(const std::string& convId, const std::string& deviceId) const; + + // Remove swarm + /** + * Remove one to one conversations related to a contact + * @param uri Of the contact + * @param ban If banned + */ + void removeContact(const std::string& uri, bool ban); + + /** + * Remove a conversation, but not the contact + * @param conversationId + * @return if successfully removed + */ + bool removeConversation(const std::string& conversationId); + + /** + * When a DHT message is coming, during swarm transition + * check if a swarm is linked to that contact and remove + * the swarm if needed + * @param peerUri the one who sent a DHT message + */ + void checkIfRemoveForCompat(const std::string& peerUri); + + // The following methods modify what is stored on the disk + static void saveConvInfos(const std::string& accountId, + const std::map<std::string, ConvInfo>& conversations); + static void saveConvInfosToPath(const std::string& path, + const std::map<std::string, ConvInfo>& conversations); + static void saveConvRequests( + const std::string& accountId, + const std::map<std::string, ConversationRequest>& conversationsRequests); + static void saveConvRequestsToPath( + const std::string& path, + const std::map<std::string, ConversationRequest>& conversationsRequests); + + static std::map<std::string, ConvInfo> convInfos(const std::string& accountId); + static std::map<std::string, ConvInfo> convInfosFromPath(const std::string& path); + static std::map<std::string, ConversationRequest> convRequests(const std::string& accountId); + static std::map<std::string, ConversationRequest> convRequestsFromPath(const std::string& path); + void addConvInfo(const ConvInfo& info); + void setConversationMembers(const std::string& convId, const std::vector<std::string>& members); + +private: + class Impl; + std::shared_ptr<Impl> pimpl_; +}; + +} // namespace jami \ No newline at end of file diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp index 21388952392bd4c0b55f28a15f763028533b87ae..1dee510574746b4607df6542d932dd203e5a312d 100644 --- a/src/jamidht/jamiaccount.cpp +++ b/src/jamidht/jamiaccount.cpp @@ -38,6 +38,7 @@ #include "server_account_manager.h" #include "jamidht/channeled_transport.h" #include "multiplexed_socket.h" +#include "conversation_channel_handler.h" #include "sip/sdp.h" #include "sip/sipvoiplink.h" @@ -106,17 +107,19 @@ using namespace std::placeholders; -struct SyncMsg -{ - jami::DeviceSync ds; - std::map<std::string, jami::ConvInfo> c; - std::map<std::string, jami::ConversationRequest> cr; - MSGPACK_DEFINE(ds, c, cr) -}; - namespace jami { constexpr pj_str_t STR_MESSAGE_ID = jami::sip_utils::CONST_PJ_STR("Message-ID"); +static constexpr const char MIME_TYPE_IMDN[] {"message/imdn+xml"}; +static constexpr const char MIME_TYPE_IM_COMPOSING[] {"application/im-iscomposing+xml"}; +static constexpr const char MIME_TYPE_INVITE[] {"application/invite"}; +static constexpr const char MIME_TYPE_INVITE_JSON[] {"application/invite+json"}; +static constexpr const char MIME_TYPE_GIT[] {"application/im-gitmessage-id"}; +static constexpr const char FILE_URI[] {"file://"}; +static constexpr const char VCARD_URI[] {"vcard://"}; +static constexpr const char SYNC_URI[] {"sync://"}; +static constexpr const char DATA_TRANSFER_URI[] {"data-transfer://"}; +static constexpr std::chrono::steady_clock::duration COMPOSING_TIMEOUT {std::chrono::seconds(12)}; struct PendingConfirmation { @@ -204,13 +207,6 @@ struct JamiAccount::PendingCall std::shared_ptr<dht::crypto::Certificate> from_cert; }; -struct JamiAccount::PendingConversationFetch -{ - bool ready {false}; - bool cloning {false}; - std::string deviceId {}; -}; - struct JamiAccount::PendingMessage { std::set<DeviceId> to; @@ -246,7 +242,6 @@ constexpr const char* const JamiAccount::ACCOUNT_TYPE; constexpr const std::pair<uint16_t, uint16_t> JamiAccount::DHT_PORT_RANGE {4000, 8888}; using ValueIdDist = std::uniform_int_distribution<dht::Value::Id>; -using LoadIdDist = std::uniform_int_distribution<uint32_t>; static std::string_view stripPrefix(std::string_view toUrl) @@ -1131,45 +1126,17 @@ JamiAccount::loadAccount(const std::string& archive_password, const std::string& conversationId, const std::vector<uint8_t>& payload, time_t received) { - auto saveReq = false; - if (!conversationId.empty()) { - auto info = accountManager_->getInfo(); - if (!info) - return; - auto itConv = info->conversations.find(conversationId); - if (itConv != info->conversations.end()) { - JAMI_INFO("[Account %s] Received a request for a conversation " - "already handled. Ignore", - getAccountID().c_str()); - return; - } - saveReq = true; - if (accountManager_->getRequest(conversationId) != std::nullopt) { - JAMI_INFO("[Account %s] Received a request for a conversation " - "already existing. Ignore", - getAccountID().c_str()); - return; - } - } - emitSignal<DRing::ConfigurationSignal::IncomingTrustRequest>(getAccountID(), - conversationId, - uri, - payload, - received); - if (saveReq && !conversationId.empty()) { - ConversationRequest req; - req.from = uri; - req.conversationId = conversationId; - req.received = std::time(nullptr); - auto details = vCard::utils::toMap( - std::string_view(reinterpret_cast<const char*>(payload.data()), payload.size())); - req.metadatas = ConversationRepository::infosFromVCard(details); - auto reqMap = req.toMap(); - accountManager_->addConversationRequest(conversationId, std::move(req)); - emitSignal<DRing::ConversationSignal::ConversationRequestReceived>(getAccountID(), - conversationId, - reqMap); + if (conversationId.empty()) { + // Old path + emitSignal<DRing::ConfigurationSignal::IncomingTrustRequest>(getAccountID(), + conversationId, + uri, + payload, + received); + return; } + if (auto cm = convModule()) // Here account can be initializing + cm->onTrustRequest(uri, conversationId, payload, received); }, [this](const std::map<DeviceId, KnownDevice>& devices) { std::map<std::string, std::string> ids; @@ -1184,9 +1151,8 @@ JamiAccount::loadAccount(const std::string& archive_password, }, [this](const std::string& conversationId) { dht::ThreadPool::computation().run([w = weak(), conversationId] { - if (auto acc = w.lock()) { - acc->acceptConversationRequest(conversationId); - } + if (auto acc = w.lock()) + acc->convModule()->acceptConversationRequest(conversationId); }); }, [this](const std::string& uri, const std::string& convFromReq) { @@ -1196,7 +1162,7 @@ JamiAccount::loadAccount(const std::string& archive_password, // In this case, delete current conversation linked with that // contact because he will not get messages anyway. if (convFromReq.empty()) - checkIfRemoveForCompat(uri); + convModule()->checkIfRemoveForCompat(uri); }}; try { @@ -1231,7 +1197,7 @@ JamiAccount::loadAccount(const std::string& archive_password, if (not isEnabled()) { setRegistrationState(RegistrationState::UNREGISTERED); } - loadConversations(); + convModule()->loadConversations(); } else if (isEnabled()) { if (not managerUri_.empty() and archive_password.empty()) { Migration::setState(accountID_, Migration::State::INVALID); @@ -1333,7 +1299,7 @@ JamiAccount::loadAccount(const std::string& archive_password, info.photo); setRegistrationState(RegistrationState::UNREGISTERED); saveConfig(); - loadConversations(); + convModule()->loadConversations(); doRegister(); }, [w = weak(), @@ -1860,67 +1826,51 @@ JamiAccount::trackPresence(const dht::InfoHash& h, BuddyInfo& buddy) if (not dht or not dht->isRunning()) { return; } - buddy.listenToken - = dht->listen<DeviceAnnouncement>(h, [this, h](DeviceAnnouncement&& dev, bool expired) { - bool wasConnected, isConnected; - { - std::lock_guard<std::mutex> lock(buddyInfoMtx); - auto buddy = trackedBuddies_.find(h); - if (buddy == trackedBuddies_.end()) - return true; - wasConnected = buddy->second.devices_cnt > 0; - if (expired) - --buddy->second.devices_cnt; - else - ++buddy->second.devices_cnt; - isConnected = buddy->second.devices_cnt > 0; - } - // NOTE: the rest can use configurationMtx_, that can be locked during unregister so - // do not retrigger on dht - runOnMainThread([w = weak(), h, dev, expired, isConnected, wasConnected]() { - auto sthis = w.lock(); - if (!sthis) - return; - if (not expired) { - // Retry messages every time a new device announce its presence - sthis->messageEngine_.onPeerOnline(h.toString()); - auto deviceId = dev.dev.toString(); - auto needsSync = false; - { - std::unique_lock<std::mutex> lk(sthis->conversationsMtx_); - for (const auto& [_, conv] : sthis->conversations_) { - if (conv->isMember(h.toString(), false) - && conv->needsFetch(deviceId)) { - needsSync = true; - break; - } - } - } - if (needsSync) { - if (dev.pk) { - sthis->requestSIPConnection(h.toString(), dev.pk->getLongId()); - } else { - sthis->findCertificate( - dev.dev, - [sthis, h](const std::shared_ptr<dht::crypto::Certificate>& cert) { - if (cert) { - auto pk = std::make_shared<dht::crypto::PublicKey>( - cert->getPublicKey()); - sthis->requestSIPConnection(h.toString(), pk->getLongId()); - } - }); - } - } - } - if (isConnected and not wasConnected) { - sthis->onTrackedBuddyOnline(h); - } else if (not isConnected and wasConnected) { - sthis->onTrackedBuddyOffline(h); - } - }); - - return true; - }); + buddy.listenToken = dht->listen< + DeviceAnnouncement>(h, [this, h](DeviceAnnouncement&& dev, bool expired) { + bool wasConnected, isConnected; + { + std::lock_guard<std::mutex> lock(buddyInfoMtx); + auto buddy = trackedBuddies_.find(h); + if (buddy == trackedBuddies_.end()) + return true; + wasConnected = buddy->second.devices_cnt > 0; + if (expired) + --buddy->second.devices_cnt; + else + ++buddy->second.devices_cnt; + isConnected = buddy->second.devices_cnt > 0; + } + // NOTE: the rest can use configurationMtx_, that can be locked during unregister so + // do not retrigger on dht + runOnMainThread([w = weak(), h, dev, expired, isConnected, wasConnected]() { + auto sthis = w.lock(); + if (!sthis) + return; + if (not expired) { + // Retry messages every time a new device announce its presence + sthis->messageEngine_.onPeerOnline(h.toString()); + sthis->findCertificate( + dev.dev, [sthis, h](const std::shared_ptr<dht::crypto::Certificate>& cert) { + if (cert) { + auto pk = std::make_shared<dht::crypto::PublicKey>(cert->getPublicKey()); + if (sthis->convModule()->needsSyncingWith(h.toString(), + pk->getLongId().toString())) + sthis->requestSIPConnection( + h.toString(), + pk->getLongId()); // Both sides will sync conversations + } + }); + } + if (isConnected and not wasConnected) { + sthis->onTrackedBuddyOnline(h); + } else if (not isConnected and wasConnected) { + sthis->onTrackedBuddyOffline(h); + } + }); + + return true; + }); JAMI_DBG("[Account %s] tracking buddy %s", getAccountID().c_str(), h.to_c_str()); } @@ -1944,14 +1894,14 @@ JamiAccount::onTrackedBuddyOnline(const dht::InfoHash& contactId) auto details = getContactDetails(id); auto it = details.find("confirmed"); if (it == details.end() or it->second == "false") { - auto convId = getOneToOneConversation(id); + auto convId = convModule()->getOneToOneConversation(id); if (convId.empty()) return; // In this case, the TrustRequest was sent but never confirmed (cause the contact was // offline maybe) To avoid the contact to never receive the conv request, retry there std::lock_guard<std::mutex> lock(configurationMutex_); if (accountManager_) - accountManager_->sendTrustRequest(id, convId, conversationVCard(convId)); + accountManager_->sendTrustRequest(id, convId, convModule()->conversationVCard(convId)); } } @@ -1965,34 +1915,6 @@ JamiAccount::onTrackedBuddyOffline(const dht::InfoHash& contactId) ""); } -void -JamiAccount::syncConversations(const std::string& peer, const DeviceId& deviceId) -{ - // Sync conversations where peer is member - std::set<std::string> toFetch; - std::set<std::string> toClone; - { - if (auto infos = accountManager_->getInfo()) { - for (const auto& [key, ci] : infos->conversations) { - auto it = conversations_.find(key); - if (it != conversations_.end() && it->second) { - if (!it->second->isRemoving() && it->second->isMember(peer, false)) - toFetch.emplace(key); - } else if (!ci.removed - && std::find(ci.members.begin(), ci.members.end(), peer) - != ci.members.end()) { - // In this case the conversation was never cloned (can be after an import) - toClone.emplace(key); - } - } - } - } - for (const auto& cid : toFetch) - fetchNewCommits(peer, deviceId, cid); - for (const auto& cid : toClone) - cloneConversation(deviceId, peer, cid); -} - void JamiAccount::doRegister_() { @@ -2158,8 +2080,7 @@ JamiAccount::doRegister_() return; std::unique_lock<std::mutex> lk(connManagerMtx_); - if (!connectionManager_) - connectionManager_ = std::make_unique<ConnectionManager>(*this); + initConnectionManager(); auto channelName = "sync://" + deviceId; if (connectionManager_->isConnecting(crt->getLongId(), channelName)) { JAMI_INFO("[Account %s] Already connecting to %s", @@ -2195,8 +2116,7 @@ JamiAccount::doRegister_() accountManager_->setDht(dht_); std::unique_lock<std::mutex> lkCM(connManagerMtx_); - if (!connectionManager_) - connectionManager_ = std::make_unique<ConnectionManager>(*this); + initConnectionManager(); connectionManager_->onDhtConnected(*accountManager_->getInfo()->devicePk); connectionManager_->onICERequest([this](const DeviceId& deviceId) { std::promise<bool> accept; @@ -2221,61 +2141,28 @@ JamiAccount::doRegister_() }); connectionManager_->onChannelRequest([this](const DeviceId& deviceId, const std::string& name) { - auto isFile = name.substr(0, 7) == "file://"; - auto isVCard = name.substr(0, 8) == "vcard://"; - auto isDataTransfer = name.substr(0, 16) == "data-transfer://"; - JAMI_WARN("[Account %s] New channel asked from %s with name %s", getAccountID().c_str(), deviceId.to_c_str(), name.c_str()); - if (name.find("git://") == 0) { - // Pre-check before acceptance. Sometimes, another device can start a conversation - // which is still not synced. So, here we decline channel's request in this case - // to avoid the other device to want to sync with us till we're not ready. - auto sep = name.find_last_of('/'); - auto conversationId = name.substr(sep + 1); - auto remoteDevice = name.substr(6, sep - 6); - - std::unique_lock<std::mutex> lk(conversationsMtx_); - auto conversation = conversations_.find(conversationId); - if (conversation == conversations_.end()) { - JAMI_WARN("[Account %s] Git server requested, but for a non existing " - "conversation (%s)", - getAccountID().c_str(), - conversationId.c_str()); - return false; - } - // Also check if peer is banned. - if (conversation->second->isBanned(remoteDevice)) { - JAMI_WARN("[Account %s] %s is a banned device in conversation %s", - getAccountID().c_str(), - remoteDevice.c_str(), - conversationId.c_str()); - return false; - } - return true; - } else if (name == "sip") { + auto uri = Uri(name); + auto itHandler = channelHandlers_.find(uri.scheme()); + if (itHandler != channelHandlers_.end() && itHandler->second) + return itHandler->second->onRequest(deviceId, name); + // TODO replace + auto isFile = name.substr(0, 7) == FILE_URI; + auto isVCard = name.substr(0, 8) == VCARD_URI; + auto isDataTransfer = name.substr(0, 16) == DATA_TRANSFER_URI; + + auto cert = tls::CertificateStore::instance().getCertificate(deviceId.toString()); + if (!cert) + return false; + + if (name == "sip") { return true; - } else if (name.find("sync://") == 0) { - // Check if sync request is from same account - std::promise<bool> accept; - std::future<bool> fut = accept.get_future(); - accountManager_ - ->findCertificate(deviceId, - [this, &accept]( - const std::shared_ptr<dht::crypto::Certificate>& cert) { - if (not cert) { - accept.set_value(false); - return; - } - accept.set_value(cert->getIssuerUID() - == accountManager_->getInfo()->accountId); - }); - fut.wait(); - auto result = fut.get(); - return result; + } else if (name.find(SYNC_URI) == 0) { + return cert->getIssuerUID() == accountManager_->getInfo()->accountId; } else if (isFile or isVCard) { auto tid = isFile ? name.substr(7) : name.substr(8); std::lock_guard<std::mutex> lk(transfersMtx_); @@ -2300,30 +2187,19 @@ JamiAccount::doRegister_() fileId = fileId.substr(0, sep); } - accountManager_->findCertificate( - deviceId, [&](const std::shared_ptr<dht::crypto::Certificate>& cert) { - if (not cert) { - accept.set_value(false); - return; - } - // Check if peer is member of the conversation - std::unique_lock<std::mutex> lk(conversationsMtx_); - auto conversation = conversations_.find(conversationId); - if (conversation == conversations_.end() or not conversation->second) { - accept.set_value(false); - return; - } - if (fileId == "profile.vcf") { - accept.set_value(conversation->second->isMember(cert->getIssuerUID())); - } else { - accept.set_value( - conversation->second->onFileChannelRequest(cert->getIssuerUID(), - fileId, - !noSha3sumVerification_)); - } - }); - fut.wait(); - return fut.get(); + // Check if peer is member of the conversation + if (fileId == "profile.vcf") { + auto members = convModule()->getConversationMembers(conversationId); + return std::find_if(members.begin(), + members.end(), + [&](auto m) { return m["uri"] == cert->getIssuerUID(); }) + != members.end(); + } + + return convModule()->onFileChannelRequest(conversationId, + cert->getIssuerUID(), + fileId, + !noSha3sumVerification_); } return false; }); @@ -2335,11 +2211,11 @@ JamiAccount::doRegister_() if (!cert || !cert->issuer) return; auto peerId = cert->issuer->getId().toString(); - auto isFile = name.substr(0, 7) == "file://"; - auto isVCard = name.substr(0, 8) == "vcard://"; + auto isFile = name.substr(0, 7) == FILE_URI; + auto isVCard = name.substr(0, 8) == VCARD_URI; if (name == "sip") { cacheSIPConnection(std::move(channel), peerId, deviceId); - } else if (name.find("sync://") == 0) { + } else if (name.find(SYNC_URI) == 0) { cacheSyncConnection(std::move(channel), peerId, deviceId); } else if (isFile or isVCard) { auto tid = isFile ? name.substr(7) : name.substr(8); @@ -2388,11 +2264,13 @@ JamiAccount::doRegister_() return; } - if (!isConversation(conversationId)) { - JAMI_WARN("[Account %s] Git server requested, but for a non existing " - "conversation (%s)", + // Check if pull from banned device + if (convModule()->isBannedDevice(conversationId, remoteDevice)) { + JAMI_WARN("[Account %s] Git server requested for conversation %s, but the device is " + "unauthorized (%s) ", getAccountID().c_str(), - conversationId.c_str()); + conversationId.c_str(), + remoteDevice.c_str()); channel->shutdown(); return; } @@ -2412,20 +2290,8 @@ JamiAccount::doRegister_() channel->channel()); auto gs = std::make_unique<GitServer>(accountId, conversationId, channel); gs->setOnFetched([w = weak(), conversationId, deviceId](const std::string&) { - auto shared = w.lock(); - if (!shared) - return; - auto remove = false; - { - std::unique_lock<std::mutex> lk(shared->conversationsMtx_); - auto it = shared->conversations_.find(conversationId); - if (it != shared->conversations_.end() && it->second) { - remove = it->second->isRemoving(); - it->second->hasFetched(deviceId.toString()); - } - } - if (remove) - shared->removeRepository(conversationId, true); + if (auto shared = w.lock()) + shared->convModule()->setFetched(conversationId, deviceId.toString()); }); const dht::Value::Id serverId = ValueIdDist()(rand); { @@ -2442,7 +2308,7 @@ JamiAccount::doRegister_() shared->gitServers_.erase(serverId); }); }); - } else if (name.find("data-transfer://") == 0) { + } else if (name.find(DATA_TRANSFER_URI) == 0) { auto idstr = name.substr(16); auto sep = idstr.find('/'); auto lastSep = idstr.find_last_of('/'); @@ -2548,6 +2414,69 @@ JamiAccount::doRegister_() } } +ConversationModule* +JamiAccount::convModule() +{ + if (!accountManager() || currentDeviceId() == "") { + JAMI_ERR() << "Calling convModule() with an uninitialized account."; + return nullptr; + } + if (!convModule_) { + convModule_ = std::make_unique<ConversationModule>( + weak(), + std::move([this] { + runOnMainThread([w = weak()] { + if (auto shared = w.lock()) + shared->syncWithConnected(); + }); + }), + std::move([this](auto&& uri, auto&& msg) { + runOnMainThread([w = weak(), uri, msg] { + if (auto shared = w.lock()) + shared->sendTextMessage(uri, msg); + }); + }), + std::move([this](const auto& convId, const auto& deviceId, auto&& cb) { + runOnMainThread([w = weak(), convId, deviceId, cb = std::move(cb)] { + auto shared = w.lock(); + if (!shared) + return; + auto gs = shared->gitSocket(DeviceId(deviceId), convId); + if (gs != std::nullopt) { + if (auto socket = gs->lock()) { + if (!cb(socket)) + socket->shutdown(); + else + cb({}); + return; + } + } + std::lock_guard<std::mutex> lkCM(shared->connManagerMtx_); + if (!shared->connectionManager_) { + cb({}); + return; + } + shared->connectionManager_ + ->connectDevice(DeviceId(deviceId), + "git://" + deviceId + "/" + convId, + [shared, cb, convId](std::shared_ptr<ChannelSocket> socket, + const DeviceId&) { + if (socket) { + socket->onShutdown( + [shared, deviceId = socket->deviceId(), convId] { + shared->removeGitSocket(deviceId, convId); + }); + if (!cb(socket)) + socket->shutdown(); + } else + cb({}); + }); + }); + })); + } + return convModule_.get(); +} + void JamiAccount::onTextMessage(const std::string& id, const std::string& from, @@ -2999,6 +2928,97 @@ JamiAccount::getToUri(const std::string& to) const return fmt::format("<sips:{};transport=tls>", to); } +std::string +getIsComposing(const std::string& conversationId, bool isWriting) +{ + // implementing https://tools.ietf.org/rfc/rfc3994.txt + return fmt::format("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" + "<isComposing><state>{}</state>{}</isComposing>", + isWriting ? "active"sv : "idle"sv, + conversationId.empty() + ? "" + : "<conversation>" + conversationId + "</conversation>"); +} + +std::string +getDisplayed(const std::string& conversationId, const std::string& messageId) +{ + // implementing https://tools.ietf.org/rfc/rfc5438.txt + return fmt::format( + "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" + "<imdn><message-id>{}</message-id>\n" + "{}" + "<display-notification><status><displayed/></status></display-notification>\n" + "</imdn>", + messageId, + conversationId.empty() ? "" : "<conversation>" + conversationId + "</conversation>"); +} + +void +JamiAccount::setIsComposing(const std::string& conversationUri, bool isWriting) +{ + Uri uri(conversationUri); + std::string conversationId = {}; + if (uri.scheme() == Uri::Scheme::SWARM) + conversationId = uri.authority(); + const auto& uid = uri.authority(); + + if (not isWriting and conversationUri != composingUri_) + return; + + if (composingTimeout_) { + composingTimeout_->cancel(); + composingTimeout_.reset(); + } + if (isWriting) { + if (not composingUri_.empty() and composingUri_ != conversationUri) { + sendInstantMessage(uid, + {{MIME_TYPE_IM_COMPOSING, getIsComposing(conversationId, false)}}); + composingTime_ = std::chrono::steady_clock::time_point::min(); + } + composingUri_.clear(); + composingUri_.insert(composingUri_.end(), conversationUri.begin(), conversationUri.end()); + auto now = std::chrono::steady_clock::now(); + if (now >= composingTime_ + COMPOSING_TIMEOUT) { + sendInstantMessage(uid, + {{MIME_TYPE_IM_COMPOSING, getIsComposing(conversationId, true)}}); + composingTime_ = now; + } + composingTimeout_ = Manager::instance().scheduleTask( + [w = weak(), uid, conversationId]() { + if (auto sthis = w.lock()) { + sthis->sendInstantMessage(uid, + {{MIME_TYPE_IM_COMPOSING, + getIsComposing(conversationId, false)}}); + sthis->composingUri_.clear(); + sthis->composingTime_ = std::chrono::steady_clock::time_point::min(); + } + }, + now + COMPOSING_TIMEOUT); + } else { + sendInstantMessage(uid, {{MIME_TYPE_IM_COMPOSING, getIsComposing(conversationId, false)}}); + composingUri_.clear(); + composingTime_ = std::chrono::steady_clock::time_point::min(); + } +} + +bool +JamiAccount::setMessageDisplayed(const std::string& conversationUri, + const std::string& messageId, + int status) +{ + Uri uri(conversationUri); + std::string conversationId = {}; + if (uri.scheme() == Uri::Scheme::SWARM) + conversationId = uri.authority(); + if (!conversationId.empty()) + convModule()->onMessageDisplayed(getUsername(), conversationId, messageId); + if (status == (int) DRing::Account::MessageStates::DISPLAYED && isReadReceiptEnabled()) + sendInstantMessage(uri.authority(), + {{MIME_TYPE_IMDN, getDisplayed(conversationId, messageId)}}); + return true; +} + pj_str_t JamiAccount::getContactHeader(pjsip_transport* t) { @@ -3032,9 +3052,9 @@ JamiAccount::getContactHeader(pjsip_transport* t) void JamiAccount::addContact(const std::string& uri, bool confirmed) { - auto conversation = getOneToOneConversation(uri); + auto conversation = convModule()->getOneToOneConversation(uri); if (!confirmed && conversation.empty()) - conversation = startConversation(ConversationMode::ONE_TO_ONE, uri); + conversation = convModule()->startConversation(ConversationMode::ONE_TO_ONE, uri); std::lock_guard<std::mutex> lock(configurationMutex_); if (accountManager_) accountManager_->addContact(uri, confirmed, conversation); @@ -3046,81 +3066,25 @@ void JamiAccount::removeContact(const std::string& uri, bool ban) { std::unique_lock<std::mutex> lock(configurationMutex_); - if (!accountManager_) { + if (accountManager_) { + accountManager_->removeContact(uri, ban); + } else { JAMI_WARN("[Account %s] removeContact: account not loaded", getAccountID().c_str()); return; } - accountManager_->removeContact(uri, ban); - auto convInfos = accountManager_->getInfo()->conversations; - - // Remove related conversation - auto isSelf = uri == getUsername(); - bool updateConvInfos = false; - std::vector<std::string> toRm; - { - std::lock_guard<std::mutex> lk(conversationsMtx_); - for (auto& [convId, conv] : convInfos) { - auto itConv = conversations_.find(convId); - if (itConv != conversations_.end() && itConv->second) { - try { - // Note it's important to check getUsername(), else - // removing self can remove all conversations - if (itConv->second->mode() == ConversationMode::ONE_TO_ONE) { - auto initMembers = itConv->second->getInitialMembers(); - if ((isSelf && initMembers.size() == 1) - || std::find(initMembers.begin(), initMembers.end(), uri) - != initMembers.end()) - toRm.emplace_back(convId); - } - } catch (const std::exception& e) { - JAMI_WARN("%s", e.what()); - } - } else if (std::find(conv.members.begin(), conv.members.end(), uri) - != conv.members.end()) { - // It's syncing with uri, mark as removed! - conv.removed = std::time(nullptr); - updateConvInfos = true; - } - } - } - if (updateConvInfos) - accountManager_->setConversations(convInfos); lock.unlock(); - for (const auto& id : toRm) { - // Note, if we ban the device, we don't send the leave cause the other peer will just - // never got the notifications, so just erase the datas - if (!ban) - removeConversation(id); - else - removeRepository(id, false, true); - } - // Remove current connections with contact - dht::ThreadPool::io().run([w = weak(), uri] { - auto shared = w.lock(); - if (!shared) - return; - // Remove current connections with contact - std::set<DeviceId> devices; - { - std::unique_lock<std::mutex> lk(shared->sipConnsMtx_); - for (auto it = shared->sipConns_.begin(); it != shared->sipConns_.end();) { - const auto& [key, value] = *it; - if (key.first == uri) { - devices.emplace(key.second); - it = shared->sipConns_.erase(it); - } else { - ++it; - } - } - } + convModule()->removeContact(uri, ban); - std::lock_guard<std::mutex> lk(shared->connManagerMtx_); - if (!shared->connectionManager_) - return; - for (const auto& device : devices) - shared->connectionManager_->closeConnectionsWith(device); - }); + // Remove current connections with contact + std::unique_lock<std::mutex> lk(sipConnsMtx_); + for (auto it = sipConns_.begin(); it != sipConns_.end();) { + const auto& [key, value] = *it; + if (key.first == uri) + it = sipConns_.erase(it); + else + ++it; + } } std::map<std::string, std::string> @@ -3171,7 +3135,7 @@ JamiAccount::acceptTrustRequest(const std::string& from, bool includeConversatio info.created = std::time(nullptr); info.members.emplace_back(getUsername()); info.members.emplace_back(from); - addNewConversation(info); + convModule()->addConvInfo(info); } return true; } @@ -3186,7 +3150,8 @@ JamiAccount::discardTrustRequest(const std::string& from) auto requests = getTrustRequests(); for (const auto& req : requests) { if (req.at(DRing::Account::TrustRequest::FROM) == from) { - declineConversationRequest(req.at(DRing::Account::TrustRequest::CONVERSATIONID)); + convModule()->declineConversationRequest( + req.at(DRing::Account::TrustRequest::CONVERSATIONID)); } } @@ -3201,9 +3166,9 @@ JamiAccount::discardTrustRequest(const std::string& from) void JamiAccount::sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload) { - auto conversation = getOneToOneConversation(to); + auto conversation = convModule()->getOneToOneConversation(to); if (conversation.empty()) - conversation = startConversation(ConversationMode::ONE_TO_ONE, to); + conversation = convModule()->startConversation(ConversationMode::ONE_TO_ONE, to); if (not conversation.empty()) { std::lock_guard<std::mutex> lock(configurationMutex_); if (accountManager_) @@ -3483,7 +3448,10 @@ JamiAccount::onIsComposing(const std::string& conversationId, { try { const std::string fromUri {parseJamiUri(peer)}; - Account::onIsComposing(conversationId, fromUri, isWriting); + emitSignal<DRing::ConfigurationSignal::ComposingStatusChanged>(accountID_, + conversationId, + peer, + isWriting ? 1 : 0); } catch (...) { JAMI_ERR("[Account %s] Can't parse URI: %s", getAccountID().c_str(), peer.c_str()); } @@ -3688,841 +3656,144 @@ JamiAccount::setActiveCodecs(const std::vector<unsigned>& list) } } -std::string -JamiAccount::startConversation(ConversationMode mode, const std::string& otherMember) +// Member management +void +JamiAccount::saveMembers(const std::string& convId, const std::vector<std::string>& members) { - // Create the conversation object - std::shared_ptr<Conversation> conversation; - try { - conversation = std::make_shared<Conversation>(weak(), mode, otherMember); - } catch (const std::exception& e) { - JAMI_ERR("[Account %s] Error while generating a conversation %s", - getAccountID().c_str(), - e.what()); - return {}; - } - auto convId = conversation->id(); - { - std::lock_guard<std::mutex> lk(conversationsMtx_); - conversations_[convId] = std::move(conversation); - } - - // Update convInfo - ConvInfo info; - info.id = convId; - info.created = std::time(nullptr); - info.members.emplace_back(getUsername()); - if (!otherMember.empty()) - info.members.emplace_back(otherMember); - addNewConversation(info); - - runOnMainThread([w = weak()]() { - // Invite connected devices for the same user - auto shared = w.lock(); - if (!shared or !shared->accountManager_) - return; - - // Send to connected devices - shared->syncWithConnected(); - }); - - emitSignal<DRing::ConversationSignal::ConversationReady>(accountID_, convId); - return convId; + convModule()->setConversationMembers(convId, members); } void -JamiAccount::acceptConversationRequest(const std::string& conversationId) +JamiAccount::sendInstantMessage(const std::string& convId, + const std::map<std::string, std::string>& msg) { - // For all conversation members, try to open a git channel with this conversation ID - auto request = accountManager_->getRequest(conversationId); - if (request == std::nullopt) { - JAMI_WARN("[Account %s] Request not found for conversation %s", - getAccountID().c_str(), - conversationId.c_str()); - return; - } - if (!startFetch(conversationId)) - return; - auto memberHash = dht::InfoHash(request->from); - if (!memberHash) { - JAMI_WARN("Invalid member detected: %s", request->from.c_str()); + auto members = convModule()->getConversationMembers(convId); + if (members.empty()) { + // TODO remove, it's for old API for contacts + sendTextMessage(convId, msg); return; } - forEachDevice(memberHash, - [this, request = *request](const std::shared_ptr<dht::crypto::PublicKey>& dev) { - auto deviceId = dev->getLongId(); - if (deviceId == dht()->getPublicKey()->getLongId()) - return; - connectionManager().connectDevice( - deviceId, - "git://" + deviceId.toString() + "/" + request.conversationId, - [this, request](std::shared_ptr<ChannelSocket> socket, - const DeviceId& dev) { - if (socket) { - std::unique_lock<std::mutex> lk(pendingConversationsFetchMtx_); - auto& pending = pendingConversationsFetch_[request.conversationId]; - if (!pending.ready) { - pending.ready = true; - pending.deviceId = dev.toString(); - lk.unlock(); - // Save the git socket - addGitSocket(dev, request.conversationId, socket); - } else { - lk.unlock(); - socket->shutdown(); - } - } - }); - }); - accountManager_->rmConversationRequest(conversationId); - ConvInfo info; - info.id = conversationId; - info.created = std::time(nullptr); - info.members.emplace_back(getUsername()); - info.members.emplace_back(request->from); - runOnMainThread([w = weak(), info = std::move(info)] { - if (auto shared = w.lock()) - shared->addNewConversation(info); - }); - checkConversationsEvents(); -} - -void -JamiAccount::checkConversationsEvents() -{ - bool hasHandler = conversationsEventHandler and not conversationsEventHandler->isCancelled(); - std::lock_guard<std::mutex> lk(pendingConversationsFetchMtx_); - if (not pendingConversationsFetch_.empty() and not hasHandler) { - conversationsEventHandler = Manager::instance().scheduler().scheduleAtFixedRate( - [w = weak()] { - if (auto this_ = w.lock()) - return this_->handlePendingConversations(); - return false; - }, - std::chrono::milliseconds(10)); - } else if (pendingConversationsFetch_.empty() and hasHandler) { - conversationsEventHandler->cancel(); - conversationsEventHandler.reset(); + for (const auto& m : members) { + auto uri = m.at("uri"); + auto token = std::uniform_int_distribution<uint64_t> {1, JAMI_ID_MAX_VAL}(rand); + // Announce to all members that a new message is sent + sendTextMessage(uri, msg, token, false, true); } } bool -JamiAccount::handlePendingConversations() -{ - std::lock_guard<std::mutex> lk(pendingConversationsFetchMtx_); - for (auto it = pendingConversationsFetch_.begin(); it != pendingConversationsFetch_.end();) { - if (it->second.ready && !it->second.cloning) { - it->second.cloning = true; - dht::ThreadPool::io().run([w = weak(), - conversationId = it->first, - deviceId = it->second.deviceId]() { - auto shared = w.lock(); - auto info = shared->accountManager_->getInfo(); - if (!shared or !info) - return; - // Clone and store conversation - try { - auto conversation = std::make_shared<Conversation>(w, deviceId, conversationId); - auto erasePending = [&] { - std::lock_guard<std::mutex> lk(shared->pendingConversationsFetchMtx_); - shared->pendingConversationsFetch_.erase(conversationId); - }; - - if (!conversation->isMember(shared->getUsername(), true)) { - JAMI_ERR("Conversation cloned but doesn't seems to be a valid member"); - conversation->erase(); - erasePending(); - return; - } - if (conversation) { - auto removeRepo = false; - { - std::lock_guard<std::mutex> lk(shared->conversationsMtx_); - // Note: a removeContact while cloning. In this case, the conversation - // must not be announced and removed. - auto& ci = info->conversations; - auto itConv = ci.find(conversationId); - if (itConv != ci.end() && itConv->second.removed) - removeRepo = true; - shared->conversations_.emplace(conversationId, conversation); - } - if (removeRepo) { - shared->removeRepository(conversationId, false, true); - erasePending(); - return; - } - auto commitId = conversation->join(); - if (!commitId.empty()) { - runOnMainThread([w, conversationId, commitId]() { - if (auto shared = w.lock()) { - std::lock_guard<std::mutex> lk(shared->conversationsMtx_); - auto it = shared->conversations_.find(conversationId); - // Do not sync as it's synched by convInfos - if (it != shared->conversations_.end()) - shared->sendMessageNotification(*it->second, - commitId, - false); - } - }); - } - erasePending(); - // Inform user that the conversation is ready - emitSignal<DRing::ConversationSignal::ConversationReady>(shared->accountID_, - conversationId); - shared - ->syncWithConnected(); // This informs other devices to clone the conversation - } - } catch (const std::exception& e) { - emitSignal<DRing::ConversationSignal::OnConversationError>(shared->accountID_, - conversationId, - EFETCH, - e.what()); - JAMI_WARN("Something went wrong when cloning conversation: %s", e.what()); - } - }); +JamiAccount::handleMessage(const std::string& from, const std::pair<std::string, std::string>& m) +{ + if (m.first == MIME_TYPE_GIT) { + Json::Value json; + std::string err; + Json::CharReaderBuilder rbuilder; + auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader()); + if (!reader->parse(m.second.data(), m.second.data() + m.second.size(), &json, &err)) { + JAMI_ERR("Can't parse server response: %s", err.c_str()); + return false; } - ++it; - } - return !pendingConversationsFetch_.empty(); -} -void -JamiAccount::declineConversationRequest(const std::string& conversationId) -{ - auto request = accountManager_->getRequest(conversationId); - if (request == std::nullopt) - return; - request->declined = std::time(nullptr); - accountManager_->addConversationRequest(conversationId, std::move(*request)); - emitSignal<DRing::ConversationSignal::ConversationRequestDeclined>(getAccountID(), - conversationId); - - syncWithConnected(); -} + JAMI_WARN("Received indication for new commit available in conversation %s", + json["id"].asString().c_str()); -bool -JamiAccount::removeConversation(const std::string& conversationId) -{ - std::unique_lock<std::mutex> lock(configurationMutex_); - if (!accountManager_) - return false; - std::unique_lock<std::mutex> lk(conversationsMtx_); - auto it = conversations_.find(conversationId); - if (it == conversations_.end()) { - JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str()); - return false; - } - auto members = it->second->getMembers(); - auto hasMembers = !(members.size() == 1 - && username_.find(members[0]["uri"]) != std::string::npos); - // Update convInfos - auto infos = accountManager_->getInfo(); - if (!infos) - return false; - auto ci = infos->conversations; - auto itConv = ci.find(conversationId); - if (itConv != ci.end()) { - itConv->second.removed = std::time(nullptr); - // Sync now, because it can take some time to really removes the datas - runOnMainThread([w = weak(), hasMembers]() { - // Invite connected devices for the same user - auto shared = w.lock(); - // Send to connected devices - if (shared && hasMembers) - shared->syncWithConnected(); - }); - } - accountManager_->setConversations(ci); - lock.unlock(); - auto commitId = it->second->leave(); - emitSignal<DRing::ConversationSignal::ConversationRemoved>(accountID_, conversationId); - if (hasMembers) { - JAMI_DBG() << "Wait that someone sync that user left conversation " << conversationId; - // Commit that we left - if (!commitId.empty()) { - // Do not sync as it's synched by convInfos - sendMessageNotification(*it->second, commitId, false); - } else { - JAMI_ERR("Failed to send message to conversation %s", conversationId.c_str()); + convModule()->onNewCommit(from, + json["deviceId"].asString(), + json["id"].asString(), + json["commit"].asString()); + return true; + } else if (m.first == MIME_TYPE_INVITE) { + convModule()->onNeedConversationRequest(from, m.second); + return true; + } else if (m.first == MIME_TYPE_INVITE_JSON) { + Json::Value json; + std::string err; + Json::CharReaderBuilder rbuilder; + auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader()); + if (!reader->parse(m.second.data(), m.second.data() + m.second.size(), &json, &err)) { + JAMI_ERR("Can't parse server response: %s", err.c_str()); + return false; } - // In this case, we wait that another peer sync the conversation - // to definitely remove it from the device. This is to inform the - // peer that we left the conversation and never want to receives - // any messages + convModule()->onConversationRequest(from, json); return true; - } - lk.unlock(); - // Else we are the last member, so we can remove - removeRepository(conversationId, true); - return true; -} - -std::vector<std::string> -JamiAccount::getConversations() -{ - std::vector<std::string> result; - if (auto info = accountManager_->getInfo()) { - std::lock_guard<std::mutex> lk(conversationsMtx_); - result.reserve(info->conversations.size()); - for (const auto& [key, conv] : info->conversations) { - if (conv.removed) - continue; - result.emplace_back(key); + } else if (m.first == MIME_TYPE_TEXT_PLAIN) { + // This means that a text is received, so that + // the conversation is not a swarm. For compatibility, + // check if we have a swarm created. It can be the case + // when the trust request confirm was not received. + convModule()->checkIfRemoveForCompat(from); + } else if (m.first == MIME_TYPE_IM_COMPOSING) { + try { + static const std::regex COMPOSING_REGEX("<state>\\s*(\\w+)\\s*<\\/state>"); + std::smatch matched_pattern; + std::regex_search(m.second, matched_pattern, COMPOSING_REGEX); + bool isComposing {false}; + if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) { + isComposing = matched_pattern[1] == "active"; + } + static const std::regex CONVID_REGEX("<conversation>\\s*(\\w+)\\s*<\\/conversation>"); + std::regex_search(m.second, matched_pattern, CONVID_REGEX); + std::string conversationId = ""; + if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) { + conversationId = matched_pattern[1]; + } + onIsComposing(conversationId, from, isComposing); + return true; + } catch (const std::exception& e) { + JAMI_WARN("Error parsing composing state: %s", e.what()); } - } - return result; -} + } else if (m.first == MIME_TYPE_IMDN) { + try { + static const std::regex IMDN_MSG_ID_REGEX("<message-id>\\s*(\\w+)\\s*<\\/message-id>"); + static const std::regex IMDN_STATUS_REGEX("<status>\\s*<(\\w+)\\/>\\s*<\\/status>"); + std::smatch matched_pattern; + + std::regex_search(m.second, matched_pattern, IMDN_MSG_ID_REGEX); + std::string messageId; + if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) { + messageId = matched_pattern[1]; + } else { + JAMI_WARN("Message displayed: can't parse message ID"); + return false; + } -std::vector<std::map<std::string, std::string>> -JamiAccount::getConversationRequests() -{ - std::vector<std::map<std::string, std::string>> requests; - if (auto info = accountManager_->getInfo()) { - auto& convReq = info->conversationsRequests; - std::lock_guard<std::mutex> lk(accountManager_->conversationsRequestsMtx); - requests.reserve(convReq.size()); - for (const auto& [id, request] : convReq) { - if (request.declined) - continue; // Do not add declined requests - requests.emplace_back(request.toMap()); + std::regex_search(m.second, matched_pattern, IMDN_STATUS_REGEX); + bool isDisplayed {false}; + if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) { + isDisplayed = matched_pattern[1] == "displayed"; + } else { + JAMI_WARN("Message displayed: can't parse status"); + return false; + } + + static const std::regex CONVID_REGEX("<conversation>\\s*(\\w+)\\s*<\\/conversation>"); + std::regex_search(m.second, matched_pattern, CONVID_REGEX); + std::string conversationId = ""; + if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) { + conversationId = matched_pattern[1]; + } + + if (!isReadReceiptEnabled()) + return true; + if (conversationId.empty()) // Old method + messageEngine_.onMessageDisplayed(from, from_hex_string(messageId), isDisplayed); + else if (isDisplayed) { + convModule()->onMessageDisplayed(from, conversationId, messageId); + JAMI_DBG() << "[message " << messageId << "] Displayed by peer"; + emitSignal<DRing::ConfigurationSignal::AccountMessageStatusChanged>( + accountID_, + conversationId, + from, + messageId, + static_cast<int>(DRing::Account::MessageStates::DISPLAYED)); + } + return true; + } catch (const std::exception& e) { + JAMI_WARN("Error parsing display notification: %s", e.what()); } } - return requests; -} - -void -JamiAccount::updateConversationInfos(const std::string& conversationId, - const std::map<std::string, std::string>& infos, - bool sync) -{ - std::lock_guard<std::mutex> lk(conversationsMtx_); - // Add a new member in the conversation - auto it = conversations_.find(conversationId); - if (it == conversations_.end()) { - JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str()); - return; - } - - it->second - ->updateInfos(infos, - [w = weak(), conversationId, sync](bool ok, const std::string& commitId) { - if (ok && sync) { - auto shared = w.lock(); - if (shared) { - std::lock_guard<std::mutex> lk(shared->conversationsMtx_); - auto it = shared->conversations_.find(conversationId); - if (it != shared->conversations_.end() && it->second) { - shared->sendMessageNotification(*it->second, commitId, true); - } - } - } else if (sync) - JAMI_WARN("Couldn't update infos on %s", conversationId.c_str()); - }); -} - -std::map<std::string, std::string> -JamiAccount::conversationInfos(const std::string& conversationId) const -{ - if (auto info = accountManager_->getInfo()) { - std::lock_guard<std::mutex> lk(conversationsMtx_); - // Add a new member in the conversation - auto it = conversations_.find(conversationId); - if (it == conversations_.end() or not it->second) { - auto itConv = info->conversations.find(conversationId); - if (itConv == info->conversations.end()) { - JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str()); - return {}; - } - return {{"syncing", "true"}}; - } - - return it->second->infos(); - } - return {}; -} - -std::vector<uint8_t> -JamiAccount::conversationVCard(const std::string& conversationId) const -{ - std::lock_guard<std::mutex> lk(conversationsMtx_); - // Add a new member in the conversation - auto it = conversations_.find(conversationId); - if (it == conversations_.end()) { - JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str()); - return {}; - } - - return it->second->vCard(); -} - -// Member management -void -JamiAccount::saveMembers(const std::string& convId, const std::vector<std::string>& members) -{ - auto infos = accountManager_->getInfo(); - if (!infos) - return; - accountManager_->setConversationMembers(convId, members); -} - -void -JamiAccount::addConversationMember(const std::string& conversationId, - const std::string& contactUri, - bool sendRequest) -{ - std::unique_lock<std::mutex> lk(conversationsMtx_); - // Add a new member in the conversation - auto it = conversations_.find(conversationId); - if (it == conversations_.end()) { - JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str()); - return; - } - - if (it->second->isMember(contactUri, true)) { - JAMI_DBG("%s is already a member of %s, resend invite", - contactUri.c_str(), - conversationId.c_str()); - // Note: This should not be necessary, but if for whatever reason the other side didn't join - // we should not forbid new invites - auto invite = it->second->generateInvitation(); - lk.unlock(); - sendTextMessage(contactUri, invite); - return; - } - - it->second->addMember(contactUri, - [w = weak(), - conversationId, - sendRequest, - contactUri](bool ok, const std::string& commitId) { - if (ok) { - auto shared = w.lock(); - if (shared) { - std::unique_lock<std::mutex> lk(shared->conversationsMtx_); - auto it = shared->conversations_.find(conversationId); - if (it != shared->conversations_.end() && it->second) { - shared->sendMessageNotification(*it->second, - commitId, - true); - if (sendRequest) { - auto invite = it->second->generateInvitation(); - lk.unlock(); - shared->sendTextMessage(contactUri, invite); - } - } - } - } - }); -} - -void -JamiAccount::removeConversationMember(const std::string& conversationId, - const std::string& contactUri, - bool isDevice) -{ - std::lock_guard<std::mutex> lk(conversationsMtx_); - auto it = conversations_.find(conversationId); - if (it != conversations_.end() && it->second) { - it->second->removeMember(contactUri, - isDevice, - [w = weak(), conversationId](bool ok, const std::string& commitId) { - if (ok) { - auto shared = w.lock(); - if (shared) { - std::lock_guard<std::mutex> lk( - shared->conversationsMtx_); - auto it = shared->conversations_.find(conversationId); - if (it != shared->conversations_.end() && it->second) { - shared->sendMessageNotification(*it->second, - commitId, - true); - } - } - } - }); - } -} - -std::vector<std::map<std::string, std::string>> -JamiAccount::getConversationMembers(const std::string& conversationId) const -{ - std::unique_lock<std::mutex> lk(conversationsMtx_); - auto conversation = conversations_.find(conversationId); - if (conversation != conversations_.end() && conversation->second) - return conversation->second->getMembers(true, true); - - lk.unlock(); - if (auto info = accountManager_->getInfo()) { - auto convIt = info->conversations.find(conversationId); - if (convIt != info->conversations.end()) { - std::vector<std::map<std::string, std::string>> result; - result.reserve(convIt->second.members.size()); - for (const auto& uri : convIt->second.members) { - result.emplace_back(std::map<std::string, std::string> {{"uri", uri}}); - } - return result; - } - } - return {}; -} - -// Message send/load -void -JamiAccount::sendMessage(const std::string& conversationId, - const std::string& message, - const std::string& parent, - const std::string& type, - bool announce, - const OnDoneCb& cb) -{ - Json::Value json; - json["body"] = message; - json["type"] = type; - sendMessage(conversationId, json, parent, announce, cb); -} - -void -JamiAccount::sendMessage(const std::string& conversationId, - const Json::Value& value, - const std::string& parent, - bool announce, - const OnDoneCb& cb) -{ - std::lock_guard<std::mutex> lk(conversationsMtx_); - auto conversation = conversations_.find(conversationId); - if (conversation != conversations_.end() && conversation->second) { - conversation->second->sendMessage( - value, - parent, - [w = weak(), conversationId, announce, cb = std::move(cb)](bool ok, - const std::string& commitId) { - if (cb) - cb(ok, commitId); - if (!announce) - return; - if (ok) { - auto shared = w.lock(); - if (shared) { - std::lock_guard<std::mutex> lk(shared->conversationsMtx_); - auto it = shared->conversations_.find(conversationId); - if (it != shared->conversations_.end() && it->second) { - shared->sendMessageNotification(*it->second, commitId, true); - } - } - } else - JAMI_ERR("Failed to send message to conversation %s", conversationId.c_str()); - }); - } -} - -void -JamiAccount::sendInstantMessage(const std::string& convId, - const std::map<std::string, std::string>& msg) -{ - std::unique_lock<std::mutex> lk(conversationsMtx_); - auto conversation = conversations_.find(convId); - if (conversation != conversations_.end() && conversation->second) { - for (const auto& members : conversation->second->getMembers()) { - auto uri = members.at("uri"); - auto token = std::uniform_int_distribution<uint64_t> {1, DRING_ID_MAX_VAL}(rand); - // Announce to all members that a new message is sent - sendTextMessage(uri, msg, token, false, true); - } - } else { - lk.unlock(); - // TODO remove, it's for old API for contacts - sendTextMessage(convId, msg); - } -} - -uint32_t -JamiAccount::loadConversationMessages(const std::string& conversationId, - const std::string& fromMessage, - size_t n) -{ - if (!isConversation(conversationId)) - return 0; - const uint32_t id = LoadIdDist()(rand); - std::lock_guard<std::mutex> lk(conversationsMtx_); - auto conversation = conversations_.find(conversationId); - if (conversation != conversations_.end() && conversation->second) { - conversation->second->loadMessages( - [w = weak(), conversationId, id](auto&& messages) { - if (auto shared = w.lock()) { - emitSignal<DRing::ConversationSignal::ConversationLoaded>(id, - shared->getAccountID(), - conversationId, - messages); - } - }, - fromMessage, - n); - } - return id; -} - -uint32_t -JamiAccount::countInteractions(const std::string& convId, - const std::string& toId, - const std::string& fromId, - const std::string& authorUri) const -{ - std::lock_guard<std::mutex> lk(conversationsMtx_); - auto conversation = conversations_.find(convId); - if (conversation != conversations_.end() && conversation->second) { - return conversation->second->countInteractions(toId, fromId, authorUri); - } - return 0; -} - -void -JamiAccount::onNewGitCommit(const std::string& peer, - const std::string& deviceId, - const std::string& conversationId, - const std::string& commitId) -{ - auto info = accountManager_->getInfo(); - if (!info) - return; - auto itConv = info->conversations.find(conversationId); - if (itConv != info->conversations.end() && itConv->second.removed) - return; // ignore new commits for removed conversation - JAMI_DBG("[Account %s] on new commit notification from %s, for %s, commit %s", - getAccountID().c_str(), - peer.c_str(), - conversationId.c_str(), - commitId.c_str()); - fetchNewCommits(peer, DeviceId(deviceId), conversationId, commitId); -} - -void -JamiAccount::onMessageDisplayed(const std::string& peer, - const std::string& conversationId, - const std::string& interactionId) -{ - std::unique_lock<std::mutex> lk(conversationsMtx_); - auto conversation = conversations_.find(conversationId); - if (conversation != conversations_.end() && conversation->second) { - conversation->second->setMessageDisplayed(peer, interactionId); - } -} - -void -JamiAccount::fetchNewCommits(const std::string& peer, - const DeviceId& deviceId, - const std::string& conversationId, - const std::string& commitId) -{ - JAMI_DBG("[Account %s] fetch commits for peer %s on device %s", - getAccountID().c_str(), - peer.c_str(), - deviceId.to_c_str()); - - std::unique_lock<std::mutex> lk(conversationsMtx_); - auto conversation = conversations_.find(conversationId); - if (conversation != conversations_.end() && conversation->second) { - if (!conversation->second->isMember(peer, true)) { - JAMI_WARN("[Account %s] %s is not a member of %s", - getAccountID().c_str(), - peer.c_str(), - conversationId.c_str()); - return; - } - if (conversation->second->isBanned(deviceId.toString())) { - JAMI_WARN("[Account %s] %s is a banned device in conversation %s", - getAccountID().c_str(), - deviceId.to_c_str(), - conversationId.c_str()); - return; - } - - // Retrieve current last message - auto lastMessageId = conversation->second->lastCommitId(); - if (lastMessageId.empty()) { - JAMI_ERR("[Account %s] No message detected. This is a bug", getAccountID().c_str()); - return; - } - - if (hasGitSocket(deviceId, conversationId)) { - conversation->second->sync( - peer, - deviceId.toString(), - [peer, deviceId, conversationId, commitId, w = weak()](bool ok) { - auto shared = w.lock(); - if (!shared) - return; - if (!ok) { - JAMI_WARN("[Account %s] Could not fetch new commit from %s for %s, other " - "peer may be disconnected", - shared->getAccountID().c_str(), - deviceId.to_c_str(), - conversationId.c_str()); - shared->removeGitSocket(deviceId, conversationId); - JAMI_INFO("[Account %s] Relaunch sync with %s for %s", - shared->getAccountID().c_str(), - deviceId.to_c_str(), - conversationId.c_str()); - runOnMainThread([=]() { - if (auto shared = w.lock()) - shared->fetchNewCommits(peer, deviceId, conversationId, commitId); - }); - } - }, - commitId); - } else { - lk.unlock(); - // Else we need to add a new gitSocket - std::lock_guard<std::mutex> lkCM(connManagerMtx_); - if (!connectionManager_ || !startFetch(conversationId)) - return; - connectionManager_->connectDevice( - deviceId, - "git://" + deviceId.toString() + "/" + conversationId, - [this, conversationId, commitId, peer](std::shared_ptr<ChannelSocket> socket, - const DeviceId& deviceId) { - dht::ThreadPool::io().run([w = weak(), - conversationId, - socket = std::move(socket), - peer, - deviceId, - commitId] { - auto shared = w.lock(); - if (!shared) - return; - std::unique_lock<std::mutex> lk(shared->conversationsMtx_); - auto conversation = shared->conversations_.find(conversationId); - if (!conversation->second) - return; - if (socket) { - shared->addGitSocket(deviceId, conversationId, socket); - - conversation->second->sync( - peer, - deviceId.toString(), - [deviceId, conversationId, w](bool ok) { - auto shared = w.lock(); - if (!shared) - return; - if (!ok) { - JAMI_WARN("[Account %s] Could not fetch new commit " - "from %s for %s", - shared->getAccountID().c_str(), - deviceId.to_c_str(), - conversationId.c_str()); - shared->removeGitSocket(deviceId, conversationId); - } - }, - commitId); - } else { - JAMI_ERR("[Account %s] Couldn't open a new git channel with %s for " - "conversation %s", - shared->getAccountID().c_str(), - deviceId.to_c_str(), - conversationId.c_str()); - } - { - std::lock_guard<std::mutex> lk(shared->pendingConversationsFetchMtx_); - shared->pendingConversationsFetch_.erase(conversationId); - } - }); - }); - } - } else { - lk.unlock(); - - if (accountManager_->getRequest(conversationId) != std::nullopt) - return; - { - // Check if the conversation is cloning - std::lock_guard<std::mutex> lk(pendingConversationsFetchMtx_); - if (pendingConversationsFetch_.find(conversationId) - != pendingConversationsFetch_.end()) { - return; - } - } - auto infos = accountManager_->getInfo(); - if (!infos) - return; - auto itConv = infos->conversations.find(conversationId); - if (itConv != infos->conversations.end()) { - cloneConversation(deviceId, peer, itConv->first); - return; - } - JAMI_WARN("[Account %s] Could not find conversation %s, ask for an invite", - getAccountID().c_str(), - conversationId.c_str()); - sendTextMessage(peer, {{"application/invite", conversationId}}); - } -} - -void -JamiAccount::onConversationRequest(const std::string& from, const Json::Value& value) -{ - ConversationRequest req(value); - JAMI_INFO("[Account %s] Receive a new conversation request for conversation %s from %s", - getAccountID().c_str(), - req.conversationId.c_str(), - from.c_str()); - auto convId = req.conversationId; - req.from = from; - - if (accountManager_->getRequest(convId) != std::nullopt) { - JAMI_INFO("[Account %s] Received a request for a conversation already existing. " - "Ignore", - getAccountID().c_str()); - return; - } - req.received = std::time(nullptr); - auto reqMap = req.toMap(); - accountManager_->addConversationRequest(convId, std::move(req)); - // Note: no need to sync here because over connected devices should receives - // the same conversation request. Will sync when the conversation will be added - - emitSignal<DRing::ConversationSignal::ConversationRequestReceived>(accountID_, convId, reqMap); -} - -void -JamiAccount::onNeedConversationRequest(const std::string& from, const std::string& conversationId) -{ - // Check if conversation exists - std::unique_lock<std::mutex> lk(conversationsMtx_); - auto itConv = conversations_.find(conversationId); - if (itConv != conversations_.end() && !itConv->second->isRemoving()) { - // Check if isMember - if (!itConv->second->isMember(from, true)) { - JAMI_WARN("%s is asking a new invite for %s, but not a member", - from.c_str(), - conversationId.c_str()); - return; - } - - // Send new invite - auto invite = itConv->second->generateInvitation(); - lk.unlock(); - JAMI_DBG("%s is asking a new invite for %s", from.c_str(), conversationId.c_str()); - sendTextMessage(from, invite); - } -} - -void -JamiAccount::checkIfRemoveForCompat(const std::string& peerUri) -{ - auto convId = getOneToOneConversation(peerUri); - if (convId.empty()) - return; - std::unique_lock<std::mutex> lk(conversationsMtx_); - auto it = conversations_.find(convId); - if (it == conversations_.end()) { - JAMI_ERR("Conversation %s doesn't exist", convId.c_str()); - return; - } - // We will only removes the conversation if the member is invited - // the contact can have mutiple devices with only some with swarm - // support, in this case, just go with recent versions. - if (it->second->isMember(peerUri)) - return; - lk.unlock(); - removeConversation(convId); + + return false; } void @@ -4728,18 +3999,6 @@ JamiAccount::needToSendProfile(const std::string& deviceId) return not fileutils::isFile(vCardPath + DIR_SEPARATOR_STR + deviceId); } -bool -JamiAccount::startFetch(const std::string& convId) -{ - std::lock_guard<std::mutex> lk(pendingConversationsFetchMtx_); - auto it = pendingConversationsFetch_.find(convId); - if (it == pendingConversationsFetch_.end()) { - pendingConversationsFetch_[convId] = PendingConversationFetch {}; - return true; - } - return it->second.ready; -} - bool JamiAccount::sendSIPMessage(SipConnection& conn, const std::string& to, @@ -4905,7 +4164,7 @@ JamiAccount::cacheSIPConnection(std::shared_ptr<ChannelSocket>&& socket, sendProfile(deviceId.toString()); - syncConversations(peerId, deviceId); + convModule()->syncConversations(peerId, deviceId.toString()); // Retry messages messageEngine_.onPeerOnline(peerId); @@ -4986,7 +4245,7 @@ JamiAccount::cacheSyncConnection(std::shared_ptr<ChannelSocket>&& socket, } }); - socket->setOnRecv([this, deviceId = device, peerId](const uint8_t* buf, size_t len) { + socket->setOnRecv([this, device, peerId](const uint8_t* buf, size_t len) { if (!buf) return len; @@ -5000,85 +4259,10 @@ JamiAccount::cacheSyncConnection(std::shared_ptr<ChannelSocket>&& socket, return len; } - std::unique_lock<std::mutex> lock(configurationMutex_); - - if (auto manager = dynamic_cast<ArchiveAccountManager*>(accountManager_.get())) { + if (auto manager = dynamic_cast<ArchiveAccountManager*>(accountManager_.get())) manager->onSyncData(std::move(msg.ds), false); - } - - std::vector<std::string> toRm; - - for (const auto& [key, convInfo] : msg.c) { - auto convId = convInfo.id; - auto removed = convInfo.removed; - accountManager_->rmConversationRequest(convId); - auto info = accountManager_->getInfo(); - if (not removed) { - // If multi devices, it can detect a conversation that was already - // removed, so just check if the convinfo contains a removed conv - auto itConv = info->conversations.find(convId); - if (itConv != info->conversations.end() && itConv->second.removed) - continue; - cloneConversation(deviceId, peerId, convId); - } else { - { - std::lock_guard<std::mutex> lk(conversationsMtx_); - auto itConv = conversations_.find(convId); - if (itConv != conversations_.end() && !itConv->second->isRemoving()) { - emitSignal<DRing::ConversationSignal::ConversationRemoved>(accountID_, - convId); - itConv->second->setRemovingFlag(); - } - } - auto ci = info->conversations; - auto itConv = ci.find(convId); - if (itConv != ci.end()) { - itConv->second.removed = std::time(nullptr); - if (convInfo.erased) { - itConv->second.erased = std::time(nullptr); - toRm.emplace_back(convId); - } - break; - } - accountManager_->setConversations(std::move(ci)); - } - } - - for (const auto& [convId, req] : msg.cr) { - if (isConversation(convId)) { - // Already accepted request - accountManager_->rmConversationRequest(convId); - continue; - } - - // New request - if (!accountManager_->addConversationRequest(convId, req)) - continue; // Already added - - if (req.declined != 0) { - // Request declined - JAMI_INFO("[Account %s] Declined request detected for conversation %s (device %s)", - getAccountID().c_str(), - convId.c_str(), - deviceId.to_c_str()); - emitSignal<DRing::ConversationSignal::ConversationRequestDeclined>(getAccountID(), - convId); - continue; - } - JAMI_INFO("[Account %s] New request detected for conversation %s (device %s)", - getAccountID().c_str(), - convId.c_str(), - deviceId.to_c_str()); - - emitSignal<DRing::ConversationSignal::ConversationRequestReceived>(getAccountID(), - convId, - req.toMap()); - } - lock.unlock(); - for (const auto& convId : toRm) { - removeRepository(convId, false); - } + convModule()->onSyncData(msg, peerId, device.toString()); return len; }); sendProfile(device.toString()); @@ -5117,20 +4301,18 @@ JamiAccount::syncWith(const DeviceId& deviceId, const std::shared_ptr<ChannelSoc void JamiAccount::syncInfos(const std::shared_ptr<ChannelSocket>& socket) { - auto info = accountManager_->getInfo(); - std::unique_lock<std::mutex> lk(accountManager_->conversationsRequestsMtx); - // Sync conversations - if (not info or not socket - or (info->conversations.empty() and info->conversationsRequests.empty())) - return; Json::Value syncValue; std::error_code ec; msgpack::sbuffer buffer(8192); SyncMsg msg; - if (info->contacts) - msg.ds = info->contacts->getSyncData(); - msg.c = info->conversations; - msg.cr = info->conversationsRequests; + { + std::lock_guard<std::mutex> lock(configurationMutex_); + if (auto info = accountManager_->getInfo()) + if (info->contacts) + msg.ds = info->contacts->getSyncData(); + } + msg.c = ConversationModule::convInfos(accountID_); + msg.cr = ConversationModule::convRequests(accountID_); msgpack::pack(buffer, msg); socket->write(reinterpret_cast<const unsigned char*>(buffer.data()), buffer.size(), ec); if (ec) @@ -5147,175 +4329,12 @@ JamiAccount::syncWithConnected() } } -void -JamiAccount::loadConvInfos() -{ - std::map<std::string, ConvInfo> convInfos; - try { - // read file - auto file = fileutils::loadFile("convInfo", idPath_); - // load values - msgpack::object_handle oh = msgpack::unpack((const char*) file.data(), file.size()); - oh.get().convert(convInfos); - } catch (const std::exception& e) { - JAMI_WARN("[convInfo] error loading convInfo: %s", e.what()); - return; - } - - for (auto& [key, info] : convInfos) { - std::lock_guard<std::mutex> lk(conversationsMtx_); - auto itConv = conversations_.find(info.id); - if (itConv != conversations_.end() && info.removed) { - itConv->second->setRemovingFlag(); - } - } - accountManager_->setConversations(convInfos); -} - -void -JamiAccount::loadConvRequests() -{ - try { - // read file - auto file = fileutils::loadFile("convRequests", idPath_); - // load values - msgpack::object_handle oh = msgpack::unpack((const char*) file.data(), file.size()); - std::map<std::string, ConversationRequest> map; - oh.get().convert(map); - accountManager_->setConversationsRequests(map); - } catch (const std::exception& e) { - JAMI_WARN("Error loading conversationsRequests: %s", e.what()); - } -} - -void -JamiAccount::loadConversations() -{ - JAMI_INFO("[Account %s] Start loading conversations…", getAccountID().c_str()); - auto conversationsRepositories = fileutils::readDirectory(idPath_ + DIR_SEPARATOR_STR - + "conversations"); - conversations_.clear(); - for (const auto& repository : conversationsRepositories) { - try { - auto conv = std::make_shared<Conversation>(weak(), repository); - std::lock_guard<std::mutex> lk(conversationsMtx_); - conversations_.emplace(repository, std::move(conv)); - } catch (const std::logic_error& e) { - JAMI_WARN("[Account %s] Conversations not loaded : %s", - getAccountID().c_str(), - e.what()); - } - } - loadConvInfos(); - loadConvRequests(); - JAMI_INFO("[Account %s] Conversations loaded!", getAccountID().c_str()); -} - std::shared_ptr<TransferManager> -JamiAccount::dataTransfer(const std::string& id) const +JamiAccount::dataTransfer(const std::string& id) { if (id.empty()) return nonSwarmTransferManager_; - std::unique_lock<std::mutex> lk(conversationsMtx_); - auto it = conversations_.find(id); - if (it != conversations_.end() && it->second) - return it->second->dataTransfer(); - return nullptr; -} - -void -JamiAccount::removeRepository(const std::string& conversationId, bool sync, bool force) -{ - std::lock_guard<std::mutex> lock(configurationMutex_); - std::unique_lock<std::mutex> lk(conversationsMtx_); - auto it = conversations_.find(conversationId); - if (it != conversations_.end() && it->second && (force || it->second->isRemoving())) { - if (!accountManager_) - return; - try { - if (it->second->mode() == ConversationMode::ONE_TO_ONE) { - for (const auto& member : it->second->getInitialMembers()) { - if (member != getUsername()) - accountManager_->removeContactConversation(member); - } - } - } catch (const std::exception& e) { - JAMI_ERR() << e.what(); - } - JAMI_DBG() << "Remove conversation: " << conversationId; - it->second->erase(); - conversations_.erase(it); - lk.unlock(); - auto info = accountManager_->getInfo(); - // Update convInfos - if (!sync or !info) - return; - auto ci = info->conversations; - auto convIt = ci.find(conversationId); - if (convIt != ci.end()) { - convIt->second.erased = std::time(nullptr); - runOnMainThread([w = weak()]() { - // Send to connected devices - if (auto shared = w.lock()) - shared->syncWithConnected(); - }); - } - accountManager_->setConversations(ci); - } -} - -void -JamiAccount::sendMessageNotification(const Conversation& conversation, - const std::string& commitId, - bool sync) -{ - Json::Value message; - message["id"] = conversation.id(); - message["commit"] = commitId; - // TODO avoid lookup - message["deviceId"] = std::string(currentDeviceId()); - Json::StreamWriterBuilder builder; - const auto text = Json::writeString(builder, message); - for (const auto& members : conversation.getMembers()) { - auto uri = members.at("uri"); - // Do not send to ourself, it's synced via convInfos - if (!sync && username_.find(uri) != std::string::npos) - continue; - // Announce to all members that a new message is sent - sendTextMessage(uri, {{"application/im-gitmessage-id", text}}); - } -} - -std::string -JamiAccount::getOneToOneConversation(const std::string& uri) const -{ - auto details = getContactDetails(uri); - auto it = details.find(DRing::Account::TrustRequest::CONVERSATIONID); - if (it != details.end()) - return it->second; - return {}; -} - -void -JamiAccount::addNewConversation(const ConvInfo& convInfo) -{ - std::lock_guard<std::mutex> lk(configurationMutex_); - if (accountManager_) - accountManager_->addConversation(convInfo); -} - -void -JamiAccount::addCallHistoryMessage(const std::string& uri, uint64_t duration_ms) -{ - auto finalUri = uri.substr(0, uri.find("@ring.dht")); - auto convId = getOneToOneConversation(finalUri); - if (!convId.empty()) { - Json::Value value; - value["to"] = finalUri; - value["type"] = "application/call-history+json"; - value["duration"] = std::to_string(duration_ms); - sendMessage(convId, value); - } + return convModule()->dataTransfer(id); } void @@ -5328,52 +4347,6 @@ JamiAccount::monitor() const connectionManager_->monitor(); } -void -JamiAccount::cloneConversation(const DeviceId& deviceId, - const std::string&, - const std::string& convId) -{ - JAMI_DBG("[Account %s] Clone conversation on device %s", - getAccountID().c_str(), - deviceId.to_c_str()); - - if (!isConversation(convId)) { - std::lock_guard<std::mutex> lkCM(connManagerMtx_); - if (!connectionManager_ || !startFetch(convId)) - return; - connectionManager_->connectDevice(deviceId, - fmt::format("git://{}/{}", deviceId, convId), - [this, convId](std::shared_ptr<ChannelSocket> socket, - const DeviceId& deviceId) { - if (socket) { - std::unique_lock<std::mutex> lk( - pendingConversationsFetchMtx_); - auto& pending = pendingConversationsFetch_[convId]; - if (!pending.ready) { - pending.ready = true; - pending.deviceId = deviceId.toString(); - lk.unlock(); - // Save the git socket - addGitSocket(deviceId, convId, socket); - checkConversationsEvents(); - } else { - lk.unlock(); - socket->shutdown(); - } - } - }); - - JAMI_INFO("[Account %s] New conversation detected: %s. Ask device %s to clone it", - getAccountID().c_str(), - convId.c_str(), - deviceId.to_c_str()); - } else { - JAMI_INFO("[Account %s] Already have conversation %s", - getAccountID().c_str(), - convId.c_str()); - } -} - void JamiAccount::sendFile(const std::string& conversationId, const std::string& path, @@ -5398,27 +4371,27 @@ JamiAccount::sendFile(const std::string& conversationId, value["sha3sum"] = fileutils::sha3File(path); value["type"] = "application/data-transfer+json"; - shared->sendMessage(conversationId, - value, - parent, - true, - [accId = shared->getAccountID(), - conversationId, - tid, - path](bool, const std::string& commitId) { - // Create a symlink to answer to re-ask - auto filelinkPath = fileutils::get_data_dir() - + DIR_SEPARATOR_STR + accId - + DIR_SEPARATOR_STR + "conversation_data" - + DIR_SEPARATOR_STR + conversationId - + DIR_SEPARATOR_STR + commitId + "_" - + std::to_string(tid); - auto extension = fileutils::getFileExtension(path); - if (!extension.empty()) - filelinkPath += "." + extension; - if (path != filelinkPath && !fileutils::isSymLink(filelinkPath)) - fileutils::createFileLink(filelinkPath, path, true); - }); + shared->convModule() + ->sendMessage(conversationId, + value, + parent, + true, + [accId = shared->getAccountID(), + conversationId, + tid, + path](bool, const std::string& commitId) { + // Create a symlink to answer to re-ask + auto filelinkPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + + accId + DIR_SEPARATOR_STR + + "conversation_data" + DIR_SEPARATOR_STR + + conversationId + DIR_SEPARATOR_STR + + commitId + "_" + std::to_string(tid); + auto extension = fileutils::getFileExtension(path); + if (!extension.empty()) + filelinkPath += "." + extension; + if (path != filelinkPath && !fileutils::isSymLink(filelinkPath)) + fileutils::createFileLink(filelinkPath, path, true); + }); } }); } @@ -5445,7 +4418,7 @@ JamiAccount::transferFile(const std::string& conversationId, size_t start, size_t end) { - auto channelName = "data-transfer://" + conversationId + "/" + currentDeviceId() + "/" + fileId; + auto channelName = DATA_TRANSFER_URI + conversationId + "/" + currentDeviceId() + "/" + fileId; std::lock_guard<std::mutex> lkCM(connManagerMtx_); if (!connectionManager_) return; @@ -5471,23 +4444,6 @@ JamiAccount::transferFile(const std::string& conversationId, }); } -bool -JamiAccount::downloadFile(const std::string& conversationId, - const std::string& interactionId, - const std::string& fileId, - const std::string& path, - size_t start, - size_t end) -{ - std::string sha3sum = {}; - std::lock_guard<std::mutex> lk(conversationsMtx_); - auto conversation = conversations_.find(conversationId); - if (conversation == conversations_.end() || !conversation->second) - return false; - - return conversation->second->downloadFile(interactionId, fileId, path, "", "", start, end); -} - void JamiAccount::askForFileChannel(const std::string& conversationId, const std::string& deviceId, @@ -5500,7 +4456,7 @@ JamiAccount::askForFileChannel(const std::string& conversationId, if (!connectionManager_) return; - auto channelName = "data-transfer://" + conversationId + "/" + currentDeviceId() + "/" + auto channelName = DATA_TRANSFER_URI + conversationId + "/" + currentDeviceId() + "/" + fileId; if (start != 0 || end != 0) { channelName += "?start=" + std::to_string(start) + "&end=" + std::to_string(end); @@ -5535,18 +4491,8 @@ JamiAccount::askForFileChannel(const std::string& conversationId, } else { // Only ask for connected devices. For others we will try // on new peer online - std::vector<std::string> members; - { - std::lock_guard<std::mutex> lk(conversationsMtx_); - auto conversation = conversations_.find(conversationId); - if (conversation != conversations_.end() && conversation->second) { - for (const auto& m : conversation->second->getMembers()) { - members.emplace_back(m.at("uri")); - } - } - } - for (const auto& member : members) { - accountManager_->forEachDevice(dht::InfoHash(member), + for (const auto& m : convModule()->getConversationMembers(conversationId)) { + accountManager_->forEachDevice(dht::InfoHash(m.at("uri")), [this, tryDevice = std::move(tryDevice)]( const std::shared_ptr<dht::crypto::PublicKey>& dev) { tryDevice(dev->getLongId()); @@ -5555,4 +4501,15 @@ JamiAccount::askForFileChannel(const std::string& conversationId, } } +void +JamiAccount::initConnectionManager() +{ + if (!connectionManager_) { + connectionManager_ = std::make_unique<ConnectionManager>(*this); + channelHandlers_[Uri::Scheme::GIT] + = std::make_unique<ConversationChannelHandler>(std::move(weak()), + *connectionManager_.get()); + } +} + } // namespace jami diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h index 15675e2b6522a7053387cc1e1539a290478ec359..0338817b4aaea3f981cbadcf19f3856454ea4ca2 100644 --- a/src/jamidht/jamiaccount.h +++ b/src/jamidht/jamiaccount.h @@ -34,6 +34,7 @@ #include "jamidht/conversation.h" #include "multiplexed_socket.h" #include "data_transfer.h" +#include "uri.h" #include "noncopyable.h" #include "ip_utils.h" @@ -42,6 +43,8 @@ #include "scheduled_executor.h" #include "connectionmanager.h" #include "gitserver.h" +#include "channel_handler.h" +#include "conversation_module.h" #include "conversationrepository.h" #include <opendht/dhtrunner.h> @@ -79,7 +82,6 @@ namespace jami { class IceTransport; struct Contact; -struct DeviceSync; struct AccountArchive; class DhtPeerConnector; class ContactList; @@ -239,6 +241,12 @@ public: */ std::string getServerUri() const { return ""; }; + void setIsComposing(const std::string& conversationUri, bool isWriting) override; + + bool setMessageDisplayed(const std::string& conversationUri, + const std::string& messageId, + int status) override; + /** * Get the contact header for * @return pj_str_t The contact header based on account information @@ -331,10 +339,8 @@ public: uint64_t sendTextMessage(const std::string& to, const std::map<std::string, std::string>& payloads) override; void sendInstantMessage(const std::string& convId, - const std::map<std::string, std::string>& msg) override; - void onIsComposing(const std::string& conversationId, - const std::string& peer, - bool isWriting) override; + const std::map<std::string, std::string>& msg); + void onIsComposing(const std::string& conversationId, const std::string& peer, bool isWriting); /* Devices */ void addDevice(const std::string& password); @@ -508,106 +514,18 @@ public: } std::string_view currentDeviceId() const; - // Conversation management - std::string startConversation(ConversationMode mode = ConversationMode::INVITES_ONLY, - const std::string& otherMember = ""); - void acceptConversationRequest(const std::string& conversationId); - void declineConversationRequest(const std::string& conversationId); - std::vector<std::string> getConversations(); - bool removeConversation(const std::string& conversationId); - std::vector<std::map<std::string, std::string>> getConversationRequests(); - - // Conversation's infos management - void updateConversationInfos(const std::string& conversationId, - const std::map<std::string, std::string>& infos, - bool sync = true); - std::map<std::string, std::string> conversationInfos(const std::string& conversationId) const; - std::vector<uint8_t> conversationVCard(const std::string& conversationId) const; // Member management void saveMembers(const std::string& convId, const std::vector<std::string>& members); // Save confInfos - void addConversationMember(const std::string& conversationId, - const std::string& contactUri, - bool sendRequest = true); - void removeConversationMember(const std::string& conversationId, - const std::string& contactUri, - bool isDevice = false); - std::vector<std::map<std::string, std::string>> getConversationMembers( - const std::string& conversationId) const; - - // Message send/load - void sendMessage(const std::string& conversationId, - const Json::Value& value, - const std::string& parent = "", - bool announce = true, - const OnDoneCb& cb = {}); - void sendMessage(const std::string& conversationId, - const std::string& message, - const std::string& parent = "", - const std::string& type = "text/plain", - bool announce = true, - const OnDoneCb& cb = {}); - /** - * Add to the related conversation the call history message - * @param uri Peer number - * @param duration_ms The call duration in ms - */ - void addCallHistoryMessage(const std::string& uri, uint64_t duration_ms); - uint32_t loadConversationMessages(const std::string& conversationId, - const std::string& fromMessage = "", - size_t n = 0); - - /** - * Retrieve how many interactions there is from HEAD to interactionId - * @param convId - * @param interactionId "" for getting the whole history - * @param authorUri author to stop counting - * @return number of interactions since interactionId - */ - uint32_t countInteractions(const std::string& convId, - const std::string& toId, - const std::string& fromId, - const std::string& authorUri = "") const; // Received a new commit notification - void onNewGitCommit(const std::string& peer, - const std::string& deviceId, - const std::string& conversationId, - const std::string& commitId) override; - // Received that a peer displayed a message - void onMessageDisplayed(const std::string& peer, - const std::string& conversationId, - const std::string& interactionId) override; - /** - * Pull remote device (do not do it if commitId is already in the current repo) - * @param peer Contact URI - * @param deviceId Contact's device - * @param conversationId - * @param commitId (optional) - */ - void fetchNewCommits(const std::string& peer, - const DeviceId& deviceId, - const std::string& conversationId, - const std::string& commitId = ""); - // Invites - void onConversationRequest(const std::string& from, const Json::Value&) override; - void onNeedConversationRequest(const std::string& from, - const std::string& conversationId) override; - void checkIfRemoveForCompat(const std::string& /*peerUri*/) override; + bool handleMessage(const std::string& from, + const std::pair<std::string, std::string>& message) override; void monitor() const; - /** - * Clone a conversation (initial) from device - * @param deviceId - * @param convId - */ - void cloneConversation(const DeviceId& deviceId, - const std::string& peer, - const std::string& convId); - // File transfer void sendFile(const std::string& conversationId, const std::string& path, @@ -626,19 +544,6 @@ public: const std::string& interactionId, size_t start = 0, size_t end = 0); - /** - * Ask conversation's members to send back a previous transfer to this deviec - * @param conversationId Related conversation - * @param interactionId Related interaction - * @param fileId Related fileId - * @param path where to download the file - */ - bool downloadFile(const std::string& conversationId, - const std::string& interactionId, - const std::string& fileId, - const std::string& path, - size_t start = 0, - size_t end = 0); void askForFileChannel(const std::string& conversationId, const std::string& deviceId, @@ -646,14 +551,14 @@ public: size_t start = 0, size_t end = 0); - void loadConversations(); - /** * Retrieve linked transfer manager * @param id conversationId or empty for fallback * @return linked transfer manager */ - std::shared_ptr<TransferManager> dataTransfer(const std::string& id = "") const; + std::shared_ptr<TransferManager> dataTransfer(const std::string& id = ""); + + ConversationModule* convModule(); /** * Send Profile via cached SIP connection @@ -664,6 +569,8 @@ public: std::string profilePath() const; + AccountManager* accountManager() { return accountManager_.get(); } + private: NON_COPYABLE(JamiAccount); @@ -674,7 +581,6 @@ private: * Private structures */ struct PendingCall; - struct PendingConversationFetch; struct PendingMessage; struct BuddyInfo; struct DiscoveredPeer; @@ -733,11 +639,6 @@ private: */ void onTrackedBuddyOnline(const dht::InfoHash&); - /** - * Sync conversations with detected peer - */ - void syncConversations(const std::string& peer, const DeviceId& deviceId); - /** * Maps require port via UPnP and other async ops */ @@ -800,10 +701,6 @@ private: */ void generateDhParams(); - void loadConvInfos(); - - void loadConvRequests(); - template<class... Args> std::shared_ptr<IceTransport> createIceTransport(const Args&... args); void newOutgoingCallHelper(const std::shared_ptr<SIPCall>& call, std::string_view toUri); @@ -838,15 +735,6 @@ private: mutable std::mutex buddyInfoMtx; std::map<dht::InfoHash, BuddyInfo> trackedBuddies_; - /** Conversations */ - mutable std::mutex conversationsMtx_ {}; - std::map<std::string, std::shared_ptr<Conversation>> conversations_; - bool isConversation(const std::string& convId) const - { - std::lock_guard<std::mutex> lk(conversationsMtx_); - return conversations_.find(convId) != conversations_.end(); - } - mutable std::mutex dhtValuesMtx_; bool dhtPublicInCalls_ {true}; @@ -1021,53 +909,13 @@ private: */ void sendProfile(const std::string& deviceId); - // Conversations - std::mutex pendingConversationsFetchMtx_ {}; - std::map<std::string, PendingConversationFetch> pendingConversationsFetch_; - bool startFetch(const std::string& convId); - std::mutex gitServersMtx_ {}; std::map<dht::Value::Id, std::unique_ptr<GitServer>> gitServers_ {}; - std::shared_ptr<RepeatedTask> conversationsEventHandler {}; - void checkConversationsEvents(); - bool handlePendingConversations(); - void syncWith(const DeviceId& deviceId, const std::shared_ptr<ChannelSocket>& socket); void syncInfos(const std::shared_ptr<ChannelSocket>& socket); void syncWithConnected(); - /** - * Remove a repository and all files - * @param convId - * @param sync If we send an update to other account's devices - * @param force True if ignore the removing flag - */ - void removeRepository(const std::string& convId, bool sync, bool force = false); - - /** - * Send a message notification to all members - * @param conversation - * @param commit - * @param sync If we send an update to other account's devices - */ - void sendMessageNotification(const Conversation& conversation, - const std::string& commitId, - bool sync); - - /** - * Get related conversation with member - * @param uri The member to search for - * @return the conversation id if found else empty - */ - std::string getOneToOneConversation(const std::string& uri) const; - - /** - * Add a new ConvInfo - * @param id of the conversation - */ - void addNewConversation(const ConvInfo& convInfo); - std::atomic_bool deviceAnnounced_ {false}; //// File transfer @@ -1076,6 +924,12 @@ private: std::map<std::string, std::shared_ptr<TransferManager>> transferManagers_ {}; bool noSha3sumVerification_ {false}; + + std::map<Uri::Scheme, std::unique_ptr<ChannelHandlerInterface>> channelHandlers_ {}; + + std::unique_ptr<ConversationModule> convModule_; + + void initConnectionManager(); }; static inline std::ostream& diff --git a/src/jamidht/p2p.cpp b/src/jamidht/p2p.cpp index 8edef02a9c714fc82b86e190ab84a927ae6affe0..c3fddd403d5afcd5add557a448bd3479d5380d10 100644 --- a/src/jamidht/p2p.cpp +++ b/src/jamidht/p2p.cpp @@ -232,14 +232,17 @@ DhtPeerConnector::requestConnection( // In a one_to_one conv with an old version, the contact here can be in an invited // state and will not support data-transfer. So if one_to_oe with non accepted, just // force to file:// for now. - auto members = acc->getConversationMembers(info.conversationId); + auto members = acc->convModule()->getConversationMembers(info.conversationId); auto preSwarmCompat = members.size() == 2 && members[1]["role"] == "invited"; if (preSwarmCompat) { - auto infos = acc->conversationInfos(info.conversationId); + auto infos = acc->convModule()->conversationInfos(info.conversationId); preSwarmCompat = infos["mode"] == "0"; } if (!preSwarmCompat) - channelName = fmt::format("data-transfer://{}/{}/{}", info.conversationId, acc->currentDeviceId(), tid); + channelName = fmt::format("data-transfer://{}/{}/{}", + info.conversationId, + acc->currentDeviceId(), + tid); // If peer is not empty this means that we want to send to one device only if (!info.peer.empty()) { acc->connectionManager().connectDevice(DeviceId(info.peer), channelName, channelReadyCb); diff --git a/src/plugin/chatservicesmanager.cpp b/src/plugin/chatservicesmanager.cpp index 6d8b48ed435e031da46831f5ccdbb15612c97886..f10bca75336ec6f65d57edf7b64d5db91cfa5e1d 100644 --- a/src/plugin/chatservicesmanager.cpp +++ b/src/plugin/chatservicesmanager.cpp @@ -64,7 +64,8 @@ ChatServicesManager::registerComponentsLifeCycleManagers(PluginManager& pluginMa for (auto& toggledList : chatHandlerToggled_) { auto handlerId = std::find_if(toggledList.second.begin(), toggledList.second.end(), - [id = (uintptr_t) handlerIt->get()](uintptr_t handlerId) { + [id = (uintptr_t) handlerIt->get()]( + uintptr_t handlerId) { return (handlerId == id); }); // If ChatHandler we're trying to destroy is currently in use, we deactivate it. @@ -95,7 +96,7 @@ ChatServicesManager::registerChatService(PluginManager& pluginManager) cm->accountId)) { try { if (cm->isSwarm) - acc->sendMessage(cm->peerId, cm->data.at("body")); + acc->convModule()->sendMessage(cm->peerId, cm->data.at("body")); else jami::Manager::instance().sendTextMessage(cm->accountId, cm->peerId, diff --git a/src/sip/sipaccountbase.cpp b/src/sip/sipaccountbase.cpp index f5cee89154195007df8ad8e79e4a1cc81bcd4c5f..3a8dc90784f3a0a3b64bdd4fe0c8e48978c1f9aa 100644 --- a/src/sip/sipaccountbase.cpp +++ b/src/sip/sipaccountbase.cpp @@ -60,13 +60,6 @@ using namespace std::literals; namespace jami { -static constexpr const char MIME_TYPE_IMDN[] {"message/imdn+xml"}; -static constexpr const char MIME_TYPE_GIT[] {"application/im-gitmessage-id"}; -static constexpr const char MIME_TYPE_INVITE_JSON[] {"application/invite+json"}; -static constexpr const char MIME_TYPE_INVITE[] {"application/invite"}; -static constexpr const char MIME_TYPE_IM_COMPOSING[] {"application/im-iscomposing+xml"}; -static constexpr std::chrono::steady_clock::duration COMPOSING_TIMEOUT {std::chrono::seconds(12)}; - SIPAccountBase::SIPAccountBase(const std::string& accountID) : Account(accountID) , messageEngine_(*this, @@ -140,82 +133,6 @@ SIPAccountBase::flush() + DIR_SEPARATOR_STR "messages"); } -std::string -getIsComposing(const std::string& conversationId, bool isWriting) -{ - // implementing https://tools.ietf.org/rfc/rfc3994.txt - return fmt::format("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" - "<isComposing><state>{}</state>{}</isComposing>", - isWriting ? "active"sv : "idle"sv, - conversationId.empty() - ? "" - : "<conversation>" + conversationId + "</conversation>"); -} - -std::string -getDisplayed(const std::string& conversationId, const std::string& messageId) -{ - // implementing https://tools.ietf.org/rfc/rfc5438.txt - return fmt::format( - "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" - "<imdn><message-id>{}</message-id>\n" - "{}" - "<display-notification><status><displayed/></status></display-notification>\n" - "</imdn>", - messageId, - conversationId.empty() ? "" : "<conversation>" + conversationId + "</conversation>"); -} - -void -SIPAccountBase::setIsComposing(const std::string& conversationUri, bool isWriting) -{ - Uri uri(conversationUri); - std::string conversationId = {}; - if (uri.scheme() == Uri::Scheme::SWARM) - conversationId = uri.authority(); - auto uid = uri.authority(); - - if (not isWriting and conversationUri != composingUri_) - return; - - if (composingTimeout_) { - composingTimeout_->cancel(); - composingTimeout_.reset(); - } - if (isWriting) { - if (not composingUri_.empty() and composingUri_ != conversationUri) { - sendInstantMessage(uid, - {{MIME_TYPE_IM_COMPOSING, getIsComposing(conversationId, false)}}); - composingTime_ = std::chrono::steady_clock::time_point::min(); - } - composingUri_.clear(); - composingUri_.insert(composingUri_.end(), conversationUri.begin(), conversationUri.end()); - auto now = std::chrono::steady_clock::now(); - if (now >= composingTime_ + COMPOSING_TIMEOUT) { - sendInstantMessage(uid, - {{MIME_TYPE_IM_COMPOSING, getIsComposing(conversationId, true)}}); - composingTime_ = now; - } - std::weak_ptr<SIPAccountBase> weak = std::static_pointer_cast<SIPAccountBase>( - shared_from_this()); - composingTimeout_ = Manager::instance().scheduleTask( - [weak, uid, conversationId]() { - if (auto sthis = weak.lock()) { - sthis->sendInstantMessage(uid, - {{MIME_TYPE_IM_COMPOSING, - getIsComposing(conversationId, false)}}); - sthis->composingUri_.clear(); - sthis->composingTime_ = std::chrono::steady_clock::time_point::min(); - } - }, - now + COMPOSING_TIMEOUT); - } else { - sendInstantMessage(uid, {{MIME_TYPE_IM_COMPOSING, getIsComposing(conversationId, false)}}); - composingUri_.clear(); - composingTime_ = std::chrono::steady_clock::time_point::min(); - } -} - template<typename T> static void validate(std::string& member, const std::string& param, const T& valid) @@ -552,127 +469,8 @@ SIPAccountBase::onTextMessage(const std::string& id, JAMI_WARN("Dropping invalid message with MIME type %s", m.first.c_str()); return; } - if (m.first == MIME_TYPE_IM_COMPOSING) { - try { - static const std::regex COMPOSING_REGEX("<state>\\s*(\\w+)\\s*<\\/state>"); - std::smatch matched_pattern; - std::regex_search(m.second, matched_pattern, COMPOSING_REGEX); - bool isComposing {false}; - if (matched_pattern.ready() && !matched_pattern.empty() - && matched_pattern[1].matched) { - isComposing = matched_pattern[1] == "active"; - } - static const std::regex CONVID_REGEX( - "<conversation>\\s*(\\w+)\\s*<\\/conversation>"); - std::regex_search(m.second, matched_pattern, CONVID_REGEX); - std::string conversationId = ""; - if (matched_pattern.ready() && !matched_pattern.empty() - && matched_pattern[1].matched) { - conversationId = matched_pattern[1]; - } - onIsComposing(conversationId, from, isComposing); - if (payloads.size() == 1) - return; - } catch (const std::exception& e) { - JAMI_WARN("Error parsing composing state: %s", e.what()); - } - } else if (m.first == MIME_TYPE_IMDN) { - try { - static const std::regex IMDN_MSG_ID_REGEX( - "<message-id>\\s*(\\w+)\\s*<\\/message-id>"); - static const std::regex IMDN_STATUS_REGEX("<status>\\s*<(\\w+)\\/>\\s*<\\/status>"); - std::smatch matched_pattern; - - std::regex_search(m.second, matched_pattern, IMDN_MSG_ID_REGEX); - std::string messageId; - if (matched_pattern.ready() && !matched_pattern.empty() - && matched_pattern[1].matched) { - messageId = matched_pattern[1]; - } else { - JAMI_WARN("Message displayed: can't parse message ID"); - continue; - } - - std::regex_search(m.second, matched_pattern, IMDN_STATUS_REGEX); - bool isDisplayed {false}; - if (matched_pattern.ready() && !matched_pattern.empty() - && matched_pattern[1].matched) { - isDisplayed = matched_pattern[1] == "displayed"; - } else { - JAMI_WARN("Message displayed: can't parse status"); - continue; - } - - static const std::regex CONVID_REGEX( - "<conversation>\\s*(\\w+)\\s*<\\/conversation>"); - std::regex_search(m.second, matched_pattern, CONVID_REGEX); - std::string conversationId = ""; - if (matched_pattern.ready() && !matched_pattern.empty() - && matched_pattern[1].matched) { - conversationId = matched_pattern[1]; - } - - if (!isReadReceiptEnabled()) - return; - if (conversationId.empty()) // Old method - messageEngine_.onMessageDisplayed(from, from_hex_string(messageId), isDisplayed); - else if (isDisplayed) { - onMessageDisplayed(from, conversationId, messageId); - JAMI_DBG() << "[message " << messageId << "] Displayed by peer"; - emitSignal<DRing::ConfigurationSignal::AccountMessageStatusChanged>( - accountID_, - conversationId, - from, - messageId, - static_cast<int>(DRing::Account::MessageStates::DISPLAYED)); - return; - } - if (payloads.size() == 1) - return; - } catch (const std::exception& e) { - JAMI_WARN("Error parsing display notification: %s", e.what()); - } - } else if (m.first == MIME_TYPE_GIT) { - Json::Value json; - std::string err; - Json::CharReaderBuilder rbuilder; - auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader()); - if (!reader->parse(m.second.data(), m.second.data() + m.second.size(), &json, &err)) { - JAMI_ERR("Can't parse server response: %s", err.c_str()); - return; - } - - JAMI_WARN("Received indication for new commit available in conversation %s", - json["id"].asString().c_str()); - - if (deviceId.empty()) { - JAMI_ERR() << "Incorrect deviceId. Can't retrieve history"; - return; - } - - onNewGitCommit(from, deviceId, json["id"].asString(), json["commit"].asString()); - return; - } else if (m.first == MIME_TYPE_INVITE_JSON) { - Json::Value json; - std::string err; - Json::CharReaderBuilder rbuilder; - auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader()); - if (!reader->parse(m.second.data(), m.second.data() + m.second.size(), &json, &err)) { - JAMI_ERR("Can't parse server response: %s", err.c_str()); - return; - } - onConversationRequest(from, json); - return; - } else if (m.first == MIME_TYPE_INVITE) { - onNeedConversationRequest(from, m.second); + if (handleMessage(from, m)) return; - } else if (m.first == MIME_TYPE_TEXT_PLAIN) { - // This means that a text is received, so that - // the conversation is not a swarm. For compatibility, - // check if we have a swarm created. It can be the case - // when the trust request confirm was not received. - checkIfRemoveForCompat(from); - } } #ifdef ENABLE_PLUGIN @@ -702,23 +500,6 @@ SIPAccountBase::onTextMessage(const std::string& id, } } -bool -SIPAccountBase::setMessageDisplayed(const std::string& conversationUri, - const std::string& messageId, - int status) -{ - Uri uri(conversationUri); - std::string conversationId = {}; - if (uri.scheme() == Uri::Scheme::SWARM) - conversationId = uri.authority(); - if (!conversationId.empty()) - onMessageDisplayed(getUsername(), conversationId, messageId); - if (status == (int) DRing::Account::MessageStates::DISPLAYED && isReadReceiptEnabled()) - sendInstantMessage(uri.authority(), - {{MIME_TYPE_IMDN, getDisplayed(conversationId, messageId)}}); - return true; -} - IpAddr SIPAccountBase::getPublishedIpAddress(uint16_t family) const { diff --git a/src/sip/sipaccountbase.h b/src/sip/sipaccountbase.h index 0f6bca8ad6d766ad25cdd5c5908381bf7ca63fa6..e5d571fe712d49e51687782627cba8762b79c483 100644 --- a/src/sip/sipaccountbase.h +++ b/src/sip/sipaccountbase.h @@ -258,12 +258,6 @@ public: return messageEngine_.sendMessage(to, payloads); } - void setIsComposing(const std::string& conversationUri, bool isWriting) override; - - bool setMessageDisplayed(const std::string& conversationUri, - const std::string& messageId, - int status) override; - im::MessageStatus getMessageStatus(uint64_t id) const override { return messageEngine_.getStatus(id); diff --git a/src/uri.cpp b/src/uri.cpp index decac06bab277801b3669a738eafedd454aae5e5..145bfb9fddc06cc024a0dff614c491f59ac69ed9 100644 --- a/src/uri.cpp +++ b/src/uri.cpp @@ -35,6 +35,8 @@ Uri::Uri(const std::string_view& uri) scheme_ = Uri::Scheme::SWARM; else if (scheme_str == "jami") scheme_ = Uri::Scheme::JAMI; + else if (scheme_str == "git") + scheme_ = Uri::Scheme::GIT; else scheme_ = Uri::Scheme::UNRECOGNIZED; authority_ = uri.substr(posSep + 1); diff --git a/src/uri.h b/src/uri.h index f62e579c43eea44505f63116a910361277637ead..9fd9d866b45dde43c97d0fab7cf6ae7d634fe8cf 100644 --- a/src/uri.h +++ b/src/uri.h @@ -31,6 +31,7 @@ public: JAMI, // Start with "jami:" and 45 ASCII chars OR 40 ASCII chars SIP, // Start with "sip:" SWARM, // Start with "swarm:" and 40 ASCII chars + GIT, // Start with "git:" UNRECOGNIZED // Anything that doesn't fit in other categories }; diff --git a/test/unitTest/conversation/conversation.cpp b/test/unitTest/conversation/conversation.cpp index 7114437fcdbfdb91365bb2abe58316ab78a8f88c..a6214b6328d3837c6472c9153ae15db11b28ba8c 100644 --- a/test/unitTest/conversation/conversation.cpp +++ b/test/unitTest/conversation/conversation.cpp @@ -278,7 +278,7 @@ ConversationTest::testCreateConversation() DRing::registerSignalHandlers(confHandlers); // Start conversation - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; }); CPPUNIT_ASSERT(conversationReady); ConversationRepository repo(aliceAccount, convId); @@ -311,9 +311,9 @@ ConversationTest::testGetConversation() { auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); auto uri = aliceAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); - auto conversations = aliceAccount->getConversations(); + auto conversations = DRing::getConversations(aliceId); CPPUNIT_ASSERT(conversations.size() == 1); CPPUNIT_ASSERT(conversations.front() == convId); } @@ -339,13 +339,13 @@ ConversationTest::testGetConversationsAfterRm() DRing::registerSignalHandlers(confHandlers); // Start conversation - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; })); - auto conversations = aliceAccount->getConversations(); + auto conversations = DRing::getConversations(aliceId); CPPUNIT_ASSERT(conversations.size() == 1); - CPPUNIT_ASSERT(aliceAccount->removeConversation(convId)); - conversations = aliceAccount->getConversations(); + CPPUNIT_ASSERT(DRing::removeConversation(aliceId, convId)); + conversations = DRing::getConversations(aliceId); CPPUNIT_ASSERT(conversations.size() == 0); } @@ -370,13 +370,13 @@ ConversationTest::testRemoveInvalidConversation() DRing::registerSignalHandlers(confHandlers); // Start conversation - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; })); - auto conversations = aliceAccount->getConversations(); + auto conversations = DRing::getConversations(aliceId); CPPUNIT_ASSERT(conversations.size() == 1); - CPPUNIT_ASSERT(!aliceAccount->removeConversation("foo")); - conversations = aliceAccount->getConversations(); + CPPUNIT_ASSERT(!DRing::removeConversation(aliceId, "foo")); + conversations = DRing::getConversations(aliceId); CPPUNIT_ASSERT(conversations.size() == 1); } @@ -401,7 +401,7 @@ ConversationTest::testRemoveConversationNoMember() DRing::registerSignalHandlers(confHandlers); // Start conversation - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; })); // Assert that repository exists @@ -412,11 +412,11 @@ ConversationTest::testRemoveConversationNoMember() CPPUNIT_ASSERT(fileutils::isDirectory(repoPath)); CPPUNIT_ASSERT(fileutils::isDirectory(dataPath)); - auto conversations = aliceAccount->getConversations(); + auto conversations = DRing::getConversations(aliceId); CPPUNIT_ASSERT(conversations.size() == 1); // Removing the conversation will erase all related files - CPPUNIT_ASSERT(aliceAccount->removeConversation(convId)); - conversations = aliceAccount->getConversations(); + CPPUNIT_ASSERT(DRing::removeConversation(aliceId, convId)); + conversations = DRing::getConversations(aliceId); CPPUNIT_ASSERT(conversations.size() == 0); CPPUNIT_ASSERT(!fileutils::isDirectory(repoPath)); CPPUNIT_ASSERT(!fileutils::isDirectory(dataPath)); @@ -428,7 +428,7 @@ ConversationTest::testRemoveConversationWithMember() auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); auto bobUri = bobAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; @@ -469,7 +469,7 @@ ConversationTest::testRemoveConversationWithMember() })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT( cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; })); @@ -483,7 +483,7 @@ ConversationTest::testRemoveConversationWithMember() CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); memberMessageGenerated = false; - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; })); auto clonedPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + bobAccount->getAccountID() + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId; @@ -495,7 +495,7 @@ ConversationTest::testRemoveConversationWithMember() cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; })); bobSeeAliceRemoved = false; - aliceAccount->removeConversation(convId); + DRing::removeConversation(aliceId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return bobSeeAliceRemoved; })); std::this_thread::sleep_for(std::chrono::seconds(3)); CPPUNIT_ASSERT(!fileutils::isDirectory(repoPath)); @@ -507,7 +507,7 @@ ConversationTest::testAddMember() auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); auto bobUri = bobAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; std::condition_variable cv; @@ -538,7 +538,7 @@ ConversationTest::testAddMember() } })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT( cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; })); // Assert that repository exists @@ -549,7 +549,7 @@ ConversationTest::testAddMember() auto bobInvited = repoPath + DIR_SEPARATOR_STR + "invited" + DIR_SEPARATOR_STR + bobUri; CPPUNIT_ASSERT(fileutils::isFile(bobInvited)); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; })); auto clonedPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + bobAccount->getAccountID() + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId; @@ -567,7 +567,7 @@ ConversationTest::testMemberAddedNoBadFile() auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); auto bobUri = bobAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; std::condition_variable cv; @@ -606,7 +606,7 @@ ConversationTest::testMemberAddedNoBadFile() "{\"conversationId\":\"" + convId + "\"}"}}); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); errorDetected = false; - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; })); DRing::unregisterSignalHandlers(); } @@ -618,7 +618,7 @@ ConversationTest::testAddOfflineMemberThenConnects() auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId); auto carlaUri = carlaAccount->getUsername(); aliceAccount->trackBuddyPresence(carlaUri, true); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; @@ -642,11 +642,11 @@ ConversationTest::testAddOfflineMemberThenConnects() })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, carlaUri); + DRing::addConversationMember(aliceId, convId, carlaUri); Manager::instance().sendRegister(carlaId, true); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&] { return requestReceived; })); - carlaAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(carlaId, convId); cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; }); auto clonedPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + carlaAccount->getAccountID() + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId; @@ -694,10 +694,10 @@ ConversationTest::testGetMembers() })); DRing::registerSignalHandlers(confHandlers); // Start a conversation and add member - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); messageReceived = false; - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() { return messageReceived; })); // Assert that repository exists @@ -705,7 +705,7 @@ ConversationTest::testGetMembers() + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId; CPPUNIT_ASSERT(fileutils::isDirectory(repoPath)); - auto members = aliceAccount->getConversationMembers(convId); + auto members = DRing::getConversationMembers(aliceId, convId); CPPUNIT_ASSERT(members.size() == 2); CPPUNIT_ASSERT(members[0]["uri"] == aliceAccount->getUsername()); CPPUNIT_ASSERT(members[0]["role"] == "admin"); @@ -714,12 +714,12 @@ ConversationTest::testGetMembers() CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); messageReceived = false; - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; }); - members = bobAccount->getConversationMembers(convId); + members = DRing::getConversationMembers(bobId, convId); CPPUNIT_ASSERT(members.size() == 2); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() { return messageReceived; })); - members = aliceAccount->getConversationMembers(convId); + members = DRing::getConversationMembers(aliceId, convId); CPPUNIT_ASSERT(members.size() == 2); CPPUNIT_ASSERT(members[0]["uri"] == aliceAccount->getUsername()); CPPUNIT_ASSERT(members[0]["role"] == "admin"); @@ -770,12 +770,12 @@ ConversationTest::testSendMessage() })); DRing::registerSignalHandlers(confHandlers); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; })); // Assert that repository exists @@ -783,9 +783,9 @@ ConversationTest::testSendMessage() + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId; CPPUNIT_ASSERT(fileutils::isDirectory(repoPath)); // Wait that alice sees Bob - cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageAliceReceived == 1; }); + cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageAliceReceived == 2; }); - aliceAccount->sendMessage(convId, "hi"s); + DRing::sendMessage(aliceId, convId, "hi"s, ""); cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageBobReceived == 1; }); DRing::unregisterSignalHandlers(); } @@ -814,10 +814,10 @@ ConversationTest::testSendMessageTriggerMessageReceived() })); DRing::registerSignalHandlers(confHandlers); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); cv.wait_for(lk, std::chrono::seconds(30), [&] { return conversationReady; }); - aliceAccount->sendMessage(convId, "hi"s); + DRing::sendMessage(aliceId, convId, "hi"s, ""); cv.wait_for(lk, std::chrono::seconds(30), [&] { return messageReceived == 1; }); CPPUNIT_ASSERT(messageReceived == 1); DRing::unregisterSignalHandlers(); @@ -830,7 +830,7 @@ ConversationTest::testMergeTwoDifferentHeads() auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId); auto carlaUri = carlaAccount->getUsername(); aliceAccount->trackBuddyPresence(carlaUri, true); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; @@ -855,7 +855,7 @@ ConversationTest::testMergeTwoDifferentHeads() })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, carlaUri, false); + aliceAccount->convModule()->addConversationMember(convId, carlaUri, false); // Cp conversations & convInfo auto repoPathAlice = fileutils::get_data_dir() + DIR_SEPARATOR_STR @@ -873,9 +873,9 @@ ConversationTest::testMergeTwoDifferentHeads() ConversationRepository repo(carlaAccount, convId); repo.join(); - aliceAccount->sendMessage(convId, "hi"s); - aliceAccount->sendMessage(convId, "sup"s); - aliceAccount->sendMessage(convId, "jami"s); + DRing::sendMessage(aliceId, convId, "hi"s, ""); + DRing::sendMessage(aliceId, convId, "sup"s, ""); + DRing::sendMessage(aliceId, convId, "jami"s, ""); // Start Carla, should merge and all messages should be there Manager::instance().sendRegister(carlaId, true); @@ -905,12 +905,12 @@ ConversationTest::testGetRequests() })); DRing::registerSignalHandlers(confHandlers); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); - auto requests = bobAccount->getConversationRequests(); + auto requests = DRing::getConversationRequests(bobId); CPPUNIT_ASSERT(requests.size() == 1); CPPUNIT_ASSERT(requests.front()["id"] == convId); DRing::unregisterSignalHandlers(); @@ -938,14 +938,14 @@ ConversationTest::testDeclineRequest() })); DRing::registerSignalHandlers(confHandlers); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); - bobAccount->declineConversationRequest(convId); + DRing::declineConversationRequest(bobId, convId); // Decline request - auto requests = bobAccount->getConversationRequests(); + auto requests = DRing::getConversationRequests(bobId); CPPUNIT_ASSERT(requests.size() == 0); DRing::unregisterSignalHandlers(); } @@ -1017,16 +1017,16 @@ ConversationTest::testSendMessageToMultipleParticipants() })); DRing::registerSignalHandlers(confHandlers); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); - aliceAccount->addConversationMember(convId, bobUri); - aliceAccount->addConversationMember(convId, carlaUri); + DRing::addConversationMember(aliceId, convId, bobUri); + DRing::addConversationMember(aliceId, convId, carlaUri); CPPUNIT_ASSERT( cv.wait_for(lk, std::chrono::seconds(60), [&]() { return requestReceived == 2; })); messageReceivedAlice = 0; - bobAccount->acceptConversationRequest(convId); - carlaAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); + DRing::acceptConversationRequest(carlaId, convId); // >= because we can have merges cause the accept commits CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() { return conversationReady == 3 && messageReceivedAlice >= 2; @@ -1040,7 +1040,7 @@ ConversationTest::testSendMessageToMultipleParticipants() + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId; CPPUNIT_ASSERT(fileutils::isDirectory(repoPath)); - aliceAccount->sendMessage(convId, "hi"s); + DRing::sendMessage(aliceId, convId, "hi"s, ""); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() { return messageReceivedBob >= 1 && messageReceivedCarla >= 1; })); @@ -1089,11 +1089,11 @@ ConversationTest::testPingPongMessages() } })); DRing::registerSignalHandlers(confHandlers); - auto convId = aliceAccount->startConversation(); - aliceAccount->addConversationMember(convId, bobUri); + auto convId = DRing::startConversation(aliceId); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); messageAliceReceived = 0; - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() { return conversationReady && messageAliceReceived == 1; })); @@ -1103,19 +1103,19 @@ ConversationTest::testPingPongMessages() CPPUNIT_ASSERT(fileutils::isDirectory(repoPath)); messageBobReceived = 0; messageAliceReceived = 0; - aliceAccount->sendMessage(convId, "ping"s); + DRing::sendMessage(aliceId, convId, "ping"s, ""); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageBobReceived == 1 && messageAliceReceived == 1; })); - bobAccount->sendMessage(convId, "pong"s); + DRing::sendMessage(bobId, convId, "pong"s, ""); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageBobReceived == 2 && messageAliceReceived == 2; })); - bobAccount->sendMessage(convId, "ping"s); + DRing::sendMessage(bobId, convId, "ping"s, ""); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageBobReceived == 3 && messageAliceReceived == 3; })); - aliceAccount->sendMessage(convId, "pong"s); + DRing::sendMessage(aliceId, convId, "pong"s, ""); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageBobReceived == 4 && messageAliceReceived == 4; })); @@ -1129,7 +1129,7 @@ ConversationTest::testIsComposing() auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); auto aliceUri = aliceAccount->getUsername(); auto bobUri = bobAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; std::condition_variable cv; @@ -1172,7 +1172,7 @@ ConversationTest::testIsComposing() } })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT( cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; })); // Assert that repository exists @@ -1184,7 +1184,7 @@ ConversationTest::testIsComposing() CPPUNIT_ASSERT(fileutils::isFile(bobInvited)); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); memberMessageGenerated = false; - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT( cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; })); @@ -1202,7 +1202,7 @@ ConversationTest::testSetMessageDisplayed() auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); auto aliceUri = aliceAccount->getUsername(); auto bobUri = bobAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; std::condition_variable cv; @@ -1247,7 +1247,7 @@ ConversationTest::testSetMessageDisplayed() } })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT( cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; })); // Assert that repository exists @@ -1259,19 +1259,19 @@ ConversationTest::testSetMessageDisplayed() CPPUNIT_ASSERT(fileutils::isFile(bobInvited)); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); memberMessageGenerated = false; - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT( cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; })); // Last displayed messages should not be set yet - auto membersInfos = bobAccount->getConversationMembers(convId); + auto membersInfos = DRing::getConversationMembers(bobId, convId); CPPUNIT_ASSERT(std::find_if(membersInfos.begin(), membersInfos.end(), [&](auto infos) { return infos["uri"] == aliceUri && infos["lastDisplayed"] == ""; }) != membersInfos.end()); - membersInfos = aliceAccount->getConversationMembers(convId); + membersInfos = DRing::getConversationMembers(aliceId, convId); CPPUNIT_ASSERT(std::find_if(membersInfos.begin(), membersInfos.end(), [&](auto infos) { @@ -1283,7 +1283,7 @@ ConversationTest::testSetMessageDisplayed() CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return msgDisplayed; })); // Now, the last displayed message should be updated in member's infos (both sides) - membersInfos = bobAccount->getConversationMembers(convId); + membersInfos = DRing::getConversationMembers(bobId, convId); CPPUNIT_ASSERT(std::find_if(membersInfos.begin(), membersInfos.end(), [&](auto infos) { @@ -1291,7 +1291,7 @@ ConversationTest::testSetMessageDisplayed() && infos["lastDisplayed"] == convId; }) != membersInfos.end()); - membersInfos = aliceAccount->getConversationMembers(convId); + membersInfos = DRing::getConversationMembers(aliceId, convId); CPPUNIT_ASSERT(std::find_if(membersInfos.begin(), membersInfos.end(), [&](auto infos) { @@ -1310,17 +1310,13 @@ ConversationTest::testSetMessageDisplayedPreference() auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); auto aliceUri = aliceAccount->getUsername(); auto bobUri = bobAccount->getUsername(); - auto convId = aliceAccount->startConversation(); - auto details = aliceAccount->getAccountDetails(); - CPPUNIT_ASSERT(details[ConfProperties::SENDREADRECEIPT] == "true"); - details[ConfProperties::SENDREADRECEIPT] = "false"; - DRing::setAccountDetails(aliceId, details); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; std::condition_variable cv; std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers; bool conversationReady = false, requestReceived = false, memberMessageGenerated = false, - msgDisplayed = false; + msgDisplayed = false, aliceRegistered = false; confHandlers.insert( DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>( [&](const std::string& /*accountId*/, @@ -1358,18 +1354,35 @@ ConversationTest::testSetMessageDisplayedPreference() cv.notify_one(); } })); + confHandlers.insert( + DRing::exportable_callback<DRing::ConfigurationSignal::VolatileDetailsChanged>( + [&](const std::string&, const std::map<std::string, std::string>&) { + auto details = aliceAccount->getVolatileAccountDetails(); + auto daemonStatus = details[DRing::Account::ConfProperties::Registration::STATUS]; + if (daemonStatus == "REGISTERED") { + aliceRegistered = true; + cv.notify_one(); + } + })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, bobUri); + + auto details = aliceAccount->getAccountDetails(); + CPPUNIT_ASSERT(details[ConfProperties::SENDREADRECEIPT] == "true"); + details[ConfProperties::SENDREADRECEIPT] = "false"; + DRing::setAccountDetails(aliceId, details); + CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return aliceRegistered; })); + + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived && memberMessageGenerated; })); memberMessageGenerated = false; - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT( cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; })); // Last displayed messages should not be set yet - auto membersInfos = aliceAccount->getConversationMembers(convId); + auto membersInfos = DRing::getConversationMembers(aliceId, convId); CPPUNIT_ASSERT(std::find_if(membersInfos.begin(), membersInfos.end(), [&](auto infos) { @@ -1382,7 +1395,7 @@ ConversationTest::testSetMessageDisplayedPreference() CPPUNIT_ASSERT(!cv.wait_for(lk, std::chrono::seconds(10), [&]() { return msgDisplayed; })); // Assert that message is set as displayed for self (for the read status) - membersInfos = aliceAccount->getConversationMembers(convId); + membersInfos = DRing::getConversationMembers(aliceId, convId); CPPUNIT_ASSERT(std::find_if(membersInfos.begin(), membersInfos.end(), [&](auto infos) { @@ -1400,7 +1413,7 @@ ConversationTest::testRemoveMember() auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); auto aliceUri = aliceAccount->getUsername(); auto bobUri = bobAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; std::condition_variable cv; @@ -1436,21 +1449,21 @@ ConversationTest::testRemoveMember() } })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); memberMessageGenerated = false; - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT( cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; })); // Now check that alice, has the only admin, can remove bob memberMessageGenerated = false; voteMessageGenerated = false; - aliceAccount->removeConversationMember(convId, bobUri); + DRing::removeConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated && voteMessageGenerated; })); - auto members = aliceAccount->getConversationMembers(convId); + auto members = DRing::getConversationMembers(aliceId, convId); CPPUNIT_ASSERT(members.size() == 1); CPPUNIT_ASSERT(members[0]["uri"] == aliceAccount->getUsername()); CPPUNIT_ASSERT(members[0]["role"] == "admin"); @@ -1464,7 +1477,7 @@ ConversationTest::testMemberBanNoBadFile() auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); auto aliceUri = aliceAccount->getUsername(); auto bobUri = bobAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId); auto carlaUri = carlaAccount->getUsername(); aliceAccount->trackBuddyPresence(carlaUri, true); @@ -1527,22 +1540,22 @@ ConversationTest::testMemberBanNoBadFile() DRing::registerSignalHandlers(confHandlers); Manager::instance().sendRegister(carlaId, true); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&] { return carlaConnected; })); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived && memberMessageGenerated; })); memberMessageGenerated = false; - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT( cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; })); requestReceived = false; - aliceAccount->addConversationMember(convId, carlaUri); + DRing::addConversationMember(aliceId, convId, carlaUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived && memberMessageGenerated; })); memberMessageGenerated = false; messageBobReceived = false; - carlaAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(carlaId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated && messageBobReceived; })); @@ -1550,7 +1563,7 @@ ConversationTest::testMemberBanNoBadFile() memberMessageGenerated = false; voteMessageGenerated = false; addFile(aliceAccount, convId, "BADFILE"); - aliceAccount->removeConversationMember(convId, carlaUri); + DRing::removeConversationMember(aliceId, convId, carlaUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; })); DRing::unregisterSignalHandlers(); } @@ -1563,7 +1576,7 @@ ConversationTest::testBanDevice() auto aliceUri = aliceAccount->getUsername(); auto bobUri = bobAccount->getUsername(); auto bobDeviceId = std::string(bobAccount->currentDeviceId()); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; std::condition_variable cv; @@ -1602,12 +1615,12 @@ ConversationTest::testBanDevice() cv.notify_one(); })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived && memberMessageGenerated; })); memberMessageGenerated = false; - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT( cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; })); @@ -1645,9 +1658,9 @@ ConversationTest::testBanDevice() memberMessageGenerated = false; voteMessageGenerated = false; bobGetMessage = false; - auto members = aliceAccount->getConversationMembers(convId); + auto members = DRing::getConversationMembers(aliceId, convId); CPPUNIT_ASSERT(members.size() == 2); - aliceAccount->removeConversationMember(convId, bobDeviceId, true); + DRing::removeConversationMember(aliceId, convId, bobDeviceId, true); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated && voteMessageGenerated; })); @@ -1657,7 +1670,7 @@ ConversationTest::testBanDevice() + DIR_SEPARATOR_STR + "banned" + DIR_SEPARATOR_STR + "devices" + DIR_SEPARATOR_STR + bobDeviceId + ".crt"; CPPUNIT_ASSERT(fileutils::isFile(bannedFile)); - members = aliceAccount->getConversationMembers(convId); + members = DRing::getConversationMembers(aliceId, convId); CPPUNIT_ASSERT(members.size() == 2); // Assert that bob2 get the message, not Bob @@ -1673,7 +1686,7 @@ ConversationTest::testMemberTryToRemoveAdmin() auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); auto aliceUri = aliceAccount->getUsername(); auto bobUri = bobAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; std::condition_variable cv; @@ -1698,25 +1711,24 @@ ConversationTest::testMemberTryToRemoveAdmin() [&](const std::string& accountId, const std::string& conversationId, std::map<std::string, std::string> message) { - if (accountId == aliceId && conversationId == convId && message["type"] == "member") { + if (accountId == aliceId && conversationId == convId && message["type"] == "member") memberMessageGenerated = true; - cv.notify_one(); - } + cv.notify_one(); })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived && memberMessageGenerated; })); memberMessageGenerated = false; - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT( cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; })); // Now check that alice, has the only admin, can remove bob memberMessageGenerated = false; - bobAccount->removeConversationMember(convId, aliceUri); - auto members = aliceAccount->getConversationMembers(convId); + DRing::removeConversationMember(bobId, convId, aliceUri); + auto members = DRing::getConversationMembers(aliceId, convId); CPPUNIT_ASSERT(members.size() == 2 && !memberMessageGenerated); DRing::unregisterSignalHandlers(); } @@ -1728,7 +1740,7 @@ ConversationTest::testBannedMemberCannotSendMessage() auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); auto aliceUri = aliceAccount->getUsername(); auto bobUri = bobAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; std::condition_variable cv; @@ -1766,27 +1778,27 @@ ConversationTest::testBannedMemberCannotSendMessage() cv.notify_one(); })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived && memberMessageGenerated; })); memberMessageGenerated = false; - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT( cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; })); memberMessageGenerated = false; voteMessageGenerated = false; - aliceAccount->removeConversationMember(convId, bobUri); + DRing::removeConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated && voteMessageGenerated; })); - auto members = aliceAccount->getConversationMembers(convId); + auto members = DRing::getConversationMembers(aliceId, convId); CPPUNIT_ASSERT(members.size() == 1); // Now check that alice doesn't receive a message from Bob aliceMessageReceived = false; - bobAccount->sendMessage(convId, "hi"s); + DRing::sendMessage(bobId, convId, "hi"s, ""); CPPUNIT_ASSERT( !cv.wait_for(lk, std::chrono::seconds(30), [&]() { return aliceMessageReceived; })); DRing::unregisterSignalHandlers(); @@ -1799,7 +1811,7 @@ ConversationTest::testAddBannedMember() auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); auto aliceUri = aliceAccount->getUsername(); auto bobUri = bobAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; std::condition_variable cv; @@ -1835,26 +1847,26 @@ ConversationTest::testAddBannedMember() } })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived && memberMessageGenerated; })); memberMessageGenerated = false; - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT( cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; })); // Now check that alice, has the only admin, can remove bob memberMessageGenerated = false; voteMessageGenerated = false; - aliceAccount->removeConversationMember(convId, bobUri); + DRing::removeConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated && voteMessageGenerated; })); // Then check that bobUri cannot be re-added memberMessageGenerated = false; - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT( !cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; })); DRing::unregisterSignalHandlers(); @@ -1929,7 +1941,10 @@ ConversationTest::simulateRemoval(std::shared_ptr<JamiAccount> account, wbuilder["indentation"] = ""; cr.commitMessage(Json::writeString(wbuilder, json)); - account->sendMessage(convId, "trigger the fake history to be pulled"s); + DRing::sendMessage(account->getAccountID(), + convId, + "trigger the fake history to be pulled"s, + ""); } void @@ -1972,7 +1987,10 @@ ConversationTest::generateFakeInvite(std::shared_ptr<JamiAccount> account, wbuilder["indentation"] = ""; cr.commitMessage(Json::writeString(wbuilder, json)); - account->sendMessage(convId, "trigger the fake history to be pulled"s); + DRing::sendMessage(account->getAccountID(), + convId, + "trigger the fake history to be pulled"s, + ""); } void @@ -2267,7 +2285,7 @@ ConversationTest::createFakeConversation(std::shared_ptr<JamiAccount> account) test.emplace_back(ConvInfoTest {commit_str, std::time(nullptr), 0, 0}); msgpack::pack(file, test); - account->loadConversations(); // necessary to load fake conv + account->convModule()->loadConversations(); // necessary to load fake conv return commit_str; } @@ -2312,7 +2330,7 @@ ConversationTest::testMemberCannotBanOther() auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); auto aliceUri = aliceAccount->getUsername(); auto bobUri = bobAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId); auto carlaUri = carlaAccount->getUsername(); aliceAccount->trackBuddyPresence(carlaUri, true); @@ -2373,23 +2391,23 @@ ConversationTest::testMemberCannotBanOther() DRing::registerSignalHandlers(confHandlers); Manager::instance().sendRegister(carlaId, true); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&] { return carlaConnected; })); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived && memberMessageGenerated; })); memberMessageGenerated = false; - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT( cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; })); requestReceived = false; memberMessageGenerated = false; - aliceAccount->addConversationMember(convId, carlaUri); + DRing::addConversationMember(aliceId, convId, carlaUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived && memberMessageGenerated; })); memberMessageGenerated = false; messageBobReceived = false; - carlaAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(carlaId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated && messageBobReceived; })); @@ -2401,7 +2419,7 @@ ConversationTest::testMemberCannotBanOther() simulateRemoval(carlaAccount, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; })); - aliceAccount->sendMessage(convId, "hi"s); + DRing::sendMessage(aliceId, convId, "hi"s, ""); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageBobReceived; })); } @@ -2412,7 +2430,7 @@ ConversationTest::testCheckAdminFakeAVoteIsDetected() auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); auto aliceUri = aliceAccount->getUsername(); auto bobUri = bobAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId); auto carlaUri = carlaAccount->getUsername(); aliceAccount->trackBuddyPresence(carlaUri, true); @@ -2475,22 +2493,22 @@ ConversationTest::testCheckAdminFakeAVoteIsDetected() DRing::registerSignalHandlers(confHandlers); Manager::instance().sendRegister(carlaId, true); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&] { return carlaConnected; })); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived && memberMessageGenerated; })); memberMessageGenerated = false; - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT( cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; })); requestReceived = false; - aliceAccount->addConversationMember(convId, carlaUri); + DRing::addConversationMember(aliceId, convId, carlaUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived && memberMessageGenerated; })); memberMessageGenerated = false; messageBobReceived = false; - carlaAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(carlaId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated && messageBobReceived; })); @@ -2508,7 +2526,7 @@ ConversationTest::testVoteNonEmpty() auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); auto aliceUri = aliceAccount->getUsername(); auto bobUri = bobAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId); auto carlaUri = carlaAccount->getUsername(); aliceAccount->trackBuddyPresence(carlaUri, true); @@ -2571,22 +2589,22 @@ ConversationTest::testVoteNonEmpty() DRing::registerSignalHandlers(confHandlers); Manager::instance().sendRegister(carlaId, true); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&] { return carlaConnected; })); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived && memberMessageGenerated; })); memberMessageGenerated = false; - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT( cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; })); requestReceived = false; - aliceAccount->addConversationMember(convId, carlaUri); + DRing::addConversationMember(aliceId, convId, carlaUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived && memberMessageGenerated; })); memberMessageGenerated = false; messageBobReceived = false; - carlaAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(carlaId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated && messageBobReceived; })); @@ -2651,12 +2669,12 @@ ConversationTest::testCommitUnauthorizedUser() })); DRing::registerSignalHandlers(confHandlers); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; })); // Assert that repository exists @@ -2665,7 +2683,7 @@ ConversationTest::testCommitUnauthorizedUser() CPPUNIT_ASSERT(fileutils::isDirectory(repoPath)); // Wait that alice sees Bob CPPUNIT_ASSERT( - cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageAliceReceived == 1; })); + cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageAliceReceived == 2; })); // Add commit from invalid user Json::Value root; @@ -2678,7 +2696,7 @@ ConversationTest::testCommitUnauthorizedUser() commitInRepo(repoPath, carlaAccount, message); errorDetected = false; - bobAccount->sendMessage(convId, "hi"s); + DRing::sendMessage(bobId, convId, "hi"s, ""); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; })); DRing::unregisterSignalHandlers(); } @@ -2688,7 +2706,7 @@ ConversationTest::testAdminCannotKickTheirself() { auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); auto aliceUri = aliceAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; std::condition_variable cv; @@ -2726,10 +2744,10 @@ ConversationTest::testAdminCannotKickTheirself() cv.notify_one(); })); DRing::registerSignalHandlers(confHandlers); - auto members = aliceAccount->getConversationMembers(convId); + auto members = DRing::getConversationMembers(aliceId, convId); CPPUNIT_ASSERT(members.size() == 1); - aliceAccount->removeConversationMember(convId, aliceUri); - members = aliceAccount->getConversationMembers(convId); + DRing::removeConversationMember(aliceId, convId, aliceUri); + members = DRing::getConversationMembers(aliceId, convId); CPPUNIT_ASSERT(members.size() == 1); } @@ -2793,10 +2811,10 @@ ConversationTest::testNoBadFileInInitialCommit() Manager::instance().sendRegister(carlaId, true); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return carlaConnected; })); - carlaAccount->addConversationMember(convId, aliceUri); + DRing::addConversationMember(carlaId, convId, aliceUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); - aliceAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(aliceId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; })); } @@ -2816,7 +2834,7 @@ ConversationTest::testPlainTextNoBadFile() bool requestReceived = false; bool conversationReady = false; bool errorDetected = false; - std::string convId = aliceAccount->startConversation(); + std::string convId = DRing::startConversation(aliceId); confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>( [&](const std::string& accountId, const std::string& conversationId, @@ -2855,13 +2873,13 @@ ConversationTest::testPlainTextNoBadFile() })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived && memberMessageGenerated; })); memberMessageGenerated = false; - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); cv.wait_for(lk, std::chrono::seconds(30), [&] { return conversationReady && memberMessageGenerated; }); @@ -2872,7 +2890,7 @@ ConversationTest::testPlainTextNoBadFile() root["body"] = "hi"; commit(aliceAccount, convId, root); errorDetected = false; - aliceAccount->sendMessage(convId, "hi"s); + DRing::sendMessage(aliceId, convId, "hi"s, ""); // Check not received due to the unwanted file CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; })); DRing::unregisterSignalHandlers(); @@ -2885,7 +2903,7 @@ ConversationTest::testVoteNoBadFile() auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); auto aliceUri = aliceAccount->getUsername(); auto bobUri = bobAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId); auto carlaUri = carlaAccount->getUsername(); aliceAccount->trackBuddyPresence(carlaUri, true); @@ -2948,22 +2966,22 @@ ConversationTest::testVoteNoBadFile() Manager::instance().sendRegister(carlaId, true); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return carlaConnected; })); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived && memberMessageGenerated; })); memberMessageGenerated = false; - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT( cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; })); requestReceived = false; - aliceAccount->addConversationMember(convId, carlaUri); + DRing::addConversationMember(aliceId, convId, carlaUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived && memberMessageGenerated; })); memberMessageGenerated = false; messageBobReceived = false; - carlaAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(carlaId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated && messageBobReceived; })); @@ -2971,13 +2989,13 @@ ConversationTest::testVoteNoBadFile() // Now Alice remove Carla without a vote. Bob will not receive the message messageBobReceived = false; addFile(aliceAccount, convId, "BADFILE"); - aliceAccount->removeConversationMember(convId, bobUri); + DRing::removeConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated && voteMessageGenerated; })); messageCarlaReceived = false; - bobAccount->sendMessage(convId, "final"s); + DRing::sendMessage(bobId, convId, "final"s, ""); CPPUNIT_ASSERT( cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageCarlaReceived; })); } @@ -3034,7 +3052,7 @@ ConversationTest::testETooBigClone() })); DRing::registerSignalHandlers(confHandlers); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); // Assert that repository exists auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + aliceAccount->getAccountID() @@ -3047,11 +3065,11 @@ ConversationTest::testETooBigClone() addAll(aliceAccount, convId); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); errorDetected = false; - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; })); DRing::unregisterSignalHandlers(); } @@ -3108,16 +3126,16 @@ ConversationTest::testETooBigFetch() })); DRing::registerSignalHandlers(confHandlers); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }); - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; }); // Wait that alice sees Bob - cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageAliceReceived == 1; }); + cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageAliceReceived == 2; }); auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + aliceAccount->getAccountID() + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId; @@ -3134,7 +3152,7 @@ ConversationTest::testETooBigFetch() json["type"] = "text/plain"; commit(aliceAccount, convId, json); - aliceAccount->sendMessage(convId, "hi"s); + DRing::sendMessage(aliceId, convId, "hi"s, ""); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; })); DRing::unregisterSignalHandlers(); } @@ -3146,7 +3164,7 @@ ConversationTest::testMemberJoinsNoBadFile() auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId); auto carlaUri = carlaAccount->getUsername(); aliceAccount->trackBuddyPresence(carlaUri, true); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; @@ -3191,7 +3209,7 @@ ConversationTest::testMemberJoinsNoBadFile() })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, carlaUri, false); + aliceAccount->convModule()->addConversationMember(convId, carlaUri, false); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(5), [&] { return memberMessageGenerated; })); // Cp conversations & convInfo @@ -3212,12 +3230,12 @@ ConversationTest::testMemberJoinsNoBadFile() ConversationRepository repo(carlaAccount, convId); // Start Carla, should merge and all messages should be there - carlaAccount->loadConversations(); // Because of the copy + carlaAccount->convModule()->loadConversations(); // Because of the copy Manager::instance().sendRegister(carlaId, true); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return carlaConnected; })); errorDetected = false; - carlaAccount->sendMessage(convId, "hi"s); + DRing::sendMessage(carlaId, convId, "hi"s, ""); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&] { return errorDetected; })); DRing::unregisterSignalHandlers(); @@ -3230,7 +3248,7 @@ ConversationTest::testMemberAddedNoCertificate() auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId); auto carlaUri = carlaAccount->getUsername(); aliceAccount->trackBuddyPresence(carlaUri, true); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; @@ -3275,7 +3293,7 @@ ConversationTest::testMemberAddedNoCertificate() })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, carlaUri, false); + aliceAccount->convModule()->addConversationMember(convId, carlaUri, false); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(5), [&] { return memberMessageGenerated; })); // Cp conversations & convInfo @@ -3306,11 +3324,11 @@ ConversationTest::testMemberAddedNoCertificate() cr.commitMessage(Json::writeString(wbuilder, json)); // Start Carla, should merge and all messages should be there - carlaAccount->loadConversations(); // Because of the copy + carlaAccount->convModule()->loadConversations(); // Because of the copy Manager::instance().sendRegister(carlaId, true); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return carlaConnected; })); - carlaAccount->sendMessage(convId, "hi"s); + DRing::sendMessage(carlaId, convId, "hi"s, ""); errorDetected = false; CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&] { return errorDetected; })); @@ -3324,7 +3342,7 @@ ConversationTest::testMemberJoinsInviteRemoved() auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId); auto carlaUri = carlaAccount->getUsername(); aliceAccount->trackBuddyPresence(carlaUri, true); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; @@ -3369,7 +3387,7 @@ ConversationTest::testMemberJoinsInviteRemoved() })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, carlaUri, false); + aliceAccount->convModule()->addConversationMember(convId, carlaUri, false); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(5), [&] { return memberMessageGenerated; })); // Cp conversations & convInfo @@ -3405,11 +3423,11 @@ ConversationTest::testMemberJoinsInviteRemoved() commit(carlaAccount, convId, json); // Start Carla, should merge and all messages should be there - carlaAccount->loadConversations(); // Because of the copy + carlaAccount->convModule()->loadConversations(); // Because of the copy Manager::instance().sendRegister(carlaId, true); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return carlaConnected; })); - carlaAccount->sendMessage(convId, "hi"s); + DRing::sendMessage(carlaId, convId, "hi"s, ""); errorDetected = false; CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&] { return errorDetected; })); @@ -3597,7 +3615,7 @@ ConversationTest::testFailAddMemberInOneToOne() aliceAccount->sendTrustRequest(bobUri, {}); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(5), [&]() { return !convId.empty(); })); memberMessageGenerated = false; - aliceAccount->addConversationMember(convId, carlaUri); + DRing::addConversationMember(aliceId, convId, carlaUri); CPPUNIT_ASSERT( !cv.wait_for(lk, std::chrono::seconds(5), [&]() { return memberMessageGenerated; })); } @@ -3608,7 +3626,7 @@ ConversationTest::testUnknownModeDetected() auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); auto bobUri = bobAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); ConversationRepository repo(aliceAccount, convId); Json::Value json; json["mode"] = 1412; @@ -3657,10 +3675,10 @@ ConversationTest::testUnknownModeDetected() cv.notify_one(); })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); errorDetected = false; - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; })); } @@ -3724,7 +3742,7 @@ ConversationTest::testRemoveContact() cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; })); // Check that getConversationMembers return "role":"left" - auto members = aliceAccount->getConversationMembers(convId); + auto members = DRing::getConversationMembers(aliceId, convId); CPPUNIT_ASSERT(std::find_if(members.begin(), members.end(), [&](auto member) { @@ -3885,7 +3903,7 @@ ConversationTest::testConversationMemberEvent() auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); auto bobUri = bobAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; std::condition_variable cv; @@ -3919,7 +3937,7 @@ ConversationTest::testConversationMemberEvent() cv.notify_one(); })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberAddGenerated; })); // Assert that repository exists auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + aliceAccount->getAccountID() @@ -3929,7 +3947,7 @@ ConversationTest::testConversationMemberEvent() auto bobInvited = repoPath + DIR_SEPARATOR_STR + "invited" + DIR_SEPARATOR_STR + bobUri; CPPUNIT_ASSERT(fileutils::isFile(bobInvited)); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; })); auto clonedPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + bobAccount->getAccountID() + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId; @@ -4113,23 +4131,23 @@ ConversationTest::testUpdateProfile() })); DRing::registerSignalHandlers(confHandlers); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); messageAliceReceived = 0; - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady && messageAliceReceived == 1; })); messageBobReceived = 0; - aliceAccount->updateConversationInfos(convId, {{"title", "My awesome swarm"}}); + aliceAccount->convModule()->updateConversationInfos(convId, {{"title", "My awesome swarm"}}); CPPUNIT_ASSERT( cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageBobReceived == 1; })); - auto infos = bobAccount->conversationInfos(convId); + auto infos = DRing::conversationInfos(bobId, convId); CPPUNIT_ASSERT(infos["title"] == "My awesome swarm"); CPPUNIT_ASSERT(infos["description"].empty()); @@ -4178,12 +4196,12 @@ ConversationTest::testCheckProfileInConversationRequest() })); DRing::registerSignalHandlers(confHandlers); - auto convId = aliceAccount->startConversation(); - aliceAccount->updateConversationInfos(convId, {{"title", "My awesome swarm"}}); + auto convId = DRing::startConversation(aliceId); + aliceAccount->convModule()->updateConversationInfos(convId, {{"title", "My awesome swarm"}}); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); - auto requests = bobAccount->getConversationRequests(); + auto requests = DRing::getConversationRequests(bobId); CPPUNIT_ASSERT(requests.size() == 1); CPPUNIT_ASSERT(requests.front()["title"] == "My awesome swarm"); @@ -4254,7 +4272,7 @@ ConversationTest::testMemberCannotUpdateProfile() auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); auto bobUri = bobAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; @@ -4301,17 +4319,17 @@ ConversationTest::testMemberCannotUpdateProfile() })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); messageAliceReceived = 0; - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady && messageAliceReceived == 1; })); messageBobReceived = 0; - bobAccount->updateConversationInfos(convId, {{"title", "My awesome swarm"}}); + bobAccount->convModule()->updateConversationInfos(convId, {{"title", "My awesome swarm"}}); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(5), [&]() { return errorDetected; })); DRing::unregisterSignalHandlers(); @@ -4323,7 +4341,7 @@ ConversationTest::testUpdateProfileWithBadFile() auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); auto bobUri = bobAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; @@ -4370,11 +4388,11 @@ ConversationTest::testUpdateProfileWithBadFile() })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); messageAliceReceived = 0; - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady && messageAliceReceived == 1; })); @@ -4391,7 +4409,7 @@ END:VCARD"; root["type"] = "application/update-profile"; commit(aliceAccount, convId, root); errorDetected = false; - aliceAccount->sendMessage(convId, "hi"s); + DRing::sendMessage(aliceId, convId, "hi"s, ""); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; })); DRing::unregisterSignalHandlers(); @@ -4403,7 +4421,7 @@ ConversationTest::testFetchProfileUnauthorized() auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); auto bobUri = bobAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; @@ -4450,11 +4468,11 @@ ConversationTest::testFetchProfileUnauthorized() })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); messageAliceReceived = 0; - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady && messageAliceReceived == 1; })); @@ -4470,7 +4488,7 @@ END:VCARD"; root["type"] = "application/update-profile"; commit(bobAccount, convId, root); errorDetected = false; - bobAccount->sendMessage(convId, "hi"s); + DRing::sendMessage(bobId, convId, "hi"s, ""); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; })); DRing::unregisterSignalHandlers(); @@ -4481,9 +4499,9 @@ ConversationTest::testDoNotLoadIncorrectConversation() { auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); auto uri = aliceAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); - auto convInfos = aliceAccount->conversationInfos(convId); + auto convInfos = DRing::conversationInfos(aliceId, convId); CPPUNIT_ASSERT(convInfos.find("mode") != convInfos.end()); CPPUNIT_ASSERT(convInfos.find("syncing") == convInfos.end()); @@ -4493,10 +4511,11 @@ ConversationTest::testDoNotLoadIncorrectConversation() + DIR_SEPARATOR_STR + ".git"; fileutils::removeAll(repoGitPath, true); // This make the repository not usable - aliceAccount->loadConversations(); // Refresh. This should detect the incorrect conversations. + aliceAccount->convModule() + ->loadConversations(); // Refresh. This should detect the incorrect conversations. // the conv should be detected as invalid and added as syncing - convInfos = aliceAccount->conversationInfos(convId); + convInfos = DRing::conversationInfos(aliceId, convId); CPPUNIT_ASSERT(convInfos.find("mode") == convInfos.end()); CPPUNIT_ASSERT(convInfos["syncing"] == "true"); } @@ -4541,13 +4560,13 @@ ConversationTest::testSyncingWhileAccepting() Manager::instance().sendRegister(aliceId, false); // This avoid to sync immediately CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri)); - auto convInfos = bobAccount->conversationInfos(convId); + auto convInfos = DRing::conversationInfos(bobId, convId); CPPUNIT_ASSERT(convInfos["syncing"] == "true"); Manager::instance().sendRegister(aliceId, true); // This avoid to sync immediately CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; })); - convInfos = bobAccount->conversationInfos(convId); + convInfos = DRing::conversationInfos(bobId, convId); CPPUNIT_ASSERT(convInfos.find("syncing") == convInfos.end()); } @@ -4591,7 +4610,7 @@ ConversationTest::testGetConversationsMembersWhileSyncing() Manager::instance().sendRegister(aliceId, false); // This avoid to sync immediately CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri)); - auto members = bobAccount->getConversationMembers(convId); + auto members = DRing::getConversationMembers(bobId, convId); CPPUNIT_ASSERT(std::find_if(members.begin(), members.end(), [&](auto memberInfo) { return memberInfo["uri"] == aliceUri; }) @@ -4642,36 +4661,39 @@ ConversationTest::testRemoveContactRemoveSyncing() Manager::instance().sendRegister(aliceId, false); // This avoid to sync immediately CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri)); - CPPUNIT_ASSERT(bobAccount->getConversations().size() == 1); + CPPUNIT_ASSERT(DRing::getConversations(bobId).size() == 1); bobAccount->removeContact(aliceUri, false); - CPPUNIT_ASSERT(bobAccount->getConversations().size() == 0); + CPPUNIT_ASSERT(DRing::getConversations(bobId).size() == 0); } void ConversationTest::testCountInteractions() { auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; std::condition_variable cv; std::string msgId1 = "", msgId2 = "", msgId3 = ""; - aliceAccount->sendMessage(convId, "1"s, "", "text/plain", true, [&](bool, std::string commitId) { - msgId1 = commitId; - cv.notify_one(); - }); + aliceAccount->convModule() + ->sendMessage(convId, "1"s, "", "text/plain", true, [&](bool, std::string commitId) { + msgId1 = commitId; + cv.notify_one(); + }); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return !msgId1.empty(); })); - aliceAccount->sendMessage(convId, "2"s, "", "text/plain", true, [&](bool, std::string commitId) { - msgId2 = commitId; - cv.notify_one(); - }); + aliceAccount->convModule() + ->sendMessage(convId, "2"s, "", "text/plain", true, [&](bool, std::string commitId) { + msgId2 = commitId; + cv.notify_one(); + }); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return !msgId2.empty(); })); - aliceAccount->sendMessage(convId, "3"s, "", "text/plain", true, [&](bool, std::string commitId) { - msgId3 = commitId; - cv.notify_one(); - }); + aliceAccount->convModule() + ->sendMessage(convId, "3"s, "", "text/plain", true, [&](bool, std::string commitId) { + msgId3 = commitId; + cv.notify_one(); + }); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return !msgId3.empty(); })); CPPUNIT_ASSERT(DRing::countInteractions(aliceId, convId, "", "", "") == 4 /* 3 + initial */); @@ -4701,7 +4723,7 @@ ConversationTest::testGetConversationMembersWithSelfOneOne() aliceAccount->addContact(aliceUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(5), [&]() { return !convId.empty(); })); - auto members = aliceAccount->getConversationMembers(convId); + auto members = DRing::getConversationMembers(aliceId, convId); CPPUNIT_ASSERT(members.size() == 1); CPPUNIT_ASSERT(members[0]["uri"] == aliceUri); } diff --git a/test/unitTest/fileTransfer/fileTransfer.cpp b/test/unitTest/fileTransfer/fileTransfer.cpp index 658e0a527708ca69951733eee0b1e25f1227f086..a441a53a5523ffacb9eff1c95daf4a7edc687ae0 100644 --- a/test/unitTest/fileTransfer/fileTransfer.cpp +++ b/test/unitTest/fileTransfer/fileTransfer.cpp @@ -481,14 +481,14 @@ FileTransferTest::testConversationFileTransfer() })); DRing::registerSignalHandlers(confHandlers); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); - aliceAccount->addConversationMember(convId, bobUri); - aliceAccount->addConversationMember(convId, carlaUri); + DRing::addConversationMember(aliceId, convId, bobUri); + DRing::addConversationMember(aliceId, convId, carlaUri); cv.wait_for(lk, std::chrono::seconds(60), [&]() { return requestReceived == 2; }); - bobAccount->acceptConversationRequest(convId); - carlaAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); + DRing::acceptConversationRequest(carlaId, convId); cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady == 3 && memberJoined == 2; }); @@ -529,7 +529,7 @@ FileTransferTest::testFileTransferInConversation() auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); auto bobUri = bobAccount->getUsername(); auto aliceUri = aliceAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; @@ -588,10 +588,10 @@ FileTransferTest::testFileTransferInConversation() })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady && bobJoined; })); @@ -630,7 +630,7 @@ FileTransferTest::testBadSha3sumOut() auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); auto bobUri = bobAccount->getUsername(); auto aliceUri = aliceAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; @@ -698,10 +698,10 @@ FileTransferTest::testBadSha3sumOut() })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady && memberJoin; })); @@ -746,7 +746,7 @@ FileTransferTest::testBadSha3sumIn() auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); auto bobUri = bobAccount->getUsername(); auto aliceUri = aliceAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; @@ -814,10 +814,10 @@ FileTransferTest::testBadSha3sumIn() })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() { return conversationReady && memberJoin; })); @@ -864,7 +864,7 @@ FileTransferTest::testAskToMultipleParticipants() auto aliceUri = aliceAccount->getUsername(); auto bobUri = bobAccount->getUsername(); auto carlaUri = carlaAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; @@ -935,10 +935,10 @@ FileTransferTest::testAskToMultipleParticipants() })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() { return conversationReady && memberJoin; })); @@ -947,10 +947,10 @@ FileTransferTest::testAskToMultipleParticipants() conversationReady = false; memberJoin = false; - aliceAccount->addConversationMember(convId, carlaUri); + DRing::addConversationMember(aliceId, convId, carlaUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); - carlaAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(carlaId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() { return conversationReady && memberJoin; })); @@ -994,7 +994,7 @@ FileTransferTest::testCancelInTransfer() auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); auto bobUri = bobAccount->getUsername(); auto aliceUri = aliceAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; @@ -1055,10 +1055,10 @@ FileTransferTest::testCancelInTransfer() })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady && bobJoined; })); @@ -1096,7 +1096,7 @@ FileTransferTest::testTransferInfo() auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId); auto bobUri = bobAccount->getUsername(); auto aliceUri = aliceAccount->getUsername(); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; @@ -1155,10 +1155,10 @@ FileTransferTest::testTransferInfo() })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady && bobJoined; })); diff --git a/test/unitTest/syncHistory/syncHistory.cpp b/test/unitTest/syncHistory/syncHistory.cpp index 7608518ab1fc111af3f672b14bfd2677077a4bd2..2cb28faa55009b64f325cb32d4de3ecc19cad1fa 100644 --- a/test/unitTest/syncHistory/syncHistory.cpp +++ b/test/unitTest/syncHistory/syncHistory.cpp @@ -111,7 +111,7 @@ SyncHistoryTest::testCreateConversationThenSync() { auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); // Start conversation - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); // Now create alice2 auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz"; @@ -181,7 +181,7 @@ SyncHistoryTest::testCreateConversationWithOnlineDevice() std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers; // Start conversation now - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); auto conversationReady = false, alice2Ready = false; confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>( [&](const std::string& accountId, const std::string& conversationId) { @@ -213,7 +213,7 @@ void SyncHistoryTest::testCreateConversationWithMessagesThenAddDevice() { auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; @@ -222,7 +222,7 @@ SyncHistoryTest::testCreateConversationWithMessagesThenAddDevice() auto conversationReady = false; auto messageReceived = false; confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>( - [&](const std::string& accountId, + [&](const std::string& /* accountId */, const std::string& /* conversationId */, std::map<std::string, std::string> /*message*/) { messageReceived = true; @@ -240,11 +240,11 @@ SyncHistoryTest::testCreateConversationWithMessagesThenAddDevice() // Start conversation messageReceived = false; - aliceAccount->sendMessage(convId, std::string("Message 1")); + DRing::sendMessage(aliceId, convId, std::string("Message 1"), ""); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(10), [&] { return messageReceived; })); - aliceAccount->sendMessage(convId, std::string("Message 2")); + DRing::sendMessage(aliceId, convId, std::string("Message 2"), ""); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(10), [&] { return messageReceived; })); - aliceAccount->sendMessage(convId, std::string("Message 3")); + DRing::sendMessage(aliceId, convId, std::string("Message 3"), ""); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(10), [&] { return messageReceived; })); // Now create alice2 @@ -263,7 +263,6 @@ SyncHistoryTest::testCreateConversationWithMessagesThenAddDevice() // Check if conversation is ready CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() { return conversationReady; })); - auto alice2Account = Manager::instance().getAccount<JamiAccount>(alice2Id); std::vector<std::map<std::string, std::string>> messages; confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationLoaded>( [&](uint32_t, @@ -276,7 +275,7 @@ SyncHistoryTest::testCreateConversationWithMessagesThenAddDevice() } })); DRing::registerSignalHandlers(confHandlers); - alice2Account->loadConversationMessages(convId); + DRing::loadConversationMessages(alice2Id, convId, "", 0); cv.wait_for(lk, std::chrono::seconds(30)); DRing::unregisterSignalHandlers(); confHandlers.clear(); @@ -294,25 +293,25 @@ SyncHistoryTest::testCreateMultipleConversationThenAddDevice() { auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId); // Start conversation - auto convId = aliceAccount->startConversation(); - aliceAccount->sendMessage(convId, std::string("Message 1")); - aliceAccount->sendMessage(convId, std::string("Message 2")); - aliceAccount->sendMessage(convId, std::string("Message 3")); + auto convId = DRing::startConversation(aliceId); + DRing::sendMessage(aliceId, convId, std::string("Message 1"), ""); + DRing::sendMessage(aliceId, convId, std::string("Message 2"), ""); + DRing::sendMessage(aliceId, convId, std::string("Message 3"), ""); std::this_thread::sleep_for(std::chrono::seconds(1)); - auto convId2 = aliceAccount->startConversation(); - aliceAccount->sendMessage(convId2, std::string("Message 1")); - aliceAccount->sendMessage(convId2, std::string("Message 2")); - aliceAccount->sendMessage(convId2, std::string("Message 3")); + auto convId2 = DRing::startConversation(aliceId); + DRing::sendMessage(aliceId, convId2, std::string("Message 1"), ""); + DRing::sendMessage(aliceId, convId2, std::string("Message 2"), ""); + DRing::sendMessage(aliceId, convId2, std::string("Message 3"), ""); std::this_thread::sleep_for(std::chrono::seconds(1)); - auto convId3 = aliceAccount->startConversation(); - aliceAccount->sendMessage(convId3, std::string("Message 1")); - aliceAccount->sendMessage(convId3, std::string("Message 2")); - aliceAccount->sendMessage(convId3, std::string("Message 3")); + auto convId3 = DRing::startConversation(aliceId); + DRing::sendMessage(aliceId, convId3, std::string("Message 1"), ""); + DRing::sendMessage(aliceId, convId3, std::string("Message 2"), ""); + DRing::sendMessage(aliceId, convId3, std::string("Message 3"), ""); std::this_thread::sleep_for(std::chrono::seconds(1)); - auto convId4 = aliceAccount->startConversation(); - aliceAccount->sendMessage(convId4, std::string("Message 1")); - aliceAccount->sendMessage(convId4, std::string("Message 2")); - aliceAccount->sendMessage(convId4, std::string("Message 3")); + auto convId4 = DRing::startConversation(aliceId); + DRing::sendMessage(aliceId, convId4, std::string("Message 1"), ""); + DRing::sendMessage(aliceId, convId4, std::string("Message 2"), ""); + DRing::sendMessage(aliceId, convId4, std::string("Message 3"), ""); // Now create alice2 auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz"; @@ -364,7 +363,7 @@ SyncHistoryTest::testReceivesInviteThenAddDevice() auto uri = aliceAccount->getUsername(); // Start conversation for Alice - auto convId = bobAccount->startConversation(); + auto convId = DRing::startConversation(bobId); // Check that alice receives the request std::mutex mtx; @@ -394,7 +393,7 @@ SyncHistoryTest::testReceivesInviteThenAddDevice() DRing::registerSignalHandlers(confHandlers); memberEvent = false; - bobAccount->addConversationMember(convId, uri); + DRing::addConversationMember(bobId, convId, uri); CPPUNIT_ASSERT( cv.wait_for(lk, std::chrono::seconds(30), [&] { return memberEvent && requestReceived; })); DRing::unregisterSignalHandlers(); @@ -453,7 +452,7 @@ SyncHistoryTest::testRemoveConversationOnAllDevices() std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers; // Start conversation now - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); bool alice2Ready = false; auto conversationReady = false, conversationRemoved = false; confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>( @@ -486,7 +485,7 @@ SyncHistoryTest::testRemoveConversationOnAllDevices() CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&] { return alice2Ready && conversationReady; })); - aliceAccount->removeConversation(convId); + DRing::removeConversation(aliceId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return conversationRemoved; })); DRing::unregisterSignalHandlers(); @@ -507,7 +506,7 @@ SyncHistoryTest::testSyncCreateAccountExportDeleteReimportOldBackup() aliceAccount->exportArchive(aliceArchive); // Start conversation - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; @@ -560,10 +559,10 @@ SyncHistoryTest::testSyncCreateAccountExportDeleteReimportOldBackup() })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; })); // Wait that alice sees Bob @@ -585,17 +584,16 @@ SyncHistoryTest::testSyncCreateAccountExportDeleteReimportOldBackup() conversationReady = false; alice2Id = Manager::instance().addAccount(details); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return alice2Ready; })); - aliceAccount = Manager::instance().getAccount<JamiAccount>(alice2Id); // This will trigger a conversation request. Cause alice2 can't know first conversation - bobAccount->sendMessage(convId, std::string("hi")); + DRing::sendMessage(bobId, convId, std::string("hi"), ""); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return requestReceived; })); - aliceAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(alice2Id, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return conversationReady; })); messageBobReceived = 0; - aliceAccount->sendMessage(convId, std::string("hi")); + DRing::sendMessage(alice2Id, convId, std::string("hi"), ""); cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageBobReceived == 1; }); std::remove(aliceArchive.c_str()); DRing::unregisterSignalHandlers(); @@ -611,7 +609,7 @@ SyncHistoryTest::testSyncCreateAccountExportDeleteReimportWithConvId() auto bobUri = bobAccount->getUsername(); // Start conversation - auto convId = aliceAccount->startConversation(); + auto convId = DRing::startConversation(aliceId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; @@ -671,10 +669,10 @@ SyncHistoryTest::testSyncCreateAccountExportDeleteReimportWithConvId() })); DRing::registerSignalHandlers(confHandlers); - aliceAccount->addConversationMember(convId, bobUri); + DRing::addConversationMember(aliceId, convId, bobUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); - bobAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(bobId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; })); // We need to track presence to know when to sync @@ -708,8 +706,7 @@ SyncHistoryTest::testSyncCreateAccountExportDeleteReimportWithConvId() CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return conversationReady; })); messageBobReceived = 0; - aliceAccount = Manager::instance().getAccount<JamiAccount>(alice2Id); - aliceAccount->sendMessage(convId, std::string("hi")); + DRing::sendMessage(alice2Id, convId, std::string("hi"), ""); cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageBobReceived == 1; }); std::remove(aliceArchive.c_str()); DRing::unregisterSignalHandlers(); @@ -725,7 +722,7 @@ SyncHistoryTest::testSyncCreateAccountExportDeleteReimportWithConvReq() auto aliceUri = aliceAccount->getUsername(); // Start conversation - auto convId = bobAccount->startConversation(); + auto convId = DRing::startConversation(bobId); std::mutex mtx; std::unique_lock<std::mutex> lk {mtx}; @@ -778,7 +775,7 @@ SyncHistoryTest::testSyncCreateAccountExportDeleteReimportWithConvReq() })); DRing::registerSignalHandlers(confHandlers); - bobAccount->addConversationMember(convId, aliceUri); + DRing::addConversationMember(bobId, convId, aliceUri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; })); // Backup alice after startConversation with member @@ -801,10 +798,9 @@ SyncHistoryTest::testSyncCreateAccountExportDeleteReimportWithConvReq() conversationReady = false; alice2Id = Manager::instance().addAccount(details); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return alice2Ready; })); - aliceAccount = Manager::instance().getAccount<JamiAccount>(alice2Id); // Should get the same request as before. - aliceAccount->acceptConversationRequest(convId); + DRing::acceptConversationRequest(alice2Id, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady && messageBobReceived == 1; })); @@ -883,7 +879,7 @@ SyncHistoryTest::testConversationRequestRemoved() auto uri = aliceAccount->getUsername(); // Start conversation for Alice - auto convId = bobAccount->startConversation(); + auto convId = DRing::startConversation(bobId); // Check that alice receives the request std::mutex mtx; @@ -903,7 +899,7 @@ SyncHistoryTest::testConversationRequestRemoved() })); DRing::registerSignalHandlers(confHandlers); - bobAccount->addConversationMember(convId, uri); + DRing::addConversationMember(bobId, convId, uri); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return requestReceived; })); DRing::unregisterSignalHandlers(); confHandlers.clear(); @@ -946,7 +942,7 @@ SyncHistoryTest::testConversationRequestRemoved() CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return requestReceived; })); // Now decline trust request, this should trigger ConversationRequestDeclined both sides for Alice - aliceAccount->declineConversationRequest(convId); + DRing::declineConversationRequest(aliceId, convId); CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return requestDeclined && requestDeclined2;