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