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