LCOV - code coverage report
Current view: top level - src - manager.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 52.1 % 1765 920
Test Date: 2026-11-09 10:41:14 Functions: 58.6 % 191 112

            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              : #ifdef HAVE_CONFIG_H
      19              : #include "config.h"
      20              : #endif
      21              : 
      22              : #include "manager.h"
      23              : 
      24              : #include "logger.h"
      25              : #include "account_schema.h"
      26              : 
      27              : #include "fileutils.h"
      28              : #include "gittransport.h"
      29              : #include "jami.h"
      30              : #include "media_attribute.h"
      31              : #include "media/system_codec_container.h"
      32              : #include "account.h"
      33              : #include "string_utils.h"
      34              : #include "jamidht/jamiaccount.h"
      35              : #include "account.h"
      36              : #include <opendht/rng.h>
      37              : 
      38              : #include "call_factory.h"
      39              : 
      40              : #include "sip/sipvoiplink.h"
      41              : #include "sip/sipaccount_config.h"
      42              : 
      43              : #include "im/instant_messaging.h"
      44              : 
      45              : #include "config/yamlparser.h"
      46              : 
      47              : #if HAVE_ALSA
      48              : #include "audio/alsa/alsalayer.h"
      49              : #endif
      50              : 
      51              : #include "audio/sound/dtmf.h"
      52              : #include "audio/ringbufferpool.h"
      53              : 
      54              : #ifdef ENABLE_PLUGIN
      55              : #include "plugin/jamipluginmanager.h"
      56              : #include "plugin/streamdata.h"
      57              : #endif
      58              : 
      59              : #include "client/videomanager.h"
      60              : 
      61              : #include "conference.h"
      62              : 
      63              : #include "client/jami_signal.h"
      64              : #include "jami/call_const.h"
      65              : 
      66              : #include "libav_utils.h"
      67              : #ifdef ENABLE_VIDEO
      68              : #include "video/sinkclient.h"
      69              : #include "video/video_base.h"
      70              : #include "media/video/video_mixer.h"
      71              : #endif
      72              : #include "audio/tonecontrol.h"
      73              : 
      74              : #include <dhtnet/ice_transport_factory.h>
      75              : #include <dhtnet/ice_transport.h>
      76              : #include <dhtnet/upnp/upnp_context.h>
      77              : 
      78              : #include <libavutil/ffversion.h>
      79              : 
      80              : #include <opendht/thread_pool.h>
      81              : 
      82              : #include <asio/io_context.hpp>
      83              : #include <asio/executor_work_guard.hpp>
      84              : 
      85              : #include <git2.h>
      86              : 
      87              : #ifndef WIN32
      88              : #include <sys/time.h>
      89              : #include <sys/resource.h>
      90              : #endif
      91              : 
      92              : #ifdef TARGET_OS_IOS
      93              : #include <CoreFoundation/CoreFoundation.h>
      94              : #endif
      95              : 
      96              : #include <cerrno>
      97              : #include <ctime>
      98              : #include <cstdlib>
      99              : #include <iostream>
     100              : #include <fstream>
     101              : #include <algorithm>
     102              : #include <memory>
     103              : #include <mutex>
     104              : #include <list>
     105              : #include <random>
     106              : 
     107              : #ifndef JAMI_DATADIR
     108              : #error "Define the JAMI_DATADIR macro as the data installation prefix of the package"
     109              : #endif
     110              : 
     111              : namespace jami {
     112              : 
     113              : /** To store uniquely a list of Call ids */
     114              : using CallIDSet = std::set<std::string>;
     115              : 
     116              : static constexpr const char* PACKAGE_OLD = "ring";
     117              : 
     118              : std::atomic_bool Manager::initialized = {false};
     119              : 
     120              : #if TARGET_OS_IOS
     121              : bool Manager::isIOSExtension = {false};
     122              : #endif
     123              : 
     124              : bool Manager::syncOnRegister = {true};
     125              : 
     126              : bool Manager::autoLoad = {true};
     127              : 
     128              : static void
     129           39 : copy_over(const std::filesystem::path& srcPath, const std::filesystem::path& destPath)
     130              : {
     131           39 :     std::ifstream src(srcPath);
     132           39 :     std::ofstream dest(destPath);
     133           39 :     dest << src.rdbuf();
     134           39 :     src.close();
     135           39 :     dest.close();
     136           39 : }
     137              : 
     138              : // Creates a backup of the file at "path" with a .bak suffix appended
     139              : static void
     140           36 : make_backup(const std::filesystem::path& path)
     141              : {
     142           36 :     auto backup_path = path;
     143           36 :     backup_path.replace_extension(".bak");
     144           36 :     copy_over(path, backup_path);
     145           36 : }
     146              : 
     147              : // Restore last backup of the configuration file
     148              : static void
     149            3 : restore_backup(const std::filesystem::path& path)
     150              : {
     151            3 :     auto backup_path = path;
     152            3 :     backup_path.replace_extension(".bak");
     153            3 :     copy_over(backup_path, path);
     154            3 : }
     155              : 
     156              : static void
     157          117 : check_rename(const std::filesystem::path& old_dir, const std::filesystem::path& new_dir)
     158              : {
     159          117 :     if (old_dir == new_dir or not std::filesystem::is_directory(old_dir))
     160           78 :         return;
     161              : 
     162           39 :     std::error_code ec;
     163           39 :     if (not std::filesystem::is_directory(new_dir)) {
     164            0 :         JAMI_WARNING("Migrating {} to {}", old_dir, new_dir);
     165            0 :         std::filesystem::rename(old_dir, new_dir, ec);
     166            0 :         if (ec)
     167            0 :             JAMI_ERROR("Failed to rename {} to {}: {}", old_dir, new_dir, ec.message());
     168              :     } else {
     169           39 :         for (const auto& file_iterator : std::filesystem::directory_iterator(old_dir, ec)) {
     170            0 :             const auto& file_path = file_iterator.path();
     171            0 :             auto new_path = new_dir / file_path.filename();
     172            0 :             if (file_iterator.is_directory() and std::filesystem::is_directory(new_path)) {
     173            0 :                 check_rename(file_path, new_path);
     174              :             } else {
     175            0 :                 JAMI_WARNING("Migrating {} to {}", old_dir, new_path);
     176            0 :                 std::filesystem::rename(file_path, new_path, ec);
     177            0 :                 if (ec)
     178            0 :                     JAMI_ERROR("Failed to rename {} to {}: {}", file_path, new_path, ec.message());
     179              :             }
     180            0 :         }
     181           39 :         std::filesystem::remove_all(old_dir, ec);
     182              :     }
     183              : }
     184              : 
     185              : /**
     186              :  * Set OpenDHT's log level based on the JAMI_LOG_DHT environment variable.
     187              :  * JAMI_LOG_DHT = 0 minimum logging (=disable)
     188              :  * JAMI_LOG_DHT = 1 logging enabled
     189              :  */
     190              : static unsigned
     191           39 : getDhtLogLevel()
     192              : {
     193           39 :     if (auto* envvar = getenv("JAMI_LOG_DHT")) {
     194            0 :         return std::clamp(to_int<unsigned>(envvar, 0), 0u, 1u);
     195              :     }
     196           39 :     return 0;
     197              : }
     198              : 
     199              : static unsigned
     200           39 : getDhtnetLogLevel()
     201              : {
     202           39 :     if (auto* envvar = getenv("JAMI_LOG_DHTNET")) {
     203            0 :         return std::clamp(to_int<unsigned>(envvar, 0), 0u, 1u);
     204              :     }
     205           39 :     return 0;
     206              : }
     207              : 
     208              : /**
     209              :  * Set pjsip's log level based on the JAMI_LOG_SIP environment variable.
     210              :  * JAMI_LOG_SIP = 0 minimum logging
     211              :  * JAMI_LOG_SIP = 6 maximum logging
     212              :  */
     213              : static void
     214           39 : setSipLogLevel()
     215              : {
     216           39 :     int level = 0;
     217           39 :     if (auto* envvar = getenv("JAMI_LOG_SIP")) {
     218            0 :         level = std::clamp(to_int<int>(envvar, 0), 0, 6);
     219              :     }
     220              : 
     221           39 :     pj_log_set_level(level);
     222           39 :     pj_log_set_log_func([](int level, const char* data, int len) {
     223            0 :         auto msg = std::string_view(data, len);
     224            0 :         if (level < 2)
     225            0 :             JAMI_XERR("{}", msg);
     226            0 :         else if (level < 4)
     227            0 :             JAMI_XWARN("{}", msg);
     228              :         else
     229            0 :             JAMI_XDBG("{}", msg);
     230            0 :     });
     231           39 : }
     232              : 
     233              : /**
     234              :  * Set gnutls's log level based on the JAMI_LOG_TLS environment variable.
     235              :  * JAMI_LOG_TLS = 0 minimum logging (default)
     236              :  * JAMI_LOG_TLS = 9 maximum logging
     237              :  */
     238              : static void
     239           39 : setGnuTlsLogLevel()
     240              : {
     241           39 :     int level = 0;
     242           39 :     if (auto* envvar = getenv("JAMI_LOG_TLS")) {
     243            0 :         level = to_int<int>(envvar, 0);
     244            0 :         level = std::clamp(level, 0, 9);
     245              :     }
     246              : 
     247           39 :     gnutls_global_set_log_level(level);
     248          117 :     gnutls_global_set_log_function([](int level, const char* msg) { JAMI_XDBG("[{:d}]GnuTLS: {:s}", level, msg); });
     249           39 : }
     250              : 
     251              : //==============================================================================
     252              : 
     253              : struct Manager::ManagerPimpl
     254              : {
     255              :     explicit ManagerPimpl(Manager& base);
     256              : 
     257              :     bool parseConfiguration();
     258              : 
     259              :     /*
     260              :      * Play one tone
     261              :      * @return false if the driver is uninitialize
     262              :      */
     263              :     void playATone(Tone::ToneId toneId);
     264              : 
     265              :     int getCurrentDeviceIndex(AudioDeviceType type);
     266              : 
     267              :     /**
     268              :      * Process remaining participant given a conference and the current call ID.
     269              :      * Mainly called when a participant is detached or call ended (hang up).
     270              :      * @param current call id
     271              :      * @param conference pointer
     272              :      */
     273              :     void processRemainingParticipants(Conference& conf);
     274              : 
     275              :     /**
     276              :      * Create config directory in home user and return configuration file path
     277              :      */
     278              :     std::filesystem::path retrieveConfigPath() const;
     279              : 
     280              :     void unsetCurrentCall();
     281              : 
     282              :     void switchCall(const std::string& id);
     283              : 
     284              :     /**
     285              :      * Add incoming callid to the waiting list
     286              :      * @param id std::string to add
     287              :      */
     288              :     void addWaitingCall(const std::string& id);
     289              : 
     290              :     /**
     291              :      * Remove incoming callid to the waiting list
     292              :      * @param id std::string to remove
     293              :      */
     294              :     void removeWaitingCall(const std::string& id);
     295              : 
     296              :     void loadAccount(const YAML::Node& item, int& errorCount);
     297              :     void cleanupAccountStorage(const std::string& accountId);
     298              : 
     299              :     void sendTextMessageToConference(const Conference& conf,
     300              :                                      const std::map<std::string, std::string>& messages,
     301              :                                      const std::string& from) const noexcept;
     302              : 
     303              :     void bindCallToConference(Call& call, Conference& conf);
     304              : 
     305              :     void addMainParticipant(Conference& conf);
     306              : 
     307              :     bool hangupConference(Conference& conf);
     308              : 
     309              :     template<class T>
     310              :     std::shared_ptr<T> findAccount(const std::function<bool(const std::shared_ptr<T>&)>&);
     311              : 
     312              :     void initAudioDriver();
     313              : 
     314              :     void processIncomingCall(const std::string& accountId, Call& incomCall);
     315              :     static void stripSipPrefix(Call& incomCall);
     316              : 
     317              :     Manager& base_; // pimpl back-pointer
     318              : 
     319              :     std::shared_ptr<asio::io_context> ioContext_;
     320              :     std::thread ioContextRunner_;
     321              : 
     322              :     std::shared_ptr<dhtnet::upnp::UPnPContext> upnpContext_;
     323              : 
     324              :     std::atomic_bool autoAnswer_ {false};
     325              : 
     326              :     /** Application wide tone controller */
     327              :     ToneControl toneCtrl_;
     328              :     std::unique_ptr<AudioDeviceGuard> toneDeviceGuard_;
     329              : 
     330              :     /** Current Call ID */
     331              :     std::string currentCall_;
     332              : 
     333              :     /** Protected current call access */
     334              :     std::mutex currentCallMutex_;
     335              : 
     336              :     /** Protected sinks access */
     337              :     std::mutex sinksMutex_;
     338              : 
     339              :     /** Audio layer */
     340              :     std::shared_ptr<AudioLayer> audiodriver_ {nullptr};
     341              :     std::array<std::atomic_uint, 3> audioStreamUsers_ {};
     342              : 
     343              :     /* Audio device users */
     344              :     std::mutex audioDeviceUsersMutex_ {};
     345              :     std::map<std::string, unsigned> audioDeviceUsers_ {};
     346              : 
     347              :     // Main thread
     348              :     std::unique_ptr<DTMF> dtmfKey_;
     349              : 
     350              :     /** Buffer to generate DTMF */
     351              :     std::shared_ptr<AudioFrame> dtmfBuf_;
     352              : 
     353              :     std::shared_ptr<asio::steady_timer> dtmfTimer_;
     354              : 
     355              :     // To handle volume control
     356              :     // short speakerVolume_;
     357              :     // short micVolume_;
     358              :     // End of sound variable
     359              : 
     360              :     /**
     361              :      * Mutex used to protect audio layer
     362              :      */
     363              :     std::mutex audioLayerMutex_;
     364              : 
     365              :     /**
     366              :      * Waiting Call Vectors
     367              :      */
     368              :     CallIDSet waitingCalls_;
     369              : 
     370              :     /**
     371              :      * Protect waiting call list, access by many VoIP/audio threads
     372              :      */
     373              :     std::mutex waitingCallsMutex_;
     374              : 
     375              :     /**
     376              :      * Path of the ConfigFile
     377              :      */
     378              :     std::filesystem::path path_;
     379              : 
     380              :     /**
     381              :      * Instance of the RingBufferPool for the whole application
     382              :      *
     383              :      * In order to send signal to other parts of the application, one must pass through the
     384              :      * RingBufferMananger. Audio instances must be registered into the RingBufferMananger and bound
     385              :      * together via the Manager.
     386              :      *
     387              :      */
     388              :     std::unique_ptr<RingBufferPool> ringbufferpool_;
     389              : 
     390              :     std::atomic_bool finished_ {false};
     391              : 
     392              :     /* ICE support */
     393              :     std::shared_ptr<dhtnet::IceTransportFactory> ice_tf_;
     394              : 
     395              :     /* Sink ID mapping */
     396              :     std::map<std::string, std::weak_ptr<video::SinkClient>> sinkMap_;
     397              : 
     398              :     std::unique_ptr<VideoManager> videoManager_;
     399              : 
     400              :     std::unique_ptr<SIPVoIPLink> sipLink_;
     401              : #ifdef ENABLE_PLUGIN
     402              :     /* Jami Plugin Manager */
     403              :     std::unique_ptr<JamiPluginManager> jami_plugin_manager;
     404              : #endif
     405              : 
     406              :     std::mutex gitTransportsMtx_ {};
     407              :     std::map<git_smart_subtransport*, std::unique_ptr<P2PSubTransport>> gitTransports_ {};
     408              : 
     409              :     std::shared_ptr<SystemCodecContainer> systemCodecContainer_;
     410              : };
     411              : 
     412           45 : Manager::ManagerPimpl::ManagerPimpl(Manager& base)
     413           45 :     : base_(base)
     414           45 :     , ioContext_(std::make_shared<asio::io_context>())
     415           45 :     , upnpContext_(std::make_shared<dhtnet::upnp::UPnPContext>(nullptr, Logger::dhtLogger()))
     416           45 :     , toneCtrl_(base.preferences)
     417           45 :     , dtmfBuf_(std::make_shared<AudioFrame>())
     418           45 :     , ringbufferpool_(new RingBufferPool)
     419              : #ifdef ENABLE_VIDEO
     420          225 :     , videoManager_(nullptr)
     421              : #endif
     422              : {
     423           45 :     jami::libav_utils::av_init();
     424           45 : }
     425              : 
     426              : bool
     427           42 : Manager::ManagerPimpl::parseConfiguration()
     428              : {
     429           42 :     bool result = true;
     430              : 
     431              :     try {
     432           42 :         std::ifstream file(path_);
     433           42 :         YAML::Node parsedFile = YAML::Load(file);
     434           42 :         file.close();
     435           42 :         const int error_count = base_.loadAccountMap(parsedFile);
     436              : 
     437           42 :         if (error_count > 0) {
     438            6 :             JAMI_WARNING("[config] Error while parsing {}", path_);
     439            6 :             result = false;
     440              :         }
     441           42 :     } catch (const YAML::BadFile& e) {
     442            0 :         JAMI_WARNING("[config] Unable to open configuration file");
     443            0 :         result = false;
     444            0 :     }
     445              : 
     446           42 :     return result;
     447              : }
     448              : 
     449              : /**
     450              :  * Multi Thread
     451              :  */
     452              : void
     453          140 : Manager::ManagerPimpl::playATone(Tone::ToneId toneId)
     454              : {
     455          140 :     if (not base_.voipPreferences.getPlayTones())
     456            0 :         return;
     457              : 
     458          140 :     std::lock_guard lock(audioLayerMutex_);
     459          140 :     if (not audiodriver_) {
     460            0 :         JAMI_ERROR("[audio] Uninitialized audio layer");
     461            0 :         return;
     462              :     }
     463              : 
     464          140 :     auto oldGuard = std::move(toneDeviceGuard_);
     465          140 :     toneDeviceGuard_ = base_.startAudioStream(AudioDeviceType::PLAYBACK);
     466          140 :     audiodriver_->flushUrgent();
     467          140 :     toneCtrl_.play(toneId);
     468          140 : }
     469              : 
     470              : int
     471            0 : Manager::ManagerPimpl::getCurrentDeviceIndex(AudioDeviceType type)
     472              : {
     473            0 :     if (not audiodriver_)
     474            0 :         return -1;
     475            0 :     switch (type) {
     476            0 :     case AudioDeviceType::PLAYBACK:
     477            0 :         return audiodriver_->getIndexPlayback();
     478            0 :     case AudioDeviceType::RINGTONE:
     479            0 :         return audiodriver_->getIndexRingtone();
     480            0 :     case AudioDeviceType::CAPTURE:
     481            0 :         return audiodriver_->getIndexCapture();
     482            0 :     default:
     483            0 :         return -1;
     484              :     }
     485              : }
     486              : 
     487              : void
     488           69 : Manager::ManagerPimpl::processRemainingParticipants(Conference& conf)
     489              : {
     490           69 :     const std::string currentCallId(base_.getCurrentCallId());
     491           69 :     CallIdSet subcalls(conf.getSubCalls());
     492           69 :     const size_t n = subcalls.size();
     493           69 :     JAMI_DEBUG("[conf:{}] Processing {} remaining participant(s)", conf.getConfId(), conf.getConferenceInfos().size());
     494              : 
     495           69 :     if (n > 1) {
     496              :         // Reset ringbuffer's readpointers
     497           27 :         for (const auto& p : subcalls) {
     498           18 :             if (auto call = base_.getCallFromCallID(p)) {
     499           18 :                 auto medias = call->getAudioStreams();
     500           36 :                 for (const auto& media : medias) {
     501           18 :                     JAMI_DEBUG("[call:{}] Remove local audio {}", p, media.first);
     502           18 :                     base_.getRingBufferPool().flush(media.first);
     503              :                 }
     504           36 :             }
     505              :         }
     506              : 
     507           27 :         base_.getRingBufferPool().flush(RingBufferPool::DEFAULT_ID);
     508              :     } else {
     509           60 :         if (auto acc = std::dynamic_pointer_cast<JamiAccount>(conf.getAccount())) {
     510              :             // Stay in a conference if 1 participants for swarm and rendezvous
     511           60 :             if (auto* cm = acc->convModule(true)) {
     512          178 :                 if (acc->isRendezVous() || cm->isHosting("", conf.getConfId())) {
     513              :                     // Check if attached
     514           11 :                     if (conf.getState() == Conference::State::ACTIVE_ATTACHED) {
     515            3 :                         return;
     516              :                     }
     517              :                 }
     518              :             }
     519           60 :         }
     520           57 :         if (n == 1) {
     521              :             // this call is the last participant (non swarm-call), hence
     522              :             // the conference is over
     523           31 :             auto p = subcalls.begin();
     524           31 :             if (auto call = base_.getCallFromCallID(*p)) {
     525              :                 // if we are not listening to this conference and not a rendez-vous
     526           31 :                 auto w = call->getAccount();
     527           31 :                 auto account = w.lock();
     528           31 :                 if (!account) {
     529            0 :                     JAMI_ERROR("[conf:{}] Account no longer available", conf.getConfId());
     530            0 :                     return;
     531              :                 }
     532           31 :                 if (currentCallId != conf.getConfId())
     533            5 :                     base_.holdCall(account->getAccountID(), call->getCallId());
     534              :                 else
     535           26 :                     switchCall(call->getCallId());
     536           62 :             }
     537              : 
     538           31 :             JAMI_DEBUG("[conf:{}] Only one participant left, removing conference", conf.getConfId());
     539           31 :             if (auto account = conf.getAccount())
     540           31 :                 account->removeConference(conf.getConfId());
     541              :         } else {
     542           26 :             JAMI_DEBUG("[conf:{}] No remaining participants, removing conference", conf.getConfId());
     543           26 :             if (auto account = conf.getAccount())
     544           26 :                 account->removeConference(conf.getConfId());
     545           26 :             unsetCurrentCall();
     546              :         }
     547              :     }
     548           72 : }
     549              : 
     550              : /**
     551              :  * Initialization: Main Thread
     552              :  */
     553              : std::filesystem::path
     554            0 : Manager::ManagerPimpl::retrieveConfigPath() const
     555              : {
     556              :     // TODO: Migrate config filename from dring.yml to jami.yml.
     557            0 :     return fileutils::get_config_dir() / "dring.yml";
     558              : }
     559              : 
     560              : void
     561          117 : Manager::ManagerPimpl::unsetCurrentCall()
     562              : {
     563          117 :     currentCall_ = "";
     564          117 : }
     565              : 
     566              : void
     567          294 : Manager::ManagerPimpl::switchCall(const std::string& id)
     568              : {
     569          294 :     std::lock_guard m(currentCallMutex_);
     570          294 :     JAMI_LOG("----- Switch current call ID to '{}' -----", not id.empty() ? id.c_str() : "none");
     571          294 :     currentCall_ = id;
     572          294 : }
     573              : 
     574              : void
     575          103 : Manager::ManagerPimpl::addWaitingCall(const std::string& id)
     576              : {
     577          103 :     std::lock_guard m(waitingCallsMutex_);
     578              :     // Enable incoming call beep if needed.
     579          103 :     if (audiodriver_ and waitingCalls_.empty() and not currentCall_.empty())
     580           53 :         audiodriver_->playIncomingCallNotification(true);
     581          103 :     waitingCalls_.insert(id);
     582          103 : }
     583              : 
     584              : void
     585          459 : Manager::ManagerPimpl::removeWaitingCall(const std::string& id)
     586              : {
     587          459 :     std::lock_guard m(waitingCallsMutex_);
     588          459 :     waitingCalls_.erase(id);
     589          459 :     if (audiodriver_ and waitingCalls_.empty())
     590          229 :         audiodriver_->playIncomingCallNotification(false);
     591          459 : }
     592              : 
     593              : void
     594            0 : Manager::ManagerPimpl::loadAccount(const YAML::Node& node, int& errorCount)
     595              : {
     596              :     using yaml_utils::parseValue;
     597              :     using yaml_utils::parseValueOptional;
     598              : 
     599            0 :     std::string accountid;
     600            0 :     parseValue(node, "id", accountid);
     601              : 
     602            0 :     std::string accountType(ACCOUNT_TYPE_SIP);
     603            0 :     parseValueOptional(node, "type", accountType);
     604              : 
     605            0 :     if (accountid.empty())
     606            0 :         return;
     607              : 
     608            0 :     if (base_.preferences.isAccountPending(accountid)) {
     609            0 :         JAMI_LOG("[account:{}] Removing pending account from disk", accountid);
     610            0 :         base_.removeAccount(accountid, true);
     611            0 :         cleanupAccountStorage(accountid);
     612            0 :         return;
     613              :     }
     614              : 
     615            0 :     if (auto a = base_.accountFactory.createAccount(accountType, accountid)) {
     616            0 :         auto config = a->buildConfig();
     617            0 :         config->unserialize(node);
     618            0 :         a->setConfig(std::move(config));
     619            0 :         return;
     620            0 :     }
     621              : 
     622            0 :     JAMI_ERROR("Failed to create account of type \"{:s}\"", accountType);
     623            0 :     ++errorCount;
     624            0 : }
     625              : 
     626              : void
     627            0 : Manager::ManagerPimpl::cleanupAccountStorage(const std::string& accountId)
     628              : {
     629            0 :     const auto cachePath = fileutils::get_cache_dir() / accountId;
     630            0 :     const auto dataPath = cachePath / "values";
     631            0 :     const auto idPath = fileutils::get_data_dir() / accountId;
     632            0 :     dhtnet::fileutils::removeAll(dataPath);
     633            0 :     dhtnet::fileutils::removeAll(cachePath);
     634            0 :     dhtnet::fileutils::removeAll(idPath, true);
     635            0 : }
     636              : 
     637              : // THREAD=VoIP
     638              : void
     639            0 : Manager::ManagerPimpl::sendTextMessageToConference(const Conference& conf,
     640              :                                                    const std::map<std::string, std::string>& messages,
     641              :                                                    const std::string& from) const noexcept
     642              : {
     643            0 :     CallIdSet subcalls(conf.getSubCalls());
     644            0 :     for (const auto& callId : subcalls) {
     645              :         try {
     646            0 :             auto call = base_.getCallFromCallID(callId);
     647            0 :             if (not call)
     648            0 :                 throw std::runtime_error("No associated call");
     649            0 :             call->sendTextMessage(messages, from);
     650            0 :         } catch (const std::exception& e) {
     651            0 :             JAMI_ERROR("[conf:{}] Failed to send message to participant {}: {}", conf.getConfId(), callId, e.what());
     652            0 :         }
     653              :     }
     654            0 : }
     655              : 
     656              : void
     657            0 : Manager::bindCallToConference(Call& call, Conference& conf)
     658              : {
     659            0 :     pimpl_->bindCallToConference(call, conf);
     660            0 : }
     661              : 
     662              : void
     663           65 : Manager::ManagerPimpl::bindCallToConference(Call& call, Conference& conf)
     664              : {
     665           65 :     const auto& callId = call.getCallId();
     666           65 :     const auto& confId = conf.getConfId();
     667           65 :     const auto& state = call.getStateStr();
     668              : 
     669              :     // ensure that calls are only in one conference at a time
     670           65 :     if (call.isConferenceParticipant())
     671            0 :         base_.detachParticipant(callId);
     672              : 
     673           65 :     JAMI_DEBUG("[call:{}] Bind to conference {} (callState={})", callId, confId, state);
     674              : 
     675           65 :     auto medias = call.getAudioStreams();
     676          130 :     for (const auto& media : medias) {
     677           65 :         JAMI_DEBUG("[call:{}] Remove local audio {}", callId, media.first);
     678           65 :         base_.getRingBufferPool().unBindAll(media.first);
     679              :     }
     680              : 
     681           65 :     conf.addSubCall(callId);
     682              : 
     683           65 :     if (state == "HOLD") {
     684            0 :         base_.resumeCall(call.getAccountId(), callId);
     685           65 :     } else if (state == "INCOMING") {
     686            0 :         base_.acceptCall(call);
     687           65 :     } else if (state == "CURRENT") {
     688            0 :     } else if (state == "INACTIVE") {
     689            0 :         base_.acceptCall(call);
     690              :     } else
     691            0 :         JAMI_WARNING("[call:{}] Call state {} unrecognized for conference", callId, state);
     692           65 : }
     693              : 
     694              : //==============================================================================
     695              : 
     696              : Manager&
     697       189062 : Manager::instance()
     698              : {
     699              :     // Meyers singleton
     700       189062 :     static Manager instance;
     701              : 
     702              :     // This will give a warning that can be ignored the first time instance()
     703              :     // is called… subsequent warnings are more serious
     704       189062 :     if (not Manager::initialized)
     705          180 :         JAMI_WARNING("Manager accessed before initialization");
     706              : 
     707       189062 :     return instance;
     708              : }
     709              : 
     710           45 : Manager::Manager()
     711           45 :     : rand_(dht::crypto::getSeededRandomEngine<std::mt19937_64>())
     712           45 :     , preferences()
     713           45 :     , voipPreferences()
     714           45 :     , audioPreference()
     715              : #ifdef ENABLE_PLUGIN
     716           45 :     , pluginPreferences()
     717              : #endif
     718              : #ifdef ENABLE_VIDEO
     719           45 :     , videoPreferences()
     720              : #endif
     721           45 :     , callFactory(rand_)
     722           90 :     , accountFactory()
     723              : {
     724              : #if defined _MSC_VER
     725              :     gnutls_global_init();
     726              : #endif
     727           45 :     pimpl_ = std::make_unique<ManagerPimpl>(*this);
     728           45 : }
     729              : 
     730           45 : Manager::~Manager() {}
     731              : 
     732              : void
     733          448 : Manager::setAutoAnswer(bool enable)
     734              : {
     735          448 :     pimpl_->autoAnswer_ = enable;
     736          448 : }
     737              : 
     738              : void
     739           39 : Manager::init(const std::filesystem::path& config_file, libjami::InitFlag flags)
     740              : {
     741              :     // FIXME: this is no good
     742           39 :     initialized = true;
     743              : 
     744           39 :     git_libgit2_init();
     745           39 :     git_libgit2_opts(GIT_OPT_ENABLE_FSYNC_GITDIR, 1);
     746           39 :     auto res = git_transport_register("git", p2p_transport_cb, nullptr);
     747           39 :     if (res < 0) {
     748            0 :         const git_error* error = giterr_last();
     749            0 :         JAMI_ERROR("Unable to initialize git transport: {}", error ? error->message : "(unknown)");
     750              :     }
     751              : 
     752              : #ifndef WIN32
     753              :     // Set the max number of open files.
     754              :     struct rlimit nofiles;
     755           39 :     if (getrlimit(RLIMIT_NOFILE, &nofiles) == 0) {
     756           39 :         if (nofiles.rlim_cur < nofiles.rlim_max && nofiles.rlim_cur <= 1024u) {
     757            0 :             nofiles.rlim_cur = std::min<rlim_t>(nofiles.rlim_max, 8192u);
     758            0 :             setrlimit(RLIMIT_NOFILE, &nofiles);
     759              :         }
     760              :     }
     761              : #endif
     762              : 
     763              : #define PJSIP_TRY(ret) \
     764              :     do { \
     765              :         if ((ret) != PJ_SUCCESS) \
     766              :             throw std::runtime_error(#ret " failed"); \
     767              :     } while (0)
     768              : 
     769           39 :     srand(time(nullptr)); // to get random number for RANDOM_PORT
     770              : 
     771              :     // Initialize PJSIP (SIP and ICE implementation)
     772           39 :     PJSIP_TRY(pj_init());
     773           39 :     setSipLogLevel();
     774           39 :     PJSIP_TRY(pjlib_util_init());
     775           39 :     PJSIP_TRY(pjnath_init());
     776              : #undef PJSIP_TRY
     777              : 
     778           39 :     setGnuTlsLogLevel();
     779           39 :     dhtLogLevel = getDhtLogLevel();
     780           39 :     dhtnetLogLevel = getDhtnetLogLevel();
     781           39 :     pimpl_->upnpContext_->setMappingLabel("JAMI-" + fileutils::getOrCreateLocalDeviceId());
     782              : 
     783           39 :     JAMI_LOG("Using PJSIP version: {:s} for {:s}", pj_get_version(), PJ_OS_NAME);
     784           39 :     JAMI_LOG("Using GnuTLS version: {:s}", gnutls_check_version(nullptr));
     785           39 :     JAMI_LOG("Using OpenDHT version: {:s}", dht::version());
     786           39 :     JAMI_LOG("Using FFmpeg version: {:s}", av_version_info());
     787           39 :     int git2_major = 0, git2_minor = 0, git2_rev = 0;
     788           39 :     if (git_libgit2_version(&git2_major, &git2_minor, &git2_rev) == 0) {
     789           39 :         JAMI_LOG("Using libgit2 version: {:d}.{:d}.{:d}", git2_major, git2_minor, git2_rev);
     790              :     }
     791              : 
     792              :     // Manager can restart without being recreated (Unit tests)
     793              :     // So only create the SipLink once
     794           39 :     pimpl_->sipLink_ = std::make_unique<SIPVoIPLink>();
     795              : 
     796           39 :     check_rename(fileutils::get_cache_dir(PACKAGE_OLD), fileutils::get_cache_dir());
     797           39 :     check_rename(fileutils::get_data_dir(PACKAGE_OLD), fileutils::get_data_dir());
     798           39 :     check_rename(fileutils::get_config_dir(PACKAGE_OLD), fileutils::get_config_dir());
     799              : 
     800           39 :     pimpl_->ice_tf_ = std::make_shared<dhtnet::IceTransportFactory>(Logger::dhtLogger());
     801              : 
     802           39 :     pimpl_->path_ = config_file.empty() ? pimpl_->retrieveConfigPath() : config_file;
     803           39 :     JAMI_LOG("Configuration file path: {}", pimpl_->path_);
     804              : 
     805              : #ifdef ENABLE_PLUGIN
     806           39 :     pimpl_->jami_plugin_manager = std::make_unique<JamiPluginManager>();
     807              : #endif
     808              : 
     809           39 :     bool no_errors = true;
     810              : 
     811              :     // manager can restart without being recreated (Unit tests)
     812           39 :     pimpl_->finished_ = false;
     813              : 
     814              :     // Create video manager
     815           39 :     if (!(flags & libjami::LIBJAMI_FLAG_NO_LOCAL_VIDEO)) {
     816           39 :         pimpl_->videoManager_.reset(new VideoManager);
     817              :     }
     818              : 
     819           39 :     if (libjami::LIBJAMI_FLAG_NO_AUTOLOAD & flags) {
     820            0 :         autoLoad = false;
     821            0 :         JAMI_DEBUG("LIBJAMI_FLAG_NO_AUTOLOAD is set, accounts will neither be loaded nor backed up");
     822              :     } else {
     823              :         try {
     824           39 :             no_errors = pimpl_->parseConfiguration();
     825            0 :         } catch (const YAML::Exception& e) {
     826            0 :             JAMI_ERROR("[config] Failed to parse configuration: {}", e.what());
     827            0 :             no_errors = false;
     828            0 :         }
     829              : 
     830              :         // always back up last error-free configuration
     831           39 :         if (no_errors) {
     832           36 :             make_backup(pimpl_->path_);
     833              :         } else {
     834              :             // restore previous configuration
     835            3 :             JAMI_WARNING("Restoring last working configuration");
     836              : 
     837              :             try {
     838              :                 // remove accounts from broken configuration
     839            3 :                 removeAccounts();
     840            3 :                 restore_backup(pimpl_->path_);
     841            3 :                 pimpl_->parseConfiguration();
     842            0 :             } catch (const YAML::Exception& e) {
     843            0 :                 JAMI_ERROR("{}", e.what());
     844            0 :                 JAMI_WARNING("Restoring backup failed");
     845            0 :             }
     846              :         }
     847              :     }
     848              : 
     849              :     // loadAccountMap() creates the codec container when the configuration is loaded;
     850              :     // create a default one when it isn't (load failed or NO_AUTOLOAD set).
     851           39 :     if (!pimpl_->systemCodecContainer_) {
     852            0 :         pimpl_->systemCodecContainer_ = std::make_shared<SystemCodecContainer>();
     853            0 :         pimpl_->systemCodecContainer_->init(false);
     854              :     }
     855              : 
     856           39 :     if (!(flags & libjami::LIBJAMI_FLAG_NO_LOCAL_AUDIO)) {
     857           39 :         std::lock_guard lock(pimpl_->audioLayerMutex_);
     858           39 :         pimpl_->initAudioDriver();
     859           39 :         if (pimpl_->audiodriver_) {
     860           39 :             auto format = pimpl_->audiodriver_->getFormat();
     861           39 :             pimpl_->toneCtrl_.setSampleRate(format.sample_rate, format.sampleFormat);
     862          117 :             pimpl_->dtmfKey_.reset(new DTMF(getRingBufferPool().getInternalSamplingRate(),
     863           78 :                                             getRingBufferPool().getInternalAudioFormat().sampleFormat));
     864              :         }
     865           39 :     }
     866              : 
     867              :     // Start ASIO event loop
     868           78 :     pimpl_->ioContextRunner_ = std::thread([context = pimpl_->ioContext_]() {
     869              :         try {
     870           39 :             auto work = asio::make_work_guard(*context);
     871           39 :             context->run();
     872           39 :         } catch (const std::exception& ex) {
     873            0 :             JAMI_ERROR("[io] Unexpected io_context thread exception: {}", ex.what());
     874            0 :             throw;
     875            0 :         }
     876           78 :     });
     877              : 
     878           39 :     if (libjami::LIBJAMI_FLAG_NO_AUTOLOAD & flags) {
     879            0 :         JAMI_DEBUG("LIBJAMI_FLAG_NO_AUTOLOAD is set, accounts and conversations will not be loaded");
     880            0 :         return;
     881              :     } else {
     882           39 :         registerAccounts();
     883              :     }
     884              : }
     885              : 
     886              : void
     887          448 : Manager::finish() noexcept
     888              : {
     889          448 :     bool expected = false;
     890          448 :     if (not pimpl_->finished_.compare_exchange_strong(expected, true))
     891          403 :         return;
     892              : 
     893              :     try {
     894              :         // Terminate UPNP context
     895           45 :         upnpContext()->shutdown();
     896              : 
     897              :         // Forbid call creation
     898           45 :         callFactory.forbid();
     899              : 
     900              :         // End all remaining active calls
     901           45 :         JAMI_LOG("End {} remaining call(s)", callFactory.callCount());
     902           45 :         for (const auto& call : callFactory.getAllCalls())
     903           45 :             hangupCall(call->getAccountId(), call->getCallId());
     904           45 :         callFactory.clear();
     905              : 
     906           45 :         for (const auto& account : getAllAccounts<JamiAccount>()) {
     907            0 :             if (account->getRegistrationState() == RegistrationState::INITIALIZING)
     908            0 :                 removeAccount(account->getAccountID(), true);
     909           45 :         }
     910              : 
     911           45 :         saveConfig();
     912              : 
     913              :         // Disconnect accounts, close link stacks and free allocated ressources
     914           45 :         unregisterAccounts();
     915           45 :         accountFactory.clear();
     916              : 
     917              :         {
     918           45 :             std::lock_guard lock(pimpl_->audioLayerMutex_);
     919           45 :             pimpl_->audiodriver_.reset();
     920           45 :         }
     921              : 
     922           45 :         JAMI_DEBUG("Stopping schedulers and worker threads");
     923              : 
     924              :         // Flush remaining tasks (free lambda' with capture)
     925           45 :         dht::ThreadPool::io().join();
     926           45 :         dht::ThreadPool::computation().join();
     927              : 
     928              :         // IceTransportFactory should be stopped after the io pool
     929              :         // as some ICE are destroyed in a ioPool (see ConnectionManager)
     930              :         // Also, it must be called before pj_shutdown to avoid any problem
     931           45 :         pimpl_->ice_tf_.reset();
     932              : 
     933              :         // NOTE: sipLink_->shutdown() is needed because this will perform
     934              :         // sipTransportBroker->shutdown(); which will call Manager::instance().sipVoIPLink()
     935              :         // so the pointer MUST NOT be resetted at this point
     936           45 :         if (pimpl_->sipLink_) {
     937           39 :             pimpl_->sipLink_->shutdown();
     938           39 :             pimpl_->sipLink_.reset();
     939              :         }
     940              : 
     941           45 :         pj_shutdown();
     942           45 :         pimpl_->gitTransports_.clear();
     943           45 :         git_libgit2_shutdown();
     944              : 
     945           45 :         if (!pimpl_->ioContext_->stopped()) {
     946           45 :             pimpl_->ioContext_->stop(); // make thread stop
     947              :         }
     948           45 :         if (pimpl_->ioContextRunner_.joinable())
     949           39 :             pimpl_->ioContextRunner_.join();
     950              : 
     951              : #if defined _MSC_VER
     952              :         gnutls_global_deinit();
     953              : #endif
     954              : 
     955            0 :     } catch (const VoipLinkException& err) {
     956            0 :         JAMI_ERROR("[voip] {}", err.what());
     957            0 :     }
     958              : }
     959              : 
     960              : void
     961            0 : Manager::monitor(bool continuous)
     962              : {
     963            0 :     Logger::setMonitorLog(true);
     964            0 :     JAMI_DEBUG("############## START MONITORING ##############");
     965            0 :     JAMI_DEBUG("Using PJSIP version: {} for {}", pj_get_version(), PJ_OS_NAME);
     966            0 :     JAMI_DEBUG("Using GnuTLS version: {}", gnutls_check_version(nullptr));
     967            0 :     JAMI_DEBUG("Using OpenDHT version: {}", dht::version());
     968              : 
     969              : #ifdef __linux__
     970              : #if defined(__ANDROID__)
     971              : #else
     972            0 :     auto opened_files = dhtnet::fileutils::readDirectory("/proc/" + std::to_string(getpid()) + "/fd").size();
     973            0 :     JAMI_DEBUG("Opened files: {}", opened_files);
     974              : #endif
     975              : #endif
     976              : 
     977            0 :     for (const auto& call : callFactory.getAllCalls())
     978            0 :         call->monitor();
     979            0 :     for (const auto& account : getAllAccounts())
     980            0 :         if (auto acc = std::dynamic_pointer_cast<JamiAccount>(account))
     981            0 :             acc->monitor();
     982            0 :     JAMI_DEBUG("############## END MONITORING ##############");
     983            0 :     Logger::setMonitorLog(continuous);
     984            0 : }
     985              : 
     986              : std::vector<std::map<std::string, std::string>>
     987            0 : Manager::getConnectionList(const std::string& accountId, const std::string& conversationId)
     988              : {
     989            0 :     std::vector<std::map<std::string, std::string>> connectionsList;
     990              : 
     991            0 :     if (accountId.empty()) {
     992            0 :         for (const auto& account : getAllAccounts<JamiAccount>()) {
     993            0 :             if (account->getRegistrationState() != RegistrationState::INITIALIZING) {
     994            0 :                 const auto& cnl = account->getConnectionList(conversationId);
     995            0 :                 connectionsList.insert(connectionsList.end(), cnl.begin(), cnl.end());
     996            0 :             }
     997            0 :         }
     998              :     } else {
     999            0 :         auto account = getAccount(accountId);
    1000            0 :         if (account) {
    1001            0 :             if (auto acc = std::dynamic_pointer_cast<JamiAccount>(account)) {
    1002            0 :                 if (acc->getRegistrationState() != RegistrationState::INITIALIZING) {
    1003            0 :                     const auto& cnl = acc->getConnectionList(conversationId);
    1004            0 :                     connectionsList.insert(connectionsList.end(), cnl.begin(), cnl.end());
    1005            0 :                 }
    1006            0 :             }
    1007              :         }
    1008            0 :     }
    1009              : 
    1010            0 :     return connectionsList;
    1011            0 : }
    1012              : 
    1013              : std::vector<std::map<std::string, std::string>>
    1014            0 : Manager::getChannelList(const std::string& accountId, const std::string& connectionId)
    1015              : {
    1016              :     // if account id is empty, return all channels
    1017              :     // else return only for specific accountid
    1018            0 :     std::vector<std::map<std::string, std::string>> channelsList;
    1019              : 
    1020            0 :     if (accountId.empty()) {
    1021            0 :         for (const auto& account : getAllAccounts<JamiAccount>()) {
    1022            0 :             if (account->getRegistrationState() != RegistrationState::INITIALIZING) {
    1023              :                 // add to channelsList all channels for this account
    1024            0 :                 const auto& cnl = account->getChannelList(connectionId);
    1025            0 :                 channelsList.insert(channelsList.end(), cnl.begin(), cnl.end());
    1026            0 :             }
    1027            0 :         }
    1028              : 
    1029              :     }
    1030              : 
    1031              :     else {
    1032              :         // get the jamiaccount for this accountid and return its channels
    1033            0 :         auto account = getAccount(accountId);
    1034            0 :         if (account) {
    1035            0 :             if (auto acc = std::dynamic_pointer_cast<JamiAccount>(account)) {
    1036            0 :                 if (acc->getRegistrationState() != RegistrationState::INITIALIZING) {
    1037            0 :                     const auto& cnl = acc->getChannelList(connectionId);
    1038            0 :                     channelsList.insert(channelsList.end(), cnl.begin(), cnl.end());
    1039            0 :                 }
    1040            0 :             }
    1041              :         }
    1042            0 :     }
    1043              : 
    1044            0 :     return channelsList;
    1045            0 : }
    1046              : 
    1047              : bool
    1048          378 : Manager::isCurrentCall(const Call& call) const
    1049              : {
    1050          378 :     return pimpl_->currentCall_ == call.getCallId();
    1051              : }
    1052              : 
    1053              : bool
    1054          317 : Manager::hasCurrentCall() const
    1055              : {
    1056         1022 :     for (const auto& call : callFactory.getAllCalls()) {
    1057          881 :         if (!call->isSubcall() && call->getStateStr() == libjami::Call::StateEvent::CURRENT)
    1058          176 :             return true;
    1059          317 :     }
    1060          141 :     return false;
    1061              : }
    1062              : 
    1063              : std::shared_ptr<Call>
    1064          113 : Manager::getCurrentCall() const
    1065              : {
    1066          113 :     return getCallFromCallID(pimpl_->currentCall_);
    1067              : }
    1068              : 
    1069              : const std::string&
    1070           77 : Manager::getCurrentCallId() const
    1071              : {
    1072           77 :     return pimpl_->currentCall_;
    1073              : }
    1074              : 
    1075              : void
    1076           45 : Manager::unregisterAccounts()
    1077              : {
    1078           45 :     for (const auto& account : getAllAccounts()) {
    1079            0 :         if (account->isEnabled()) {
    1080            0 :             account->doUnregister(true);
    1081              :         }
    1082           45 :     }
    1083           45 : }
    1084              : 
    1085              : ///////////////////////////////////////////////////////////////////////////////
    1086              : // Management of events' IP-phone user
    1087              : ///////////////////////////////////////////////////////////////////////////////
    1088              : /* Main Thread */
    1089              : 
    1090              : std::string
    1091          123 : Manager::outgoingCall(const std::string& account_id,
    1092              :                       const std::string& to,
    1093              :                       const std::vector<libjami::MediaMap>& mediaList)
    1094              : {
    1095          123 :     JAMI_LOG("Attempt outgoing call to '{}' with account '{}'", to, account_id);
    1096              : 
    1097          123 :     std::shared_ptr<Call> call;
    1098              : 
    1099              :     try {
    1100          123 :         call = newOutgoingCall(trim(to), account_id, mediaList);
    1101            0 :     } catch (const std::exception& e) {
    1102            0 :         JAMI_ERROR("{}", e.what());
    1103            0 :         return {};
    1104            0 :     }
    1105              : 
    1106          123 :     if (not call)
    1107           11 :         return {};
    1108              : 
    1109          112 :     stopTone();
    1110              : 
    1111          112 :     pimpl_->switchCall(call->getCallId());
    1112              : 
    1113          112 :     return call->getCallId();
    1114          123 : }
    1115              : 
    1116              : // THREAD=Main : for outgoing Call
    1117              : bool
    1118           88 : Manager::acceptCall(const std::string& accountId,
    1119              :                     const std::string& callId,
    1120              :                     const std::vector<libjami::MediaMap>& mediaList)
    1121              : {
    1122           88 :     if (auto account = getAccount(accountId)) {
    1123           88 :         if (auto call = account->getCall(callId)) {
    1124           88 :             return acceptCall(*call, mediaList);
    1125           88 :         }
    1126           88 :     }
    1127            0 :     return false;
    1128              : }
    1129              : 
    1130              : bool
    1131          103 : Manager::acceptCall(Call& call, const std::vector<libjami::MediaMap>& mediaList)
    1132              : {
    1133          103 :     JAMI_LOG("Answer call {}", call.getCallId());
    1134              : 
    1135          103 :     if (call.getConnectionState() != Call::ConnectionState::RINGING) {
    1136              :         // The call is already answered
    1137            0 :         return true;
    1138              :     }
    1139              : 
    1140              :     // If ringing
    1141          103 :     stopTone();
    1142          103 :     pimpl_->removeWaitingCall(call.getCallId());
    1143              : 
    1144              :     try {
    1145          103 :         call.answer(mediaList);
    1146            0 :     } catch (const std::runtime_error& e) {
    1147            0 :         JAMI_ERROR("[call:{}] Failed to answer: {}", call.getCallId(), e.what());
    1148            0 :         return false;
    1149            0 :     }
    1150              : 
    1151              :     // if we dragged this call into a conference already
    1152          103 :     if (auto conf = call.getConference())
    1153            0 :         pimpl_->switchCall(conf->getConfId());
    1154              :     else
    1155          103 :         pimpl_->switchCall(call.getCallId());
    1156              : 
    1157          103 :     addAudio(call);
    1158              : 
    1159              :     // Start recording if set in preference
    1160          103 :     if (audioPreference.getIsAlwaysRecording()) {
    1161            1 :         auto recResult = call.toggleRecording();
    1162            1 :         emitSignal<libjami::CallSignal::RecordPlaybackFilepath>(call.getCallId(), call.getPath());
    1163            1 :         emitSignal<libjami::CallSignal::RecordingStateChanged>(call.getCallId(), recResult);
    1164              :     }
    1165          103 :     return true;
    1166              : }
    1167              : 
    1168              : // THREAD=Main
    1169              : bool
    1170          130 : Manager::hangupCall(const std::string& accountId, const std::string& callId)
    1171              : {
    1172          130 :     auto account = getAccount(accountId);
    1173          130 :     if (not account)
    1174            0 :         return false;
    1175              :     // store the current call id
    1176          130 :     stopTone();
    1177          130 :     pimpl_->removeWaitingCall(callId);
    1178              : 
    1179              :     /* We often get here when the call was hungup before being created */
    1180          130 :     auto call = account->getCall(callId);
    1181          130 :     if (not call) {
    1182            0 :         JAMI_WARNING("Unable to hang up nonexistent call {}", callId);
    1183            0 :         return false;
    1184              :     }
    1185              : 
    1186              :     // Disconnect streams
    1187          130 :     removeAudio(*call);
    1188              : 
    1189          130 :     if (call->isConferenceParticipant()) {
    1190           57 :         removeParticipant(*call);
    1191              :     } else {
    1192              :         // we are not participating in a conference, current call switched to ""
    1193           73 :         if (isCurrentCall(*call))
    1194           29 :             pimpl_->unsetCurrentCall();
    1195              :     }
    1196              : 
    1197              :     try {
    1198          130 :         call->hangup(0);
    1199            0 :     } catch (const VoipLinkException& e) {
    1200            0 :         JAMI_ERROR("[call:{}] Failed to hangup: {}", call->getCallId(), e.what());
    1201            0 :         return false;
    1202            0 :     }
    1203              : 
    1204          130 :     return true;
    1205          130 : }
    1206              : 
    1207              : bool
    1208           30 : Manager::hangupConference(const std::string& accountId, const std::string& confId)
    1209              : {
    1210           30 :     if (auto account = getAccount(accountId)) {
    1211           30 :         if (auto conference = account->getConference(confId)) {
    1212           29 :             return pimpl_->hangupConference(*conference);
    1213              :         } else {
    1214            1 :             JAMI_ERROR("[conf:{}] Conference not found", confId);
    1215           30 :         }
    1216           30 :     }
    1217            1 :     return false;
    1218              : }
    1219              : 
    1220              : // THREAD=Main
    1221              : bool
    1222            8 : Manager::holdCall(const std::string&, const std::string& callId)
    1223              : {
    1224            8 :     bool result = true;
    1225              : 
    1226            8 :     stopTone();
    1227              : 
    1228            8 :     std::string current_callId(getCurrentCallId());
    1229              : 
    1230            8 :     if (auto call = getCallFromCallID(callId)) {
    1231              :         try {
    1232            8 :             result = call->hold([=](bool ok) {
    1233            8 :                 if (!ok) {
    1234            0 :                     JAMI_ERROR("CallID {} holdCall failed", callId);
    1235            0 :                     return;
    1236              :                 }
    1237            8 :                 removeAudio(*call); // Unbind calls in main buffer
    1238              :                 // Remove call from the queue if it was still there
    1239            8 :                 pimpl_->removeWaitingCall(callId);
    1240              : 
    1241              :                 // Keeps current call ID if the action does not hold this call
    1242              :                 // or a new outgoing call. This could happen in case of a conference
    1243            8 :                 if (current_callId == callId)
    1244            4 :                     pimpl_->unsetCurrentCall();
    1245              :             });
    1246            0 :         } catch (const VoipLinkException& e) {
    1247            0 :             JAMI_ERROR("[call:{}] Failed to hold: {}", callId, e.what());
    1248            0 :             result = false;
    1249            0 :         }
    1250              :     } else {
    1251            0 :         JAMI_LOG("CallID {} doesn't exist in call holdCall", callId);
    1252            0 :         return false;
    1253            8 :     }
    1254              : 
    1255            8 :     return result;
    1256            8 : }
    1257              : 
    1258              : // THREAD=Main
    1259              : bool
    1260            3 : Manager::resumeCall(const std::string&, const std::string& callId)
    1261              : {
    1262            3 :     bool result = true;
    1263              : 
    1264            3 :     stopTone();
    1265              : 
    1266            3 :     std::shared_ptr<Call> call = getCallFromCallID(callId);
    1267            3 :     if (!call)
    1268            0 :         return false;
    1269              : 
    1270              :     try {
    1271            3 :         result = call->resume([=](bool ok) {
    1272            3 :             if (!ok) {
    1273            0 :                 JAMI_ERROR("CallID {} resumeCall failed", callId);
    1274            0 :                 return;
    1275              :             }
    1276              : 
    1277            3 :             if (auto conf = call->getConference())
    1278            0 :                 pimpl_->switchCall(conf->getConfId());
    1279              :             else
    1280            3 :                 pimpl_->switchCall(call->getCallId());
    1281              : 
    1282            3 :             addAudio(*call);
    1283              :         });
    1284            0 :     } catch (const VoipLinkException& e) {
    1285            0 :         JAMI_ERROR("[call] Failed to resume: {}", e.what());
    1286            0 :         return false;
    1287            0 :     }
    1288              : 
    1289            3 :     return result;
    1290            3 : }
    1291              : 
    1292              : // THREAD=Main
    1293              : bool
    1294            2 : Manager::transferCall(const std::string& accountId, const std::string& callId, const std::string& to)
    1295              : {
    1296            2 :     auto account = getAccount(accountId);
    1297            2 :     if (not account)
    1298            0 :         return false;
    1299            2 :     if (auto call = account->getCall(callId)) {
    1300            2 :         if (call->isConferenceParticipant())
    1301            0 :             removeParticipant(*call);
    1302            2 :         call->transfer(to);
    1303              :     } else
    1304            2 :         return false;
    1305              : 
    1306              :     // remove waiting call in case we make transfer without even answer
    1307            2 :     pimpl_->removeWaitingCall(callId);
    1308              : 
    1309            2 :     return true;
    1310            2 : }
    1311              : 
    1312              : void
    1313            0 : Manager::transferFailed()
    1314              : {
    1315            0 :     emitSignal<libjami::CallSignal::TransferFailed>();
    1316            0 : }
    1317              : 
    1318              : void
    1319            0 : Manager::transferSucceeded()
    1320              : {
    1321            0 :     emitSignal<libjami::CallSignal::TransferSucceeded>();
    1322            0 : }
    1323              : 
    1324              : // THREAD=Main : Call:Incoming
    1325              : bool
    1326            2 : Manager::refuseCall(const std::string& accountId, const std::string& id)
    1327              : {
    1328            2 :     if (auto account = getAccount(accountId)) {
    1329            2 :         if (auto call = account->getCall(id)) {
    1330            2 :             stopTone();
    1331            2 :             call->refuse();
    1332            2 :             pimpl_->removeWaitingCall(id);
    1333            2 :             removeAudio(*call);
    1334            2 :             return true;
    1335            2 :         }
    1336            2 :     }
    1337            0 :     return false;
    1338              : }
    1339              : 
    1340              : bool
    1341            0 : Manager::holdConference(const std::string& accountId, const std::string& confId)
    1342              : {
    1343            0 :     JAMI_LOG("[conf:{}] Hold conference", confId);
    1344              : 
    1345            0 :     if (const auto account = getAccount(accountId)) {
    1346            0 :         if (auto conf = account->getConference(confId)) {
    1347            0 :             conf->detachHost();
    1348            0 :             emitSignal<libjami::CallSignal::ConferenceChanged>(accountId, conf->getConfId(), conf->getStateStr());
    1349            0 :             return true;
    1350            0 :         }
    1351            0 :     }
    1352            0 :     return false;
    1353              : }
    1354              : 
    1355              : bool
    1356            0 : Manager::resumeConference(const std::string& accountId, const std::string& confId)
    1357              : {
    1358            0 :     JAMI_DEBUG("[conf:{}] Resume conference", confId);
    1359              : 
    1360            0 :     if (const auto account = getAccount(accountId)) {
    1361            0 :         if (auto conf = account->getConference(confId)) {
    1362              :             // Resume conf only if it was in hold state otherwise…
    1363              :             // all participants are restarted
    1364            0 :             if (conf->getState() == Conference::State::HOLD) {
    1365            0 :                 for (const auto& item : conf->getSubCalls())
    1366            0 :                     resumeCall(accountId, item);
    1367              : 
    1368            0 :                 pimpl_->switchCall(confId);
    1369            0 :                 conf->setState(Conference::State::ACTIVE_ATTACHED);
    1370            0 :                 emitSignal<libjami::CallSignal::ConferenceChanged>(accountId, conf->getConfId(), conf->getStateStr());
    1371            0 :                 return true;
    1372            0 :             } else if (conf->getState() == Conference::State::ACTIVE_DETACHED) {
    1373            0 :                 pimpl_->addMainParticipant(*conf);
    1374              :             }
    1375            0 :         }
    1376            0 :     }
    1377            0 :     return false;
    1378              : }
    1379              : 
    1380              : bool
    1381           12 : Manager::addSubCall(const std::string& accountId,
    1382              :                     const std::string& callId,
    1383              :                     const std::string& account2Id,
    1384              :                     const std::string& conferenceId)
    1385              : {
    1386           12 :     auto account = getAccount(accountId);
    1387           12 :     auto account2 = getAccount(account2Id);
    1388           12 :     if (account && account2) {
    1389           12 :         auto call = account->getCall(callId);
    1390           12 :         auto conf = account2->getConference(conferenceId);
    1391           12 :         if (!call or !conf)
    1392            0 :             return false;
    1393           12 :         auto callConf = call->getConference();
    1394           12 :         if (callConf != conf)
    1395           12 :             return addSubCall(*call, *conf);
    1396           36 :     }
    1397            0 :     return false;
    1398           12 : }
    1399              : 
    1400              : bool
    1401           12 : Manager::addSubCall(Call& call, Conference& conference)
    1402              : {
    1403           12 :     JAMI_DEBUG("[conf:{}] Adding participant {}", conference.getConfId(), call.getCallId());
    1404              : 
    1405              :     // Store the current call ID (it will change in resumeCall or in acceptCall)
    1406           12 :     pimpl_->bindCallToConference(call, conference);
    1407              : 
    1408              :     // Don't attach current user yet
    1409           12 :     if (conference.getState() == Conference::State::ACTIVE_DETACHED) {
    1410            0 :         return true;
    1411              :     }
    1412              : 
    1413              :     // TODO: remove this ugly hack → There should be different calls when double clicking
    1414              :     // a conference to add main participant to it, or (in this case) adding a participant
    1415              :     // to conference
    1416           12 :     pimpl_->unsetCurrentCall();
    1417           12 :     pimpl_->addMainParticipant(conference);
    1418           12 :     pimpl_->switchCall(conference.getConfId());
    1419           12 :     addAudio(call);
    1420              : 
    1421           12 :     return true;
    1422              : }
    1423              : 
    1424              : void
    1425           12 : Manager::ManagerPimpl::addMainParticipant(Conference& conf)
    1426              : {
    1427           12 :     JAMI_DEBUG("[conf:{}] Adding main participant", conf.getConfId());
    1428           12 :     conf.attachHost(conf.getLastMediaList());
    1429           12 :     emitSignal<libjami::CallSignal::ConferenceChanged>(conf.getAccountId(), conf.getConfId(), conf.getStateStr());
    1430           12 :     switchCall(conf.getConfId());
    1431           12 : }
    1432              : 
    1433              : bool
    1434           29 : Manager::ManagerPimpl::hangupConference(Conference& conference)
    1435              : {
    1436           29 :     JAMI_DEBUG("[conf:{}] Hanging up conference", conference.getConfId());
    1437           29 :     CallIdSet subcalls(conference.getSubCalls());
    1438           29 :     conference.detachHost();
    1439           29 :     if (subcalls.empty()) {
    1440            5 :         if (auto account = conference.getAccount())
    1441            5 :             account->removeConference(conference.getConfId());
    1442              :     }
    1443           80 :     for (const auto& callId : subcalls) {
    1444           51 :         if (auto call = base_.getCallFromCallID(callId))
    1445           51 :             base_.hangupCall(call->getAccountId(), callId);
    1446              :     }
    1447           29 :     unsetCurrentCall();
    1448           29 :     return true;
    1449           29 : }
    1450              : 
    1451              : bool
    1452            0 : Manager::addMainParticipant(const std::string& accountId, const std::string& conferenceId)
    1453              : {
    1454            0 :     JAMI_LOG("[conf:{}] Adding main participant", conferenceId);
    1455              : 
    1456            0 :     if (auto account = getAccount(accountId)) {
    1457            0 :         if (auto conf = account->getConference(conferenceId)) {
    1458            0 :             pimpl_->addMainParticipant(*conf);
    1459            0 :             return true;
    1460              :         } else
    1461            0 :             JAMI_WARNING("[conf:{}] Failed to add main participant (conference not found)", conferenceId);
    1462            0 :     }
    1463            0 :     return false;
    1464              : }
    1465              : 
    1466              : std::shared_ptr<Call>
    1467          636 : Manager::getCallFromCallID(const std::string& callID) const
    1468              : {
    1469          636 :     return callFactory.getCall(callID);
    1470              : }
    1471              : 
    1472              : bool
    1473           26 : Manager::joinParticipant(const std::string& accountId,
    1474              :                          const std::string& callId1,
    1475              :                          const std::string& account2Id,
    1476              :                          const std::string& callId2,
    1477              :                          bool attached)
    1478              : {
    1479           26 :     JAMI_DEBUG("Joining participants {} and {}, attached={}", callId1, callId2, attached);
    1480           26 :     auto account = getAccount(accountId);
    1481           26 :     auto account2 = getAccount(account2Id);
    1482           26 :     if (not account or not account2) {
    1483            0 :         return false;
    1484              :     }
    1485              : 
    1486           26 :     JAMI_LOG("Creating conference for participants {} and {}, host attached: {}", callId1, callId2, attached);
    1487              : 
    1488           26 :     if (callId1 == callId2) {
    1489            0 :         JAMI_ERROR("Unable to join participant {} to itself", callId1);
    1490            0 :         return false;
    1491              :     }
    1492              : 
    1493              :     // Set corresponding conference ids for call 1
    1494           26 :     auto call1 = account->getCall(callId1);
    1495           26 :     if (!call1) {
    1496            0 :         JAMI_ERROR("Unable to find call {}", callId1);
    1497            0 :         return false;
    1498              :     }
    1499              : 
    1500              :     // Set corresponding conference details
    1501           26 :     auto call2 = account2->getCall(callId2);
    1502           26 :     if (!call2) {
    1503            0 :         JAMI_ERROR("Unable to find call {}", callId2);
    1504            0 :         return false;
    1505              :     }
    1506              : 
    1507           26 :     auto mediaAttr = call1->getMediaAttributeList();
    1508           26 :     if (mediaAttr.empty()) {
    1509            0 :         JAMI_WARNING("[call:{}] No media attribute found, using media attribute from call [{}]", callId1, callId2);
    1510            0 :         mediaAttr = call2->getMediaAttributeList();
    1511              :     }
    1512              : 
    1513              :     // Filter out secondary audio streams that are muted: these are SDP
    1514              :     // negotiation artifacts (the host answered a participant's extra audio
    1515              :     // offer with a muted slot) and do not represent real host audio sources.
    1516              :     {
    1517           26 :         bool audioFound = false;
    1518           26 :         mediaAttr.erase(std::remove_if(mediaAttr.begin(),
    1519              :                                        mediaAttr.end(),
    1520           48 :                                        [&audioFound](const MediaAttribute& attr) {
    1521           48 :                                            if (attr.type_ == MediaType::MEDIA_AUDIO) {
    1522           26 :                                                if (audioFound && attr.muted_)
    1523            0 :                                                    return true; // remove secondary audio streams
    1524           26 :                                                audioFound = true;
    1525              :                                            }
    1526           48 :                                            return false;
    1527              :                                        }),
    1528           26 :                         mediaAttr.end());
    1529              :     }
    1530              : 
    1531           26 :     JAMI_DEBUG("[call:{}] Media attributes for conference:", callId1);
    1532           74 :     for (const auto& media : mediaAttr) {
    1533           48 :         JAMI_DEBUG("- {}", media.toString(true));
    1534              :     }
    1535              : 
    1536           26 :     auto conf = std::make_shared<Conference>(account);
    1537           26 :     conf->attachHost(MediaAttribute::mediaAttributesToMediaMaps(mediaAttr));
    1538           26 :     account->attach(conf);
    1539           26 :     emitSignal<libjami::CallSignal::ConferenceCreated>(account->getAccountID(), "", conf->getConfId());
    1540              : 
    1541              :     // Bind calls according to their state
    1542           26 :     pimpl_->bindCallToConference(*call1, *conf);
    1543           26 :     pimpl_->bindCallToConference(*call2, *conf);
    1544              : 
    1545              :     // Switch current call id to this conference
    1546           26 :     if (attached) {
    1547           26 :         pimpl_->switchCall(conf->getConfId());
    1548           26 :         conf->setState(Conference::State::ACTIVE_ATTACHED);
    1549              :     } else {
    1550            0 :         conf->detachHost();
    1551              :     }
    1552           26 :     emitSignal<libjami::CallSignal::ConferenceChanged>(account->getAccountID(), conf->getConfId(), conf->getStateStr());
    1553              : 
    1554           26 :     return true;
    1555           26 : }
    1556              : 
    1557              : void
    1558            0 : Manager::createConfFromParticipantList(const std::string& accountId, const std::vector<std::string>& participantList)
    1559              : {
    1560            0 :     auto account = getAccount(accountId);
    1561            0 :     if (not account) {
    1562            0 :         JAMI_WARNING("[account:{}] Account not found", accountId);
    1563            0 :         return;
    1564              :     }
    1565              : 
    1566              :     // we must have at least 2 participant for a conference
    1567            0 :     if (participantList.size() <= 1) {
    1568            0 :         JAMI_ERROR("[conf] Participant number must be greater than or equal to 2");
    1569            0 :         return;
    1570              :     }
    1571              : 
    1572            0 :     auto conf = std::make_shared<Conference>(account);
    1573              :     // attach host with empty medialist
    1574              :     // which will result in a default list set by initSourcesForHost
    1575            0 :     conf->attachHost({});
    1576              : 
    1577            0 :     unsigned successCounter = 0;
    1578            0 :     for (const auto& numberaccount : participantList) {
    1579            0 :         std::string tostr(numberaccount.substr(0, numberaccount.find(',')));
    1580            0 :         std::string account(numberaccount.substr(numberaccount.find(',') + 1, numberaccount.size()));
    1581              : 
    1582            0 :         pimpl_->unsetCurrentCall();
    1583              : 
    1584              :         // Create call
    1585            0 :         auto callId = outgoingCall(account, tostr, {});
    1586            0 :         if (callId.empty())
    1587            0 :             continue;
    1588              : 
    1589              :         // Manager methods may behave differently if the call id participates in a conference
    1590            0 :         conf->addSubCall(callId);
    1591            0 :         successCounter++;
    1592            0 :     }
    1593              : 
    1594              :     // Create the conference if and only if at least 2 calls have been successfully created
    1595            0 :     if (successCounter >= 2) {
    1596            0 :         account->attach(conf);
    1597            0 :         emitSignal<libjami::CallSignal::ConferenceCreated>(accountId, "", conf->getConfId());
    1598              :     }
    1599            0 : }
    1600              : 
    1601              : bool
    1602            0 : Manager::detachHost(const std::shared_ptr<Conference>& conf)
    1603              : {
    1604            0 :     if (not conf)
    1605            0 :         return false;
    1606              : 
    1607            0 :     JAMI_LOG("[conf:{}] Detaching host", conf->getConfId());
    1608            0 :     conf->detachHost();
    1609            0 :     emitSignal<libjami::CallSignal::ConferenceChanged>(conf->getAccountId(), conf->getConfId(), conf->getStateStr());
    1610            0 :     pimpl_->unsetCurrentCall();
    1611            0 :     return true;
    1612              : }
    1613              : 
    1614              : bool
    1615            0 : Manager::detachParticipant(const std::string& callId)
    1616              : {
    1617            0 :     JAMI_DEBUG("Detaching participant {}", callId);
    1618              : 
    1619            0 :     auto call = getCallFromCallID(callId);
    1620            0 :     if (!call) {
    1621            0 :         JAMI_ERROR("Unable to find call {}", callId);
    1622            0 :         return false;
    1623              :     }
    1624              : 
    1625              :     // Don't hold ringing calls when detaching them from conferences
    1626            0 :     if (call->getStateStr() != "RINGING")
    1627            0 :         holdCall(call->getAccountId(), callId);
    1628              : 
    1629            0 :     removeParticipant(*call);
    1630            0 :     return true;
    1631            0 : }
    1632              : 
    1633              : void
    1634           69 : Manager::removeParticipant(Call& call)
    1635              : {
    1636           69 :     JAMI_DEBUG("Removing participant {}", call.getCallId());
    1637              : 
    1638           69 :     auto conf = call.getConference();
    1639           69 :     if (not conf) {
    1640            0 :         JAMI_ERROR("[call:{}] No conference associated, unable to remove participant", call.getCallId());
    1641            0 :         return;
    1642              :     }
    1643              : 
    1644           69 :     conf->removeSubCall(call.getCallId());
    1645              : 
    1646           69 :     removeAudio(call);
    1647              : 
    1648           69 :     emitSignal<libjami::CallSignal::ConferenceChanged>(conf->getAccountId(), conf->getConfId(), conf->getStateStr());
    1649              : 
    1650           69 :     pimpl_->processRemainingParticipants(*conf);
    1651           69 : }
    1652              : 
    1653              : bool
    1654            0 : Manager::joinConference(const std::string& accountId,
    1655              :                         const std::string& confId1,
    1656              :                         const std::string& account2Id,
    1657              :                         const std::string& confId2)
    1658              : {
    1659            0 :     auto account = getAccount(accountId);
    1660            0 :     auto account2 = getAccount(account2Id);
    1661            0 :     if (not account) {
    1662            0 :         JAMI_ERROR("Unable to find account: {}", accountId);
    1663            0 :         return false;
    1664              :     }
    1665            0 :     if (not account2) {
    1666            0 :         JAMI_ERROR("Unable to find account: {}", account2Id);
    1667            0 :         return false;
    1668              :     }
    1669              : 
    1670            0 :     auto conf = account->getConference(confId1);
    1671            0 :     if (not conf) {
    1672            0 :         JAMI_ERROR("[conf:{}] Invalid conference ID", confId1);
    1673            0 :         return false;
    1674              :     }
    1675              : 
    1676            0 :     auto conf2 = account2->getConference(confId2);
    1677            0 :     if (not conf2) {
    1678            0 :         JAMI_ERROR("[conf:{}] Invalid conference ID", confId2);
    1679            0 :         return false;
    1680              :     }
    1681              : 
    1682            0 :     CallIdSet subcalls(conf->getSubCalls());
    1683              : 
    1684            0 :     std::vector<std::shared_ptr<Call>> calls;
    1685            0 :     calls.reserve(subcalls.size());
    1686              : 
    1687              :     // Detach and remove all participant from conf1 before add
    1688              :     // ... to conf2
    1689            0 :     for (const auto& callId : subcalls) {
    1690            0 :         JAMI_DEBUG("Detach participant {}", callId);
    1691            0 :         if (auto call = account->getCall(callId)) {
    1692            0 :             conf->removeSubCall(callId);
    1693            0 :             removeAudio(*call);
    1694            0 :             calls.emplace_back(std::move(call));
    1695              :         } else {
    1696            0 :             JAMI_ERROR("Unable to find call {}", callId);
    1697            0 :         }
    1698              :     }
    1699              :     // Remove conf1
    1700            0 :     account->removeConference(confId1);
    1701              : 
    1702            0 :     for (const auto& c : calls)
    1703            0 :         addSubCall(*c, *conf2);
    1704              : 
    1705            0 :     return true;
    1706            0 : }
    1707              : 
    1708              : void
    1709          228 : Manager::addAudio(Call& call)
    1710              : {
    1711          228 :     if (call.isConferenceParticipant())
    1712           12 :         return;
    1713          216 :     const auto& callId = call.getCallId();
    1714          216 :     JAMI_LOG("Add audio to call {}", callId);
    1715              : 
    1716              :     // bind to main
    1717          216 :     auto medias = call.getAudioStreams();
    1718          435 :     for (const auto& media : medias) {
    1719          219 :         JAMI_DEBUG("[call:{}] Attach audio stream {}", callId, media.first);
    1720          657 :         getRingBufferPool().bindRingBuffers(media.first, RingBufferPool::DEFAULT_ID);
    1721              :     }
    1722          216 :     auto oldGuard = std::move(call.audioGuard);
    1723          216 :     call.audioGuard = startAudioStream(AudioDeviceType::PLAYBACK);
    1724              : 
    1725          216 :     std::lock_guard lock(pimpl_->audioLayerMutex_);
    1726          216 :     if (!pimpl_->audiodriver_) {
    1727            0 :         JAMI_ERROR("Uninitialized audio driver");
    1728            0 :         return;
    1729              :     }
    1730          216 :     pimpl_->audiodriver_->flushUrgent();
    1731          216 :     getRingBufferPool().flushAllBuffers();
    1732          216 : }
    1733              : 
    1734              : void
    1735          423 : Manager::removeAudio(Call& call)
    1736              : {
    1737          423 :     const auto& callId = call.getCallId();
    1738          423 :     auto medias = call.getAudioStreams();
    1739          849 :     for (const auto& media : medias) {
    1740          426 :         JAMI_DEBUG("[call:{}] Remove local audio {}", callId, media.first);
    1741          426 :         getRingBufferPool().unBindAll(media.first);
    1742              :     }
    1743          423 : }
    1744              : 
    1745              : std::shared_ptr<asio::io_context>
    1746        40223 : Manager::ioContext() const
    1747              : {
    1748        40223 :     return pimpl_->ioContext_;
    1749              : }
    1750              : 
    1751              : std::shared_ptr<dhtnet::upnp::UPnPContext>
    1752         1077 : Manager::upnpContext() const
    1753              : {
    1754         1077 :     return pimpl_->upnpContext_;
    1755              : }
    1756              : 
    1757              : void
    1758         1328 : Manager::saveConfig(const std::shared_ptr<Account>& acc)
    1759              : {
    1760         1328 :     if (auto account = std::dynamic_pointer_cast<JamiAccount>(acc))
    1761         1301 :         account->saveConfig();
    1762              :     else
    1763         1328 :         saveConfig();
    1764         1328 : }
    1765              : 
    1766              : void
    1767         3204 : Manager::saveConfig()
    1768              : {
    1769         3204 :     JAMI_LOG("Saving configuration to '{}'", pimpl_->path_);
    1770              : 
    1771         3204 :     if (pimpl_->audiodriver_) {
    1772         3198 :         audioPreference.setVolumemic(pimpl_->audiodriver_->getCaptureGain());
    1773         3198 :         audioPreference.setVolumespkr(pimpl_->audiodriver_->getPlaybackGain());
    1774         3198 :         audioPreference.setCaptureMuted(pimpl_->audiodriver_->isCaptureMuted());
    1775         3198 :         audioPreference.setPlaybackMuted(pimpl_->audiodriver_->isPlaybackMuted());
    1776              :     }
    1777              : 
    1778              :     try {
    1779         3204 :         YAML::Emitter out;
    1780              : 
    1781              :         // FIXME maybe move this into accountFactory?
    1782         3204 :         out << YAML::BeginMap << YAML::Key << "accounts";
    1783         3204 :         out << YAML::Value << YAML::BeginSeq;
    1784              : 
    1785        10582 :         for (const auto& account : accountFactory.getAllAccounts()) {
    1786         7378 :             if (auto jamiAccount = std::dynamic_pointer_cast<JamiAccount>(account)) {
    1787         7179 :                 auto accountConfig = jamiAccount->getPath() / "config.yml";
    1788         7179 :                 if (not std::filesystem::is_regular_file(accountConfig)) {
    1789            0 :                     saveConfig(jamiAccount);
    1790              :                 }
    1791         7179 :             } else {
    1792          199 :                 account->config().serialize(out);
    1793         7378 :             }
    1794         3204 :         }
    1795         3204 :         out << YAML::EndSeq;
    1796              : 
    1797              :         // FIXME: this is a hack until we get rid of accountOrder
    1798         3204 :         preferences.verifyAccountOrder(getAccountList());
    1799         3204 :         preferences.serialize(out);
    1800         3204 :         voipPreferences.serialize(out);
    1801         3204 :         audioPreference.serialize(out);
    1802              : #ifdef ENABLE_VIDEO
    1803         3204 :         videoPreferences.serialize(out);
    1804              : #endif
    1805              : #ifdef ENABLE_PLUGIN
    1806         3204 :         pluginPreferences.serialize(out);
    1807              : #endif
    1808              : 
    1809         3204 :         std::lock_guard lock(dhtnet::fileutils::getFileLock(pimpl_->path_));
    1810         3204 :         std::ofstream fout(pimpl_->path_);
    1811         3204 :         fout.write(out.c_str(), static_cast<long>(out.size()));
    1812         3204 :     } catch (const YAML::Exception& e) {
    1813            0 :         JAMI_ERROR("[config] YAML error: {}", e.what());
    1814            0 :     } catch (const std::runtime_error& e) {
    1815            0 :         JAMI_ERROR("[config] {}", e.what());
    1816            0 :     }
    1817         3204 : }
    1818              : 
    1819              : // THREAD=Main | VoIPLink
    1820              : void
    1821            0 : Manager::playDtmf(char code)
    1822              : {
    1823            0 :     stopTone();
    1824              : 
    1825            0 :     if (not voipPreferences.getPlayDtmf()) {
    1826            0 :         return;
    1827              :     }
    1828              : 
    1829              :     // length in milliseconds
    1830            0 :     int pulselen = voipPreferences.getPulseLength();
    1831              : 
    1832            0 :     if (pulselen == 0) {
    1833            0 :         return;
    1834              :     }
    1835              : 
    1836            0 :     std::lock_guard lock(pimpl_->audioLayerMutex_);
    1837              : 
    1838              :     // fast return, no sound, so no dtmf
    1839            0 :     if (not pimpl_->audiodriver_ or not pimpl_->dtmfKey_) {
    1840            0 :         return;
    1841              :     }
    1842              : 
    1843            0 :     std::shared_ptr<AudioDeviceGuard> audioGuard = startAudioStream(AudioDeviceType::PLAYBACK);
    1844            0 :     if (not pimpl_->audiodriver_->waitForStart(std::chrono::seconds(1))) {
    1845            0 :         JAMI_ERROR("[audio] Failed to start audio layer for DTMF");
    1846            0 :         return;
    1847              :     }
    1848              : 
    1849              :     // number of data sampling in one pulselen depends on samplerate
    1850              :     // size (n sampling) = time_ms * sampling/s
    1851              :     //                     ---------------------
    1852              :     //                            ms/s
    1853            0 :     unsigned size = (unsigned) ((pulselen * (long) pimpl_->audiodriver_->getSampleRate()) / 1000ul);
    1854            0 :     if (!pimpl_->dtmfBuf_ or pimpl_->dtmfBuf_->getFrameSize() != size)
    1855            0 :         pimpl_->dtmfBuf_ = std::make_shared<AudioFrame>(pimpl_->audiodriver_->getFormat(), size);
    1856              : 
    1857              :     // Handle dtmf
    1858            0 :     pimpl_->dtmfKey_->startTone(code);
    1859              : 
    1860              :     // copy the sound
    1861            0 :     if (pimpl_->dtmfKey_->generateDTMF(pimpl_->dtmfBuf_->pointer())) {
    1862              :         // Put buffer to urgentRingBuffer
    1863              :         // put the size in bytes…
    1864              :         // so size * 1 channel (mono) * sizeof (bytes for the data)
    1865              :         // audiolayer->flushUrgent();
    1866              : 
    1867            0 :         pimpl_->audiodriver_->putUrgent(pimpl_->dtmfBuf_);
    1868              :     }
    1869              : 
    1870            0 :     auto dtmfTimer = std::make_unique<asio::steady_timer>(*pimpl_->ioContext_, std::chrono::milliseconds(pulselen));
    1871            0 :     dtmfTimer->async_wait([this, audioGuard, t = dtmfTimer.get()](const asio::error_code& ec) {
    1872            0 :         if (ec)
    1873            0 :             return;
    1874            0 :         JAMI_LOG("End of dtmf");
    1875            0 :         std::lock_guard lock(pimpl_->audioLayerMutex_);
    1876            0 :         if (pimpl_->dtmfTimer_.get() == t)
    1877            0 :             pimpl_->dtmfTimer_.reset();
    1878            0 :     });
    1879            0 :     if (pimpl_->dtmfTimer_)
    1880            0 :         pimpl_->dtmfTimer_->cancel();
    1881            0 :     pimpl_->dtmfTimer_ = std::move(dtmfTimer);
    1882            0 : }
    1883              : 
    1884              : // Multi-thread
    1885              : bool
    1886          122 : Manager::incomingCallsWaiting()
    1887              : {
    1888          122 :     std::lock_guard m(pimpl_->waitingCallsMutex_);
    1889          244 :     return not pimpl_->waitingCalls_.empty();
    1890          122 : }
    1891              : 
    1892              : void
    1893          113 : Manager::incomingCall(const std::string& accountId, Call& call)
    1894              : {
    1895          113 :     if (not accountId.empty()) {
    1896          113 :         pimpl_->stripSipPrefix(call);
    1897              :     }
    1898              : 
    1899          113 :     auto const& account = getAccount(accountId);
    1900          113 :     if (not account) {
    1901            0 :         JAMI_ERROR("Incoming call {} on unknown account {}", call.getCallId(), accountId);
    1902            0 :         return;
    1903              :     }
    1904              : 
    1905              :     // Process the call.
    1906          113 :     pimpl_->processIncomingCall(accountId, call);
    1907          113 : }
    1908              : 
    1909              : void
    1910            0 : Manager::incomingMessage(const std::string& accountId,
    1911              :                          const std::string& callId,
    1912              :                          const std::string& from,
    1913              :                          const std::map<std::string, std::string>& messages)
    1914              : {
    1915            0 :     auto account = getAccount(accountId);
    1916            0 :     if (not account) {
    1917            0 :         return;
    1918              :     }
    1919            0 :     if (auto call = account->getCall(callId)) {
    1920            0 :         if (call->isConferenceParticipant()) {
    1921            0 :             if (auto conf = call->getConference()) {
    1922              :                 // filter out vcards messages  as they could be resent by master as its own vcard
    1923              :                 // TODO. Implement a protocol to handle vcard messages
    1924            0 :                 bool sendToOtherParicipants = true;
    1925            0 :                 for (auto& message : messages) {
    1926            0 :                     if (message.first.find("x-ring/ring.profile.vcard") != std::string::npos) {
    1927            0 :                         sendToOtherParicipants = false;
    1928              :                     }
    1929              :                 }
    1930            0 :                 if (sendToOtherParicipants) {
    1931            0 :                     pimpl_->sendTextMessageToConference(*conf, messages, from);
    1932              :                 }
    1933              : 
    1934              :                 // in case of a conference we must notify client using conference id
    1935            0 :                 emitSignal<libjami::CallSignal::IncomingMessage>(accountId, conf->getConfId(), from, messages);
    1936              :             } else {
    1937            0 :                 JAMI_ERROR("[call:{}] No conference associated to call", callId);
    1938            0 :             }
    1939              :         } else {
    1940            0 :             emitSignal<libjami::CallSignal::IncomingMessage>(accountId, callId, from, messages);
    1941              :         }
    1942            0 :     }
    1943            0 : }
    1944              : 
    1945              : void
    1946            0 : Manager::sendCallTextMessage(const std::string& accountId,
    1947              :                              const std::string& callID,
    1948              :                              const std::map<std::string, std::string>& messages,
    1949              :                              const std::string& from,
    1950              :                              bool /*isMixed TODO: use it */)
    1951              : {
    1952            0 :     auto account = getAccount(accountId);
    1953            0 :     if (not account) {
    1954            0 :         return;
    1955              :     }
    1956              : 
    1957            0 :     if (auto conf = account->getConference(callID)) {
    1958            0 :         pimpl_->sendTextMessageToConference(*conf, messages, from);
    1959            0 :     } else if (auto call = account->getCall(callID)) {
    1960            0 :         if (call->isConferenceParticipant()) {
    1961            0 :             if (auto conf = call->getConference()) {
    1962            0 :                 pimpl_->sendTextMessageToConference(*conf, messages, from);
    1963              :             } else {
    1964            0 :                 JAMI_ERROR("[call:{}] No conference associated to call", callID);
    1965            0 :             }
    1966              :         } else {
    1967              :             try {
    1968            0 :                 call->sendTextMessage(messages, from);
    1969            0 :             } catch (const im::InstantMessageException& e) {
    1970            0 :                 JAMI_ERROR("Failed to send message to call {}: {}", call->getCallId(), e.what());
    1971            0 :             }
    1972              :         }
    1973              :     } else {
    1974            0 :         JAMI_ERROR("Failed to send message to {}: nonexistent call ID", callID);
    1975            0 :     }
    1976            0 : }
    1977              : 
    1978              : // THREAD=VoIP CALL=Outgoing
    1979              : void
    1980          103 : Manager::peerAnsweredCall(Call& call)
    1981              : {
    1982          103 :     const auto& callId = call.getCallId();
    1983          103 :     JAMI_LOG("[call:{}] Peer answered", callId);
    1984              : 
    1985              :     // The if statement is useful only if we sent two calls at the same time.
    1986          103 :     if (isCurrentCall(call))
    1987            1 :         stopTone();
    1988              : 
    1989          103 :     addAudio(call);
    1990              : 
    1991          103 :     if (pimpl_->audiodriver_) {
    1992          103 :         std::lock_guard lock(pimpl_->audioLayerMutex_);
    1993          103 :         getRingBufferPool().flushAllBuffers();
    1994          103 :         pimpl_->audiodriver_->flushUrgent();
    1995          103 :     }
    1996              : 
    1997          103 :     if (audioPreference.getIsAlwaysRecording()) {
    1998            1 :         auto result = call.toggleRecording();
    1999            1 :         emitSignal<libjami::CallSignal::RecordPlaybackFilepath>(callId, call.getPath());
    2000            1 :         emitSignal<libjami::CallSignal::RecordingStateChanged>(callId, result);
    2001              :     }
    2002          103 : }
    2003              : 
    2004              : // THREAD=VoIP Call=Outgoing
    2005              : void
    2006          214 : Manager::peerRingingCall(Call& call)
    2007              : {
    2008          214 :     JAMI_LOG("[call:{}] Peer ringing", call.getCallId());
    2009          214 :     if (!hasCurrentCall())
    2010           79 :         ringback();
    2011          214 : }
    2012              : 
    2013              : // THREAD=VoIP Call=Outgoing/Ingoing
    2014              : void
    2015          113 : Manager::peerHungupCall(Call& call)
    2016              : {
    2017          113 :     const auto& callId = call.getCallId();
    2018          113 :     JAMI_LOG("[call:{}] Peer hung up", callId);
    2019              : 
    2020          113 :     if (call.isConferenceParticipant()) {
    2021           12 :         removeParticipant(call);
    2022          101 :     } else if (isCurrentCall(call)) {
    2023           13 :         stopTone();
    2024           13 :         pimpl_->unsetCurrentCall();
    2025              :     }
    2026              : 
    2027          113 :     call.peerHungup();
    2028              : 
    2029          113 :     pimpl_->removeWaitingCall(callId);
    2030          113 :     if (not incomingCallsWaiting())
    2031           81 :         stopTone();
    2032              : 
    2033          113 :     removeAudio(call);
    2034          113 : }
    2035              : 
    2036              : // THREAD=VoIP
    2037              : void
    2038            0 : Manager::callBusy(Call& call)
    2039              : {
    2040            0 :     JAMI_LOG("[call:{}] Busy", call.getCallId());
    2041              : 
    2042            0 :     if (isCurrentCall(call)) {
    2043            0 :         pimpl_->unsetCurrentCall();
    2044              :     }
    2045              : 
    2046            0 :     pimpl_->removeWaitingCall(call.getCallId());
    2047            0 :     if (not incomingCallsWaiting())
    2048            0 :         stopTone();
    2049            0 : }
    2050              : 
    2051              : // THREAD=VoIP
    2052              : void
    2053          101 : Manager::callFailure(Call& call)
    2054              : {
    2055          101 :     JAMI_LOG("[call:{}] {} failed", call.getCallId(), call.isSubcall() ? "Sub-call" : "Parent call");
    2056              : 
    2057          101 :     if (isCurrentCall(call)) {
    2058            4 :         pimpl_->unsetCurrentCall();
    2059              :     }
    2060              : 
    2061          101 :     if (call.isConferenceParticipant()) {
    2062            0 :         JAMI_LOG("[call:{}] Participating in conference, removing participant", call.getCallId());
    2063              :         // remove this participant
    2064            0 :         removeParticipant(call);
    2065              :     }
    2066              : 
    2067          101 :     pimpl_->removeWaitingCall(call.getCallId());
    2068          101 :     if (not call.isSubcall() && not incomingCallsWaiting())
    2069            5 :         stopTone();
    2070          101 :     removeAudio(call);
    2071          101 : }
    2072              : 
    2073              : /**
    2074              :  * Multi Thread
    2075              :  */
    2076              : void
    2077          571 : Manager::stopTone()
    2078              : {
    2079          571 :     if (not voipPreferences.getPlayTones())
    2080            0 :         return;
    2081              : 
    2082          571 :     pimpl_->toneCtrl_.stop();
    2083          571 :     pimpl_->toneDeviceGuard_.reset();
    2084              : }
    2085              : 
    2086              : /**
    2087              :  * Multi Thread
    2088              :  */
    2089              : void
    2090            0 : Manager::playTone()
    2091              : {
    2092            0 :     pimpl_->playATone(Tone::ToneId::DIALTONE);
    2093            0 : }
    2094              : 
    2095              : /**
    2096              :  * Multi Thread
    2097              :  */
    2098              : void
    2099            0 : Manager::playToneWithMessage()
    2100              : {
    2101            0 :     pimpl_->playATone(Tone::ToneId::CONGESTION);
    2102            0 : }
    2103              : 
    2104              : /**
    2105              :  * Multi Thread
    2106              :  */
    2107              : void
    2108            0 : Manager::congestion()
    2109              : {
    2110            0 :     pimpl_->playATone(Tone::ToneId::CONGESTION);
    2111            0 : }
    2112              : 
    2113              : /**
    2114              :  * Multi Thread
    2115              :  */
    2116              : void
    2117          140 : Manager::ringback()
    2118              : {
    2119          140 :     pimpl_->playATone(Tone::ToneId::RINGTONE);
    2120          140 : }
    2121              : 
    2122              : /**
    2123              :  * Multi Thread
    2124              :  */
    2125              : void
    2126           61 : Manager::playRingtone(const std::string& accountID)
    2127              : {
    2128           61 :     const auto account = getAccount(accountID);
    2129           61 :     if (!account) {
    2130            0 :         JAMI_WARNING("[account:{}] Invalid account for ringtone", accountID);
    2131            0 :         return;
    2132              :     }
    2133              : 
    2134           61 :     if (!account->getRingtoneEnabled()) {
    2135            0 :         ringback();
    2136            0 :         return;
    2137              :     }
    2138              : 
    2139              :     {
    2140           61 :         std::lock_guard lock(pimpl_->audioLayerMutex_);
    2141              : 
    2142           61 :         if (not pimpl_->audiodriver_) {
    2143            0 :             JAMI_ERROR("[audio] No audio layer for ringtone");
    2144            0 :             return;
    2145              :         }
    2146              :         // start audio if not started AND flush all buffers (main and urgent)
    2147           61 :         auto oldGuard = std::move(pimpl_->toneDeviceGuard_);
    2148           61 :         pimpl_->toneDeviceGuard_ = startAudioStream(AudioDeviceType::RINGTONE);
    2149           61 :         auto format = pimpl_->audiodriver_->getFormat();
    2150           61 :         pimpl_->toneCtrl_.setSampleRate(format.sample_rate, format.sampleFormat);
    2151           61 :     }
    2152              : 
    2153           61 :     if (not pimpl_->toneCtrl_.setAudioFile(account->getRingtonePath().string()))
    2154           61 :         ringback();
    2155           61 : }
    2156              : 
    2157              : std::shared_ptr<AudioLoop>
    2158            0 : Manager::getTelephoneTone()
    2159              : {
    2160            0 :     return pimpl_->toneCtrl_.getTelephoneTone();
    2161              : }
    2162              : 
    2163              : std::shared_ptr<AudioLoop>
    2164            0 : Manager::getTelephoneFile()
    2165              : {
    2166            0 :     return pimpl_->toneCtrl_.getTelephoneFile();
    2167              : }
    2168              : 
    2169              : /**
    2170              :  * Set input audio plugin
    2171              :  */
    2172              : void
    2173            0 : Manager::setAudioPlugin(const std::string& audioPlugin)
    2174              : {
    2175              :     {
    2176            0 :         std::lock_guard lock(pimpl_->audioLayerMutex_);
    2177            0 :         audioPreference.setAlsaPlugin(audioPlugin);
    2178            0 :         pimpl_->audiodriver_.reset();
    2179            0 :         pimpl_->initAudioDriver();
    2180            0 :     }
    2181              :     // Recreate audio driver with new settings
    2182            0 :     saveConfig();
    2183            0 : }
    2184              : 
    2185              : /**
    2186              :  * Set audio output device
    2187              :  */
    2188              : void
    2189            0 : Manager::setAudioDevice(int index, AudioDeviceType type)
    2190              : {
    2191            0 :     std::lock_guard lock(pimpl_->audioLayerMutex_);
    2192              : 
    2193            0 :     if (not pimpl_->audiodriver_) {
    2194            0 :         JAMI_ERROR("[audio] Uninitialized audio driver");
    2195            0 :         return;
    2196              :     }
    2197            0 :     if (pimpl_->getCurrentDeviceIndex(type) == index) {
    2198            0 :         JAMI_DEBUG("[audio] Audio device already selected, doing nothing");
    2199            0 :         return;
    2200              :     }
    2201              : 
    2202            0 :     pimpl_->audiodriver_->updatePreference(audioPreference, index, type);
    2203              : 
    2204              :     // Recreate audio driver with new settings
    2205            0 :     pimpl_->audiodriver_.reset();
    2206            0 :     pimpl_->initAudioDriver();
    2207            0 :     saveConfig();
    2208            0 : }
    2209              : 
    2210              : /**
    2211              :  * Get list of supported audio output device
    2212              :  */
    2213              : std::vector<std::string>
    2214            0 : Manager::getAudioOutputDeviceList()
    2215              : {
    2216            0 :     std::lock_guard lock(pimpl_->audioLayerMutex_);
    2217              : 
    2218            0 :     if (not pimpl_->audiodriver_) {
    2219            0 :         JAMI_ERROR("[audio] Uninitialized audio layer");
    2220            0 :         return {};
    2221              :     }
    2222              : 
    2223            0 :     return pimpl_->audiodriver_->getPlaybackDeviceList();
    2224            0 : }
    2225              : 
    2226              : /**
    2227              :  * Get list of supported audio input device
    2228              :  */
    2229              : std::vector<std::string>
    2230            0 : Manager::getAudioInputDeviceList()
    2231              : {
    2232            0 :     std::lock_guard lock(pimpl_->audioLayerMutex_);
    2233              : 
    2234            0 :     if (not pimpl_->audiodriver_) {
    2235            0 :         JAMI_ERROR("[audio] Uninitialized audio layer");
    2236            0 :         return {};
    2237              :     }
    2238              : 
    2239            0 :     return pimpl_->audiodriver_->getCaptureDeviceList();
    2240            0 : }
    2241              : 
    2242              : /**
    2243              :  * Get string array representing integer indexes of output and input device
    2244              :  */
    2245              : std::vector<std::string>
    2246            0 : Manager::getCurrentAudioDevicesIndex()
    2247              : {
    2248            0 :     std::lock_guard lock(pimpl_->audioLayerMutex_);
    2249            0 :     if (not pimpl_->audiodriver_) {
    2250            0 :         JAMI_ERROR("[audio] Uninitialized audio layer");
    2251            0 :         return {};
    2252              :     }
    2253              : 
    2254            0 :     return {std::to_string(pimpl_->audiodriver_->getIndexPlayback()),
    2255            0 :             std::to_string(pimpl_->audiodriver_->getIndexCapture()),
    2256            0 :             std::to_string(pimpl_->audiodriver_->getIndexRingtone())};
    2257            0 : }
    2258              : 
    2259          716 : AudioDeviceGuard::AudioDeviceGuard(Manager& manager, AudioDeviceType type)
    2260          716 :     : manager_(manager)
    2261          716 :     , type_(type)
    2262              : {
    2263          716 :     auto streamId = (unsigned) type;
    2264         1432 :     if (streamId >= manager_.pimpl_->audioStreamUsers_.size())
    2265            0 :         throw std::invalid_argument("Invalid audio device type");
    2266          716 :     if (manager_.pimpl_->audioStreamUsers_[streamId]++ == 0) {
    2267          249 :         if (auto layer = manager_.getAudioDriver())
    2268          249 :             layer->startStream(type);
    2269              :     }
    2270          716 : }
    2271              : 
    2272            0 : AudioDeviceGuard::AudioDeviceGuard(Manager& manager, const std::string& captureDevice)
    2273            0 :     : manager_(manager)
    2274            0 :     , type_(AudioDeviceType::CAPTURE)
    2275            0 :     , captureDevice_(captureDevice)
    2276              : {
    2277            0 :     std::lock_guard lock(manager_.pimpl_->audioDeviceUsersMutex_);
    2278            0 :     auto& users = manager_.pimpl_->audioDeviceUsers_[captureDevice];
    2279            0 :     if (users++ == 0) {
    2280            0 :         if (auto layer = manager_.getAudioDriver()) {
    2281            0 :             layer->startCaptureStream(captureDevice);
    2282            0 :         }
    2283              :     }
    2284            0 : }
    2285              : 
    2286         1432 : AudioDeviceGuard::~AudioDeviceGuard()
    2287              : {
    2288          716 :     if (captureDevice_.empty()) {
    2289          716 :         auto streamId = (unsigned) type_;
    2290          716 :         if (--manager_.pimpl_->audioStreamUsers_[streamId] == 0) {
    2291          249 :             if (auto layer = manager_.getAudioDriver())
    2292          249 :                 layer->stopStream(type_);
    2293              :         }
    2294              :     } else {
    2295            0 :         std::lock_guard lock(manager_.pimpl_->audioDeviceUsersMutex_);
    2296            0 :         auto it = manager_.pimpl_->audioDeviceUsers_.find(captureDevice_);
    2297            0 :         if (it != manager_.pimpl_->audioDeviceUsers_.end()) {
    2298            0 :             if (--it->second == 0) {
    2299            0 :                 if (auto layer = manager_.getAudioDriver())
    2300            0 :                     layer->stopCaptureStream(captureDevice_);
    2301            0 :                 manager_.pimpl_->audioDeviceUsers_.erase(it);
    2302              :             }
    2303              :         }
    2304            0 :     }
    2305          716 : }
    2306              : 
    2307              : bool
    2308            0 : Manager::getIsAlwaysRecording() const
    2309              : {
    2310            0 :     return audioPreference.getIsAlwaysRecording();
    2311              : }
    2312              : 
    2313              : void
    2314            6 : Manager::setIsAlwaysRecording(bool isAlwaysRec)
    2315              : {
    2316            6 :     audioPreference.setIsAlwaysRecording(isAlwaysRec);
    2317            6 :     saveConfig();
    2318            6 : }
    2319              : 
    2320              : bool
    2321           11 : Manager::toggleRecordingCall(const std::string& accountId, const std::string& id)
    2322              : {
    2323           11 :     bool result = false;
    2324           11 :     if (auto account = getAccount(accountId)) {
    2325           11 :         std::shared_ptr<Recordable> rec;
    2326           11 :         if (auto conf = account->getConference(id)) {
    2327            2 :             JAMI_DEBUG("[conf:{}] Toggling recording", id);
    2328            2 :             rec = conf;
    2329            9 :         } else if (auto call = account->getCall(id)) {
    2330            9 :             JAMI_DEBUG("[call:{}] Toggling recording", id);
    2331            9 :             rec = call;
    2332              :         } else {
    2333            0 :             JAMI_ERROR("Unable to find recordable instance {}", id);
    2334            0 :             return false;
    2335           20 :         }
    2336           11 :         result = rec->toggleRecording();
    2337           11 :         emitSignal<libjami::CallSignal::RecordPlaybackFilepath>(id, rec->getPath());
    2338           11 :         emitSignal<libjami::CallSignal::RecordingStateChanged>(id, result);
    2339           22 :     }
    2340           11 :     return result;
    2341              : }
    2342              : 
    2343              : bool
    2344            0 : Manager::startRecordedFilePlayback(const std::string& filepath)
    2345              : {
    2346            0 :     JAMI_DEBUG("[audio] Start recorded file playback: {}", filepath);
    2347              : 
    2348              :     {
    2349            0 :         std::lock_guard lock(pimpl_->audioLayerMutex_);
    2350              : 
    2351            0 :         if (not pimpl_->audiodriver_) {
    2352            0 :             JAMI_ERROR("[audio] No audio layer for recorded file playback");
    2353            0 :             return false;
    2354              :         }
    2355              : 
    2356            0 :         auto oldGuard = std::move(pimpl_->toneDeviceGuard_);
    2357            0 :         pimpl_->toneDeviceGuard_ = startAudioStream(AudioDeviceType::RINGTONE);
    2358            0 :         auto format = pimpl_->audiodriver_->getFormat();
    2359            0 :         pimpl_->toneCtrl_.setSampleRate(format.sample_rate, format.sampleFormat);
    2360            0 :     }
    2361              : 
    2362            0 :     return pimpl_->toneCtrl_.setAudioFile(filepath);
    2363              : }
    2364              : 
    2365              : void
    2366            0 : Manager::recordingPlaybackSeek(const double value)
    2367              : {
    2368            0 :     pimpl_->toneCtrl_.seek(value);
    2369            0 : }
    2370              : 
    2371              : void
    2372            0 : Manager::stopRecordedFilePlayback()
    2373              : {
    2374            0 :     JAMI_DEBUG("[audio] Stop recorded file playback");
    2375              : 
    2376            0 :     pimpl_->toneCtrl_.stopAudioFile();
    2377            0 :     pimpl_->toneDeviceGuard_.reset();
    2378            0 : }
    2379              : 
    2380              : void
    2381            0 : Manager::setHistoryLimit(int days)
    2382              : {
    2383            0 :     JAMI_DEBUG("[config] Set history limit to {} days", days);
    2384            0 :     preferences.setHistoryLimit(days);
    2385            0 :     saveConfig();
    2386            0 : }
    2387              : 
    2388              : int
    2389            0 : Manager::getHistoryLimit() const
    2390              : {
    2391            0 :     return preferences.getHistoryLimit();
    2392              : }
    2393              : 
    2394              : void
    2395            0 : Manager::setRingingTimeout(std::chrono::seconds timeout)
    2396              : {
    2397            0 :     JAMI_DEBUG("[config] Set ringing timeout to {} seconds", timeout);
    2398            0 :     preferences.setRingingTimeout(timeout);
    2399            0 :     saveConfig();
    2400            0 : }
    2401              : 
    2402              : std::chrono::seconds
    2403          114 : Manager::getRingingTimeout() const
    2404              : {
    2405          114 :     return preferences.getRingingTimeout();
    2406              : }
    2407              : 
    2408              : bool
    2409            0 : Manager::setAudioManager(const std::string& api)
    2410              : {
    2411              :     {
    2412            0 :         std::lock_guard lock(pimpl_->audioLayerMutex_);
    2413              : 
    2414            0 :         if (not pimpl_->audiodriver_)
    2415            0 :             return false;
    2416              : 
    2417            0 :         if (api == audioPreference.getAudioApi()) {
    2418            0 :             JAMI_DEBUG("[audio] Audio manager '{}' already in use", api);
    2419            0 :             return true;
    2420              :         }
    2421            0 :     }
    2422              : 
    2423              :     {
    2424            0 :         std::lock_guard lock(pimpl_->audioLayerMutex_);
    2425            0 :         audioPreference.setAudioApi(api);
    2426            0 :         pimpl_->audiodriver_.reset();
    2427            0 :         pimpl_->initAudioDriver();
    2428            0 :     }
    2429              : 
    2430            0 :     saveConfig();
    2431              : 
    2432              :     // ensure that we completed the transition (i.e. no fallback was used)
    2433            0 :     return api == audioPreference.getAudioApi();
    2434              : }
    2435              : 
    2436              : std::string
    2437            0 : Manager::getAudioManager() const
    2438              : {
    2439            0 :     return audioPreference.getAudioApi();
    2440              : }
    2441              : 
    2442              : int
    2443            0 : Manager::getAudioInputDeviceIndex(const std::string& name)
    2444              : {
    2445            0 :     std::lock_guard lock(pimpl_->audioLayerMutex_);
    2446              : 
    2447            0 :     if (not pimpl_->audiodriver_) {
    2448            0 :         JAMI_ERROR("[audio] Uninitialized audio layer");
    2449            0 :         return 0;
    2450              :     }
    2451              : 
    2452            0 :     return pimpl_->audiodriver_->getAudioDeviceIndex(name, AudioDeviceType::CAPTURE);
    2453            0 : }
    2454              : 
    2455              : int
    2456            0 : Manager::getAudioOutputDeviceIndex(const std::string& name)
    2457              : {
    2458            0 :     std::lock_guard lock(pimpl_->audioLayerMutex_);
    2459              : 
    2460            0 :     if (not pimpl_->audiodriver_) {
    2461            0 :         JAMI_ERROR("[audio] Uninitialized audio layer");
    2462            0 :         return 0;
    2463              :     }
    2464              : 
    2465            0 :     return pimpl_->audiodriver_->getAudioDeviceIndex(name, AudioDeviceType::PLAYBACK);
    2466            0 : }
    2467              : 
    2468              : std::string
    2469            0 : Manager::getCurrentAudioOutputPlugin() const
    2470              : {
    2471            0 :     return audioPreference.getAlsaPlugin();
    2472              : }
    2473              : 
    2474              : std::string
    2475            0 : Manager::getNoiseSuppressState() const
    2476              : {
    2477            0 :     return audioPreference.getNoiseReduce();
    2478              : }
    2479              : 
    2480              : void
    2481            0 : Manager::setNoiseSuppressState(const std::string& state)
    2482              : {
    2483            0 :     audioPreference.setNoiseReduce(state);
    2484            0 :     saveConfig();
    2485            0 : }
    2486              : 
    2487              : std::string
    2488            0 : Manager::getEchoCancellationState() const
    2489              : {
    2490            0 :     return audioPreference.getEchoCanceller();
    2491              : }
    2492              : 
    2493              : void
    2494            0 : Manager::setEchoCancellationState(const std::string& state)
    2495              : {
    2496            0 :     audioPreference.setEchoCancel(state);
    2497            0 :     saveConfig();
    2498            0 : }
    2499              : 
    2500              : bool
    2501            0 : Manager::getVoiceActivityDetectionState() const
    2502              : {
    2503            0 :     return audioPreference.getVadEnabled();
    2504              : }
    2505              : 
    2506              : void
    2507            0 : Manager::setVoiceActivityDetectionState(bool state)
    2508              : {
    2509            0 :     audioPreference.setVad(state);
    2510            0 :     saveConfig();
    2511            0 : }
    2512              : 
    2513              : bool
    2514            0 : Manager::isAGCEnabled() const
    2515              : {
    2516            0 :     return audioPreference.isAGCEnabled();
    2517              : }
    2518              : 
    2519              : void
    2520            0 : Manager::setAGCState(bool state)
    2521              : {
    2522            0 :     audioPreference.setAGCState(state);
    2523            0 :     saveConfig();
    2524            0 : }
    2525              : 
    2526              : /**
    2527              :  * Initialization: Main Thread
    2528              :  */
    2529              : void
    2530           39 : Manager::ManagerPimpl::initAudioDriver()
    2531              : {
    2532           39 :     audiodriver_.reset(base_.audioPreference.createAudioLayer());
    2533           39 :     constexpr std::array<AudioDeviceType, 3> TYPES {AudioDeviceType::CAPTURE,
    2534              :                                                     AudioDeviceType::PLAYBACK,
    2535              :                                                     AudioDeviceType::RINGTONE};
    2536          156 :     for (const auto& type : TYPES)
    2537          117 :         if (audioStreamUsers_[(unsigned) type])
    2538            0 :             audiodriver_->startStream(type);
    2539           39 : }
    2540              : 
    2541              : // Internal helper method
    2542              : void
    2543          113 : Manager::ManagerPimpl::stripSipPrefix(Call& incomCall)
    2544              : {
    2545              :     // strip sip: which is not required and causes confusion with IP-to-IP calls
    2546              :     // when placing new call from history.
    2547          113 :     std::string peerNumber(incomCall.getPeerNumber());
    2548              : 
    2549          113 :     const char SIP_PREFIX[] = "sip:";
    2550          113 :     size_t startIndex = peerNumber.find(SIP_PREFIX);
    2551              : 
    2552          113 :     if (startIndex != std::string::npos)
    2553            0 :         incomCall.setPeerNumber(peerNumber.substr(startIndex + sizeof(SIP_PREFIX) - 1));
    2554          113 : }
    2555              : 
    2556              : // Internal helper method
    2557              : void
    2558          113 : Manager::ManagerPimpl::processIncomingCall(const std::string& accountId, Call& incomCall)
    2559              : {
    2560          113 :     base_.stopTone();
    2561              : 
    2562          113 :     auto incomCallId = incomCall.getCallId();
    2563          113 :     auto currentCall = base_.getCurrentCall();
    2564              : 
    2565          113 :     auto account = incomCall.getAccount().lock();
    2566          113 :     if (!account) {
    2567            0 :         JAMI_ERROR("[call:{}] No account detected", incomCallId);
    2568            0 :         return;
    2569              :     }
    2570              : 
    2571          113 :     auto username = incomCall.toUsername();
    2572          113 :     if (account->getAccountType() == ACCOUNT_TYPE_JAMI && username.find('/') != std::string::npos) {
    2573              :         // Avoid to do heavy stuff in SIPVoIPLink's transaction_request_cb
    2574           10 :         dht::ThreadPool::io().run([account, incomCallId, username]() {
    2575           10 :             if (auto jamiAccount = std::dynamic_pointer_cast<JamiAccount>(account))
    2576           10 :                 jamiAccount->handleIncomingConversationCall(incomCallId, username);
    2577           10 :         });
    2578           10 :         return;
    2579              :     }
    2580              : 
    2581          103 :     auto const& mediaList = MediaAttribute::mediaAttributesToMediaMaps(incomCall.getMediaAttributeList());
    2582              : 
    2583          103 :     if (mediaList.empty())
    2584            0 :         JAMI_WARNING("Incoming call {} has an empty media list", incomCallId);
    2585              : 
    2586          103 :     JAMI_DEBUG("Incoming call {} on account {} with {} media", incomCallId, accountId, mediaList.size());
    2587              : 
    2588          103 :     emitSignal<libjami::CallSignal::IncomingCall>(accountId, incomCallId, incomCall.getPeerNumber(), mediaList);
    2589              : 
    2590          103 :     if (not base_.hasCurrentCall()) {
    2591           62 :         incomCall.setState(Call::ConnectionState::RINGING);
    2592              : #if !(defined(TARGET_OS_IOS) && TARGET_OS_IOS)
    2593           62 :         if (not account->isRendezVous())
    2594           61 :             base_.playRingtone(accountId);
    2595              : #endif
    2596              :     } else {
    2597           41 :         if (account->isDenySecondCallEnabled()) {
    2598            0 :             base_.refuseCall(account->getAccountID(), incomCallId);
    2599            0 :             return;
    2600              :         }
    2601              :     }
    2602              : 
    2603          103 :     addWaitingCall(incomCallId);
    2604              : 
    2605          103 :     if (account->isRendezVous()) {
    2606            1 :         dht::ThreadPool::io().run([this, account, incomCall = incomCall.shared_from_this()] {
    2607            1 :             base_.acceptCall(*incomCall);
    2608              : 
    2609            2 :             for (const auto& callId : account->getCallList()) {
    2610            1 :                 if (auto call = account->getCall(callId)) {
    2611            1 :                     if (call->getState() != Call::CallState::ACTIVE)
    2612            0 :                         continue;
    2613            1 :                     if (call != incomCall) {
    2614            0 :                         if (auto conf = call->getConference()) {
    2615            0 :                             base_.addSubCall(*incomCall, *conf);
    2616              :                         } else {
    2617            0 :                             base_.joinParticipant(account->getAccountID(),
    2618            0 :                                                   incomCall->getCallId(),
    2619            0 :                                                   account->getAccountID(),
    2620              :                                                   call->getCallId(),
    2621              :                                                   false);
    2622            0 :                         }
    2623            0 :                         return;
    2624              :                     }
    2625            1 :                 }
    2626            1 :             }
    2627              : 
    2628              :             // First call
    2629            1 :             auto conf = std::make_shared<Conference>(account);
    2630            1 :             account->attach(conf);
    2631            1 :             emitSignal<libjami::CallSignal::ConferenceCreated>(account->getAccountID(), "", conf->getConfId());
    2632              : 
    2633              :             // Bind calls according to their state
    2634            1 :             bindCallToConference(*incomCall, *conf);
    2635            1 :             conf->detachHost();
    2636            2 :             emitSignal<libjami::CallSignal::ConferenceChanged>(account->getAccountID(),
    2637            1 :                                                                conf->getConfId(),
    2638              :                                                                conf->getStateStr());
    2639            1 :         });
    2640          102 :     } else if (autoAnswer_ || account->isAutoAnswerEnabled()) {
    2641            8 :         dht::ThreadPool::io().run([this, incomCall = incomCall.shared_from_this()] { base_.acceptCall(*incomCall); });
    2642           98 :     } else if (currentCall && currentCall->getCallId() != incomCallId) {
    2643              :         // Test if already calling this person
    2644           94 :         auto peerNumber = incomCall.getPeerNumber();
    2645           94 :         auto currentPeerNumber = currentCall->getPeerNumber();
    2646          376 :         string_replace(peerNumber, "@ring.dht", "");
    2647          282 :         string_replace(currentPeerNumber, "@ring.dht", "");
    2648           94 :         if (currentCall->getAccountId() == account->getAccountID() && currentPeerNumber == peerNumber) {
    2649            0 :             auto answerToCall = false;
    2650            0 :             auto downgradeToAudioOnly = currentCall->isAudioOnly() != incomCall.isAudioOnly();
    2651            0 :             if (downgradeToAudioOnly)
    2652              :                 // Accept the incoming audio only
    2653            0 :                 answerToCall = incomCall.isAudioOnly();
    2654              :             else
    2655              :                 // Accept the incoming call from the higher id number
    2656            0 :                 answerToCall = (account->getUsername().compare(peerNumber) < 0);
    2657              : 
    2658            0 :             if (answerToCall) {
    2659            0 :                 runOnMainThread([accountId = currentCall->getAccountId(),
    2660            0 :                                  currentCallID = currentCall->getCallId(),
    2661              :                                  incomCall = incomCall.shared_from_this()] {
    2662            0 :                     auto& mgr = Manager::instance();
    2663            0 :                     mgr.acceptCall(*incomCall);
    2664            0 :                     mgr.hangupCall(accountId, currentCallID);
    2665            0 :                 });
    2666              :             }
    2667              :         }
    2668           94 :     }
    2669          143 : }
    2670              : 
    2671              : AudioFormat
    2672          114 : Manager::hardwareAudioFormatChanged(AudioFormat format)
    2673              : {
    2674          114 :     return audioFormatUsed(format);
    2675              : }
    2676              : 
    2677              : AudioFormat
    2678          114 : Manager::audioFormatUsed(AudioFormat format)
    2679              : {
    2680          114 :     AudioFormat currentFormat = pimpl_->ringbufferpool_->getInternalAudioFormat();
    2681          114 :     if (currentFormat == format)
    2682          114 :         return format;
    2683              : 
    2684            0 :     JAMI_DEBUG("Audio format changed: {} → {}", currentFormat.toString(), format.toString());
    2685              : 
    2686            0 :     pimpl_->ringbufferpool_->setInternalAudioFormat(format);
    2687            0 :     pimpl_->toneCtrl_.setSampleRate(format.sample_rate, format.sampleFormat);
    2688            0 :     pimpl_->dtmfKey_.reset(new DTMF(format.sample_rate, format.sampleFormat));
    2689              : 
    2690            0 :     return format;
    2691              : }
    2692              : 
    2693              : void
    2694            0 : Manager::setAccountsOrder(const std::string& order)
    2695              : {
    2696            0 :     JAMI_LOG("Set accounts order: {}", order);
    2697            0 :     preferences.setAccountOrder(order);
    2698            0 :     saveConfig();
    2699            0 :     emitSignal<libjami::ConfigurationSignal::AccountsChanged>();
    2700            0 : }
    2701              : 
    2702              : std::vector<std::string>
    2703         4622 : Manager::getAccountList() const
    2704              : {
    2705              :     // Concatenate all account pointers in a single map
    2706         4622 :     std::vector<std::string> v;
    2707         4622 :     v.reserve(accountCount());
    2708        14397 :     for (const auto& account : getAllAccounts()) {
    2709         9775 :         v.emplace_back(account->getAccountID());
    2710         4622 :     }
    2711              : 
    2712         4622 :     return v;
    2713            0 : }
    2714              : 
    2715              : std::map<std::string, std::string>
    2716            2 : Manager::getAccountDetails(const std::string& accountID) const
    2717              : {
    2718            2 :     const auto account = getAccount(accountID);
    2719              : 
    2720            2 :     if (account) {
    2721            2 :         return account->getAccountDetails();
    2722              :     } else {
    2723            0 :         JAMI_ERROR("[account:{}] Unable to get account details on nonexistent account", accountID);
    2724              :         // return an empty map since unable to throw an exception to D-Bus
    2725            0 :         return {};
    2726              :     }
    2727            2 : }
    2728              : 
    2729              : std::map<std::string, std::string>
    2730            2 : Manager::getVolatileAccountDetails(const std::string& accountID) const
    2731              : {
    2732            2 :     const auto account = getAccount(accountID);
    2733              : 
    2734            2 :     if (account) {
    2735            2 :         return account->getVolatileAccountDetails();
    2736              :     } else {
    2737            0 :         JAMI_ERROR("[account:{}] Unable to get volatile account details on nonexistent account", accountID);
    2738            0 :         return {};
    2739              :     }
    2740            2 : }
    2741              : 
    2742              : void
    2743           18 : Manager::setAccountDetails(const std::string& accountID, const std::map<std::string, std::string>& details)
    2744              : {
    2745           18 :     JAMI_DEBUG("[account:{}] Set account details", accountID);
    2746              : 
    2747           18 :     auto account = getAccount(accountID);
    2748           18 :     if (not account) {
    2749            0 :         JAMI_ERROR("[account:{}] Unable to find account", accountID);
    2750            0 :         return;
    2751              :     }
    2752              : 
    2753              :     // Ignore if nothing has changed
    2754           18 :     if (details == account->getAccountDetails())
    2755            0 :         return;
    2756              : 
    2757              :     // Unregister before modifying any account information
    2758           18 :     account->doUnregister();
    2759              : 
    2760           18 :     account->setAccountDetails(details);
    2761              : 
    2762           18 :     if (account->isUsable())
    2763           18 :         account->doRegister();
    2764              :     else
    2765            0 :         account->doUnregister();
    2766              : 
    2767              :     // Update account details to the client side
    2768           18 :     emitSignal<libjami::ConfigurationSignal::AccountDetailsChanged>(accountID, details);
    2769           18 : }
    2770              : 
    2771              : std::mt19937_64
    2772         3819 : Manager::getSeededRandomEngine()
    2773              : {
    2774         3819 :     std::lock_guard l(randMutex_);
    2775         7638 :     return dht::crypto::getDerivedRandomEngine(rand_);
    2776         3819 : }
    2777              : 
    2778              : std::string
    2779         1034 : Manager::getNewAccountId()
    2780              : {
    2781         1034 :     std::string random_id;
    2782              :     do {
    2783         1034 :         random_id = to_hex_string(std::uniform_int_distribution<uint64_t>()(rand_));
    2784         1034 :     } while (getAccount(random_id));
    2785         1034 :     return random_id;
    2786            0 : }
    2787              : 
    2788              : std::string
    2789         1038 : Manager::addAccount(const std::map<std::string, std::string>& details, const std::string& accountId)
    2790              : {
    2791              :     /** @todo Deal with both the accountMap_ and the Configuration */
    2792         1038 :     auto newAccountID = accountId.empty() ? getNewAccountId() : accountId;
    2793              : 
    2794              :     // Get the type
    2795         1038 :     std::string_view accountType;
    2796         1038 :     auto typeIt = details.find(Conf::CONFIG_ACCOUNT_TYPE);
    2797         1038 :     if (typeIt != details.end())
    2798         1038 :         accountType = typeIt->second;
    2799              :     else
    2800            0 :         accountType = AccountFactory::DEFAULT_ACCOUNT_TYPE;
    2801              : 
    2802         1038 :     JAMI_DEBUG("Adding account {:s} with type {}", newAccountID, accountType);
    2803              : 
    2804         1038 :     auto newAccount = accountFactory.createAccount(accountType, newAccountID);
    2805         1038 :     if (!newAccount) {
    2806            0 :         JAMI_ERROR("Unknown {:s} param when calling addAccount(): {:s}", Conf::CONFIG_ACCOUNT_TYPE, accountType);
    2807            0 :         return "";
    2808              :     }
    2809              : 
    2810         1038 :     newAccount->setAccountDetails(details);
    2811         1038 :     saveConfig(newAccount);
    2812         1038 :     newAccount->doRegister();
    2813              : 
    2814         1038 :     preferences.addAccount(newAccountID);
    2815         1038 :     if (accountType != ACCOUNT_TYPE_SIP)
    2816         1011 :         markAccountPending(newAccountID);
    2817              : 
    2818         1038 :     emitSignal<libjami::ConfigurationSignal::AccountsChanged>();
    2819              : 
    2820         1038 :     return newAccountID;
    2821         1038 : }
    2822              : 
    2823              : void
    2824         1011 : Manager::markAccountPending(const std::string& accountId)
    2825              : {
    2826         1011 :     if (preferences.addPendingAccountId(accountId))
    2827         1011 :         saveConfig();
    2828         1011 : }
    2829              : 
    2830              : void
    2831         1031 : Manager::markAccountReady(const std::string& accountId)
    2832              : {
    2833         1031 :     if (preferences.removePendingAccountId(accountId))
    2834         1009 :         saveConfig();
    2835         1031 : }
    2836              : 
    2837              : void
    2838         1038 : Manager::removeAccount(const std::string& accountID, bool flush)
    2839              : {
    2840              :     // Get it down and dying
    2841         1038 :     if (const auto& remAccount = getAccount(accountID)) {
    2842         1038 :         if (auto acc = std::dynamic_pointer_cast<JamiAccount>(remAccount)) {
    2843         1011 :             acc->hangupCalls();
    2844         1038 :         }
    2845         1038 :         remAccount->doUnregister(true);
    2846         1038 :         if (flush)
    2847         1038 :             remAccount->flush();
    2848         1038 :         accountFactory.removeAccount(*remAccount);
    2849         1038 :     }
    2850              : 
    2851         1038 :     preferences.removeAccount(accountID);
    2852         1038 :     preferences.removePendingAccountId(accountID);
    2853              : 
    2854         1038 :     saveConfig();
    2855              : 
    2856         1038 :     emitSignal<libjami::ConfigurationSignal::AccountsChanged>();
    2857         1038 : }
    2858              : 
    2859              : void
    2860            3 : Manager::removeAccounts()
    2861              : {
    2862            3 :     for (const auto& acc : getAccountList())
    2863            3 :         removeAccount(acc);
    2864            3 : }
    2865              : 
    2866              : std::vector<std::string_view>
    2867         4800 : Manager::loadAccountOrder() const
    2868              : {
    2869         4800 :     return split_string(preferences.getAccountOrder(), '/');
    2870              : }
    2871              : 
    2872              : int
    2873           42 : Manager::loadAccountMap(const YAML::Node& node)
    2874              : {
    2875           42 :     int errorCount = 0;
    2876              :     try {
    2877              :         // build preferences
    2878           42 :         preferences.unserialize(node);
    2879           36 :         voipPreferences.unserialize(node);
    2880           36 :         audioPreference.unserialize(node);
    2881              : #ifdef ENABLE_VIDEO
    2882           36 :         videoPreferences.unserialize(node);
    2883              : #endif
    2884              : #ifdef ENABLE_PLUGIN
    2885           36 :         pluginPreferences.unserialize(node);
    2886              : #endif
    2887            6 :     } catch (const YAML::Exception& e) {
    2888            6 :         JAMI_ERROR("[config] Preferences unserialize YAML exception: {}", e.what());
    2889            6 :         ++errorCount;
    2890            6 :     } catch (const std::exception& e) {
    2891            0 :         JAMI_ERROR("[config] Preferences unserialize exception: {}", e.what());
    2892            0 :         ++errorCount;
    2893            0 :     } catch (...) {
    2894            0 :         JAMI_ERROR("[config] Preferences unserialize unknown exception");
    2895            0 :         ++errorCount;
    2896            0 :     }
    2897              : 
    2898           42 :     pimpl_->systemCodecContainer_ = std::make_shared<SystemCodecContainer>();
    2899              : #ifdef ENABLE_VIDEO
    2900           42 :     pimpl_->systemCodecContainer_->init(videoPreferences.getEncodingAccelerated());
    2901              : #else
    2902              :     pimpl_->systemCodecContainer_->init(false);
    2903              : #endif
    2904              : 
    2905              :     // load saved preferences for IP2IP account from configuration file
    2906           42 :     const auto& accountList = node["accounts"];
    2907              : 
    2908           42 :     for (auto& a : accountList) {
    2909            0 :         pimpl_->loadAccount(a, errorCount);
    2910            0 :     }
    2911              : 
    2912           42 :     const auto& accountBaseDir = fileutils::get_data_dir();
    2913           42 :     auto dirs = dhtnet::fileutils::readDirectory(accountBaseDir);
    2914              : 
    2915           42 :     std::condition_variable cv;
    2916           42 :     std::mutex lock;
    2917           42 :     size_t remaining {0};
    2918           42 :     std::unique_lock l(lock);
    2919           84 :     for (const auto& dir : dirs) {
    2920           42 :         if (accountFactory.hasAccount<JamiAccount>(dir)) {
    2921            0 :             continue;
    2922              :         }
    2923              : 
    2924           42 :         if (preferences.isAccountPending(dir)) {
    2925            0 :             JAMI_LOG("[account:{}] Removing pending account from disk", dir);
    2926            0 :             removeAccount(dir, true);
    2927            0 :             pimpl_->cleanupAccountStorage(dir);
    2928            0 :             continue;
    2929              :         }
    2930              : 
    2931           42 :         remaining++;
    2932           84 :         dht::ThreadPool::computation().run(
    2933           84 :             [this, dir, &cv, &remaining, &lock, configFile = accountBaseDir / dir / "config.yml"] {
    2934           42 :                 if (std::filesystem::is_regular_file(configFile)) {
    2935              :                     try {
    2936            0 :                         auto configNode = YAML::LoadFile(configFile.string());
    2937            0 :                         if (auto a = accountFactory.createAccount(JamiAccount::ACCOUNT_TYPE, dir)) {
    2938            0 :                             auto config = a->buildConfig();
    2939            0 :                             config->unserialize(configNode);
    2940            0 :                             a->setConfig(std::move(config));
    2941            0 :                         }
    2942            0 :                     } catch (const std::exception& e) {
    2943            0 :                         JAMI_ERROR("[account:{}] Unable to import account: {}", dir, e.what());
    2944            0 :                     }
    2945              :                 }
    2946           42 :                 std::lock_guard l(lock);
    2947           42 :                 remaining--;
    2948           42 :                 cv.notify_one();
    2949           42 :             });
    2950              :     }
    2951          126 :     cv.wait(l, [&remaining] { return remaining == 0; });
    2952              : 
    2953              : #ifdef ENABLE_PLUGIN
    2954           42 :     if (pluginPreferences.getPluginsEnabled()) {
    2955           11 :         jami::Manager::instance().getJamiPluginManager().loadPlugins();
    2956              :     }
    2957              : #endif
    2958              : 
    2959           42 :     return errorCount;
    2960           42 : }
    2961              : 
    2962              : std::vector<std::string>
    2963            0 : Manager::getCallList() const
    2964              : {
    2965            0 :     std::vector<std::string> results;
    2966            0 :     for (const auto& call : callFactory.getAllCalls()) {
    2967            0 :         if (!call->isSubcall())
    2968            0 :             results.push_back(call->getCallId());
    2969            0 :     }
    2970            0 :     return results;
    2971            0 : }
    2972              : 
    2973              : void
    2974           39 : Manager::registerAccounts()
    2975              : {
    2976           39 :     for (auto& a : getAllAccounts()) {
    2977            0 :         if (a->isUsable())
    2978            0 :             a->doRegister();
    2979           39 :     }
    2980           39 : }
    2981              : 
    2982              : void
    2983          290 : Manager::sendRegister(const std::string& accountID, bool enable)
    2984              : {
    2985          290 :     const auto acc = getAccount(accountID);
    2986          290 :     if (!acc)
    2987            0 :         return;
    2988              : 
    2989          290 :     acc->setEnabled(enable);
    2990          290 :     saveConfig(acc);
    2991              : 
    2992          290 :     if (acc->isEnabled()) {
    2993           63 :         acc->doRegister();
    2994              :     } else
    2995          227 :         acc->doUnregister();
    2996          290 : }
    2997              : 
    2998              : uint64_t
    2999            0 : Manager::sendTextMessage(const std::string& accountID,
    3000              :                          const std::string& to,
    3001              :                          const std::map<std::string, std::string>& payloads,
    3002              :                          bool fromPlugin,
    3003              :                          bool onlyConnected)
    3004              : {
    3005            0 :     if (const auto acc = getAccount(accountID)) {
    3006              :         try {
    3007              : #ifdef ENABLE_PLUGIN // modifies send message
    3008            0 :             auto& pluginChatManager = getJamiPluginManager().getChatServicesManager();
    3009            0 :             if (pluginChatManager.hasHandlers()) {
    3010            0 :                 auto cm = std::make_shared<JamiMessage>(accountID, to, false, payloads, fromPlugin);
    3011            0 :                 pluginChatManager.publishMessage(cm);
    3012            0 :                 return acc->sendTextMessage(cm->peerId, "", cm->data, 0, onlyConnected);
    3013            0 :             } else
    3014              : #endif // ENABLE_PLUGIN
    3015            0 :                 return acc->sendTextMessage(to, "", payloads, 0, onlyConnected);
    3016            0 :         } catch (const std::exception& e) {
    3017            0 :             JAMI_ERROR("[account:{}] Exception during text message sending: {}", accountID, e.what());
    3018            0 :         }
    3019            0 :     }
    3020            0 :     return 0;
    3021              : }
    3022              : 
    3023              : int
    3024            0 : Manager::getMessageStatus(uint64_t) const
    3025              : {
    3026            0 :     JAMI_ERROR("Deprecated method. Please use status from message");
    3027            0 :     return 0;
    3028              : }
    3029              : 
    3030              : int
    3031            0 : Manager::getMessageStatus(const std::string&, uint64_t) const
    3032              : {
    3033            0 :     JAMI_ERROR("Deprecated method. Please use status from message");
    3034            0 :     return 0;
    3035              : }
    3036              : 
    3037              : void
    3038            2 : Manager::setAccountActive(const std::string& accountID, bool active, bool shutdownConnections)
    3039              : {
    3040            2 :     const auto acc = getAccount(accountID);
    3041            2 :     if (!acc || acc->isActive() == active)
    3042            0 :         return;
    3043            2 :     acc->setActive(active);
    3044            2 :     if (acc->isEnabled()) {
    3045            2 :         if (active) {
    3046            1 :             acc->doRegister();
    3047              :         } else {
    3048            1 :             acc->doUnregister(shutdownConnections);
    3049              :         }
    3050              :     }
    3051            2 :     emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(accountID, acc->getVolatileAccountDetails());
    3052            2 : }
    3053              : 
    3054              : void
    3055            0 : Manager::loadAccountAndConversation(const std::string& accountId, bool loadAll, const std::string& convId)
    3056              : {
    3057            0 :     auto account = getAccount(accountId);
    3058            0 :     if (!account && !autoLoad) {
    3059              :         /*
    3060              :          With the LIBJAMI_FLAG_NO_AUTOLOAD flag active, accounts are not
    3061              :          automatically created during manager initialization, nor are
    3062              :          their configurations set or backed up. This is because account
    3063              :          creation triggers the initialization of the certStore. There why
    3064              :          account creation now occurs here in response to a received notification.
    3065              :          */
    3066            0 :         const auto& accountBaseDir = fileutils::get_data_dir();
    3067            0 :         auto configFile = accountBaseDir / accountId / "config.yml";
    3068              :         try {
    3069            0 :             if ((account = accountFactory.createAccount(JamiAccount::ACCOUNT_TYPE, accountId))) {
    3070            0 :                 account->enableAutoLoadConversations(false);
    3071            0 :                 auto configNode = YAML::LoadFile(configFile.string());
    3072            0 :                 auto config = account->buildConfig();
    3073            0 :                 config->unserialize(configNode);
    3074            0 :                 account->setConfig(std::move(config));
    3075            0 :             }
    3076            0 :         } catch (const std::runtime_error& e) {
    3077            0 :             JAMI_WARNING("[account:{}] Failed to load account: {}", accountId, e.what());
    3078            0 :             return;
    3079            0 :         }
    3080            0 :     }
    3081              : 
    3082            0 :     if (!account) {
    3083            0 :         JAMI_WARNING("[account:{}] Unable to load account", accountId);
    3084            0 :         return;
    3085              :     }
    3086              : 
    3087            0 :     if (auto jamiAcc = std::dynamic_pointer_cast<JamiAccount>(account)) {
    3088            0 :         jamiAcc->setActive(true);
    3089            0 :         jamiAcc->reloadContacts();
    3090            0 :         if (jamiAcc->isUsable())
    3091            0 :             jamiAcc->doRegister();
    3092            0 :         if (auto* convModule = jamiAcc->convModule()) {
    3093            0 :             convModule->reloadRequests();
    3094            0 :             if (loadAll) {
    3095            0 :                 convModule->loadConversations();
    3096            0 :             } else if (!convId.empty()) {
    3097            0 :                 jamiAcc->loadConversation(convId);
    3098              :             }
    3099              :         }
    3100            0 :     }
    3101            0 : }
    3102              : 
    3103              : std::shared_ptr<SystemCodecContainer>
    3104         1059 : Manager::getSystemCodecContainer() const
    3105              : {
    3106         1059 :     return pimpl_->systemCodecContainer_;
    3107              : }
    3108              : 
    3109              : std::shared_ptr<AudioLayer>
    3110          498 : Manager::getAudioDriver()
    3111              : {
    3112          498 :     return pimpl_->audiodriver_;
    3113              : }
    3114              : 
    3115              : std::shared_ptr<Call>
    3116          125 : Manager::newOutgoingCall(std::string_view toUrl,
    3117              :                          const std::string& accountId,
    3118              :                          const std::vector<libjami::MediaMap>& mediaList)
    3119              : {
    3120          125 :     auto account = getAccount(accountId);
    3121          125 :     if (not account) {
    3122            0 :         JAMI_WARNING("[account:{}] No account matches ID", accountId);
    3123            0 :         return {};
    3124              :     }
    3125              : 
    3126          125 :     if (not account->isUsable()) {
    3127            0 :         JAMI_WARNING("[account:{}] Account is unusable", accountId);
    3128            0 :         return {};
    3129              :     }
    3130              : 
    3131          125 :     return account->newOutgoingCall(toUrl, mediaList);
    3132          125 : }
    3133              : 
    3134              : #ifdef ENABLE_VIDEO
    3135              : std::shared_ptr<video::SinkClient>
    3136          286 : Manager::createSinkClient(const std::string& id, bool mixer)
    3137              : {
    3138          286 :     std::lock_guard lk(pimpl_->sinksMutex_);
    3139          286 :     auto& sinkRef = pimpl_->sinkMap_[id];
    3140          286 :     if (auto sink = sinkRef.lock())
    3141          286 :         return sink;
    3142          256 :     auto sink = std::make_shared<video::SinkClient>(id, mixer);
    3143          256 :     sinkRef = sink;
    3144          256 :     return sink;
    3145          286 : }
    3146              : 
    3147              : void
    3148          401 : Manager::createSinkClients(const std::string& callId,
    3149              :                            const ConfInfo& infos,
    3150              :                            const std::vector<std::shared_ptr<video::VideoFrameActiveWriter>>& videoStreams,
    3151              :                            std::map<std::string, std::shared_ptr<video::SinkClient>>& sinksMap,
    3152              :                            const std::string& accountId)
    3153              : {
    3154          401 :     auto account = accountId.empty() ? nullptr : getAccount<JamiAccount>(accountId);
    3155              : 
    3156          401 :     std::set<std::string> sinkIdsList {};
    3157          401 :     std::vector<std::pair<std::shared_ptr<video::SinkClient>, std::pair<int, int>>> newSinks;
    3158              : 
    3159              :     // create video sinks
    3160          401 :     std::unique_lock lk(pimpl_->sinksMutex_);
    3161         1412 :     for (const auto& participant : infos) {
    3162         1011 :         std::string sinkId = participant.sinkId;
    3163         1011 :         if (sinkId.empty()) {
    3164          192 :             sinkId = callId;
    3165          192 :             sinkId += string_remove_suffix(participant.uri, '@') + participant.device;
    3166              :         }
    3167         1011 :         if (participant.w && participant.h && !participant.videoMuted) {
    3168            8 :             auto& currentSinkW = pimpl_->sinkMap_[sinkId];
    3169           16 :             if (account && string_remove_suffix(participant.uri, '@') == account->getUsername()
    3170           16 :                 && participant.device == account->currentDeviceId()) {
    3171              :                 // This is a local sink that must already exist
    3172            8 :                 continue;
    3173              :             }
    3174            0 :             if (auto existingIt = sinksMap.find(sinkId); existingIt != sinksMap.end()) {
    3175            0 :                 existingIt->second->setCrop(participant.x, participant.y, participant.w, participant.h);
    3176            0 :                 currentSinkW = existingIt->second;
    3177            0 :                 sinkIdsList.emplace(sinkId);
    3178            0 :                 continue;
    3179              :             }
    3180            0 :             if (auto currentSink = currentSinkW.lock()) {
    3181              :                 // If sink exists, update it and ensure it is tracked in sinksMap
    3182            0 :                 currentSink->setCrop(participant.x, participant.y, participant.w, participant.h);
    3183            0 :                 newSinks.emplace_back(currentSink, std::make_pair(participant.w, participant.h));
    3184            0 :                 sinksMap.insert_or_assign(sinkId, std::move(currentSink));
    3185            0 :                 sinkIdsList.emplace(sinkId);
    3186            0 :                 continue;
    3187            0 :             }
    3188            0 :             auto newSink = std::make_shared<video::SinkClient>(sinkId, false);
    3189            0 :             currentSinkW = newSink;
    3190            0 :             newSink->setCrop(participant.x, participant.y, participant.w, participant.h);
    3191            0 :             newSinks.emplace_back(newSink, std::make_pair(participant.w, participant.h));
    3192            0 :             sinksMap.insert_or_assign(sinkId, std::move(newSink));
    3193            0 :             sinkIdsList.emplace(sinkId);
    3194            0 :         }
    3195         1011 :     }
    3196          401 :     lk.unlock();
    3197              : 
    3198              :     // remove unused video sinks
    3199          401 :     for (auto it = sinksMap.begin(); it != sinksMap.end();) {
    3200            0 :         if (sinkIdsList.find(it->first) == sinkIdsList.end()) {
    3201            0 :             for (auto& videoStream : videoStreams)
    3202            0 :                 videoStream->detach(it->second.get());
    3203            0 :             it->second->stop();
    3204            0 :             it = sinksMap.erase(it);
    3205              :         } else {
    3206            0 :             it++;
    3207              :         }
    3208              :     }
    3209              : 
    3210              :     // create new video sinks
    3211          401 :     for (const auto& [sink, size] : newSinks) {
    3212            0 :         if (sinkIdsList.find(sink->getId()) == sinkIdsList.end())
    3213            0 :             continue;
    3214            0 :         sink->start();
    3215            0 :         sink->setFrameSize(size.first, size.second);
    3216            0 :         for (auto& videoStream : videoStreams)
    3217            0 :             videoStream->attach(sink.get());
    3218              :     }
    3219          401 : }
    3220              : 
    3221              : std::shared_ptr<video::SinkClient>
    3222            2 : Manager::getSinkClient(const std::string& id)
    3223              : {
    3224            2 :     std::lock_guard lk(pimpl_->sinksMutex_);
    3225            2 :     const auto& iter = pimpl_->sinkMap_.find(id);
    3226            4 :     if (iter != std::end(pimpl_->sinkMap_))
    3227            0 :         if (auto sink = iter->second.lock())
    3228            0 :             return sink;
    3229            2 :     return nullptr;
    3230            2 : }
    3231              : #endif // ENABLE_VIDEO
    3232              : 
    3233              : RingBufferPool&
    3234        81569 : Manager::getRingBufferPool()
    3235              : {
    3236        81569 :     return *pimpl_->ringbufferpool_;
    3237              : }
    3238              : 
    3239              : bool
    3240            0 : Manager::hasAccount(const std::string& accountID)
    3241              : {
    3242            0 :     return accountFactory.hasAccount(accountID);
    3243              : }
    3244              : 
    3245              : const std::shared_ptr<dhtnet::IceTransportFactory>&
    3246         1184 : Manager::getIceTransportFactory()
    3247              : {
    3248         1184 :     return pimpl_->ice_tf_;
    3249              : }
    3250              : 
    3251              : VideoManager*
    3252         5199 : Manager::getVideoManager() const
    3253              : {
    3254         5199 :     return pimpl_->videoManager_.get();
    3255              : }
    3256              : 
    3257              : std::vector<libjami::Message>
    3258            0 : Manager::getLastMessages(const std::string& accountID, const uint64_t& base_timestamp)
    3259              : {
    3260            0 :     if (const auto acc = getAccount(accountID))
    3261            0 :         return acc->getLastMessages(base_timestamp);
    3262            0 :     return {};
    3263              : }
    3264              : 
    3265              : SIPVoIPLink&
    3266         4224 : Manager::sipVoIPLink() const
    3267              : {
    3268         4224 :     return *pimpl_->sipLink_;
    3269              : }
    3270              : 
    3271              : #ifdef ENABLE_PLUGIN
    3272              : JamiPluginManager&
    3273         4659 : Manager::getJamiPluginManager() const
    3274              : {
    3275         4659 :     return *pimpl_->jami_plugin_manager;
    3276              : }
    3277              : #endif
    3278              : 
    3279              : std::shared_ptr<dhtnet::ChannelSocket>
    3280         2319 : Manager::gitSocket(std::string_view accountId, std::string_view deviceId, std::string_view conversationId)
    3281              : {
    3282         2319 :     if (const auto acc = getAccount<JamiAccount>(accountId))
    3283         2319 :         if (auto* convModule = acc->convModule(true))
    3284         2319 :             return convModule->gitSocket(deviceId, conversationId);
    3285            0 :     return nullptr;
    3286              : }
    3287              : 
    3288              : std::map<std::string, std::string>
    3289            0 : Manager::getNearbyPeers(const std::string& accountID)
    3290              : {
    3291            0 :     if (const auto acc = getAccount<JamiAccount>(accountID))
    3292            0 :         return acc->getNearbyPeers();
    3293            0 :     return {};
    3294              : }
    3295              : 
    3296              : void
    3297            0 : Manager::setDefaultModerator(const std::string& accountID, const std::string& peerURI, bool state)
    3298              : {
    3299            0 :     auto acc = getAccount(accountID);
    3300            0 :     if (!acc) {
    3301            0 :         JAMI_ERROR("[account:{}] Failed to change default moderator: account not found", accountID);
    3302            0 :         return;
    3303              :     }
    3304              : 
    3305            0 :     if (state)
    3306            0 :         acc->addDefaultModerator(peerURI);
    3307              :     else
    3308            0 :         acc->removeDefaultModerator(peerURI);
    3309            0 :     saveConfig(acc);
    3310            0 : }
    3311              : 
    3312              : std::vector<std::string>
    3313            0 : Manager::getDefaultModerators(const std::string& accountID)
    3314              : {
    3315            0 :     auto acc = getAccount(accountID);
    3316            0 :     if (!acc) {
    3317            0 :         JAMI_ERROR("[account:{}] Failed to get default moderators: account not found", accountID);
    3318            0 :         return {};
    3319              :     }
    3320              : 
    3321            0 :     auto set = acc->getDefaultModerators();
    3322            0 :     return std::vector<std::string>(set.begin(), set.end());
    3323            0 : }
    3324              : 
    3325              : void
    3326            0 : Manager::enableLocalModerators(const std::string& accountID, bool isModEnabled)
    3327              : {
    3328            0 :     if (auto acc = getAccount(accountID))
    3329            0 :         acc->editConfig([&](AccountConfig& config) { config.localModeratorsEnabled = isModEnabled; });
    3330            0 : }
    3331              : 
    3332              : bool
    3333            0 : Manager::isLocalModeratorsEnabled(const std::string& accountID)
    3334              : {
    3335            0 :     auto acc = getAccount(accountID);
    3336            0 :     if (!acc) {
    3337            0 :         JAMI_ERROR("[account:{}] Failed to get local moderators: account not found", accountID);
    3338            0 :         return true; // Default value
    3339              :     }
    3340            0 :     return acc->isLocalModeratorsEnabled();
    3341            0 : }
    3342              : 
    3343              : void
    3344            0 : Manager::setAllModerators(const std::string& accountID, bool allModerators)
    3345              : {
    3346            0 :     if (auto acc = getAccount(accountID))
    3347            0 :         acc->editConfig([&](AccountConfig& config) { config.allModeratorsEnabled = allModerators; });
    3348            0 : }
    3349              : 
    3350              : bool
    3351            0 : Manager::isAllModerators(const std::string& accountID)
    3352              : {
    3353            0 :     auto acc = getAccount(accountID);
    3354            0 :     if (!acc) {
    3355            0 :         JAMI_ERROR("[account:{}] Failed to get all moderators: account not found", accountID);
    3356            0 :         return true; // Default value
    3357              :     }
    3358            0 :     return acc->isAllModerators();
    3359            0 : }
    3360              : 
    3361              : void
    3362         2319 : Manager::insertGitTransport(git_smart_subtransport* tr, std::unique_ptr<P2PSubTransport>&& sub)
    3363              : {
    3364         2319 :     std::lock_guard lk(pimpl_->gitTransportsMtx_);
    3365         2319 :     pimpl_->gitTransports_[tr] = std::move(sub);
    3366         2319 : }
    3367              : 
    3368              : void
    3369         2319 : Manager::eraseGitTransport(git_smart_subtransport* tr)
    3370              : {
    3371         2319 :     std::lock_guard lk(pimpl_->gitTransportsMtx_);
    3372         2319 :     pimpl_->gitTransports_.erase(tr);
    3373         2319 : }
    3374              : 
    3375              : dhtnet::tls::CertificateStore&
    3376        12511 : Manager::certStore(const std::string& accountId) const
    3377              : {
    3378        12511 :     if (const auto& account = getAccount<JamiAccount>(accountId)) {
    3379        25022 :         return account->certStore();
    3380        12511 :     }
    3381            0 :     throw std::runtime_error("No account found");
    3382              : }
    3383              : 
    3384              : } // namespace jami
        

Generated by: LCOV version 2.0-1