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