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