LCOV - code coverage report
Current view: top level - src - manager.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 47.1 % 1756 827
Test Date: 2026-07-29 09:02:12 Functions: 55.0 % 191 105

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

Generated by: LCOV version 2.0-1