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