diff --git a/src/jamidht/account_manager.cpp b/src/jamidht/account_manager.cpp index 8074a284d5c1b1b7073b3dca6a3f2bc7604869d8..9652197798954115645359af2088e1b711ad11f9 100644 --- a/src/jamidht/account_manager.cpp +++ b/src/jamidht/account_manager.cpp @@ -181,6 +181,7 @@ AccountManager::useIdentity(const std::string& accountId, const std::string& username, const OnChangeCallback& onChange) { + accountId_ = accountId; if (receipt.empty() or receiptSignature.empty()) return nullptr; diff --git a/src/jamidht/account_manager.h b/src/jamidht/account_manager.h index 9703012f43025ee1c8c9569a8685ec2ea1a8708d..3c8e10b578a40dff8dfeff61568c9b300bad447a 100644 --- a/src/jamidht/account_manager.h +++ b/src/jamidht/account_manager.h @@ -270,6 +270,7 @@ protected: std::filesystem::path path_; OnChangeCallback onChange_; std::unique_ptr<AccountInfo> info_; + std::string accountId_; std::shared_ptr<dht::DhtRunner> dht_; std::reference_wrapper<NameDirectory> nameDir_; }; diff --git a/src/jamidht/conversation_module.cpp b/src/jamidht/conversation_module.cpp index 704e57a485e8c41d21ff12da37ba6b027fc6d4ff..c1a3db60ba6b9c1218644e884511ea1ee034e568 100644 --- a/src/jamidht/conversation_module.cpp +++ b/src/jamidht/conversation_module.cpp @@ -323,7 +323,9 @@ public: /** * @note convInfosMtx_ should be locked */ - void saveConvInfos() const { ConversationModule::saveConvInfos(accountId_, convInfos_); } + void saveConvInfos() const { + ConversationModule::saveConvInfos(accountId_, convInfos_); + } /** * @note conversationsRequestsMtx_ should be locked */ @@ -403,6 +405,10 @@ public: void fixStructures(std::shared_ptr<JamiAccount> account, const std::vector<std::tuple<std::string, std::string, std::string>>& updateContactConv, const std::set<std::string>& toRm); void cloneConversationFrom(const std::shared_ptr<SyncedConversation> conv, const std::string& deviceId, const std::string& oldConvId = ""); + void bootstrap(const std::string& convId); + void cloneConversationFrom(const std::string& conversationId, + const std::string& uri, + const std::string& oldConvId = ""); }; ConversationModule::Impl::Impl(std::weak_ptr<JamiAccount>&& account, @@ -1225,6 +1231,85 @@ ConversationModule::Impl::cloneConversationFrom(const std::shared_ptr<SyncedConv MIME_TYPE_GIT); } +void +ConversationModule::Impl::bootstrap(const std::string& convId) +{ + std::vector<DeviceId> kd; + if (auto acc = account_.lock()) + for (const auto& [id, _] : acc->getKnownDevices()) + kd.emplace_back(id); + auto bootstrap = [&](auto& conv) { + if (conv) { +#ifdef LIBJAMI_TESTABLE + conv->onBootstrapStatus(bootstrapCbTest_); +#endif // LIBJAMI_TESTABLE + conv->bootstrap(std::bind(&ConversationModule::Impl::bootstrapCb, + this, + conv->id()), + kd); + } + }; + std::vector<std::string> toClone; + if (convId.empty()) { + std::lock_guard<std::mutex> lk(convInfosMtx_); + for (const auto& [conversationId, convInfo] : convInfos_) { + auto conv = getConversation(conversationId); + if (!conv) + return; + if ((!conv->conversation && !conv->info.removed)) { + // Because we're not tracking contact presence in order to sync now, + // we need to ask to clone requests when bootstraping all conversations + // else it can stay syncing + toClone.emplace_back(conversationId); + } else if (conv->conversation) { + bootstrap(conv->conversation); + } + } + } else if (auto conv = getConversation(convId)) { + std::lock_guard<std::mutex> lk(conv->mtx); + if (conv->conversation) + bootstrap(conv->conversation); + } + + for (const auto& cid : toClone) { + auto members = getConversationMembers(cid); + for (const auto& member : members) { + if (member.at("uri") != username_) + cloneConversationFrom(cid, member.at("uri")); + } + } +} + +void +ConversationModule::Impl::cloneConversationFrom(const std::string& conversationId, const std::string& uri, const std::string& oldConvId) +{ + auto acc = account_.lock(); + auto memberHash = dht::InfoHash(uri); + if (!acc || !memberHash) { + JAMI_WARNING("Invalid member detected: {}", uri); + return; + } + auto conv = startConversation(conversationId); + conv->info = {}; + conv->info.id = conversationId; + conv->info.created = std::time(nullptr); + conv->info.members.emplace_back(username_); + conv->info.members.emplace_back(uri); + + std::lock_guard<std::mutex> lk(conv->mtx); + + acc->forEachDevice( + memberHash, + [w = weak(), conv, conversationId, oldConvId]( + const std::shared_ptr<dht::crypto::PublicKey>& pk) { + auto sthis = w.lock(); + auto deviceId = pk->getLongId().toString(); + if (!sthis or deviceId == sthis->deviceId_) + return; + sthis->cloneConversationFrom(conv, deviceId, oldConvId); + }); + addConvInfo(conv->info); +} //////////////////////////////////////////////////////////////// @@ -1449,51 +1534,9 @@ ConversationModule::loadConversations() void ConversationModule::bootstrap(const std::string& convId) { - std::vector<DeviceId> kd; - if (auto acc = pimpl_->account_.lock()) - for (const auto& [id, _] : acc->getKnownDevices()) - kd.emplace_back(id); - auto bootstrap = [&](auto& conv) { - if (conv) { -#ifdef LIBJAMI_TESTABLE - conv->onBootstrapStatus(pimpl_->bootstrapCbTest_); -#endif // LIBJAMI_TESTABLE - conv->bootstrap(std::bind(&ConversationModule::Impl::bootstrapCb, - pimpl_.get(), - conv->id()), - kd); - } - }; - std::vector<std::string> toClone; - if (convId.empty()) { - std::lock_guard<std::mutex> lk(pimpl_->convInfosMtx_); - for (const auto& [conversationId, convInfo] : pimpl_->convInfos_) { - auto conv = pimpl_->getConversation(conversationId); - if (!conv) - return; - if ((!conv->conversation && !conv->info.removed)) { - // Because we're not tracking contact presence in order to sync now, - // we need to ask to clone requests when bootstraping all conversations - // else it can stay syncing - toClone.emplace_back(conversationId); - } else if (conv->conversation) { - bootstrap(conv->conversation); - } - } - } else if (auto conv = pimpl_->getConversation(convId)) { - std::lock_guard<std::mutex> lk(conv->mtx); - if (conv->conversation) - bootstrap(conv->conversation); - } - - for (const auto& cid : toClone) { - auto members = getConversationMembers(cid); - for (const auto& member : members) { - if (member.at("uri") != pimpl_->username_) - cloneConversationFrom(cid, member.at("uri")); - } - } + pimpl_->bootstrap(convId); } + void ConversationModule::monitor() { @@ -1605,6 +1648,7 @@ ConversationModule::onTrustRequest(const std::string& uri, emitSignal<libjami::ConversationSignal::ConversationRequestReceived>(pimpl_->accountId_, conversationId, reqMap); + pimpl_->needsSyncingCb_({}); } } @@ -1775,32 +1819,7 @@ ConversationModule::cloneConversationFrom(const std::string& conversationId, const std::string& uri, const std::string& oldConvId) { - auto acc = pimpl_->account_.lock(); - auto memberHash = dht::InfoHash(uri); - if (!acc || !memberHash) { - JAMI_WARNING("Invalid member detected: {}", uri); - return; - } - auto conv = pimpl_->startConversation(conversationId); - conv->info = {}; - conv->info.id = conversationId; - conv->info.created = std::time(nullptr); - conv->info.members.emplace_back(pimpl_->username_); - conv->info.members.emplace_back(uri); - - std::lock_guard<std::mutex> lk(conv->mtx); - - acc->forEachDevice( - memberHash, - [w = pimpl_->weak(), conv, conversationId, oldConvId]( - const std::shared_ptr<dht::crypto::PublicKey>& pk) { - auto sthis = w.lock(); - auto deviceId = pk->getLongId().toString(); - if (!sthis or deviceId == sthis->deviceId_) - return; - sthis->cloneConversationFrom(conv, deviceId, oldConvId); - }); - addConvInfo(conv->info); + pimpl_->cloneConversationFrom(conversationId, uri, oldConvId); } // Message send/load @@ -2044,6 +2063,7 @@ ConversationModule::onSyncData(const SyncMsg& msg, const std::string& peerId, const std::string& deviceId) { + std::vector<std::string> toClone; for (const auto& [key, convInfo] : msg.c) { const auto& convId = convInfo.id; pimpl_->rmConversationRequest(convId); @@ -2062,8 +2082,17 @@ ConversationModule::onSyncData(const SyncMsg& msg, JAMI_DEBUG("Re-add previously removed conversation {:s}", convId); } conv->info = convInfo; - if (!conv->conversation) - pimpl_->cloneConversation(deviceId, peerId, conv, convInfo.lastDisplayed); + if (!conv->conversation) { + if (deviceId != "") { + pimpl_->cloneConversation(deviceId, peerId, conv, convInfo.lastDisplayed); + } else { + // In this case, informations are from JAMS + // JAMS doesn't store the conversation itself, so we + // must use informations to clone the conversation + addConvInfo(convInfo); + toClone.emplace_back(convId); + } + } } else { if (conv->conversation && !conv->conversation->isRemoving()) { emitSignal<libjami::ConversationSignal::ConversationRemoved>(pimpl_->accountId_, @@ -2085,6 +2114,14 @@ ConversationModule::onSyncData(const SyncMsg& msg, } } + for (const auto& cid : toClone) { + auto members = getConversationMembers(cid); + for (const auto& member : members) { + if (member.at("uri") != pimpl_->username_) + cloneConversationFrom(cid, member.at("uri")); + } + } + for (const auto& [convId, req] : msg.cr) { if (pimpl_->isAcceptedConversation(convId)) { // Already accepted request diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp index 55efcf4dcab563a8f795efc102512d9c829cceea..7607ca92aadaad4f56fdde7ff465d88344f0e216 100644 --- a/src/jamidht/jamiaccount.cpp +++ b/src/jamidht/jamiaccount.cpp @@ -2138,9 +2138,15 @@ JamiAccount::convModule() weak(), [this](auto&& syncMsg) { dht::ThreadPool::io().run([w = weak(), syncMsg] { - if (auto shared = w.lock()) + if (auto shared = w.lock()) { + auto& config = shared->config(); + // For JAMS account, we must update the server + if (!config.managerUri.empty()) + if (auto am = shared->accountManager()) + am->syncDevices(); if (auto sm = shared->syncModule()) sm->syncWithConnected(syncMsg); + } }); }, [this](auto&& uri, auto&& device, auto&& msg, auto token = 0) { diff --git a/src/jamidht/server_account_manager.cpp b/src/jamidht/server_account_manager.cpp index 9b391eb4798cd9582ff93d1258342cbc2878f7b3..056d24a6d2790f89f8450f869c2b8e8fdaa0bcf2 100644 --- a/src/jamidht/server_account_manager.cpp +++ b/src/jamidht/server_account_manager.cpp @@ -24,6 +24,8 @@ #include <opendht/log.h> #include <opendht/thread_pool.h> +#include "conversation_module.h" +#include "jamiaccount.h" #include "manager.h" #include <algorithm> @@ -41,6 +43,8 @@ constexpr std::string_view PATH_DEVICE = JAMI_PATH_AUTH "/device"; constexpr std::string_view PATH_DEVICES = JAMI_PATH_AUTH "/devices"; constexpr std::string_view PATH_SEARCH = JAMI_PATH_AUTH "/directory/search"; constexpr std::string_view PATH_CONTACTS = JAMI_PATH_AUTH "/contacts"; +constexpr std::string_view PATH_CONVERSATIONS = JAMI_PATH_AUTH "/conversations"; +constexpr std::string_view PATH_CONVERSATIONS_REQUESTS = JAMI_PATH_AUTH "/conversationRequests"; constexpr std::string_view PATH_BLUEPRINT = JAMI_PATH_AUTH "/policyData"; ServerAccountManager::ServerAccountManager(const std::filesystem::path& path, @@ -379,8 +383,95 @@ ServerAccountManager::syncDevices() { const std::string urlDevices = managerHostname_ + PATH_DEVICES; const std::string urlContacts = managerHostname_ + PATH_CONTACTS; + const std::string urlConversations = managerHostname_ + PATH_CONVERSATIONS; + const std::string urlConversationsRequests = managerHostname_ + PATH_CONVERSATIONS_REQUESTS; - JAMI_WARN("[Auth] syncContacts %s", urlContacts.c_str()); + JAMI_WARNING("[Auth] sync conversations {}", urlConversations); + Json::Value jsonConversations(Json::arrayValue); + for (const auto& [key, convInfo] : ConversationModule::convInfos(accountId_)) { + jsonConversations.append(convInfo.toJson()); + } + sendDeviceRequest(std::make_shared<Request>( + *Manager::instance().ioContext(), + urlConversations, + jsonConversations, + [w=weak_from_this()](Json::Value json, const dht::http::Response& response) { + JAMI_DEBUG("[Auth] Got conversation sync request callback with status code={}", + response.status_code); + auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock()); + if (!this_) return; + if (response.status_code >= 200 && response.status_code < 300) { + try { + JAMI_WARNING("[Auth] Got server response: {}", response.body); + if (not json.isArray()) { + JAMI_ERROR("[Auth] Can't parse server response: not an array"); + } else { + SyncMsg convs; + for (unsigned i = 0, n = json.size(); i < n; i++) { + const auto& e = json[i]; + auto ci = ConvInfo(e); + convs.c[ci.id] = std::move(ci); + } + dht::ThreadPool::io().run([accountId=this_->accountId_, convs] { + if (auto acc = Manager::instance().getAccount<JamiAccount>(accountId)) { + acc->convModule()->onSyncData(convs, "", ""); + } + }); + } + } catch (const std::exception& e) { + JAMI_ERROR("Error when iterating conversation list: {}", e.what()); + } + } else if (response.status_code == 401) + this_->authError(TokenScope::Device); + + this_->clearRequest(response.request); + }, + logger_)); + + JAMI_WARNING("[Auth] sync conversations requests {}", urlConversationsRequests); + Json::Value jsonConversationsRequests(Json::arrayValue); + for (const auto& [key, convRequest] : ConversationModule::convRequests(accountId_)) { + auto jsonConversation = convRequest.toJson(); + jsonConversationsRequests.append(std::move(jsonConversation)); + } + sendDeviceRequest(std::make_shared<Request>( + *Manager::instance().ioContext(), + urlConversationsRequests, + jsonConversationsRequests, + [w=weak_from_this()](Json::Value json, const dht::http::Response& response) { + JAMI_DEBUG("[Auth] Got conversations requests sync request callback with status code={}", + response.status_code); + auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock()); + if (!this_) return; + if (response.status_code >= 200 && response.status_code < 300) { + try { + JAMI_WARNING("[Auth] Got server response: {}", response.body); + if (not json.isArray()) { + JAMI_ERROR("[Auth] Can't parse server response: not an array"); + } else { + SyncMsg convReqs; + for (unsigned i = 0, n = json.size(); i < n; i++) { + const auto& e = json[i]; + auto cr = ConversationRequest(e); + convReqs.cr[cr.conversationId] = std::move(cr); + } + dht::ThreadPool::io().run([accountId=this_->accountId_, convReqs] { + if (auto acc = Manager::instance().getAccount<JamiAccount>(accountId)) { + acc->convModule()->onSyncData(convReqs, "", ""); + } + }); + } + } catch (const std::exception& e) { + JAMI_ERROR("Error when iterating conversations requests list: {}", e.what()); + } + } else if (response.status_code == 401) + this_->authError(TokenScope::Device); + + this_->clearRequest(response.request); + }, + logger_)); + + JAMI_WARNING("[Auth] syncContacts {}", urlContacts); Json::Value jsonContacts(Json::arrayValue); for (const auto& contact : info_->contacts->getContacts()) { auto jsonContact = contact.second.toJson(); @@ -392,15 +483,15 @@ ServerAccountManager::syncDevices() urlContacts, jsonContacts, [w=weak_from_this()](Json::Value json, const dht::http::Response& response) { - JAMI_DBG("[Auth] Got contact sync request callback with status code=%u", + JAMI_DEBUG("[Auth] Got contact sync request callback with status code={}", response.status_code); auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock()); if (!this_) return; if (response.status_code >= 200 && response.status_code < 300) { try { - JAMI_WARN("[Auth] Got server response: %s", response.body.c_str()); + JAMI_WARNING("[Auth] Got server response: {}", response.body); if (not json.isArray()) { - JAMI_ERR("[Auth] Can't parse server response: not an array"); + JAMI_ERROR("[Auth] Can't parse server response: not an array"); } else { for (unsigned i = 0, n = json.size(); i < n; i++) { const auto& e = json[i]; @@ -411,7 +502,7 @@ ServerAccountManager::syncDevices() this_->info_->contacts->saveContacts(); } } catch (const std::exception& e) { - JAMI_ERR("Error when iterating contact list: %s", e.what()); + JAMI_ERROR("Error when iterating contact list: {}", e.what()); } } else if (response.status_code == 401) this_->authError(TokenScope::Device); @@ -420,19 +511,19 @@ ServerAccountManager::syncDevices() }, logger_)); - JAMI_WARN("[Auth] syncDevices %s", urlDevices.c_str()); + JAMI_WARNING("[Auth] syncDevices {}", urlDevices); sendDeviceRequest(std::make_shared<Request>( *Manager::instance().ioContext(), urlDevices, [w=weak_from_this()](Json::Value json, const dht::http::Response& response) { - JAMI_DBG("[Auth] Got request callback with status code=%u", response.status_code); + JAMI_DEBUG("[Auth] Got request callback with status code={}", response.status_code); auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock()); if (!this_) return; if (response.status_code >= 200 && response.status_code < 300) { try { - JAMI_WARN("[Auth] Got server response: %s", response.body.c_str()); + JAMI_WARNING("[Auth] Got server response: {}", response.body); if (not json.isArray()) { - JAMI_ERR("[Auth] Can't parse server response: not an array"); + JAMI_ERROR("[Auth] Can't parse server response: not an array"); } else { for (unsigned i = 0, n = json.size(); i < n; i++) { const auto& e = json[i]; @@ -445,7 +536,7 @@ ServerAccountManager::syncDevices() } } } catch (const std::exception& e) { - JAMI_ERR("Error when iterating device list: %s", e.what()); + JAMI_ERROR("Error when iterating device list: {}", e.what()); } } else if (response.status_code == 401) this_->authError(TokenScope::Device); @@ -497,24 +588,24 @@ ServerAccountManager::revokeDevice(const std::string& password, return false; } const std::string url = managerHostname_ + PATH_DEVICE + "/" + device; - JAMI_WARN("[Revoke] Revoking device of %s at %s", info_->username.c_str(), url.c_str()); + JAMI_WARNING("[Revoke] Revoking device of {} at {}", info_->username, url); auto request = std::make_shared<Request>( *Manager::instance().ioContext(), url, [cb, w=weak_from_this()](Json::Value json, const dht::http::Response& response) { - JAMI_DBG("[Revoke] Got request callback with status code=%u", response.status_code); + JAMI_DEBUG("[Revoke] Got request callback with status code={}", response.status_code); auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock()); if (!this_) return; if (response.status_code >= 200 && response.status_code < 300) { try { - JAMI_WARN("[Revoke] Got server response"); + JAMI_WARNING("[Revoke] Got server response"); if (json["errorDetails"].empty()) { if (cb) cb(RevokeDeviceResult::SUCCESS); this_->syncDevices(); } } catch (const std::exception& e) { - JAMI_ERR("Error when loading device list: %s", e.what()); + JAMI_ERROR("Error when loading device list: {}", e.what()); } } else if (cb) cb(RevokeDeviceResult::ERROR_NETWORK); @@ -536,7 +627,7 @@ bool ServerAccountManager::searchUser(const std::string& query, SearchCallback cb) { const std::string url = managerHostname_ + PATH_SEARCH + "?queryString=" + query; - JAMI_WARN("[Search] Searching user %s at %s", query.c_str(), url.c_str()); + JAMI_WARNING("[Search] Searching user {} at {}", query, url); sendDeviceRequest(std::make_shared<Request>( *Manager::instance().ioContext(), url, @@ -550,7 +641,7 @@ ServerAccountManager::searchUser(const std::string& query, SearchCallback cb) Json::Value::ArrayIndex rcount = profiles.size(); std::vector<std::map<std::string, std::string>> results; results.reserve(rcount); - JAMI_WARN("[Search] Got server response: %s", response.body.c_str()); + JAMI_WARNING("[Search] Got server response: {}", response.body); for (Json::Value::ArrayIndex i = 0; i < rcount; i++) { const auto& ruser = profiles[i]; std::map<std::string, std::string> user; @@ -564,7 +655,7 @@ ServerAccountManager::searchUser(const std::string& query, SearchCallback cb) if (cb) cb(results, SearchResponse::found); } catch (const std::exception& e) { - JAMI_ERR("[Search] Error during search: %s", e.what()); + JAMI_ERROR("[Search] Error during search: {}", e.what()); } } else { if (response.status_code == 401)