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_->stop(); // make thread stop
916 }
917 if (pimpl_->ioContextRunner_.joinable())
918 pimpl_->ioContextRunner_.join();
919
920#if defined _MSC_VER
921 gnutls_global_deinit();
922#endif
923
924 } catch (const VoipLinkException& err) {
925 JAMI_ERR("%s", err.what());
926 }
927}
928
929void
930Manager::monitor(bool continuous)
931{
932 Logger::setMonitorLog(true);
933 JAMI_DBG("############## START MONITORING ##############");
934 JAMI_DBG("Using PJSIP version: %s for %s", pj_get_version(), PJ_OS_NAME);
935 JAMI_DBG("Using GnuTLS version: %s", gnutls_check_version(nullptr));
936 JAMI_DBG("Using OpenDHT version: %s", dht::version());
937
938#ifdef __linux__
939#if defined(__ANDROID__)
940#else
941 auto opened_files
942 = dhtnet::fileutils::readDirectory("/proc/" + std::to_string(getpid()) + "/fd").size();
943 JAMI_DBG("Opened files: %lu", opened_files);
944#endif
945#endif
946
947 for (const auto& call : callFactory.getAllCalls())
948 call->monitor();
949 for (const auto& account : getAllAccounts())
950 if (auto acc = std::dynamic_pointer_cast<JamiAccount>(account))
951 acc->monitor();
952 JAMI_DBG("############## END MONITORING ##############");
953 Logger::setMonitorLog(continuous);
954}
955
956std::vector<std::map<std::string, std::string>>
957Manager::getConnectionList(const std::string& accountId, const std::string& conversationId)
958{
959 std::vector<std::map<std::string, std::string>> connectionsList;
960
961 if (accountId.empty()) {
962 for (const auto& account : getAllAccounts<JamiAccount>()) {
963 if (account->getRegistrationState() != RegistrationState::INITIALIZING) {
964 const auto& cnl = account->getConnectionList(conversationId);
965 connectionsList.insert(connectionsList.end(), cnl.begin(), cnl.end());
966 }
967 }
968 } else {
969 auto account = getAccount(accountId);
970 if (account) {
971 if (auto acc = std::dynamic_pointer_cast<JamiAccount>(account)) {
972 if (acc->getRegistrationState() != RegistrationState::INITIALIZING) {
973 const auto& cnl = acc->getConnectionList(conversationId);
974 connectionsList.insert(connectionsList.end(), cnl.begin(), cnl.end());
975 }
976 }
977 }
978 }
979
980 return connectionsList;
981}
982
983std::vector<std::map<std::string, std::string>>
984Manager::getChannelList(const std::string& accountId, const std::string& connectionId)
985{
986 // if account id is empty, return all channels
987 // else return only for specific accountid
988 std::vector<std::map<std::string, std::string>> channelsList;
989
990 if (accountId.empty()) {
991 for (const auto& account : getAllAccounts<JamiAccount>()) {
992 if (account->getRegistrationState() != RegistrationState::INITIALIZING) {
993 // add to channelsList all channels for this account
994 const auto& cnl = account->getChannelList(connectionId);
995 channelsList.insert(channelsList.end(), cnl.begin(), cnl.end());
996 }
997 }
998
999 }
1000
1001 else {
1002 // get the jamiaccount for this accountid and return its channels
1003 auto account = getAccount(accountId);
1004 if (account) {
1005 if (auto acc = std::dynamic_pointer_cast<JamiAccount>(account)) {
1006 if (acc->getRegistrationState() != RegistrationState::INITIALIZING) {
1007 const auto& cnl = acc->getChannelList(connectionId);
1008 channelsList.insert(channelsList.end(), cnl.begin(), cnl.end());
1009 }
1010 }
1011 }
1012 }
1013
1014 return channelsList;
1015}
1016
1017bool
1018Manager::isCurrentCall(const Call& call) const
1019{
1020 return pimpl_->currentCall_ == call.getCallId();
1021}
1022
1023bool
1024Manager::hasCurrentCall() const
1025{
1026 for (const auto& call : callFactory.getAllCalls()) {
1027 if (!call->isSubcall() && call->getStateStr() == libjami::Call::StateEvent::CURRENT)
1028 return true;
1029 }
1030 return false;
1031}
1032
1033std::shared_ptr<Call>
1034Manager::getCurrentCall() const
1035{
1036 return getCallFromCallID(pimpl_->currentCall_);
1037}
1038
1039const std::string&
1040Manager::getCurrentCallId() const
1041{
1042 return pimpl_->currentCall_;
1043}
1044
1045void
1046Manager::unregisterAccounts()
1047{
1048 for (const auto& account : getAllAccounts()) {
1049 if (account->isEnabled()) {
1050 account->doUnregister(true);
1051 }
1052 }
1053}
1054
1056// Management of events' IP-phone user
1058/* Main Thread */
1059
1060std::string
1061Manager::outgoingCall(const std::string& account_id,
1062 const std::string& to,
1063 const std::vector<libjami::MediaMap>& mediaList)
1064{
1065 JAMI_DBG() << "Attempt outgoing call to '" << to << "'"
1066 << " with account '" << account_id << "'";
1067
1068 std::shared_ptr<Call> call;
1069
1070 try {
1071 call = newOutgoingCall(trim(to), account_id, mediaList);
1072 } catch (const std::exception& e) {
1073 JAMI_ERROR("{}", e.what());
1074 return {};
1075 }
1076
1077 if (not call)
1078 return {};
1079
1080 stopTone();
1081
1082 pimpl_->switchCall(call->getCallId());
1083
1084 return call->getCallId();
1085}
1086
1087// THREAD=Main : for outgoing Call
1088bool
1089Manager::answerCall(const std::string& accountId,
1090 const std::string& callId,
1091 const std::vector<libjami::MediaMap>& mediaList)
1092{
1093 if (auto account = getAccount(accountId)) {
1094 if (auto call = account->getCall(callId)) {
1095 return answerCall(*call, mediaList);
1096 }
1097 }
1098 return false;
1099}
1100
1101bool
1102Manager::answerCall(Call& call, const std::vector<libjami::MediaMap>& mediaList)
1103{
1104 JAMI_LOG("Answer call {}", call.getCallId());
1105
1106 if (call.getConnectionState() != Call::ConnectionState::RINGING) {
1107 // The call is already answered
1108 return true;
1109 }
1110
1111 // If ringing
1112 stopTone();
1113 pimpl_->removeWaitingCall(call.getCallId());
1114
1115 try {
1116 call.answer(mediaList);
1117 } catch (const std::runtime_error& e) {
1118 JAMI_ERR("%s", e.what());
1119 return false;
1120 }
1121
1122 // if we dragged this call into a conference already
1123 if (auto conf = call.getConference())
1124 pimpl_->switchCall(conf->getConfId());
1125 else
1126 pimpl_->switchCall(call.getCallId());
1127
1128 addAudio(call);
1129
1130 // Start recording if set in preference
1131 if (audioPreference.getIsAlwaysRecording()) {
1132 auto recResult = call.toggleRecording();
1133 emitSignal<libjami::CallSignal::RecordPlaybackFilepath>(call.getCallId(), call.getPath());
1134 emitSignal<libjami::CallSignal::RecordingStateChanged>(call.getCallId(), recResult);
1135 }
1136 return true;
1137}
1138
1139// THREAD=Main
1140bool
1141Manager::hangupCall(const std::string& accountId, const std::string& callId)
1142{
1143 auto account = getAccount(accountId);
1144 if (not account)
1145 return false;
1146 // store the current call id
1147 stopTone();
1148 pimpl_->removeWaitingCall(callId);
1149
1150 /* We often get here when the call was hungup before being created */
1151 auto call = account->getCall(callId);
1152 if (not call) {
1153 JAMI_WARN("Unable to hang up nonexistent call %s", callId.c_str());
1154 return false;
1155 }
1156
1157 // Disconnect streams
1158 removeAudio(*call);
1159
1160 if (call->isConferenceParticipant()) {
1161 removeParticipant(*call);
1162 } else {
1163 // we are not participating in a conference, current call switched to ""
1164 if (isCurrentCall(*call))
1165 pimpl_->unsetCurrentCall();
1166 }
1167
1168 try {
1169 call->hangup(0);
1170 } catch (const VoipLinkException& e) {
1171 JAMI_ERR("%s", e.what());
1172 return false;
1173 }
1174
1175 return true;
1176}
1177
1178bool
1179Manager::hangupConference(const std::string& accountId, const std::string& confId)
1180{
1181 if (auto account = getAccount(accountId)) {
1182 if (auto conference = account->getConference(confId)) {
1183 return pimpl_->hangupConference(*conference);
1184 } else {
1185 JAMI_ERROR("No such conference {}", confId);
1186 }
1187 }
1188 return false;
1189}
1190
1191// THREAD=Main
1192bool
1193Manager::onHoldCall(const std::string&, const std::string& callId)
1194{
1195 bool result = true;
1196
1197 stopTone();
1198
1199 std::string current_callId(getCurrentCallId());
1200
1201 if (auto call = getCallFromCallID(callId)) {
1202 try {
1203 result = call->onhold([=](bool ok) {
1204 if (!ok) {
1205 JAMI_ERR("Hold failed for call %s", callId.c_str());
1206 return;
1207 }
1208 removeAudio(*call); // Unbind calls in main buffer
1209 // Remove call from the queue if it was still there
1210 pimpl_->removeWaitingCall(callId);
1211
1212 // keeps current call id if the action is not holding this call
1213 // or a new outgoing call. This could happen in case of a conference
1214 if (current_callId == callId)
1215 pimpl_->unsetCurrentCall();
1216 });
1217 } catch (const VoipLinkException& e) {
1218 JAMI_ERR("%s", e.what());
1219 result = false;
1220 }
1221 } else {
1222 JAMI_DBG("CallID %s doesn't exist in call onHold", callId.c_str());
1223 return false;
1224 }
1225
1226 return result;
1227}
1228
1229// THREAD=Main
1230bool
1231Manager::offHoldCall(const std::string&, const std::string& callId)
1232{
1233 bool result = true;
1234
1235 stopTone();
1236
1237 std::shared_ptr<Call> call = getCallFromCallID(callId);
1238 if (!call)
1239 return false;
1240
1241 try {
1242 result = call->offhold([=](bool ok) {
1243 if (!ok) {
1244 JAMI_ERR("offHold failed for call %s", callId.c_str());
1245 return;
1246 }
1247
1248 if (auto conf = call->getConference())
1249 pimpl_->switchCall(conf->getConfId());
1250 else
1251 pimpl_->switchCall(call->getCallId());
1252
1253 addAudio(*call);
1254 });
1255 } catch (const VoipLinkException& e) {
1256 JAMI_ERR("%s", e.what());
1257 return false;
1258 }
1259
1260 return result;
1261}
1262
1263// THREAD=Main
1264bool
1265Manager::transferCall(const std::string& accountId, const std::string& callId, const std::string& to)
1266{
1267 auto account = getAccount(accountId);
1268 if (not account)
1269 return false;
1270 if (auto call = account->getCall(callId)) {
1271 if (call->isConferenceParticipant())
1272 removeParticipant(*call);
1273 call->transfer(to);
1274 } else
1275 return false;
1276
1277 // remove waiting call in case we make transfer without even answer
1278 pimpl_->removeWaitingCall(callId);
1279
1280 return true;
1281}
1282
1283void
1284Manager::transferFailed()
1285{
1286 emitSignal<libjami::CallSignal::TransferFailed>();
1287}
1288
1289void
1290Manager::transferSucceeded()
1291{
1292 emitSignal<libjami::CallSignal::TransferSucceeded>();
1293}
1294
1295// THREAD=Main : Call:Incoming
1296bool
1297Manager::refuseCall(const std::string& accountId, const std::string& id)
1298{
1299 if (auto account = getAccount(accountId)) {
1300 if (auto call = account->getCall(id)) {
1301 stopTone();
1302 call->refuse();
1303 pimpl_->removeWaitingCall(id);
1304 removeAudio(*call);
1305 return true;
1306 }
1307 }
1308 return false;
1309}
1310
1311bool
1312Manager::holdConference(const std::string& accountId, const std::string& confId)
1313{
1314 JAMI_INFO("Hold conference %s", confId.c_str());
1315
1316 if (const auto account = getAccount(accountId)) {
1317 if (auto conf = account->getConference(confId)) {
1318 conf->detachHost();
1319 emitSignal<libjami::CallSignal::ConferenceChanged>(accountId,
1320 conf->getConfId(),
1321 conf->getStateStr());
1322 return true;
1323 }
1324 }
1325 return false;
1326}
1327
1328bool
1329Manager::unHoldConference(const std::string& accountId, const std::string& confId)
1330{
1331 JAMI_DBG("[conf:%s] Unholding conference", confId.c_str());
1332
1333 if (const auto account = getAccount(accountId)) {
1334 if (auto conf = account->getConference(confId)) {
1335 // Unhold conf only if it was in hold state otherwise…
1336 // all participants are restarted
1337 if (conf->getState() == Conference::State::HOLD) {
1338 for (const auto& item : conf->getSubCalls())
1339 offHoldCall(accountId, item);
1340
1341 pimpl_->switchCall(confId);
1342 conf->setState(Conference::State::ACTIVE_ATTACHED);
1343 emitSignal<libjami::CallSignal::ConferenceChanged>(accountId,
1344 conf->getConfId(),
1345 conf->getStateStr());
1346 return true;
1347 } else if (conf->getState() == Conference::State::ACTIVE_DETACHED) {
1348 pimpl_->addMainParticipant(*conf);
1349 }
1350 }
1351 }
1352 return false;
1353}
1354
1355bool
1356Manager::addSubCall(const std::string& accountId,
1357 const std::string& callId,
1358 const std::string& account2Id,
1359 const std::string& conferenceId)
1360{
1361 auto account = getAccount(accountId);
1362 auto account2 = getAccount(account2Id);
1363 if (account && account2) {
1364 auto call = account->getCall(callId);
1365 auto conf = account2->getConference(conferenceId);
1366 if (!call or !conf)
1367 return false;
1368 auto callConf = call->getConference();
1369 if (callConf != conf)
1370 return addSubCall(*call, *conf);
1371 }
1372 return false;
1373}
1374
1375bool
1376Manager::addSubCall(Call& call, Conference& conference)
1377{
1378 JAMI_DEBUG("Add participant {} to conference {}", call.getCallId(), conference.getConfId());
1379
1380 // store the current call id (it will change in offHoldCall or in answerCall)
1381 pimpl_->bindCallToConference(call, conference);
1382
1383 // Don't attach current user yet
1384 if (conference.getState() == Conference::State::ACTIVE_DETACHED) {
1385 return true;
1386 }
1387
1388 // TODO: remove this ugly hack → There should be different calls when double clicking
1389 // a conference to add main participant to it, or (in this case) adding a participant
1390 // to conference
1391 pimpl_->unsetCurrentCall();
1392 pimpl_->addMainParticipant(conference);
1393 pimpl_->switchCall(conference.getConfId());
1394 addAudio(call);
1395
1396 return true;
1397}
1398
1399void
1400Manager::ManagerPimpl::addMainParticipant(Conference& conf)
1401{
1402 conf.attachHost();
1403 emitSignal<libjami::CallSignal::ConferenceChanged>(conf.getAccountId(),
1404 conf.getConfId(),
1405 conf.getStateStr());
1406 switchCall(conf.getConfId());
1407}
1408
1409bool
1410Manager::ManagerPimpl::hangupConference(Conference& conference)
1411{
1412 JAMI_DEBUG("hangupConference {}", conference.getConfId());
1413 CallIdSet subcalls(conference.getSubCalls());
1414 conference.detachHost();
1415 if (subcalls.empty()) {
1416 if (auto account = conference.getAccount())
1417 account->removeConference(conference.getConfId());
1418 }
1419 for (const auto& callId : subcalls) {
1420 if (auto call = base_.getCallFromCallID(callId))
1421 base_.hangupCall(call->getAccountId(), callId);
1422 }
1423 unsetCurrentCall();
1424 return true;
1425}
1426
1427bool
1428Manager::addMainParticipant(const std::string& accountId, const std::string& conferenceId)
1429{
1430 JAMI_INFO("Add main participant to conference %s", conferenceId.c_str());
1431
1432 if (auto account = getAccount(accountId)) {
1433 if (auto conf = account->getConference(conferenceId)) {
1434 pimpl_->addMainParticipant(*conf);
1435 JAMI_DBG("Successfully added main participant to conference %s", conferenceId.c_str());
1436 return true;
1437 } else
1438 JAMI_WARN("Failed to add main participant to conference %s", conferenceId.c_str());
1439 }
1440 return false;
1441}
1442
1443std::shared_ptr<Call>
1444Manager::getCallFromCallID(const std::string& callID) const
1445{
1446 return callFactory.getCall(callID);
1447}
1448
1449bool
1450Manager::joinParticipant(const std::string& accountId,
1451 const std::string& callId1,
1452 const std::string& account2Id,
1453 const std::string& callId2,
1454 bool attached)
1455{
1456 JAMI_INFO("JoinParticipant(%s, %s, %i)", callId1.c_str(), callId2.c_str(), attached);
1457 auto account = getAccount(accountId);
1458 auto account2 = getAccount(account2Id);
1459 if (not account or not account2) {
1460 return false;
1461 }
1462
1463 JAMI_INFO("Creating conference for participants %s and %s. Attach host [%s]",
1464 callId1.c_str(),
1465 callId2.c_str(),
1466 attached ? "YES" : "NO");
1467
1468 if (callId1 == callId2) {
1469 JAMI_ERR("Unable to join participant %s to itself", callId1.c_str());
1470 return false;
1471 }
1472
1473 // Set corresponding conference ids for call 1
1474 auto call1 = account->getCall(callId1);
1475 if (!call1) {
1476 JAMI_ERR("Unable to find call %s", callId1.c_str());
1477 return false;
1478 }
1479
1480 // Set corresponding conference details
1481 auto call2 = account2->getCall(callId2);
1482 if (!call2) {
1483 JAMI_ERR("Unable to find call %s", callId2.c_str());
1484 return false;
1485 }
1486
1487 auto mediaAttr = call1->getMediaAttributeList();
1488 if (mediaAttr.empty())
1489 mediaAttr = call2->getMediaAttributeList();
1490 auto conf = std::make_shared<Conference>(account);
1491 conf->attachHost();
1492 account->attach(conf);
1493 emitSignal<libjami::CallSignal::ConferenceCreated>(account->getAccountID(), "", conf->getConfId());
1494
1495 // Bind calls according to their state
1496 pimpl_->bindCallToConference(*call1, *conf);
1497 pimpl_->bindCallToConference(*call2, *conf);
1498
1499 // Switch current call id to this conference
1500 if (attached) {
1501 pimpl_->switchCall(conf->getConfId());
1502 conf->setState(Conference::State::ACTIVE_ATTACHED);
1503 } else {
1504 conf->detachHost();
1505 }
1506 emitSignal<libjami::CallSignal::ConferenceChanged>(account->getAccountID(),
1507 conf->getConfId(),
1508 conf->getStateStr());
1509
1510 return true;
1511}
1512
1513void
1514Manager::createConfFromParticipantList(const std::string& accountId,
1515 const std::vector<std::string>& participantList)
1516{
1517 auto account = getAccount(accountId);
1518 if (not account) {
1519 JAMI_WARN("Unable to find account");
1520 return;
1521 }
1522
1523 // we must have at least 2 participant for a conference
1524 if (participantList.size() <= 1) {
1525 JAMI_ERR("Participant number must be greater than or equal to 2");
1526 return;
1527 }
1528
1529 auto conf = std::make_shared<Conference>(account);
1530 conf->attachHost();
1531
1532 unsigned successCounter = 0;
1533 for (const auto& numberaccount : participantList) {
1534 std::string tostr(numberaccount.substr(0, numberaccount.find(',')));
1535 std::string account(numberaccount.substr(numberaccount.find(',') + 1, numberaccount.size()));
1536
1537 pimpl_->unsetCurrentCall();
1538
1539 // Create call
1540 auto callId = outgoingCall(account, tostr, {});
1541 if (callId.empty())
1542 continue;
1543
1544 // Manager methods may behave differently if the call id participates in a conference
1545 conf->addSubCall(callId);
1546 successCounter++;
1547 }
1548
1549 // Create the conference if and only if at least 2 calls have been successfully created
1550 if (successCounter >= 2) {
1551 account->attach(conf);
1552 emitSignal<libjami::CallSignal::ConferenceCreated>(accountId, "", conf->getConfId());
1553 }
1554}
1555
1556bool
1557Manager::detachHost(const std::shared_ptr<Conference>& conf)
1558{
1559 if (not conf)
1560 return false;
1561
1562 JAMI_LOG("Detach local participant from conference {}", conf->getConfId());
1563 conf->detachHost();
1564 emitSignal<libjami::CallSignal::ConferenceChanged>(conf->getAccountId(),
1565 conf->getConfId(),
1566 conf->getStateStr());
1567 pimpl_->unsetCurrentCall();
1568 return true;
1569}
1570
1571bool
1572Manager::detachParticipant(const std::string& callId)
1573{
1574 JAMI_DBG("Detach participant %s", callId.c_str());
1575
1576 auto call = getCallFromCallID(callId);
1577 if (!call) {
1578 JAMI_ERR("Unable to find call %s", callId.c_str());
1579 return false;
1580 }
1581
1582 // Don't hold ringing calls when detaching them from conferences
1583 if (call->getStateStr() != "RINGING")
1584 onHoldCall(call->getAccountId(), callId);
1585
1586 removeParticipant(*call);
1587 return true;
1588}
1589
1590void
1591Manager::removeParticipant(Call& call)
1592{
1593 JAMI_DBG("Remove participant %s", call.getCallId().c_str());
1594
1595 auto conf = call.getConference();
1596 if (not conf) {
1597 JAMI_ERR("No conference, unable to remove participant");
1598 return;
1599 }
1600
1601 conf->removeSubCall(call.getCallId());
1602
1603 removeAudio(call);
1604
1605 emitSignal<libjami::CallSignal::ConferenceChanged>(conf->getAccountId(),
1606 conf->getConfId(),
1607 conf->getStateStr());
1608
1609 pimpl_->processRemainingParticipants(*conf);
1610}
1611
1612bool
1613Manager::joinConference(const std::string& accountId,
1614 const std::string& confId1,
1615 const std::string& account2Id,
1616 const std::string& confId2)
1617{
1618 auto account = getAccount(accountId);
1619 auto account2 = getAccount(account2Id);
1620 if (not account) {
1621 JAMI_ERR("Unable to find account: %s", accountId.c_str());
1622 return false;
1623 }
1624 if (not account2) {
1625 JAMI_ERR("Unable to find account: %s", account2Id.c_str());
1626 return false;
1627 }
1628
1629 auto conf = account->getConference(confId1);
1630 if (not conf) {
1631 JAMI_ERR("Invalid conference ID: %s", confId1.c_str());
1632 return false;
1633 }
1634
1635 auto conf2 = account2->getConference(confId2);
1636 if (not conf2) {
1637 JAMI_ERR("Invalid conference ID: %s", confId2.c_str());
1638 return false;
1639 }
1640
1641 CallIdSet subcalls(conf->getSubCalls());
1642
1643 std::vector<std::shared_ptr<Call>> calls;
1644 calls.reserve(subcalls.size());
1645
1646 // Detach and remove all participant from conf1 before add
1647 // ... to conf2
1648 for (const auto& callId : subcalls) {
1649 JAMI_DEBUG("Detach participant {}", callId);
1650 if (auto call = account->getCall(callId)) {
1651 conf->removeSubCall(callId);
1652 removeAudio(*call);
1653 calls.emplace_back(std::move(call));
1654 } else {
1655 JAMI_ERROR("Unable to find call {}", callId);
1656 }
1657 }
1658 // Remove conf1
1659 account->removeConference(confId1);
1660
1661 for (const auto& c : calls)
1662 addSubCall(*c, *conf2);
1663
1664 return true;
1665}
1666
1667void
1668Manager::addAudio(Call& call)
1669{
1670 if (call.isConferenceParticipant())
1671 return;
1672 const auto& callId = call.getCallId();
1673 JAMI_LOG("Add audio to call {}", callId);
1674
1675 // bind to main
1676 auto medias = call.getAudioStreams();
1677 for (const auto& media : medias) {
1678 JAMI_DEBUG("[call:{}] Attach audio", media.first);
1679 getRingBufferPool().bindRingBuffers(media.first, RingBufferPool::DEFAULT_ID);
1680 }
1681 auto oldGuard = std::move(call.audioGuard);
1682 call.audioGuard = startAudioStream(AudioDeviceType::PLAYBACK);
1683
1684 std::lock_guard lock(pimpl_->audioLayerMutex_);
1685 if (!pimpl_->audiodriver_) {
1686 JAMI_ERROR("Uninitialized audio driver");
1687 return;
1688 }
1689 pimpl_->audiodriver_->flushUrgent();
1690 getRingBufferPool().flushAllBuffers();
1691}
1692
1693void
1694Manager::removeAudio(Call& call)
1695{
1696 const auto& callId = call.getCallId();
1697 auto medias = call.getAudioStreams();
1698 for (const auto& media : medias) {
1699 JAMI_DEBUG("[call:{}] Remove local audio {}", callId, media.first);
1700 getRingBufferPool().unBindAll(media.first);
1701 }
1702}
1703
1705Manager::scheduler()
1706{
1707 return pimpl_->scheduler_;
1708}
1709
1710std::shared_ptr<asio::io_context>
1711Manager::ioContext() const
1712{
1713 return pimpl_->ioContext_;
1714}
1715
1716std::shared_ptr<dhtnet::upnp::UPnPContext>
1717Manager::upnpContext() const
1718{
1719 return pimpl_->upnpContext_;
1720}
1721
1722std::shared_ptr<Task>
1723Manager::scheduleTask(std::function<void()>&& task,
1724 std::chrono::steady_clock::time_point when,
1725 const char* filename,
1726 uint32_t linum)
1727{
1728 return pimpl_->scheduler_.schedule(std::move(task), when, filename, linum);
1729}
1730
1731std::shared_ptr<Task>
1732Manager::scheduleTaskIn(std::function<void()>&& task,
1733 std::chrono::steady_clock::duration timeout,
1734 const char* filename,
1735 uint32_t linum)
1736{
1737 return pimpl_->scheduler_.scheduleIn(std::move(task), timeout, filename, linum);
1738}
1739
1740void
1741Manager::saveConfig(const std::shared_ptr<Account>& acc)
1742{
1743 if (auto ringAcc = std::dynamic_pointer_cast<JamiAccount>(acc))
1744 ringAcc->saveConfig();
1745 else
1746 saveConfig();
1747}
1748
1749void
1750Manager::saveConfig()
1751{
1752 JAMI_LOG("Saving configuration to '{}'", pimpl_->path_);
1753
1754 if (pimpl_->audiodriver_) {
1755 audioPreference.setVolumemic(pimpl_->audiodriver_->getCaptureGain());
1756 audioPreference.setVolumespkr(pimpl_->audiodriver_->getPlaybackGain());
1757 audioPreference.setCaptureMuted(pimpl_->audiodriver_->isCaptureMuted());
1758 audioPreference.setPlaybackMuted(pimpl_->audiodriver_->isPlaybackMuted());
1759 }
1760
1761 try {
1762 YAML::Emitter out;
1763
1764 // FIXME maybe move this into accountFactory?
1765 out << YAML::BeginMap << YAML::Key << "accounts";
1766 out << YAML::Value << YAML::BeginSeq;
1767
1768 for (const auto& account : accountFactory.getAllAccounts()) {
1769 if (auto jamiAccount = std::dynamic_pointer_cast<JamiAccount>(account)) {
1770 auto accountConfig = jamiAccount->getPath() / "config.yml";
1771 if (not std::filesystem::is_regular_file(accountConfig)) {
1772 saveConfig(jamiAccount);
1773 }
1774 } else {
1775 account->config().serialize(out);
1776 }
1777 }
1778 out << YAML::EndSeq;
1779
1780 // FIXME: this is a hack until we get rid of accountOrder
1781 preferences.verifyAccountOrder(getAccountList());
1782 preferences.serialize(out);
1783 voipPreferences.serialize(out);
1784 audioPreference.serialize(out);
1785#ifdef ENABLE_VIDEO
1786 videoPreferences.serialize(out);
1787#endif
1788#ifdef ENABLE_PLUGIN
1789 pluginPreferences.serialize(out);
1790#endif
1791
1792 std::lock_guard lock(dhtnet::fileutils::getFileLock(pimpl_->path_));
1793 std::ofstream fout(pimpl_->path_);
1794 fout.write(out.c_str(), out.size());
1795 } catch (const YAML::Exception& e) {
1796 JAMI_ERR("%s", e.what());
1797 } catch (const std::runtime_error& e) {
1798 JAMI_ERR("%s", e.what());
1799 }
1800}
1801
1802// THREAD=Main | VoIPLink
1803void
1804Manager::playDtmf(char code)
1805{
1806 stopTone();
1807
1808 if (not voipPreferences.getPlayDtmf()) {
1809 JAMI_DBG("Do not have to play a tone…");
1810 return;
1811 }
1812
1813 // length in milliseconds
1814 int pulselen = voipPreferences.getPulseLength();
1815
1816 if (pulselen == 0) {
1817 JAMI_DBG("Pulse length is not set…");
1818 return;
1819 }
1820
1821 std::lock_guard lock(pimpl_->audioLayerMutex_);
1822
1823 // fast return, no sound, so no dtmf
1824 if (not pimpl_->audiodriver_ or not pimpl_->dtmfKey_) {
1825 JAMI_DBG("No audio layer…");
1826 return;
1827 }
1828
1829 std::shared_ptr<AudioDeviceGuard> audioGuard = startAudioStream(AudioDeviceType::PLAYBACK);
1830 if (not pimpl_->audiodriver_->waitForStart(std::chrono::seconds(1))) {
1831 JAMI_ERR("Failed to start audio layer…");
1832 return;
1833 }
1834
1835 // number of data sampling in one pulselen depends on samplerate
1836 // size (n sampling) = time_ms * sampling/s
1837 // ---------------------
1838 // ms/s
1839 unsigned size = (unsigned) ((pulselen * (long) pimpl_->audiodriver_->getSampleRate()) / 1000ul);
1840 if (!pimpl_->dtmfBuf_ or pimpl_->dtmfBuf_->getFrameSize() != size)
1841 pimpl_->dtmfBuf_ = std::make_shared<AudioFrame>(pimpl_->audiodriver_->getFormat(), size);
1842
1843 // Handle dtmf
1844 pimpl_->dtmfKey_->startTone(code);
1845
1846 // copy the sound
1847 if (pimpl_->dtmfKey_->generateDTMF(pimpl_->dtmfBuf_->pointer())) {
1848 // Put buffer to urgentRingBuffer
1849 // put the size in bytes…
1850 // so size * 1 channel (mono) * sizeof (bytes for the data)
1851 // audiolayer->flushUrgent();
1852
1853 pimpl_->audiodriver_->putUrgent(pimpl_->dtmfBuf_);
1854 }
1855
1856 scheduler().scheduleIn([audioGuard] { JAMI_WARN("End of dtmf"); },
1857 std::chrono::milliseconds(pulselen));
1858
1859 // TODO Cache the DTMF
1860}
1861
1862// Multi-thread
1863bool
1864Manager::incomingCallsWaiting()
1865{
1866 std::lock_guard m(pimpl_->waitingCallsMutex_);
1867 return not pimpl_->waitingCalls_.empty();
1868}
1869
1870void
1871Manager::incomingCall(const std::string& accountId, Call& call)
1872{
1873 if (not accountId.empty()) {
1874 pimpl_->stripSipPrefix(call);
1875 }
1876
1877 auto const& account = getAccount(accountId);
1878 if (not account) {
1879 JAMI_ERROR("Incoming call {} on unknown account {}",
1880 call.getCallId(),
1881 accountId);
1882 return;
1883 }
1884
1885 // Process the call.
1886 pimpl_->processIncomingCall(accountId, call);
1887}
1888
1889void
1890Manager::incomingMessage(const std::string& accountId,
1891 const std::string& callId,
1892 const std::string& from,
1893 const std::map<std::string, std::string>& messages)
1894{
1895 auto account = getAccount(accountId);
1896 if (not account) {
1897 return;
1898 }
1899 if (auto call = account->getCall(callId)) {
1900 if (call->isConferenceParticipant()) {
1901 if (auto conf = call->getConference()) {
1902 JAMI_DBG("Is a conference, send incoming message to everyone");
1903 // filter out vcards messages as they could be resent by master as its own vcard
1904 // TODO. Implement a protocol to handle vcard messages
1905 bool sendToOtherParicipants = true;
1906 for (auto& message : messages) {
1907 if (message.first.find("x-ring/ring.profile.vcard") != std::string::npos) {
1908 sendToOtherParicipants = false;
1909 }
1910 }
1911 if (sendToOtherParicipants) {
1912 pimpl_->sendTextMessageToConference(*conf, messages, from);
1913 }
1914
1915 // in case of a conference we must notify client using conference id
1916 emitSignal<libjami::CallSignal::IncomingMessage>(accountId,
1917 conf->getConfId(),
1918 from,
1919 messages);
1920 } else {
1921 JAMI_ERR("No conference associated to ID %s", callId.c_str());
1922 }
1923 } else {
1924 emitSignal<libjami::CallSignal::IncomingMessage>(accountId, callId, from, messages);
1925 }
1926 }
1927}
1928
1929void
1930Manager::sendCallTextMessage(const std::string& accountId,
1931 const std::string& callID,
1932 const std::map<std::string, std::string>& messages,
1933 const std::string& from,
1934 bool /*isMixed TODO: use it */)
1935{
1936 auto account = getAccount(accountId);
1937 if (not account) {
1938 return;
1939 }
1940
1941 if (auto conf = account->getConference(callID)) {
1942 JAMI_DBG("Is a conference, send instant message to everyone");
1943 pimpl_->sendTextMessageToConference(*conf, messages, from);
1944 } else if (auto call = account->getCall(callID)) {
1945 if (call->isConferenceParticipant()) {
1946 if (auto conf = call->getConference()) {
1947 JAMI_DBG("Call is participant in a conference, send instant message to everyone");
1948 pimpl_->sendTextMessageToConference(*conf, messages, from);
1949 } else {
1950 JAMI_ERR("No conference associated to call ID %s", callID.c_str());
1951 }
1952 } else {
1953 try {
1954 call->sendTextMessage(messages, from);
1955 } catch (const im::InstantMessageException& e) {
1956 JAMI_ERR("Failed to send message to call %s: %s",
1957 call->getCallId().c_str(),
1958 e.what());
1959 }
1960 }
1961 } else {
1962 JAMI_ERR("Failed to send message to %s: nonexistent call ID", callID.c_str());
1963 }
1964}
1965
1966// THREAD=VoIP CALL=Outgoing
1967void
1968Manager::peerAnsweredCall(Call& call)
1969{
1970 const auto& callId = call.getCallId();
1971 JAMI_DBG("[call:%s] Peer answered", callId.c_str());
1972
1973 // The if statement is useful only if we sent two calls at the same time.
1974 if (isCurrentCall(call))
1975 stopTone();
1976
1977 addAudio(call);
1978
1979 if (pimpl_->audiodriver_) {
1980 std::lock_guard lock(pimpl_->audioLayerMutex_);
1981 getRingBufferPool().flushAllBuffers();
1982 pimpl_->audiodriver_->flushUrgent();
1983 }
1984
1985 if (audioPreference.getIsAlwaysRecording()) {
1986 auto result = call.toggleRecording();
1987 emitSignal<libjami::CallSignal::RecordPlaybackFilepath>(callId, call.getPath());
1988 emitSignal<libjami::CallSignal::RecordingStateChanged>(callId, result);
1989 }
1990}
1991
1992// THREAD=VoIP Call=Outgoing
1993void
1994Manager::peerRingingCall(Call& call)
1995{
1996 JAMI_LOG("[call:{}] Peer ringing", call.getCallId());
1997 if (!hasCurrentCall())
1998 ringback();
1999}
2000
2001// THREAD=VoIP Call=Outgoing/Ingoing
2002void
2003Manager::peerHungupCall(Call& call)
2004{
2005 const auto& callId = call.getCallId();
2006 JAMI_LOG("[call:{}] Peer hung up", callId);
2007
2008 if (call.isConferenceParticipant()) {
2009 removeParticipant(call);
2010 } else if (isCurrentCall(call)) {
2011 stopTone();
2012 pimpl_->unsetCurrentCall();
2013 }
2014
2015 call.peerHungup();
2016
2017 pimpl_->removeWaitingCall(callId);
2018 if (not incomingCallsWaiting())
2019 stopTone();
2020
2021 removeAudio(call);
2022}
2023
2024// THREAD=VoIP
2025void
2026Manager::callBusy(Call& call)
2027{
2028 JAMI_LOG("[call:{}] Busy", call.getCallId());
2029
2030 if (isCurrentCall(call)) {
2031 pimpl_->unsetCurrentCall();
2032 }
2033
2034 pimpl_->removeWaitingCall(call.getCallId());
2035 if (not incomingCallsWaiting())
2036 stopTone();
2037}
2038
2039// THREAD=VoIP
2040void
2041Manager::callFailure(Call& call)
2042{
2043 JAMI_LOG("[call:{}] {} failed",
2044 call.getCallId(),
2045 call.isSubcall() ? "Sub-call" : "Parent call");
2046
2047 if (isCurrentCall(call)) {
2048 pimpl_->unsetCurrentCall();
2049 }
2050
2051 if (call.isConferenceParticipant()) {
2052 JAMI_LOG("[call {}] Participating in a conference. Remove", call.getCallId());
2053 // remove this participant
2054 removeParticipant(call);
2055 }
2056
2057 pimpl_->removeWaitingCall(call.getCallId());
2058 if (not call.isSubcall() && not incomingCallsWaiting())
2059 stopTone();
2060 removeAudio(call);
2061}
2062
2066void
2067Manager::stopTone()
2068{
2069 if (not voipPreferences.getPlayTones())
2070 return;
2071
2072 pimpl_->toneCtrl_.stop();
2073 pimpl_->toneDeviceGuard_.reset();
2074}
2075
2079void
2080Manager::playTone()
2081{
2082 pimpl_->playATone(Tone::ToneId::DIALTONE);
2083}
2084
2088void
2089Manager::playToneWithMessage()
2090{
2091 pimpl_->playATone(Tone::ToneId::CONGESTION);
2092}
2093
2097void
2098Manager::congestion()
2099{
2100 pimpl_->playATone(Tone::ToneId::CONGESTION);
2101}
2102
2106void
2107Manager::ringback()
2108{
2109 pimpl_->playATone(Tone::ToneId::RINGTONE);
2110}
2111
2115void
2116Manager::playRingtone(const std::string& accountID)
2117{
2118 const auto account = getAccount(accountID);
2119 if (!account) {
2120 JAMI_WARN("Invalid account in ringtone");
2121 return;
2122 }
2123
2124 if (!account->getRingtoneEnabled()) {
2125 ringback();
2126 return;
2127 }
2128
2129 {
2130 std::lock_guard lock(pimpl_->audioLayerMutex_);
2131
2132 if (not pimpl_->audiodriver_) {
2133 JAMI_ERR("No audio layer in ringtone");
2134 return;
2135 }
2136 // start audio if not started AND flush all buffers (main and urgent)
2137 auto oldGuard = std::move(pimpl_->toneDeviceGuard_);
2138 pimpl_->toneDeviceGuard_ = startAudioStream(AudioDeviceType::RINGTONE);
2139 auto format = pimpl_->audiodriver_->getFormat();
2140 pimpl_->toneCtrl_.setSampleRate(format.sample_rate, format.sampleFormat);
2141 }
2142
2143 if (not pimpl_->toneCtrl_.setAudioFile(account->getRingtonePath().string()))
2144 ringback();
2145}
2146
2147std::shared_ptr<AudioLoop>
2148Manager::getTelephoneTone()
2149{
2150 return pimpl_->toneCtrl_.getTelephoneTone();
2151}
2152
2153std::shared_ptr<AudioLoop>
2154Manager::getTelephoneFile()
2155{
2156 return pimpl_->toneCtrl_.getTelephoneFile();
2157}
2158
2162void
2163Manager::setAudioPlugin(const std::string& audioPlugin)
2164{
2165 {
2166 std::lock_guard lock(pimpl_->audioLayerMutex_);
2167 audioPreference.setAlsaPlugin(audioPlugin);
2168 pimpl_->audiodriver_.reset();
2169 pimpl_->initAudioDriver();
2170 }
2171 // Recreate audio driver with new settings
2172 saveConfig();
2173}
2174
2178void
2179Manager::setAudioDevice(int index, AudioDeviceType type)
2180{
2181 std::lock_guard lock(pimpl_->audioLayerMutex_);
2182
2183 if (not pimpl_->audiodriver_) {
2184 JAMI_ERR("Uninitialized audio driver");
2185 return;
2186 }
2187 if (pimpl_->getCurrentDeviceIndex(type) == index) {
2188 JAMI_WARN("Audio device already selected, doing nothing.");
2189 return;
2190 }
2191
2192 pimpl_->audiodriver_->updatePreference(audioPreference, index, type);
2193
2194 // Recreate audio driver with new settings
2195 pimpl_->audiodriver_.reset();
2196 pimpl_->initAudioDriver();
2197 saveConfig();
2198}
2199
2203std::vector<std::string>
2204Manager::getAudioOutputDeviceList()
2205{
2206 std::lock_guard lock(pimpl_->audioLayerMutex_);
2207
2208 if (not pimpl_->audiodriver_) {
2209 JAMI_ERR("Uninitialized audio layer");
2210 return {};
2211 }
2212
2213 return pimpl_->audiodriver_->getPlaybackDeviceList();
2214}
2215
2219std::vector<std::string>
2220Manager::getAudioInputDeviceList()
2221{
2222 std::lock_guard lock(pimpl_->audioLayerMutex_);
2223
2224 if (not pimpl_->audiodriver_) {
2225 JAMI_ERR("Uninitialized audio layer");
2226 return {};
2227 }
2228
2229 return pimpl_->audiodriver_->getCaptureDeviceList();
2230}
2231
2235std::vector<std::string>
2236Manager::getCurrentAudioDevicesIndex()
2237{
2238 std::lock_guard lock(pimpl_->audioLayerMutex_);
2239 if (not pimpl_->audiodriver_) {
2240 JAMI_ERR("Uninitialized audio layer");
2241 return {};
2242 }
2243
2244 return {std::to_string(pimpl_->audiodriver_->getIndexPlayback()),
2245 std::to_string(pimpl_->audiodriver_->getIndexCapture()),
2246 std::to_string(pimpl_->audiodriver_->getIndexRingtone())};
2247}
2248
2249void
2250Manager::startAudio()
2251{
2252 if (!pimpl_->audiodriver_)
2253 pimpl_->audiodriver_.reset(pimpl_->base_.audioPreference.createAudioLayer());
2254 constexpr std::array<AudioDeviceType, 3> TYPES {AudioDeviceType::CAPTURE,
2255 AudioDeviceType::PLAYBACK,
2256 AudioDeviceType::RINGTONE};
2257 for (const auto& type : TYPES)
2258 if (pimpl_->audioStreamUsers_[(unsigned) type])
2259 pimpl_->audiodriver_->startStream(type);
2260}
2261
2262AudioDeviceGuard::AudioDeviceGuard(Manager& manager, AudioDeviceType type)
2263 : manager_(manager)
2264 , type_(type)
2265{
2266 auto streamId = (unsigned) type;
2267 if (streamId >= manager_.pimpl_->audioStreamUsers_.size())
2268 throw std::invalid_argument("Invalid audio device type");
2269 if (manager_.pimpl_->audioStreamUsers_[streamId]++ == 0) {
2270 if (auto layer = manager_.getAudioDriver())
2271 layer->startStream(type);
2272 }
2273}
2274
2276{
2277 auto streamId = (unsigned) type_;
2278 if (--manager_.pimpl_->audioStreamUsers_[streamId] == 0) {
2279 if (auto layer = manager_.getAudioDriver())
2280 layer->stopStream(type_);
2281 }
2282}
2283
2284bool
2289
2290void
2296
2297bool
2298Manager::toggleRecordingCall(const std::string& accountId, const std::string& id)
2299{
2300 bool result = false;
2301 if (auto account = getAccount(accountId)) {
2302 std::shared_ptr<Recordable> rec;
2303 if (auto conf = account->getConference(id)) {
2304 JAMI_DBG("Toggle recording for conference %s", id.c_str());
2305 rec = conf;
2306 } else if (auto call = account->getCall(id)) {
2307 JAMI_DBG("Toggle recording for call %s", id.c_str());
2308 rec = call;
2309 } else {
2310 JAMI_ERR("Unable to find recordable instance %s", id.c_str());
2311 return false;
2312 }
2313 result = rec->toggleRecording();
2316 }
2317 return result;
2318}
2319
2320bool
2322{
2323 JAMI_DBG("Start recorded file playback %s", filepath.c_str());
2324
2325 {
2326 std::lock_guard lock(pimpl_->audioLayerMutex_);
2327
2328 if (not pimpl_->audiodriver_) {
2329 JAMI_ERR("No audio layer in start recorded file playback");
2330 return false;
2331 }
2332
2333 auto oldGuard = std::move(pimpl_->toneDeviceGuard_);
2334 pimpl_->toneDeviceGuard_ = startAudioStream(AudioDeviceType::RINGTONE);
2335 auto format = pimpl_->audiodriver_->getFormat();
2336 pimpl_->toneCtrl_.setSampleRate(format.sample_rate, format.sampleFormat);
2337 }
2338
2339 return pimpl_->toneCtrl_.setAudioFile(filepath);
2340}
2341
2342void
2344{
2345 pimpl_->toneCtrl_.seek(value);
2346}
2347
2348void
2350{
2351 JAMI_DBG("Stop recorded file playback");
2352
2353 pimpl_->toneCtrl_.stopAudioFile();
2354 pimpl_->toneDeviceGuard_.reset();
2355}
2356
2357void
2359{
2360 JAMI_DBG("Set history limit");
2362 saveConfig();
2363}
2364
2365int
2367{
2369}
2370
2371void
2373{
2374 JAMI_DBG("Set ringing timeout");
2376 saveConfig();
2377}
2378
2379int
2384
2385bool
2386Manager::setAudioManager(const std::string& api)
2387{
2388 {
2389 std::lock_guard lock(pimpl_->audioLayerMutex_);
2390
2391 if (not pimpl_->audiodriver_)
2392 return false;
2393
2394 if (api == audioPreference.getAudioApi()) {
2395 JAMI_DBG("Audio manager chosen already in use. No changes made.");
2396 return true;
2397 }
2398 }
2399
2400 {
2401 std::lock_guard lock(pimpl_->audioLayerMutex_);
2403 pimpl_->audiodriver_.reset();
2404 pimpl_->initAudioDriver();
2405 }
2406
2407 saveConfig();
2408
2409 // ensure that we completed the transition (i.e. no fallback was used)
2410 return api == audioPreference.getAudioApi();
2411}
2412
2413std::string
2415{
2417}
2418
2419int
2421{
2422 std::lock_guard lock(pimpl_->audioLayerMutex_);
2423
2424 if (not pimpl_->audiodriver_) {
2425 JAMI_ERR("Uninitialized audio layer");
2426 return 0;
2427 }
2428
2429 return pimpl_->audiodriver_->getAudioDeviceIndex(name, AudioDeviceType::CAPTURE);
2430}
2431
2432int
2434{
2435 std::lock_guard lock(pimpl_->audioLayerMutex_);
2436
2437 if (not pimpl_->audiodriver_) {
2438 JAMI_ERR("Uninitialized audio layer");
2439 return 0;
2440 }
2441
2442 return pimpl_->audiodriver_->getAudioDeviceIndex(name, AudioDeviceType::PLAYBACK);
2443}
2444
2445std::string
2450
2451std::string
2456
2457void
2458Manager::setNoiseSuppressState(const std::string& state)
2459{
2461}
2462
2463bool
2465{
2467}
2468
2469void
2471{
2473}
2474
2478void
2480{
2482 constexpr std::array<AudioDeviceType, 3> TYPES {AudioDeviceType::CAPTURE,
2485 for (const auto& type : TYPES)
2486 if (audioStreamUsers_[(unsigned) type])
2487 audiodriver_->startStream(type);
2488}
2489
2490// Internal helper method
2491void
2493{
2494 // strip sip: which is not required and causes confusion with IP-to-IP calls
2495 // when placing new call from history.
2496 std::string peerNumber(incomCall.getPeerNumber());
2497
2498 const char SIP_PREFIX[] = "sip:";
2499 size_t startIndex = peerNumber.find(SIP_PREFIX);
2500
2501 if (startIndex != std::string::npos)
2502 incomCall.setPeerNumber(peerNumber.substr(startIndex + sizeof(SIP_PREFIX) - 1));
2503}
2504
2505// Internal helper method
2506void
2508{
2509 base_.stopTone();
2510
2511 auto incomCallId = incomCall.getCallId();
2512 auto currentCall = base_.getCurrentCall();
2513
2514 auto account = incomCall.getAccount().lock();
2515 if (!account) {
2516 JAMI_ERR("No account detected");
2517 return;
2518 }
2519
2520 auto username = incomCall.toUsername();
2521 if (account->getAccountType() == ACCOUNT_TYPE_JAMI && username.find('/') != std::string::npos) {
2522 // Avoid to do heavy stuff in SIPVoIPLink's transaction_request_cb
2523 dht::ThreadPool::io().run([account, incomCallId, username]() {
2524 if (auto jamiAccount = std::dynamic_pointer_cast<JamiAccount>(account))
2525 jamiAccount->handleIncomingConversationCall(incomCallId, username);
2526 });
2527 return;
2528 }
2529
2531 incomCall.getMediaAttributeList());
2532
2533 if (mediaList.empty())
2534 JAMI_WARNING("Incoming call {} has an empty media list", incomCallId);
2535
2536 JAMI_DEBUG("Incoming call {} on account {} with {} media",
2538 accountId,
2539 mediaList.size());
2540
2543 incomCall.getPeerNumber(),
2544 mediaList);
2545
2546 if (not base_.hasCurrentCall()) {
2548#if !(defined(TARGET_OS_IOS) && TARGET_OS_IOS)
2549 if (not account->isRendezVous())
2550 base_.playRingtone(accountId);
2551#endif
2552 } else {
2553 if (account->isDenySecondCallEnabled()) {
2554 base_.refuseCall(account->getAccountID(), incomCallId);
2555 return;
2556 }
2557 }
2558
2559 addWaitingCall(incomCallId);
2560
2561 if (account->isRendezVous()) {
2562 dht::ThreadPool::io().run([this, account, incomCall = incomCall.shared_from_this()] {
2563 base_.answerCall(*incomCall);
2564
2565 for (const auto& callId : account->getCallList()) {
2566 if (auto call = account->getCall(callId)) {
2567 if (call->getState() != Call::CallState::ACTIVE)
2568 continue;
2569 if (call != incomCall) {
2570 if (auto conf = call->getConference()) {
2571 base_.addSubCall(*incomCall, *conf);
2572 } else {
2573 base_.joinParticipant(account->getAccountID(),
2574 incomCall->getCallId(),
2575 account->getAccountID(),
2576 call->getCallId(),
2577 false);
2578 }
2579 return;
2580 }
2581 }
2582 }
2583
2584 // First call
2585 auto conf = std::make_shared<Conference>(account);
2586 account->attach(conf);
2588 conf->getConfId());
2589
2590 // Bind calls according to their state
2592 conf->detachHost();
2594 conf->getConfId(),
2595 conf->getStateStr());
2596 });
2597 } else if (autoAnswer_ || account->isAutoAnswerEnabled()) {
2598 dht::ThreadPool::io().run(
2599 [this, incomCall = incomCall.shared_from_this()] { base_.answerCall(*incomCall); });
2600 } else if (currentCall && currentCall->getCallId() != incomCallId) {
2601 // Test if already calling this person
2602 auto peerNumber = incomCall.getPeerNumber();
2603 auto currentPeerNumber = currentCall->getPeerNumber();
2604 string_replace(peerNumber, "@ring.dht", "");
2605 string_replace(currentPeerNumber, "@ring.dht", "");
2606 if (currentCall->getAccountId() == account->getAccountID()
2608 auto answerToCall = false;
2609 auto downgradeToAudioOnly = currentCall->isAudioOnly() != incomCall.isAudioOnly();
2611 // Accept the incoming audio only
2612 answerToCall = incomCall.isAudioOnly();
2613 else
2614 // Accept the incoming call from the higher id number
2615 answerToCall = (account->getUsername().compare(peerNumber) < 0);
2616
2617 if (answerToCall) {
2618 runOnMainThread([accountId = currentCall->getAccountId(),
2619 currentCallID = currentCall->getCallId(),
2620 incomCall = incomCall.shared_from_this()] {
2621 auto& mgr = Manager::instance();
2622 mgr.answerCall(*incomCall);
2623 mgr.hangupCall(accountId, currentCallID);
2624 });
2625 }
2626 }
2627 }
2628}
2629
2630AudioFormat
2632{
2633 return audioFormatUsed(format);
2634}
2635
2638{
2639 AudioFormat currentFormat = pimpl_->ringbufferpool_->getInternalAudioFormat();
2640 format.nb_channels = std::max(currentFormat.nb_channels,
2641 std::min(format.nb_channels, 2u)); // max 2 channels.
2642 format.sample_rate = std::max(currentFormat.sample_rate, format.sample_rate);
2643
2644 if (currentFormat == format)
2645 return format;
2646
2647 JAMI_DEBUG("Audio format changed: {} → {}", currentFormat.toString(), format.toString());
2648
2649 pimpl_->ringbufferpool_->setInternalAudioFormat(format);
2650 pimpl_->toneCtrl_.setSampleRate(format.sample_rate, format.sampleFormat);
2651 pimpl_->dtmfKey_.reset(new DTMF(format.sample_rate, format.sampleFormat));
2652
2653 return format;
2654}
2655
2656void
2657Manager::setAccountsOrder(const std::string& order)
2658{
2659 JAMI_LOG("Set accounts order: {}", order);
2661 saveConfig();
2663}
2664
2665std::vector<std::string>
2667{
2668 // Concatenate all account pointers in a single map
2669 std::vector<std::string> v;
2670 v.reserve(accountCount());
2671 for (const auto& account : getAllAccounts()) {
2672 v.emplace_back(account->getAccountID());
2673 }
2674
2675 return v;
2676}
2677
2678std::map<std::string, std::string>
2679Manager::getAccountDetails(const std::string& accountID) const
2680{
2681 const auto account = getAccount(accountID);
2682
2683 if (account) {
2684 return account->getAccountDetails();
2685 } else {
2686 JAMI_ERR("Unable to get account details on a nonexistent accountID %s", accountID.c_str());
2687 // return an empty map since unable to throw an exception to D-Bus
2688 return {};
2689 }
2690}
2691
2692std::map<std::string, std::string>
2694{
2695 const auto account = getAccount(accountID);
2696
2697 if (account) {
2698 return account->getVolatileAccountDetails();
2699 } else {
2700 JAMI_ERR("Unable to get volatile account details on a nonexistent accountID %s",
2701 accountID.c_str());
2702 return {};
2703 }
2704}
2705
2706void
2708 const std::map<std::string, std::string>& details)
2709{
2710 JAMI_DBG("Set account details for %s", accountID.c_str());
2711
2713 if (not account) {
2714 JAMI_ERR("Unable to find account %s", accountID.c_str());
2715 return;
2716 }
2717
2718 // Ignore if nothing has changed
2719 if (details == account->getAccountDetails())
2720 return;
2721
2722 // Unregister before modifying any account information
2723 account->doUnregister();
2724
2725 account->setAccountDetails(details);
2726
2727 if (account->isUsable())
2728 account->doRegister();
2729 else
2730 account->doUnregister();
2731
2732 // Update account details to the client side
2734}
2735
2736std::mt19937_64
2738{
2739 return dht::crypto::getDerivedRandomEngine(rand_);
2740}
2741
2742std::string
2744{
2745 std::string random_id;
2746 do {
2747 random_id = to_hex_string(std::uniform_int_distribution<uint64_t>()(rand_));
2748 } while (getAccount(random_id));
2749 return random_id;
2750}
2751
2752std::string
2753Manager::addAccount(const std::map<std::string, std::string>& details, const std::string& accountId)
2754{
2756 auto newAccountID = accountId.empty() ? getNewAccountId() : accountId;
2757
2758 // Get the type
2759 std::string_view accountType;
2761 if (typeIt != details.end())
2762 accountType = typeIt->second;
2763 else
2765
2766 JAMI_DEBUG("Adding account {:s} with type {}", newAccountID, accountType);
2767
2769 if (!newAccount) {
2770 JAMI_ERROR("Unknown {:s} param when calling addAccount(): {:s}",
2772 accountType);
2773 return "";
2774 }
2775
2776 newAccount->setAccountDetails(details);
2778 newAccount->doRegister();
2779
2781 saveConfig();
2782
2784
2785 return newAccountID;
2786}
2787
2788void
2789Manager::removeAccount(const std::string& accountID, bool flush)
2790{
2791 // Get it down and dying
2792 if (const auto& remAccount = getAccount(accountID)) {
2793 if (auto acc = std::dynamic_pointer_cast<JamiAccount>(remAccount)) {
2794 acc->hangupCalls();
2795 }
2796 remAccount->doUnregister(true);
2797 if (flush)
2798 remAccount->flush();
2800 }
2801
2803
2804 saveConfig();
2805
2807}
2808
2809void
2811{
2812 for (const auto& acc : getAccountList())
2813 removeAccount(acc);
2814}
2815
2816std::vector<std::string_view>
2821
2822int
2823Manager::loadAccountMap(const YAML::Node& node)
2824{
2825 int errorCount = 0;
2826 try {
2827 // build preferences
2831#ifdef ENABLE_VIDEO
2832 videoPreferences.unserialize(node);
2833#endif
2834#ifdef ENABLE_PLUGIN
2835 pluginPreferences.unserialize(node);
2836#endif
2837 } catch (const YAML::Exception& e) {
2838 JAMI_ERR("Preferences node unserialize YAML exception: %s", e.what());
2839 ++errorCount;
2840 } catch (const std::exception& e) {
2841 JAMI_ERR("Preferences node unserialize standard exception: %s", e.what());
2842 ++errorCount;
2843 } catch (...) {
2844 JAMI_ERR("Preferences node unserialize unknown exception");
2845 ++errorCount;
2846 }
2847
2848 const std::string accountOrder = preferences.getAccountOrder();
2849
2850 // load saved preferences for IP2IP account from configuration file
2851 const auto& accountList = node["accounts"];
2852
2853 for (auto& a : accountList) {
2854 pimpl_->loadAccount(a, errorCount);
2855 }
2856
2858 auto dirs = dhtnet::fileutils::readDirectory(accountBaseDir);
2859
2860 std::condition_variable cv;
2861 std::mutex lock;
2862 size_t remaining {0};
2863 std::unique_lock l(lock);
2864 for (const auto& dir : dirs) {
2866 continue;
2867 }
2868 remaining++;
2869 dht::ThreadPool::computation().run(
2870 [this, dir, &cv, &remaining, &lock, configFile = accountBaseDir / dir / "config.yml"] {
2871 if (std::filesystem::is_regular_file(configFile)) {
2872 try {
2873 auto configNode = YAML::LoadFile(configFile.string());
2875 auto config = a->buildConfig();
2876 config->unserialize(configNode);
2877 a->setConfig(std::move(config));
2878 }
2879 } catch (const std::exception& e) {
2880 JAMI_ERR("Unable to import account %s: %s", dir.c_str(), e.what());
2881 }
2882 }
2883 std::lock_guard l(lock);
2884 remaining--;
2885 cv.notify_one();
2886 });
2887 }
2888 cv.wait(l, [&remaining] { return remaining == 0; });
2889
2890#ifdef ENABLE_PLUGIN
2891 if (pluginPreferences.getPluginsEnabled()) {
2892 jami::Manager::instance().getJamiPluginManager().loadPlugins();
2893 }
2894#endif
2895
2896 return errorCount;
2897}
2898
2899std::vector<std::string>
2901{
2902 std::vector<std::string> results;
2903 for (const auto& call : callFactory.getAllCalls()) {
2904 if (!call->isSubcall())
2905 results.push_back(call->getCallId());
2906 }
2907 return results;
2908}
2909
2910void
2912{
2913 for (auto& a : getAllAccounts()) {
2914 if (a->isUsable())
2915 a->doRegister();
2916 }
2917}
2918
2919void
2920Manager::sendRegister(const std::string& accountID, bool enable)
2921{
2922 const auto acc = getAccount(accountID);
2923 if (!acc)
2924 return;
2925
2926 acc->setEnabled(enable);
2927 saveConfig(acc);
2928
2929 if (acc->isEnabled()) {
2930 acc->doRegister();
2931 } else
2932 acc->doUnregister();
2933}
2934
2937 const std::string& to,
2938 const std::map<std::string, std::string>& payloads,
2939 bool fromPlugin,
2940 bool onlyConnected)
2941{
2942 if (const auto acc = getAccount(accountID)) {
2943 try {
2944#ifdef ENABLE_PLUGIN // modifies send message
2945 auto& pluginChatManager = getJamiPluginManager().getChatServicesManager();
2946 if (pluginChatManager.hasHandlers()) {
2947 auto cm = std::make_shared<JamiMessage>(accountID, to, false, payloads, fromPlugin);
2948 pluginChatManager.publishMessage(cm);
2949 return acc->sendTextMessage(cm->peerId, "", cm->data, 0, onlyConnected);
2950 } else
2951#endif // ENABLE_PLUGIN
2952 return acc->sendTextMessage(to, "", payloads, 0, onlyConnected);
2953 } catch (const std::exception& e) {
2954 JAMI_ERR("Exception during text message sending: %s", e.what());
2955 }
2956 }
2957 return 0;
2958}
2959
2960int
2962{
2963 JAMI_ERROR("Deprecated method. Please use status from message");
2964 return 0;
2965}
2966
2967int
2968Manager::getMessageStatus(const std::string&, uint64_t) const
2969{
2970 JAMI_ERROR("Deprecated method. Please use status from message");
2971 return 0;
2972}
2973
2974void
2975Manager::setAccountActive(const std::string& accountID, bool active, bool shutdownConnections)
2976{
2977 const auto acc = getAccount(accountID);
2978 if (!acc || acc->isActive() == active)
2979 return;
2980 acc->setActive(active);
2981 if (acc->isEnabled()) {
2982 if (active) {
2983 acc->doRegister();
2984 } else {
2985 acc->doUnregister(shutdownConnections);
2986 }
2987 }
2989 accountID, acc->getVolatileAccountDetails());
2990}
2991
2992void
2993Manager::loadAccountAndConversation(const std::string& accountId,
2994 bool loadAll,
2995 const std::string& convId)
2996{
2997 auto account = getAccount(accountId);
2998 if (!account && !autoLoad) {
2999 /*
3000 With the LIBJAMI_FLAG_NO_AUTOLOAD flag active, accounts are not
3001 automatically created during manager initialization, nor are
3002 their configurations set or backed up. This is because account
3003 creation triggers the initialization of the certStore. There why
3004 account creation now occurs here in response to a received notification.
3005 */
3007 auto configFile = accountBaseDir / accountId / "config.yml";
3008 try {
3010 account->enableAutoLoadConversations(false);
3011 auto configNode = YAML::LoadFile(configFile.string());
3012 auto config = account->buildConfig();
3013 config->unserialize(configNode);
3014 account->setConfig(std::move(config));
3015 }
3016 } catch (const std::runtime_error& e) {
3017 JAMI_WARN("Failed to load account: %s", e.what());
3018 return;
3019 }
3020 }
3021
3022 if (!account) {
3023 JAMI_WARN("Unable to load account %s", accountId.c_str());
3024 return;
3025 }
3026
3027 if (auto jamiAcc = std::dynamic_pointer_cast<JamiAccount>(account)) {
3028 jamiAcc->setActive(true);
3029 jamiAcc->reloadContacts();
3030 if (jamiAcc->isUsable())
3031 jamiAcc->doRegister();
3032 if (auto convModule = jamiAcc->convModule()) {
3033 convModule->reloadRequests();
3034 if (loadAll) {
3035 convModule->loadConversations();
3036 } else if (!convId.empty()) {
3037 jamiAcc->loadConversation(convId);
3038 }
3039 }
3040 }
3041}
3042
3043std::shared_ptr<AudioLayer>
3045{
3046 return pimpl_->audiodriver_;
3047}
3048
3049std::shared_ptr<Call>
3051 const std::string& accountId,
3052 const std::vector<libjami::MediaMap>& mediaList)
3053{
3054 auto account = getAccount(accountId);
3055 if (not account) {
3056 JAMI_WARN("No account matches ID %s", accountId.c_str());
3057 return {};
3058 }
3059
3060 if (not account->isUsable()) {
3061 JAMI_WARN("Account %s is unusable", accountId.c_str());
3062 return {};
3063 }
3064
3065 return account->newOutgoingCall(toUrl, mediaList);
3066}
3067
3068#ifdef ENABLE_VIDEO
3069std::shared_ptr<video::SinkClient>
3070Manager::createSinkClient(const std::string& id, bool mixer)
3071{
3072 const auto& iter = pimpl_->sinkMap_.find(id);
3073 if (iter != std::end(pimpl_->sinkMap_)) {
3074 if (auto sink = iter->second.lock())
3075 return sink;
3076 pimpl_->sinkMap_.erase(iter); // remove expired weak_ptr
3077 }
3078
3079 auto sink = std::make_shared<video::SinkClient>(id, mixer);
3080 pimpl_->sinkMap_.emplace(id, sink);
3081 return sink;
3082}
3083
3084void
3085Manager::createSinkClients(
3086 const std::string& callId,
3087 const ConfInfo& infos,
3088 const std::vector<std::shared_ptr<video::VideoFrameActiveWriter>>& videoStreams,
3089 std::map<std::string, std::shared_ptr<video::SinkClient>>& sinksMap,
3090 const std::string& accountId)
3091{
3092 std::lock_guard lk(pimpl_->sinksMutex_);
3093 std::set<std::string> sinkIdsList {};
3094
3095 // create video sinks
3096 for (const auto& participant : infos) {
3097 std::string sinkId = participant.sinkId;
3098 if (sinkId.empty()) {
3099 sinkId = callId;
3100 sinkId += string_remove_suffix(participant.uri, '@') + participant.device;
3101 }
3102 if (participant.w && participant.h && !participant.videoMuted) {
3103 auto currentSink = getSinkClient(sinkId);
3104 if (!accountId.empty() && currentSink
3105 && string_remove_suffix(participant.uri, '@') == getAccount(accountId)->getUsername()
3106 && participant.device == getAccount<JamiAccount>(accountId)->currentDeviceId()) {
3107 // This is a local sink that must already exist
3108 continue;
3109 }
3110 if (currentSink) {
3111 // If sink exists, update it
3113 sinkIdsList.emplace(sinkId);
3114 continue;
3115 }
3116 auto newSink = createSinkClient(sinkId);
3117 newSink->start();
3119 newSink->setFrameSize(participant.w, participant.h);
3120
3121 for (auto& videoStream : videoStreams)
3122 videoStream->attach(newSink.get());
3123
3124 sinksMap.emplace(sinkId, newSink);
3125 sinkIdsList.emplace(sinkId);
3126 } else {
3127 sinkIdsList.erase(sinkId);
3128 }
3129 }
3130
3131 // remove unused video sinks
3132 for (auto it = sinksMap.begin(); it != sinksMap.end();) {
3133 if (sinkIdsList.find(it->first) == sinkIdsList.end()) {
3134 for (auto& videoStream : videoStreams)
3135 videoStream->detach(it->second.get());
3136 it->second->stop();
3137 it = sinksMap.erase(it);
3138 } else {
3139 it++;
3140 }
3141 }
3142}
3143
3144std::shared_ptr<video::SinkClient>
3145Manager::getSinkClient(const std::string& id)
3146{
3147 const auto& iter = pimpl_->sinkMap_.find(id);
3148 if (iter != std::end(pimpl_->sinkMap_))
3149 if (auto sink = iter->second.lock())
3150 return sink;
3151 return nullptr;
3152}
3153#endif // ENABLE_VIDEO
3154
3155RingBufferPool&
3157{
3158 return *pimpl_->ringbufferpool_;
3159}
3160
3161bool
3163{
3165}
3166
3167const std::shared_ptr<dhtnet::IceTransportFactory>&
3169{
3170 return pimpl_->ice_tf_;
3171}
3172
3175{
3176 return pimpl_->videoManager_.get();
3177}
3178
3179std::vector<libjami::Message>
3181{
3182 if (const auto acc = getAccount(accountID))
3183 return acc->getLastMessages(base_timestamp);
3184 return {};
3185}
3186
3189{
3190 return *pimpl_->sipLink_;
3191}
3192
3193#ifdef ENABLE_PLUGIN
3195Manager::getJamiPluginManager() const
3196{
3197 return *pimpl_->jami_plugin_manager;
3198}
3199#endif
3200
3201std::shared_ptr<dhtnet::ChannelSocket>
3202Manager::gitSocket(std::string_view accountId,
3203 std::string_view deviceId,
3204 std::string_view conversationId)
3205{
3206 if (const auto acc = getAccount<JamiAccount>(accountId))
3207 if (auto convModule = acc->convModule(true))
3208 return convModule->gitSocket(deviceId, conversationId);
3209 return nullptr;
3210}
3211
3212std::map<std::string, std::string>
3214{
3215 if (const auto acc = getAccount<JamiAccount>(accountID))
3216 return acc->getNearbyPeers();
3217 return {};
3218}
3219
3220void
3221Manager::setDefaultModerator(const std::string& accountID, const std::string& peerURI, bool state)
3222{
3223 auto acc = getAccount(accountID);
3224 if (!acc) {
3225 JAMI_ERR("Failed to change default moderator, account %s not found", accountID.c_str());
3226 return;
3227 }
3228
3229 if (state)
3230 acc->addDefaultModerator(peerURI);
3231 else
3232 acc->removeDefaultModerator(peerURI);
3233 saveConfig(acc);
3234}
3235
3236std::vector<std::string>
3238{
3239 auto acc = getAccount(accountID);
3240 if (!acc) {
3241 JAMI_ERR("Failed to get default moderators, account %s not found", accountID.c_str());
3242 return {};
3243 }
3244
3245 auto set = acc->getDefaultModerators();
3246 return std::vector<std::string>(set.begin(), set.end());
3247}
3248
3249void
3251{
3252 if (auto acc = getAccount(accountID))
3253 acc->editConfig(
3254 [&](AccountConfig& config) { config.localModeratorsEnabled = isModEnabled; });
3255}
3256
3257bool
3259{
3260 auto acc = getAccount(accountID);
3261 if (!acc) {
3262 JAMI_ERR("Failed to get local moderators, account %s not found", accountID.c_str());
3263 return true; // Default value
3264 }
3265 return acc->isLocalModeratorsEnabled();
3266}
3267
3268void
3270{
3271 if (auto acc = getAccount(accountID))
3272 acc->editConfig([&](AccountConfig& config) { config.allModeratorsEnabled = allModerators; });
3273}
3274
3275bool
3277{
3278 auto acc = getAccount(accountID);
3279 if (!acc) {
3280 JAMI_ERR("Failed to get all moderators, account %s not found", accountID.c_str());
3281 return true; // Default value
3282 }
3283 return acc->isAllModerators();
3284}
3285
3286void
3287Manager::insertGitTransport(git_smart_subtransport* tr, std::unique_ptr<P2PSubTransport>&& sub)
3288{
3289 std::lock_guard lk(pimpl_->gitTransportsMtx_);
3290 pimpl_->gitTransports_[tr] = std::move(sub);
3291}
3292
3293void
3295{
3296 std::lock_guard lk(pimpl_->gitTransportsMtx_);
3297 pimpl_->gitTransports_.erase(tr);
3298}
3299
3300dhtnet::tls::CertificateStore&
3301Manager::certStore(const std::string& accountId) const
3302{
3303 if (const auto& account = getAccount<JamiAccount>(accountId)) {
3304 return account->certStore();
3305 }
3306 throw std::runtime_error("No account found");
3307}
3308
3309} // 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:2372
std::map< std::string, std::string > getAccountDetails(const std::string &accountID) const
Retrieve details about a given account.
Definition manager.cpp:2679
void enableLocalModerators(const std::string &accountID, bool state)
Definition manager.cpp:3250
void loadAccountAndConversation(const std::string &accountId, bool loadAll, const std::string &convId)
Definition manager.cpp:2993
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:3237
std::vector< std::string > getAccountList() const
Get account list.
Definition manager.cpp:2666
bool isLocalModeratorsEnabled(const std::string &accountID)
Definition manager.cpp:3258
void setAccountsOrder(const std::string &order)
Set the account order in the config file.
Definition manager.cpp:2657
bool startRecordedFilePlayback(const std::string &)
Start playback fo a recorded file if and only if audio layer is not already started.
Definition manager.cpp:2321
void setAllModerators(const std::string &accountID, bool allModerators)
Definition manager.cpp:3269
std::vector< std::string_view > loadAccountOrder() const
Load the accounts order set by the user from the jamirc config file.
Definition manager.cpp:2817
void setAutoAnswer(bool enable)
Definition manager.cpp:712
void insertGitTransport(git_smart_subtransport *tr, std::unique_ptr< P2PSubTransport > &&sub)
Definition manager.cpp:3287
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:3050
static LIBJAMI_TEST_EXPORT Manager & instance()
Definition manager.cpp:676
void setAccountActive(const std::string &accountID, bool active, bool shutdownConnections)
Definition manager.cpp:2975
void eraseGitTransport(git_smart_subtransport *tr)
Definition manager.cpp:3294
void registerAccounts()
Send registration for all enabled accounts.
Definition manager.cpp:2911
void setHistoryLimit(int days)
Set the maximum number of days to keep in the history.
Definition manager.cpp:2358
std::map< std::string, std::string > getVolatileAccountDetails(const std::string &accountID) const
Retrieve volatile details such as recent registration errors.
Definition manager.cpp:2693
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:2637
bool getIsAlwaysRecording() const
Get is always recording functionality.
Definition manager.cpp:2285
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:2707
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:2753
void saveConfig()
Save config to file.
Definition manager.cpp:1750
std::vector< std::string > getCallList() const
Get list of calls (internal subcalls are filter-out)
Definition manager.cpp:2900
void setNoiseSuppressState(const std::string &state)
Set the noise reduction engine state in the current audio layer.
Definition manager.cpp:2458
bool toggleRecordingCall(const std::string &accountId, const std::string &id)
Set recording on / off Start recording.
Definition manager.cpp:2298
void bindCallToConference(Call &call, Conference &conf)
Definition manager.cpp:636
SIPVoIPLink & sipVoIPLink() const
Definition manager.cpp:3188
static std::atomic_bool initialized
Definition manager.h:108
VideoManager * getVideoManager() const
Definition manager.cpp:3174
void recordingPlaybackSeek(const double value)
Definition manager.cpp:2343
AccountFactory accountFactory
Definition manager.h:853
std::string getAudioManager() const
Get the audio manager.
Definition manager.cpp:2414
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:3202
void setDefaultModerator(const std::string &accountID, const std::string &peerURI, bool state)
Definition manager.cpp:3221
void stopRecordedFilePlayback()
Stop playback of recorded file.
Definition manager.cpp:2349
std::map< std::string, std::string > getNearbyPeers(const std::string &accountID)
Definition manager.cpp:3213
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:2386
RingBufferPool & getRingBufferPool()
Return a pointer to the instance of the RingBufferPool.
Definition manager.cpp:3156
const std::shared_ptr< dhtnet::IceTransportFactory > & getIceTransportFactory()
Definition manager.cpp:3168
int getRingingTimeout() const
Get ringing timeout (number of seconds after which a call will enter BUSY state if not answered).
Definition manager.cpp:2380
int getAudioInputDeviceIndex(const std::string &name)
Get index of an audio device.
Definition manager.cpp:2420
static bool syncOnRegister
Definition manager.h:114
std::vector< libjami::Message > getLastMessages(const std::string &accountID, const uint64_t &base_timestamp)
Definition manager.cpp:3180
VoipPreference voipPreferences
Voip related preferences.
Definition manager.h:85
dhtnet::tls::CertificateStore & certStore(const std::string &accountId) const
Definition manager.cpp:3301
void removeAccounts()
Definition manager.cpp:2810
bool hasAccount(const std::string &accountID)
Definition manager.cpp:3162
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:2936
int getHistoryLimit() const
Get the maximum number of days to keep in the history.
Definition manager.cpp:2366
bool isAllModerators(const std::string &accountID)
Definition manager.cpp:3276
std::string getCurrentAudioOutputPlugin() const
Get current alsa plugin.
Definition manager.cpp:2446
std::mt19937_64 getSeededRandomEngine()
Definition manager.cpp:2737
unsigned dhtLogLevel
Definition manager.h:852
std::shared_ptr< AudioLayer > getAudioDriver()
Accessor to audiodriver.
Definition manager.cpp:3044
void setIsAlwaysRecording(bool isAlwaysRec)
Set is always recording functionality, every calls will then be set in RECORDING mode once answered.
Definition manager.cpp:2291
std::string getNoiseSuppressState() const
Get the noise reduction engine state from the current audio layer.
Definition manager.cpp:2452
void removeAccount(const std::string &accountID, bool flush=false)
Delete an existing account, unregister VoIPLink associated, and purge from configuration.
Definition manager.cpp:2789
bool isAGCEnabled() const
Definition manager.cpp:2464
void setAGCState(bool enabled)
Definition manager.cpp:2470
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:2920
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:2743
AudioPreference audioPreference
Audio preferences.
Definition manager.h:90
int getAudioOutputDeviceIndex(const std::string &name)
Definition manager.cpp:2433
AudioFormat hardwareAudioFormatChanged(AudioFormat format)
Callback called when the audio layer initialised with its preferred format.
Definition manager.cpp:2631
int getMessageStatus(uint64_t id) const
Definition manager.cpp:2961
std::size_t accountCount() const
Definition manager.h:749
int loadAccountMap(const YAML::Node &node)
Load the account map from configuration.
Definition manager.cpp:2823
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:2492
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:1410
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:2507
std::mutex sinksMutex_
Protected sinks access.
Definition manager.cpp:342
void addMainParticipant(Conference &conf)
Definition manager.cpp:1400
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:2479
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