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