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