LCOV - code coverage report
Current view: top level - src - manager.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 894 1705 52.4 %
Date: 2024-04-19 19:18:04 Functions: 140 243 57.6 %

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

Generated by: LCOV version 1.14