LCOV - code coverage report
Current view: top level - src - manager.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 906 1707 53.1 %
Date: 2024-12-21 08:56:24 Functions: 145 255 56.9 %

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

Generated by: LCOV version 1.14