LCOV - code coverage report
Current view: top level - foo/src - manager.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 853 1687 50.6 %
Date: 2025-12-18 10:07:43 Functions: 170 336 50.6 %

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

Generated by: LCOV version 1.14