managerimpl.cpp 75.3 KB
Newer Older
jpbl's avatar
jpbl committed
1
/*
Adrien Béraud's avatar
Adrien Béraud committed
2
 *  Copyright (C) 2004-2015 Savoir-Faire Linux Inc.
3
 *  Author: Alexandre Bourget <alexandre.bourget@savoirfairelinux.com>
jpbl's avatar
jpbl committed
4
 *  Author: Yan Morin <yan.morin@savoirfairelinux.com>
5
 *  Author: Laurielle Lea <laurielle.lea@savoirfairelinux.com>
6
 *  Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com>
7
 *  Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
8
 *  Author: Guillaume Carmel-Archambault <guillaume.carmel-archambault@savoirfairelinux.com>
9 10
 *  Author : Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
 *
jpbl's avatar
jpbl committed
11 12
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
13
 *  the Free Software Foundation; either version 3 of the License, or
jpbl's avatar
jpbl committed
14
 *  (at your option) any later version.
15
 *
jpbl's avatar
jpbl committed
16 17 18 19
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
20
 *
jpbl's avatar
jpbl committed
21 22
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
23
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
24 25 26 27 28 29 30 31 32 33 34
 *
 *  Additional permission under GNU GPL version 3 section 7:
 *
 *  If you modify this program, or any covered work, by linking or
 *  combining it with the OpenSSL project's OpenSSL library (or a
 *  modified version of that library), containing parts covered by the
 *  terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
 *  grants you additional permission to convey the resulting work.
 *  Corresponding Source for a non-source form of such a combination
 *  shall include the source code for the parts of OpenSSL used as well
 *  as that of the covered work.
jpbl's avatar
jpbl committed
35 36
 */

37
#ifdef HAVE_CONFIG_H
38
#include "config.h"
39
#endif
40

41
#include "logger.h"
42
#include "managerimpl.h"
43
#include "account_schema.h"
44
#include "plugin_manager.h"
45

46
#include "fileutils.h"
47
#include "map_utils.h"
48
#include "account.h"
49
#include "string_utils.h"
Adrien Béraud's avatar
Adrien Béraud committed
50
#if HAVE_DHT
51
#include "ringdht/ringaccount.h"
Adrien Béraud's avatar
Adrien Béraud committed
52
#endif
53

Guillaume Roguez's avatar
Guillaume Roguez committed
54 55 56 57
#include "call_factory.h"

#include "sip/sip_utils.h"

58 59
#include "im/instant_messaging.h"

60
#include "numbercleaner.h"
61
#include "config/yamlparser.h"
62

63
#if HAVE_ALSA
64
#include "audio/alsa/alsalayer.h"
65
#endif
66

67
#include "audio/sound/tonelist.h"
68 69
#include "audio/sound/audiofile.h"
#include "audio/sound/dtmf.h"
70
#include "audio/ringbufferpool.h"
Emeric Vigier's avatar
Emeric Vigier committed
71
#include "history/history.h"
72
#include "manager.h"
73

74
#include "client/configurationmanager.h"
75
#include "client/callmanager.h"
76
#include "client/presencemanager.h"
77

Adrien Béraud's avatar
Adrien Béraud committed
78
#ifdef RING_VIDEO
79
#include "client/videomanager.h"
80
#endif
81

82
#include "conference.h"
83
#include "ice_transport.h"
84

85
#include <cerrno>
86
#include <algorithm>
87
#include <ctime>
jpbl's avatar
jpbl committed
88 89 90
#include <cstdlib>
#include <iostream>
#include <fstream>
91
#include <sstream>
jpbl's avatar
jpbl committed
92
#include <sys/types.h> // mkdir(2)
93
#include <sys/stat.h>  // mkdir(2)
94
#include <memory>
95

Guillaume Roguez's avatar
Guillaume Roguez committed
96
namespace ring {
97

98 99
std::atomic_bool ManagerImpl::initialized = {false};

100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
static void
copy_over(const std::string &srcPath, const std::string &destPath)
{
    std::ifstream src(srcPath.c_str());
    std::ofstream dest(destPath.c_str());
    dest << src.rdbuf();
    src.close();
    dest.close();
}

// Creates a backup of the file at "path" with a .bak suffix appended
static void
make_backup(const std::string &path)
{
    const std::string backup_path(path + ".bak");
    copy_over(path, backup_path);
}
Guillaume Roguez's avatar
Guillaume Roguez committed
117

118 119 120 121 122 123 124 125
// Restore last backup of the configuration file
static void
restore_backup(const std::string &path)
{
    const std::string backup_path(path + ".bak");
    copy_over(backup_path, path);
}

126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
/**
 * Set pjsip's log level based on the SIPLOGLEVEL environment variable.
 * SIPLOGLEVEL = 0 minimum logging
 * SIPLOGLEVEL = 6 maximum logging
 */

/** Environment variable used to set pjsip's logging level */
static constexpr const char* SIPLOGLEVEL = "SIPLOGLEVEL";

static void
setSipLogLevel()
{
    char* envvar = getenv(SIPLOGLEVEL);
    int level = 0;

    if (envvar != nullptr) {
        if (not (std::istringstream(envvar) >> level))
            level = 0;

        // From 0 (min) to 6 (max)
        level = std::max(0, std::min(level, 6));
    }

    pj_log_set_level(level);
}

152 153
void
ManagerImpl::loadDefaultAccountMap()
154
{
155
    accountFactory_.initIP2IPAccount();
156 157
}

158
ManagerImpl::ManagerImpl() :
159 160
    pluginManager_(new PluginManager)
    , preferences(), voipPreferences(),
161
    hookPreference(),  audioPreference(), shortcutPreferences(),
162 163 164 165 166 167 168 169 170 171
    hasTriedToRegister_(false), audioCodecFactory(*pluginManager_),
    callManager_(new CallManager), configurationManager_(new ConfigurationManager),
    presenceManager_(new PresenceManager)
#ifdef RING_VIDEO
    , videoManager_(new VideoManager)
#endif
#ifdef USE_NETWORKMANAGER
    , networkManager_(0)
#endif
    , currentCallMutex_(), dtmfKey_(), dtmfBuf_(0, AudioFormat::MONO()),
172
    toneMutex_(), telephoneTone_(), audiofile_(), audioLayerMutex_(),
173
    waitingCalls_(), waitingCallsMutex_(), path_()
Guillaume Roguez's avatar
Guillaume Roguez committed
174
    , ringbufferpool_(new RingBufferPool)
175 176
    , callFactory(), conferenceMap_(), history_()
    , accountFactory_(), ice_tf_()
Emmanuel Milou's avatar
[#2402]  
Emmanuel Milou committed
177
{
178 179 180 181 182
    // initialize random generator
    // mt19937_64 should be seeded with 2 x 32 bits
    std::random_device rdev;
    std::seed_seq seed {rdev(), rdev()};
    rand_.seed(seed);
jpbl's avatar
jpbl committed
183 184
}

185
ManagerImpl::~ManagerImpl()
Guillaume Roguez's avatar
Guillaume Roguez committed
186
{}
187

188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
CallManager*
ManagerImpl::getCallManager()
{
    return callManager_.get();
}

ConfigurationManager*
ManagerImpl::getConfigurationManager()
{
    return configurationManager_.get();
}

PresenceManager*
ManagerImpl::getPresenceManager()
{
    return presenceManager_.get();
}

#ifdef RING_VIDEO
VideoManager*
ManagerImpl::getVideoManager()
{
    return videoManager_.get();
}
#endif

Guillaume Roguez's avatar
Guillaume Roguez committed
214 215
bool
ManagerImpl::parseConfiguration()
Emmanuel Milou's avatar
[#2402]  
Emmanuel Milou committed
216
{
217
    bool result = true;
218

219
    try {
Tristan Matthews's avatar
Tristan Matthews committed
220 221 222 223
        YAML::Node parsedFile = YAML::LoadFile(path_);
        const int error_count = loadAccountMap(parsedFile);

        if (error_count > 0) {
Adrien Béraud's avatar
Adrien Béraud committed
224
            RING_WARN("Errors while parsing %s", path_.c_str());
Tristan Matthews's avatar
Tristan Matthews committed
225
            result = false;
226
        }
Tristan Matthews's avatar
Tristan Matthews committed
227
    } catch (const YAML::BadFile &e) {
Adrien Béraud's avatar
Adrien Béraud committed
228
        RING_WARN("Could not open config file: creating default account map");
Tristan Matthews's avatar
Tristan Matthews committed
229
        loadDefaultAccountMap();
230 231 232 233 234
    }

    return result;
}

Guillaume Roguez's avatar
Guillaume Roguez committed
235 236
void
ManagerImpl::init(const std::string &config_file)
237
{
238 239 240
    // FIXME: this is no good
    initialized = true;

241 242 243 244 245
#define TRY(ret) do {      \
        if (ret != PJ_SUCCESS)                               \
            throw std::runtime_error(#ret " failed");        \
    } while (0)

246 247 248
    srand(time(NULL)); // to get random number for RANDOM_PORT

    // Initialize PJSIP (SIP and ICE implementation)
249
    TRY(pj_init());
250
    setSipLogLevel();
251 252 253 254
    TRY(pjlib_util_init());
    TRY(pjnath_init());
#undef TRY

255 256 257
    RING_DBG("pjsip version %s for %s initialized",
             pj_get_version(), PJ_OS_NAME);

258 259
    ice_tf_.reset(new IceTransportFactory());

260
    path_ = config_file.empty() ? retrieveConfigPath() : config_file;
Adrien Béraud's avatar
Adrien Béraud committed
261
    RING_DBG("Configuration file path: %s", path_.c_str());
262 263 264

    bool no_errors = true;

265 266 267
    // manager can restart without being recreated (android)
    finished_ = false;

268 269
    try {
        no_errors = parseConfiguration();
Tristan Matthews's avatar
Tristan Matthews committed
270
    } catch (const YAML::Exception &e) {
Adrien Béraud's avatar
Adrien Béraud committed
271
        RING_ERR("%s", e.what());
272
        no_errors = false;
273 274
    }

275
    // always back up last error-free configuration
276
    if (no_errors) {
277
        make_backup(path_);
278 279
    } else {
        // restore previous configuration
Adrien Béraud's avatar
Adrien Béraud committed
280
        RING_WARN("Restoring last working configuration");
Guillaume Roguez's avatar
Guillaume Roguez committed
281

282
        try {
283 284
            // remove accounts from broken configuration
            removeAccounts();
285 286
            restore_backup(path_);
            parseConfiguration();
Tristan Matthews's avatar
Tristan Matthews committed
287
        } catch (const YAML::Exception &e) {
Adrien Béraud's avatar
Adrien Béraud committed
288 289
            RING_ERR("%s", e.what());
            RING_WARN("Restoring backup failed, creating default account map");
290 291 292
            loadDefaultAccountMap();
        }
    }
293

294
    initAudioDriver();
295

Tristan Matthews's avatar
Tristan Matthews committed
296
    {
Adrien Béraud's avatar
Adrien Béraud committed
297
        std::lock_guard<std::mutex> lock(audioLayerMutex_);
Guillaume Roguez's avatar
Guillaume Roguez committed
298

Tristan Matthews's avatar
Tristan Matthews committed
299
        if (audiodriver_) {
300
            {
Adrien Béraud's avatar
Adrien Béraud committed
301
                std::lock_guard<std::mutex> toneLock(toneMutex_);
Guillaume Roguez's avatar
Guillaume Roguez committed
302
                telephoneTone_.reset(new TelephoneTone(preferences.getZoneToneChoice(), audiodriver_->getSampleRate()));
303
            }
Guillaume Roguez's avatar
Guillaume Roguez committed
304
            dtmfKey_.reset(new DTMF(getRingBufferPool().getInternalSamplingRate()));
Tristan Matthews's avatar
Tristan Matthews committed
305
        }
306
    }
307

308
    history_.load(preferences.getHistoryLimit());
Stepan Salenikovich's avatar
Stepan Salenikovich committed
309

310
    registerAccounts();
jpbl's avatar
jpbl committed
311 312
}

Guillaume Roguez's avatar
Guillaume Roguez committed
313 314 315
void
ManagerImpl::setPath(const std::string &path)
{
316
    history_.setPath(path);
Emeric Vigier's avatar
Emeric Vigier committed
317 318
}

Guillaume Roguez's avatar
Guillaume Roguez committed
319 320
void
ManagerImpl::finish()
321
{
322 323
    bool expected = false;
    if (not finished_.compare_exchange_strong(expected, true))
324 325
        return;

326
    try {
Guillaume Roguez's avatar
Guillaume Roguez committed
327 328
        // Forbid call creation
        callFactory.forbid();
329

Guillaume Roguez's avatar
Guillaume Roguez committed
330
        // Hangup all remaining active calls
Adrien Béraud's avatar
Adrien Béraud committed
331
        RING_DBG("Hangup %zu remaining call(s)", callFactory.callCount());
Guillaume Roguez's avatar
Guillaume Roguez committed
332 333 334
        for (const auto call : callFactory.getAllCalls())
            hangupCall(call->getCallId());
        callFactory.clear();
335

Guillaume Roguez's avatar
Guillaume Roguez committed
336
        // Save accounts config and call's history
337
        saveConfig();
Guillaume Roguez's avatar
Guillaume Roguez committed
338
        saveHistory();
339

Guillaume Roguez's avatar
Guillaume Roguez committed
340
        // Disconnect accounts, close link stacks and free allocated ressources
341
        unregisterAccounts();
342
        accountFactory_.clear();
343

344 345
        {
            std::lock_guard<std::mutex> lock(audioLayerMutex_);
346

347
            audiodriver_.reset();
348
        }
349 350 351

        ice_tf_.reset();
        pj_shutdown();
352
    } catch (const VoipLinkException &err) {
Adrien Béraud's avatar
Adrien Béraud committed
353
        RING_ERR("%s", err.what());
354
    }
jpbl's avatar
jpbl committed
355 356
}

Guillaume Roguez's avatar
Guillaume Roguez committed
357
bool
358
ManagerImpl::isCurrentCall(const Call& call) const
Emmanuel Milou's avatar
[#2402]  
Emmanuel Milou committed
359
{
360
    return currentCall_.get() == &call;
jpbl's avatar
jpbl committed
361 362
}

Guillaume Roguez's avatar
Guillaume Roguez committed
363 364
bool
ManagerImpl::hasCurrentCall() const
Emmanuel Milou's avatar
[#2402]  
Emmanuel Milou committed
365
{
366
    return static_cast<bool>(currentCall_);
jpbl's avatar
jpbl committed
367 368
}

369 370 371 372 373 374 375
std::shared_ptr<Call>
ManagerImpl::getCurrentCall() const
{
    return currentCall_;
}

const std::string
376
ManagerImpl::getCurrentCallId() const
Emmanuel Milou's avatar
[#2402]  
Emmanuel Milou committed
377
{
378
    return currentCall_ ? currentCall_->getCallId() : "";
jpbl's avatar
jpbl committed
379 380
}

381 382 383 384 385
/**
 * Set current call ID to empty string
 */
void
ManagerImpl::unsetCurrentCall()
386
{
387
    currentCall_.reset();
388 389
}

390 391 392 393 394 395
/**
 * Switch of current call id
 * @param id The new callid
 */
void
ManagerImpl::switchCall(std::shared_ptr<Call> call)
Emmanuel Milou's avatar
[#2402]  
Emmanuel Milou committed
396
{
Adrien Béraud's avatar
Adrien Béraud committed
397
    std::lock_guard<std::mutex> m(currentCallMutex_);
Adrien Béraud's avatar
Adrien Béraud committed
398
    RING_DBG("----- Switch current call id to '%s' -----",
399
          call ? call->getCallId().c_str() : "<nullptr>");
400
    currentCall_ = call;
jpbl's avatar
jpbl committed
401 402 403 404 405
}

///////////////////////////////////////////////////////////////////////////////
// Management of events' IP-phone user
///////////////////////////////////////////////////////////////////////////////
406
/* Main Thread */
Emmanuel Milou's avatar
Emmanuel Milou committed
407

Guillaume Roguez's avatar
Guillaume Roguez committed
408
bool
Guillaume Roguez's avatar
Guillaume Roguez committed
409
ManagerImpl::outgoingCall(const std::string& preferred_account_id,
Guillaume Roguez's avatar
Guillaume Roguez committed
410 411 412
                          const std::string& call_id,
                          const std::string& to,
                          const std::string& conf_id)
Emmanuel Milou's avatar
[#2402]  
Emmanuel Milou committed
413
{
414
    if (call_id.empty()) {
Adrien Béraud's avatar
Adrien Béraud committed
415
        RING_DBG("New outgoing call abort, missing callid");
Emmanuel Lepage's avatar
Emmanuel Lepage committed
416 417
        return false;
    }
418

419
    // Call ID must be unique
420
    if (isValidCall(call_id)) {
Adrien Béraud's avatar
Adrien Béraud committed
421
        RING_ERR("Call id already exists in outgoing call");
422 423 424
        return false;
    }

Adrien Béraud's avatar
Adrien Béraud committed
425
    RING_DBG("New outgoing call %s to %s", call_id.c_str(), to.c_str());
426

427 428
    stopTone();

429
    std::string current_call_id(getCurrentCallId());
430

431
    std::string prefix(hookPreference.getNumberAddPrefix());
432

433
    std::string to_cleaned(NumberCleaner::clean(to, prefix));
Emmanuel Milou's avatar
Emmanuel Milou committed
434

435 436
    // in any cases we have to detach from current communication
    if (hasCurrentCall()) {
Adrien Béraud's avatar
Adrien Béraud committed
437
        RING_DBG("Has current call (%s) put it onhold", current_call_id.c_str());
438

439
        // if this is not a conference and this and is not a conference participant
440
        if (not isConference(current_call_id) and not isConferenceParticipant(current_call_id))
441
            onHoldCall(current_call_id);
442
        else if (isConference(current_call_id) and not isConferenceParticipant(call_id))
Guillaume Roguez's avatar
Guillaume Roguez committed
443
            detachParticipant(RingBufferPool::DEFAULT_ID);
444
    }
445

446 447
    std::shared_ptr<Call> call;

448
    try {
Adrien Béraud's avatar
Adrien Béraud committed
449
        /* RING_WARN: after this call the account_id is obsolete
Guillaume Roguez's avatar
Guillaume Roguez committed
450 451
         * as the factory may decide to use another account (like IP2IP).
         */
Adrien Béraud's avatar
Adrien Béraud committed
452
        RING_DBG("New outgoing call to %s", to_cleaned.c_str());
453
        call = newOutgoingCall(call_id, to_cleaned, preferred_account_id);
454
    } catch (const std::exception &e) {
Adrien Béraud's avatar
Adrien Béraud committed
455
        RING_ERR("%s", e.what());
456
        return false;
457
    }
458 459 460 461 462 463 464 465 466 467 468 469 470 471

    if (not call)
        return false;

    // try to reverse match the peer name using the cache
    if (call->getDisplayName().empty()) {
        const auto& name = history_.getNameFromHistory(call->getPeerNumber(),
                                                       call->getAccountId());
        const std::string pseudo_contact_name(name);
        if (not pseudo_contact_name.empty())
            call->setDisplayName(pseudo_contact_name);
    }
    switchCall(call);
    call->setConfId(conf_id);
472
    return true;
jpbl's avatar
jpbl committed
473 474
}

yanmorin's avatar
 
yanmorin committed
475
//THREAD=Main : for outgoing Call
Guillaume Roguez's avatar
Guillaume Roguez committed
476 477
bool
ManagerImpl::answerCall(const std::string& call_id)
Emmanuel Milou's avatar
[#2402]  
Emmanuel Milou committed
478
{
479
    bool result = true;
480

481 482
    auto call = getCallFromCallID(call_id);
    if (!call) {
Adrien Béraud's avatar
Adrien Béraud committed
483
        RING_ERR("Call %s is NULL", call_id.c_str());
484 485
        return false;
    }
486

Adrien Béraud's avatar
Adrien Béraud committed
487
    // If ring is ringing
488
    stopTone();
489

490
    // store the current call id
491
    std::string current_call_id(getCurrentCallId());
492

493 494
    // in any cases we have to detach from current communication
    if (hasCurrentCall()) {
495

Adrien Béraud's avatar
Adrien Béraud committed
496
        RING_DBG("Currently conversing with %s", current_call_id.c_str());
497

498
        if (not isConference(current_call_id) and not isConferenceParticipant(current_call_id)) {
Adrien Béraud's avatar
Adrien Béraud committed
499
            RING_DBG("Answer call: Put the current call (%s) on hold", current_call_id.c_str());
500
            onHoldCall(current_call_id);
501
        } else if (isConference(current_call_id) and not isConferenceParticipant(call_id)) {
502
            // if we are talking to a conference and we are answering an incoming call
Adrien Béraud's avatar
Adrien Béraud committed
503
            RING_DBG("Detach main participant from conference");
Guillaume Roguez's avatar
Guillaume Roguez committed
504
            detachParticipant(RingBufferPool::DEFAULT_ID);
505 506
        }
    }
507

508
    try {
509
        call->answer();
510
    } catch (const std::runtime_error &e) {
Adrien Béraud's avatar
Adrien Béraud committed
511
        RING_ERR("%s", e.what());
512
        result = false;
513
    }
Alexandre Savard's avatar
Alexandre Savard committed
514

515
    // if it was waiting, it's waiting no more
516
    removeWaitingCall(call_id);
517

518
    // if we dragged this call into a conference already
519
    if (isConferenceParticipant(call_id))
520
        switchCall(callFactory.getCall(call->getConfId()));
521
    else
522
        switchCall(call);
523

524
    // Connect streams
525
    addStream(*call);
526

527
    // Start recording if set in preference
528
    if (audioPreference.getIsAlwaysRecording())
529
        toggleRecordingCall(call_id);
530

531
    getCallManager()->callStateChanged(call_id, "CURRENT");
532

533
    return result;
jpbl's avatar
jpbl committed
534 535
}

Guillaume Roguez's avatar
Guillaume Roguez committed
536 537
void
ManagerImpl::checkAudio()
538 539
{
    if (getCallList().empty()) {
Adrien Béraud's avatar
Adrien Béraud committed
540
        std::lock_guard<std::mutex> lock(audioLayerMutex_);
541 542 543
        if (audiodriver_)
            audiodriver_->stopStream();
    }
jpbl's avatar
jpbl committed
544 545
}

yanmorin's avatar
 
yanmorin committed
546
//THREAD=Main
Guillaume Roguez's avatar
Guillaume Roguez committed
547 548
bool
ManagerImpl::hangupCall(const std::string& callId)
Emmanuel Milou's avatar
[#2402]  
Emmanuel Milou committed
549
{
550
    // store the current call id
551
    std::string currentCallId(getCurrentCallId());
yanmorin's avatar
 
yanmorin committed
552

553
    stopTone();
554

Adrien Béraud's avatar
Adrien Béraud committed
555
    RING_DBG("Send call state change (HUNGUP) for id %s", callId.c_str());
556
    getCallManager()->callStateChanged(callId, "HUNGUP");
557

558
    /* We often get here when the call was hungup before being created */
559 560
    auto call = getCallFromCallID(callId);
    if (not call) {
Adrien Béraud's avatar
Adrien Béraud committed
561
        RING_WARN("Could not hang up non-existant call %s", callId.c_str());
562
        checkAudio();
563
        return false;
564 565
    }

566
    // Disconnect streams
567
    removeStream(*call);
568

569
    if (isConferenceParticipant(callId)) {
570
        removeParticipant(callId);
571
    } else {
572 573
        // we are not participating in a conference, current call switched to ""
        if (not isConference(currentCallId))
574
            unsetCurrentCall();
575
    }
576

577
    try {
578 579 580 581
        history_.addCall(call.get(), preferences.getHistoryLimit());
        call->hangup(0);
        checkAudio();
        saveHistory();
582
    } catch (const VoipLinkException &e) {
Adrien Béraud's avatar
Adrien Béraud committed
583
        RING_ERR("%s", e.what());
584
        return false;
585
    }
586

587
    return true;
jpbl's avatar
jpbl committed
588 589
}

Guillaume Roguez's avatar
Guillaume Roguez committed
590 591
bool
ManagerImpl::hangupConference(const std::string& id)
Emmanuel Milou's avatar
[#2402]  
Emmanuel Milou committed
592
{
Adrien Béraud's avatar
Adrien Béraud committed
593
    RING_DBG("Hangup conference %s", id.c_str());
Alexandre Savard's avatar
Alexandre Savard committed
594

595
    ConferenceMap::iterator iter_conf = conferenceMap_.find(id);
Alexandre Savard's avatar
Alexandre Savard committed
596

597
    if (iter_conf != conferenceMap_.end()) {
598
        auto conf = iter_conf->second;
Alexandre Savard's avatar
Alexandre Savard committed
599

600 601
        if (conf) {
            ParticipantSet participants(conf->getParticipantList());
602

603 604
            for (const auto &item : participants)
                hangupCall(item);
605
        } else {
Adrien Béraud's avatar
Adrien Béraud committed
606
            RING_ERR("No such conference %s", id.c_str());
607 608
            return false;
        }
609
    }
Alexandre Savard's avatar
Alexandre Savard committed
610

611
    unsetCurrentCall();
612

613
    return true;
Alexandre Savard's avatar
Alexandre Savard committed
614 615
}

yanmorin's avatar
 
yanmorin committed
616
//THREAD=Main
Guillaume Roguez's avatar
Guillaume Roguez committed
617 618
bool
ManagerImpl::onHoldCall(const std::string& callId)
Emmanuel Milou's avatar
[#2402]  
Emmanuel Milou committed
619
{
620
    bool result = true;
621

622
    stopTone();
623

624
    std::string current_call_id(getCurrentCallId());
625

626 627
    if (auto call = getCallFromCallID(callId)) {
        try {
628
            call->onhold();
629 630
            removeStream(*call); // Unbind calls in main buffer
        } catch (const VoipLinkException &e) {
Adrien Béraud's avatar
Adrien Béraud committed
631
            RING_ERR("%s", e.what());
632
            result = false;
633
        }
634
    } else {
Adrien Béraud's avatar
Adrien Béraud committed
635
        RING_DBG("CallID %s doesn't exist in call onHold", callId.c_str());
636
        return false;
637
    }
638

639
    // Remove call from teh queue if it was still there
640
    removeWaitingCall(callId);
641

642
    // keeps current call id if the action is not holding this call or a new outgoing call
643
    // this could happen in case of a conference
644
    if (current_call_id == callId)
645
        unsetCurrentCall();
646

647
    getCallManager()->callStateChanged(callId, "HOLD");
648

649
    return result;
yanmorin's avatar
 
yanmorin committed
650 651 652
}

//THREAD=Main
Guillaume Roguez's avatar
Guillaume Roguez committed
653 654
bool
ManagerImpl::offHoldCall(const std::string& callId)
Emmanuel Milou's avatar
[#2402]  
Emmanuel Milou committed
655
{
656
    bool result = true;
657

658
    stopTone();
659

Tristan Matthews's avatar
Tristan Matthews committed
660
    const std::string currentCallId(getCurrentCallId());
661

Tristan Matthews's avatar
Tristan Matthews committed
662
    // Place current call on hold if it isn't
663
    if (hasCurrentCall()) {
Tristan Matthews's avatar
Tristan Matthews committed
664
        if (not isConference(currentCallId) and not isConferenceParticipant(currentCallId)) {
Adrien Béraud's avatar
Adrien Béraud committed
665
            RING_DBG("Has current call (%s), put on hold", currentCallId.c_str());
666
            onHoldCall(currentCallId);
667 668
        } else if (isConference(currentCallId) && callId != currentCallId) {
            holdConference(currentCallId);
669
        } else if (isConference(currentCallId) and not isConferenceParticipant(callId))
Guillaume Roguez's avatar
Guillaume Roguez committed
670
            detachParticipant(RingBufferPool::DEFAULT_ID);
671
    }
alexandresavard's avatar
alexandresavard committed
672

673
    std::shared_ptr<Call> call;
674
    try {
675 676
        call = getCallFromCallID(callId);
        if (call)
677
            call->offhold();
678 679
        else
            result = false;
680
    } catch (const VoipLinkException &e) {
Adrien Béraud's avatar
Adrien Béraud committed
681
        RING_ERR("%s", e.what());
682
        return false;
683
    }
684

685
    getCallManager()->callStateChanged(callId, "UNHOLD");
686

687 688 689 690
    if (isConferenceParticipant(callId))
        switchCall(getCallFromCallID(call->getConfId()));
    else
        switchCall(call);
691

692
    addStream(*call);
693

694
    return result;
jpbl's avatar
jpbl committed
695 696
}

yanmorin's avatar
 
yanmorin committed
697
//THREAD=Main
Guillaume Roguez's avatar
Guillaume Roguez committed
698 699
bool
ManagerImpl::transferCall(const std::string& callId, const std::string& to)
Emmanuel Milou's avatar
[#2402]  
Emmanuel Milou committed
700
{
701
    if (isConferenceParticipant(callId)) {
702
        removeParticipant(callId);
Tristan Matthews's avatar
cleanup  
Tristan Matthews committed
703
    } else if (not isConference(getCurrentCallId()))
704
        unsetCurrentCall();
705

706