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