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