LCOV - code coverage report
Current view: top level - foo/src - manager.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 892 1708 52.2 %
Date: 2025-10-16 08:11:43 Functions: 155 278 55.8 %

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

Generated by: LCOV version 1.14