LCOV - code coverage report
Current view: top level - src/im - message_engine.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 77.2 % 189 146
Test Date: 2026-11-09 10:41:14 Functions: 83.3 % 18 15

            Line data    Source code
       1              : /*
       2              :  *  Copyright (C) 2004-2026 Savoir-faire Linux Inc.
       3              :  *
       4              :  *  This program is free software: you can redistribute it and/or modify
       5              :  *  it under the terms of the GNU General Public License as published by
       6              :  *  the Free Software Foundation, either version 3 of the License, or
       7              :  *  (at your option) any later version.
       8              :  *
       9              :  *  This program is distributed in the hope that it will be useful,
      10              :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11              :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12              :  *  GNU General Public License for more details.
      13              :  *
      14              :  *  You should have received a copy of the GNU General Public License
      15              :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16              :  */
      17              : 
      18              : #include "message_engine.h"
      19              : #include "sip/sipaccountbase.h"
      20              : #include "manager.h"
      21              : #include "fileutils.h"
      22              : 
      23              : #include "client/jami_signal.h"
      24              : #include "jami/account_const.h"
      25              : 
      26              : #include <dhtnet/fileutils.h>
      27              : #include <opendht/thread_pool.h>
      28              : #include <fmt/std.h>
      29              : 
      30              : #include <cstring>
      31              : #include <fstream>
      32              : #include <vector>
      33              : 
      34              : namespace jami {
      35              : namespace im {
      36              : 
      37         1043 : MessageEngine::MessageEngine(SIPAccountBase& acc, const std::filesystem::path& path)
      38         1043 :     : account_(acc)
      39         1043 :     , savePath_(path)
      40         1043 :     , ioContext_(Manager::instance().ioContext())
      41         1043 :     , saveTimer_(*ioContext_)
      42              : {
      43         1043 :     dhtnet::fileutils::check_dir(savePath_.parent_path());
      44         1043 :     load();
      45         1043 : }
      46              : 
      47              : MessageToken
      48        13981 : MessageEngine::sendMessage(const std::string& to,
      49              :                            const std::string& deviceId,
      50              :                            const std::map<std::string, std::string>& payloads,
      51              :                            uint64_t refreshToken)
      52              : {
      53        13981 :     if (payloads.empty() or to.empty())
      54            0 :         return 0;
      55        13981 :     MessageToken token = 0;
      56              :     {
      57        13981 :         std::lock_guard lock(messagesMutex_);
      58        13981 :         auto& peerMessages = deviceId.empty() ? messages_[to] : messagesDevices_[deviceId];
      59        13981 :         if (refreshToken != 0) {
      60        11518 :             for (auto& m : peerMessages) {
      61          990 :                 if (m.token == refreshToken) {
      62          990 :                     token = refreshToken;
      63          990 :                     m.to = to;
      64          990 :                     m.payloads = payloads;
      65          990 :                     m.status = MessageStatus::IDLE;
      66          990 :                     break;
      67              :                 }
      68              :             }
      69              :         }
      70        13981 :         if (token == 0) {
      71        12991 :             token = std::uniform_int_distribution<MessageToken> {1, JAMI_ID_MAX_VAL}(account_.rand);
      72        12991 :             auto& m = peerMessages.emplace_back(Message {token});
      73        12991 :             m.to = to;
      74        12991 :             m.payloads = payloads;
      75              :         }
      76        13981 :         scheduleSave();
      77        13981 :     }
      78        13981 :     asio::post(*ioContext_, [this, w = account_.weak_from_this(), to, deviceId]() {
      79        13981 :         if (w.lock())
      80        13981 :             retrySend(to, deviceId, true);
      81        13981 :     });
      82        13981 :     return token;
      83              : }
      84              : 
      85              : void
      86         4552 : MessageEngine::onPeerOnline(const std::string& peer, const std::string& deviceId, bool retryOnTimeout)
      87              : {
      88         4552 :     retrySend(peer, deviceId, retryOnTimeout);
      89         4552 : }
      90              : 
      91              : void
      92          938 : MessageEngine::onRegistrationResumed()
      93              : {
      94          938 :     std::vector<std::string> peers;
      95          938 :     std::vector<std::string> devices;
      96              :     {
      97          938 :         std::lock_guard lock(messagesMutex_);
      98          938 :         peers.reserve(messages_.size());
      99          959 :         for (const auto& [peer, _] : messages_)
     100           21 :             peers.emplace_back(peer);
     101          938 :         devices.reserve(messagesDevices_.size());
     102          950 :         for (const auto& [device, _] : messagesDevices_)
     103           12 :             devices.emplace_back(device);
     104          938 :     }
     105              : 
     106          938 :     auto w = account_.weak_from_this();
     107          959 :     for (auto& peer : peers) {
     108           21 :         asio::post(*ioContext_, [this, w, peer = std::move(peer)] {
     109           21 :             if (w.lock())
     110           21 :                 retrySend(peer, {}, true);
     111           21 :         });
     112              :     }
     113          950 :     for (auto& device : devices) {
     114           12 :         asio::post(*ioContext_, [this, w, device = std::move(device)] {
     115           12 :             if (w.lock())
     116           12 :                 retrySend({}, device, true);
     117           12 :         });
     118              :     }
     119          938 : }
     120              : 
     121              : void
     122        18566 : MessageEngine::retrySend(const std::string& peer, const std::string& deviceId, bool retryOnTimeout)
     123              : {
     124              :     struct PendingMsg
     125              :     {
     126              :         MessageToken token;
     127              :         std::string to;
     128              :         std::map<std::string, std::string> payloads;
     129              :     };
     130        18566 :     std::vector<PendingMsg> pending {};
     131        18566 :     auto now = clock::now();
     132              :     {
     133        18566 :         std::lock_guard lock(messagesMutex_);
     134        18566 :         auto& m = deviceId.empty() ? messages_ : messagesDevices_;
     135        18566 :         auto p = m.find(deviceId.empty() ? peer : deviceId);
     136        18566 :         if (p == m.end())
     137         3557 :             return;
     138        15009 :         auto& messages = p->second;
     139              : 
     140        29910 :         for (auto& m : messages) {
     141        14901 :             if (m.status == MessageStatus::IDLE) {
     142        14889 :                 m.status = MessageStatus::SENDING;
     143        14889 :                 m.retried++;
     144        14889 :                 m.last_op = now;
     145        14889 :                 pending.emplace_back(PendingMsg {m.token, m.to, m.payloads});
     146              :             }
     147              :         }
     148        18566 :     }
     149              :     // avoid locking while calling callback
     150        29898 :     for (const auto& p : pending) {
     151        14889 :         JAMI_DEBUG("[Account {:s}] [message {:d}] Reattempt sending", account_.getAccountID(), p.token);
     152        44667 :         if (p.payloads.find("application/im-gitmessage-id") == p.payloads.end())
     153          587 :             emitSignal<libjami::ConfigurationSignal::AccountMessageStatusChanged>(
     154          587 :                 account_.getAccountID(),
     155              :                 "",
     156          587 :                 p.to,
     157         1174 :                 std::to_string(p.token),
     158              :                 (int) libjami::Account::MessageStates::SENDING);
     159        14889 :         account_.sendMessage(p.to, deviceId, p.payloads, p.token, retryOnTimeout, false);
     160              :     }
     161        18566 : }
     162              : 
     163              : MessageStatus
     164            0 : MessageEngine::getStatus(MessageToken t) const
     165              : {
     166            0 :     std::lock_guard lock(messagesMutex_);
     167            0 :     for (const auto& p : messages_) {
     168            0 :         for (const auto& m : p.second) {
     169            0 :             if (m.token == t)
     170            0 :                 return m.status;
     171              :         }
     172              :     }
     173            0 :     return MessageStatus::UNKNOWN;
     174            0 : }
     175              : 
     176              : void
     177        14889 : MessageEngine::onMessageSent(const std::string& peer, MessageToken token, bool ok, const std::string& deviceId)
     178              : {
     179        14889 :     JAMI_DEBUG("[Account {:s}] [message {:d}] Message sent: {:s}",
     180              :                account_.getAccountID(),
     181              :                token,
     182              :                ok ? "success"sv : "failure"sv);
     183        14889 :     std::lock_guard lock(messagesMutex_);
     184        14889 :     auto& m = deviceId.empty() ? messages_ : messagesDevices_;
     185              : 
     186        14889 :     auto p = m.find(deviceId.empty() ? peer : deviceId);
     187        14889 :     if (p == m.end()) {
     188            0 :         JAMI_WARNING("[Account {:s}] onMessageSent: Peer not found: id:{} device:{}",
     189              :                      account_.getAccountID(),
     190              :                      peer,
     191              :                      deviceId);
     192            0 :         return;
     193              :     }
     194              : 
     195        29881 :     auto f = std::find_if(p->second.begin(), p->second.end(), [&](const Message& m) { return m.token == token; });
     196        14889 :     if (f != p->second.end()) {
     197        29778 :         auto emit = f->payloads.find("application/im-gitmessage-id") == f->payloads.end();
     198        14889 :         if (f->status == MessageStatus::SENDING) {
     199        14886 :             if (ok) {
     200        12556 :                 f->status = MessageStatus::SENT;
     201        12556 :                 JAMI_LOG("[Account {:s}] [message {:d}] Status changed to SENT", account_.getAccountID(), token);
     202        12556 :                 if (emit)
     203          482 :                     emitSignal<libjami::ConfigurationSignal::AccountMessageStatusChanged>(
     204          482 :                         account_.getAccountID(),
     205              :                         "",
     206          482 :                         f->to,
     207          964 :                         std::to_string(token),
     208              :                         static_cast<int>(libjami::Account::MessageStates::SENT));
     209        12556 :                 p->second.erase(f);
     210        12556 :                 scheduleSave();
     211         2330 :             } else if (f->retried >= MAX_RETRIES) {
     212            3 :                 f->status = MessageStatus::FAILURE;
     213            3 :                 JAMI_WARNING("[Account {:s}] [message {:d}] Status changed to FAILURE", account_.getAccountID(), token);
     214            3 :                 if (emit)
     215            0 :                     emitSignal<libjami::ConfigurationSignal::AccountMessageStatusChanged>(
     216            0 :                         account_.getAccountID(),
     217              :                         "",
     218            0 :                         f->to,
     219            0 :                         std::to_string(token),
     220              :                         static_cast<int>(libjami::Account::MessageStates::FAILURE));
     221            3 :                 p->second.erase(f);
     222            3 :                 scheduleSave();
     223              :             } else {
     224         2327 :                 f->status = MessageStatus::IDLE;
     225         2327 :                 JAMI_DEBUG("[Account {:s}] [message {:d}] Status changed to IDLE", account_.getAccountID(), token);
     226              :             }
     227              :         } else {
     228            3 :             JAMI_DEBUG("[Account {:s}] [message {:d}] State is not SENDING", account_.getAccountID(), token);
     229              :         }
     230              :     } else {
     231            0 :         JAMI_DEBUG("[Account {:s}] [message {:d}] Unable to find message", account_.getAccountID(), token);
     232              :     }
     233        14889 : }
     234              : 
     235              : void
     236         1043 : MessageEngine::load()
     237              : {
     238              :     try {
     239         1043 :         decltype(messages_) messages;
     240         1043 :         decltype(messagesDevices_) deviceMessages;
     241              :         {
     242         1043 :             std::lock_guard lock(dhtnet::fileutils::getFileLock(savePath_));
     243         2086 :             const auto data = fileutils::loadFile(savePath_);
     244            0 :             msgpack::unpacker unpacker;
     245            0 :             unpacker.reserve_buffer(data.size());
     246            0 :             std::memcpy(unpacker.buffer(), data.data(), data.size());
     247            0 :             unpacker.buffer_consumed(data.size());
     248              : 
     249            0 :             msgpack::object_handle object;
     250            0 :             if (!unpacker.next(object))
     251            0 :                 throw std::runtime_error("Invalid message queue");
     252            0 :             object.get().convert(messages);
     253            0 :             if (unpacker.next(object))
     254            0 :                 object.get().convert(deviceMessages);
     255            0 :         }
     256            0 :         std::lock_guard lock(messagesMutex_);
     257            0 :         messages_ = std::move(messages);
     258            0 :         messagesDevices_ = std::move(deviceMessages);
     259            0 :         normalizeLoadedMessages();
     260            0 :         if (not messages_.empty() || not messagesDevices_.empty())
     261            0 :             JAMI_LOG("[Account {}] Loaded {} peer and {} device message queues from {}",
     262              :                      account_.getAccountID(),
     263              :                      messages_.size(),
     264              :                      messagesDevices_.size(),
     265              :                      savePath_);
     266         3129 :     } catch (const std::exception& e) {
     267         1043 :         JAMI_LOG("[Account {}] Unable to load messages from {}: {}", account_.getAccountID(), savePath_, e.what());
     268         1043 :     }
     269         1043 : }
     270              : 
     271              : void
     272            0 : MessageEngine::normalizeLoadedMessages()
     273              : {
     274            0 :     auto normalize = [](auto& queues) {
     275            0 :         for (auto& [_, messages] : queues)
     276            0 :             for (auto& message : messages)
     277            0 :                 if (message.status == MessageStatus::SENDING)
     278            0 :                     message.status = MessageStatus::IDLE;
     279            0 :     };
     280            0 :     normalize(messages_);
     281            0 :     normalize(messagesDevices_);
     282            0 : }
     283              : 
     284              : void
     285         1117 : MessageEngine::save() const
     286              : {
     287         1117 :     std::lock_guard lock(messagesMutex_);
     288         1117 :     save_();
     289         1117 : }
     290              : 
     291              : void
     292        26540 : MessageEngine::scheduleSave()
     293              : {
     294        26540 :     saveTimer_.expires_after(std::chrono::seconds(5));
     295        26540 :     saveTimer_.async_wait([this, w = account_.weak_from_this()](const std::error_code& ec) {
     296        26540 :         if (!ec)
     297          207 :             if (auto acc = w.lock())
     298          207 :                 save();
     299        26540 :     });
     300        26540 : }
     301              : 
     302              : void
     303         1117 : MessageEngine::save_() const
     304              : {
     305              :     try {
     306         1117 :         std::ofstream file;
     307         1117 :         file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
     308         1117 :         file.open(savePath_, std::ios::trunc | std::ios::binary);
     309         1114 :         if (file.is_open()) {
     310         1114 :             msgpack::pack(file, messages_);
     311         1114 :             msgpack::pack(file, messagesDevices_);
     312              :         }
     313         1120 :     } catch (const std::exception& e) {
     314            3 :         JAMI_ERROR("[Account {}] Unable to serialize pending messages: {}", account_.getAccountID(), e.what());
     315            3 :     }
     316         1117 : }
     317              : 
     318              : } // namespace im
     319              : } // namespace jami
        

Generated by: LCOV version 2.0-1