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