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