manager.cpp 84.3 KB
Newer Older
1
/*
Sébastien Blin's avatar
Sébastien Blin committed
2
 *  Copyright (C) 2004-2019 Savoir-faire Linux Inc.
Guillaume Roguez's avatar
Guillaume Roguez committed
3
 *
4 5 6 7 8 9 10 11
 *  Author: Alexandre Bourget <alexandre.bourget@savoirfairelinux.com>
 *  Author: Yan Morin <yan.morin@savoirfairelinux.com>
 *  Author: Laurielle Lea <laurielle.lea@savoirfairelinux.com>
 *  Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com>
 *  Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
 *  Author: Guillaume Carmel-Archambault <guillaume.carmel-archambault@savoirfairelinux.com>
 *  Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com>
 *  Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>
Adrien Béraud's avatar
Adrien Béraud committed
12
 *  Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com>
13 14 15 16 17 18 19 20 21 22 23 24 25
 *
 *  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
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  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.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
26
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
27 28
 */

29 30 31 32 33
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "manager.h"
Guillaume Roguez's avatar
Guillaume Roguez committed
34 35

#include "logger.h"
36 37 38 39 40 41
#include "account_schema.h"

#include "fileutils.h"
#include "map_utils.h"
#include "account.h"
#include "string_utils.h"
Adrien Béraud's avatar
Adrien Béraud committed
42
#include "jamidht/jamiaccount.h"
43 44
#include <opendht/rng.h>
using random_device = dht::crypto::random_device;
45 46 47 48

#include "call_factory.h"

#include "sip/sip_utils.h"
49
#include "sip/sipvoiplink.h"
50
#include "sip/sipaccount.h"
51 52 53 54 55 56 57 58 59

#include "im/instant_messaging.h"

#include "config/yamlparser.h"

#if HAVE_ALSA
#include "audio/alsa/alsalayer.h"
#endif

60
#include "media/localrecordermanager.h"
61 62 63 64
#include "audio/sound/tonelist.h"
#include "audio/sound/dtmf.h"
#include "audio/ringbufferpool.h"

Adrien Béraud's avatar
Adrien Béraud committed
65
#ifdef ENABLE_VIDEO
66
#include "client/videomanager.h"
Alexandre Lision's avatar
Alexandre Lision committed
67
#include "video/video_scaler.h"
68 69 70 71 72 73
#endif

#include "conference.h"
#include "ice_transport.h"

#include "client/ring_signal.h"
74
#include "dring/call_const.h"
Adrien Béraud's avatar
Adrien Béraud committed
75
#include "dring/account_const.h"
76 77 78

#include "libav_utils.h"
#include "video/sinkclient.h"
79
#include "audio/tonecontrol.h"
80

81 82
#include "data_transfer.h"

Adrien Béraud's avatar
Adrien Béraud committed
83 84
#include <opendht/thread_pool.h>

85 86 87 88 89
#ifndef WIN32
#include <sys/time.h>
#include <sys/resource.h>
#endif

90 91 92 93
#ifdef TARGET_OS_IOS
#include <CoreFoundation/CoreFoundation.h>
#endif

94 95 96 97 98 99
#include <cerrno>
#include <ctime>
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <sstream>
100
#include <algorithm>
101 102
#include <memory>
#include <mutex>
103 104 105
#include <list>
#include <random>

Adrien Béraud's avatar
Adrien Béraud committed
106
namespace jami {
Guillaume Roguez's avatar
Guillaume Roguez committed
107

108 109 110 111 112 113
/** To store conference objects by conference ids */
using ConferenceMap = std::map<std::string, std::shared_ptr<Conference>>;

/** To store uniquely a list of Call ids */
using CallIDSet = std::set<std::string>;

114
static constexpr int ICE_INIT_TIMEOUT {10};
115
static constexpr const char* PACKAGE_OLD = "ring";
116

117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
std::atomic_bool Manager::initialized = {false};

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);
}

// 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);
}

145 146 147
void
check_rename(const std::string& old_dir, const std::string& new_dir)
{
148 149 150 151
    if (old_dir == new_dir or not fileutils::isDirectory(old_dir))
        return;

    if (not fileutils::isDirectory(new_dir)) {
Adrien Béraud's avatar
Adrien Béraud committed
152
        JAMI_WARN() << "Migrating " << old_dir << " to " << new_dir;
153
        std::rename(old_dir.c_str(), new_dir.c_str());
154 155 156 157 158 159 160
    } else {
        for (const auto &file : fileutils::readDirectory(old_dir)) {
            auto old_dest = fileutils::getFullPath(old_dir, file);
            auto new_dest = fileutils::getFullPath(new_dir, file);
            if (fileutils::isDirectory(old_dest) and fileutils::isDirectory(new_dest)) {
                check_rename(old_dest, new_dest);
            } else {
Adrien Béraud's avatar
Adrien Béraud committed
161
                JAMI_WARN() << "Migrating " << old_dest << " to " << new_dest;
162 163 164 165
                std::rename(old_dest.c_str(), new_dest.c_str());
            }
        }
        fileutils::removeAll(old_dir);
166 167 168
    }
}

169 170 171 172 173 174 175 176 177 178 179 180 181 182
/**
 * Set OpenDHT's log level based on the DHTLOGLEVEL environment variable.
 * DHTLOGLEVEL = 0 minimum logging (=disable)
 * DHTLOGLEVEL = 1 (=ERROR only)
 * DHTLOGLEVEL = 2 (+=WARN)
 * DHTLOGLEVEL = 3 maximum logging (+=DEBUG)
 */

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

static void
setDhtLogLevel()
{
183
#ifndef RING_UWP
184 185 186 187 188 189 190 191 192
    char* envvar = getenv(DHTLOGLEVEL);
    int level = 0;

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

        // From 0 (min) to 3 (max)
        level = std::max(0, std::min(level, 3));
Adrien Béraud's avatar
Adrien Béraud committed
193
        JAMI_DBG("DHTLOGLEVEL=%u", level);
194 195
    }
    Manager::instance().dhtLogLevel = level;
196 197 198
#else
    Manager::instance().dhtLogLevel = 0;
#endif
199 200
}

201 202 203 204 205 206 207 208 209 210 211 212
/**
 * 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()
{
213
#ifndef RING_UWP
214
    char* envvar = getenv(SIPLOGLEVEL);
215

216 217 218 219 220 221 222 223 224
    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));
    }
Andreas Traczyk's avatar
Andreas Traczyk committed
225 226 227
#else
    int level = 0;
#endif
228 229

    pj_log_set_level(level);
Adrien Béraud's avatar
Adrien Béraud committed
230
    pj_log_set_log_func([](int level, const char *data, int /*len*/) {
Adrien Béraud's avatar
Adrien Béraud committed
231 232 233
        if      (level < 2) JAMI_ERR() << data;
        else if (level < 4) JAMI_WARN() << data;
        else                JAMI_DBG() << data;
Adrien Béraud's avatar
Adrien Béraud committed
234
    });
235 236 237 238 239 240 241 242 243 244 245 246 247
}

/**
 * Set gnutls's log level based on the RING_TLS_LOGLEVEL environment variable.
 * RING_TLS_LOGLEVEL = 0 minimum logging (default)
 * RING_TLS_LOGLEVEL = 9 maximum logging
 */

static constexpr int RING_TLS_LOGLEVEL = 0;

static void
tls_print_logs(int level, const char* msg)
{
Adrien Béraud's avatar
Adrien Béraud committed
248
    JAMI_XDBG("[%d]GnuTLS: %s", level, msg);
249 250 251 252 253
}

static void
setGnuTlsLogLevel()
{
254
#ifndef RING_UWP
255 256 257 258 259 260 261 262 263 264 265 266 267
    char* envvar = getenv("RING_TLS_LOGLEVEL");
    int level = RING_TLS_LOGLEVEL;

    if (envvar != nullptr) {
        int var_level;
        if (std::istringstream(envvar) >> var_level)
            level = var_level;

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

    gnutls_global_set_log_level(level);
268 269 270
#else
    gnutls_global_set_log_level(RING_TLS_LOGLEVEL);
#endif
271 272 273
    gnutls_global_set_log_function(tls_print_logs);
}

274 275 276
//==============================================================================

struct Manager::ManagerPimpl
277
{
278
    explicit ManagerPimpl(Manager& base);
279

280
    bool parseConfiguration();
281

282 283 284 285 286
    /*
     * Play one tone
     * @return false if the driver is uninitialize
     */
    void playATone(Tone::TONEID toneId);
Guillaume Roguez's avatar
Guillaume Roguez committed
287

288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
    int getCurrentDeviceIndex(DeviceType type);

    /**
     * Process remaining participant given a conference and the current call id.
     * Mainly called when a participant is detached or hagned up
     * @param current call id
     * @param conference pointer
     */
    void processRemainingParticipants(Conference &conf);

    /**
     * Create config directory in home user and return configuration file path
     */
    std::string retrieveConfigPath() const;

    void unsetCurrentCall();

    void switchCall(const std::string& id);
    void switchCall(const std::shared_ptr<Call>& call);

    /**
     * Add incoming callid to the waiting list
     * @param id std::string to add
     */
    void addWaitingCall(const std::string& id);

    /**
     * Remove incoming callid to the waiting list
     * @param id std::string to remove
     */
    void removeWaitingCall(const std::string& id);

320
    void loadAccount(const YAML::Node &item, int &errorCount);
321 322 323 324 325 326 327 328 329 330 331 332

    void sendTextMessageToConference(const Conference& conf,
                                     const std::map<std::string, std::string>& messages,
                                     const std::string& from) const noexcept;

    void bindCallToConference(Call& call, Conference& conf);

    template <class T>
    std::shared_ptr<T> findAccount(const std::function<bool(const std::shared_ptr<T>&)>&);

    Manager& base_; // pimpl back-pointer

333 334 335
    /** Main scheduler */
    ScheduledExecutor scheduler_;

336 337
    std::atomic_bool autoAnswer_ {false};

Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
338
    /** Application wide tone controller */
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
    ToneControl toneCtrl_;

    /** Current Call ID */
    std::string currentCall_;

    /** Protected current call access */
    std::mutex currentCallMutex_;

    /** Audio layer */
    std::shared_ptr<AudioLayer> audiodriver_{nullptr};

    // Main thread
    std::unique_ptr<DTMF> dtmfKey_;

    /** Buffer to generate DTMF */
    AudioBuffer dtmfBuf_;

    // To handle volume control
    // short speakerVolume_;
    // short micVolume_;
    // End of sound variable

    /**
     * Mutex used to protect audio layer
     */
    std::mutex audioLayerMutex_;

    /**
     * Waiting Call Vectors
     */
    CallIDSet waitingCalls_;

    /**
     * Protect waiting call list, access by many voip/audio threads
     */
    std::mutex waitingCallsMutex_;

    /**
     * Path of the ConfigFile
     */
    std::string path_;

    /**
     * Instance of the RingBufferPool for the whole application
     *
     * In order to send signal to other parts of the application, one must pass through the RingBufferMananger.
     * Audio instances must be registered into the RingBufferMananger and bound together via the Manager.
     *
     */
    std::unique_ptr<RingBufferPool> ringbufferpool_;

    // Map containing conference pointers
    ConferenceMap conferenceMap_;

    std::atomic_bool finished_ {false};

    std::mt19937_64 rand_;

    /* ICE support */
    std::unique_ptr<IceTransportFactory> ice_tf_;

    /* Sink ID mapping */
    std::map<std::string, std::weak_ptr<video::SinkClient>> sinkMap_;
402

Adrien Béraud's avatar
Adrien Béraud committed
403
#ifdef ENABLE_VIDEO
404
    std::unique_ptr<VideoManager> videoManager_;
405
#endif
406 407 408 409 410 411 412 413
};

Manager::ManagerPimpl::ManagerPimpl(Manager& base)
    : base_(base)
    , toneCtrl_(base.preferences)
    , currentCallMutex_()
    , dtmfKey_()
    , dtmfBuf_(0, AudioFormat::MONO())
414
    , audioLayerMutex_()
415 416 417
    , waitingCalls_()
    , waitingCallsMutex_()
    , path_()
418
    , ringbufferpool_(new RingBufferPool)
419 420
    , conferenceMap_()
    , ice_tf_()
Adrien Béraud's avatar
Adrien Béraud committed
421
#ifdef ENABLE_VIDEO
422 423
    , videoManager_(new VideoManager)
#endif
424 425 426
{
    // initialize random generator
    // mt19937_64 should be seeded with 2 x 32 bits
427 428

    random_device rdev;
429 430 431
    std::seed_seq seed {rdev(), rdev()};
    rand_.seed(seed);

Adrien Béraud's avatar
Adrien Béraud committed
432
    jami::libav_utils::ring_avcodec_init();
433 434 435
}

bool
436
Manager::ManagerPimpl::parseConfiguration()
437 438 439 440 441
{
    bool result = true;

    try {
        YAML::Node parsedFile = YAML::LoadFile(path_);
442
        const int error_count = base_.loadAccountMap(parsedFile);
443 444

        if (error_count > 0) {
Adrien Béraud's avatar
Adrien Béraud committed
445
            JAMI_WARN("Errors while parsing %s", path_.c_str());
446 447 448
            result = false;
        }
    } catch (const YAML::BadFile &e) {
Adrien Béraud's avatar
Adrien Béraud committed
449
        JAMI_WARN("Could not open configuration file");
450 451
    }

452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467
    return result;
}

/**
 * Multi Thread
 */
void
Manager::ManagerPimpl::playATone(Tone::TONEID toneId)
{
    if (not base_.voipPreferences.getPlayTones())
        return;

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

        if (not audiodriver_) {
Adrien Béraud's avatar
Adrien Béraud committed
468
            JAMI_ERR("Audio layer not initialized");
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
            return;
        }

        audiodriver_->flushUrgent();
        audiodriver_->startStream();
    }

    toneCtrl_.play(toneId);
}

int
Manager::ManagerPimpl::getCurrentDeviceIndex(DeviceType type)
{
    if (not audiodriver_)
        return -1;
    switch (type) {
        case DeviceType::PLAYBACK:
            return audiodriver_->getIndexPlayback();
        case DeviceType::RINGTONE:
            return audiodriver_->getIndexRingtone();
        case DeviceType::CAPTURE:
            return audiodriver_->getIndexCapture();
        default:
            return -1;
    }
}

void
Manager::ManagerPimpl::processRemainingParticipants(Conference &conf)
{
    const std::string current_call_id(base_.getCurrentCallId());
    ParticipantSet participants(conf.getParticipantList());
    const size_t n = participants.size();
Adrien Béraud's avatar
Adrien Béraud committed
502
    JAMI_DBG("Process remaining %zu participant(s) from conference %s",
503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524
          n, conf.getConfID().c_str());

    if (n > 1) {
        // Reset ringbuffer's readpointers
        for (const auto &p : participants)
            base_.getRingBufferPool().flush(p);

        base_.getRingBufferPool().flush(RingBufferPool::DEFAULT_ID);
    } else if (n == 1) {
        // this call is the last participant, hence
        // the conference is over
        ParticipantSet::iterator p = participants.begin();

        if (auto call = base_.getCallFromCallID(*p)) {
            call->setConfId("");
            // if we are not listening to this conference
            if (current_call_id != conf.getConfID())
                base_.onHoldCall(call->getCallId());
            else
                switchCall(call);
        }

Adrien Béraud's avatar
Adrien Béraud committed
525
        JAMI_DBG("No remaining participants, remove conference");
526 527
        base_.removeConference(conf.getConfID());
    } else {
Adrien Béraud's avatar
Adrien Béraud committed
528
        JAMI_DBG("No remaining participants, remove conference");
529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553
        base_.removeConference(conf.getConfID());
        unsetCurrentCall();
    }
}

/**
 * Initialization: Main Thread
 */
std::string
Manager::ManagerPimpl::retrieveConfigPath() const
{
    static const char * const PROGNAME = "dring";
    return fileutils::get_config_dir() + DIR_SEPARATOR_STR + PROGNAME + ".yml";
}

void
Manager::ManagerPimpl::unsetCurrentCall()
{
    currentCall_ = "";
}

void
Manager::ManagerPimpl::switchCall(const std::string& id)
{
    std::lock_guard<std::mutex> m(currentCallMutex_);
Adrien Béraud's avatar
Adrien Béraud committed
554
    JAMI_DBG("----- Switch current call id to '%s' -----", not id.empty() ? id.c_str() : "none");
555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578
    currentCall_ = id;
}

void
Manager::ManagerPimpl::switchCall(const std::shared_ptr<Call>& call)
{
    switchCall(call->getCallId());
}

void
Manager::ManagerPimpl::addWaitingCall(const std::string& id)
{
    std::lock_guard<std::mutex> m(waitingCallsMutex_);
    waitingCalls_.insert(id);
}

void
Manager::ManagerPimpl::removeWaitingCall(const std::string& id)
{
    std::lock_guard<std::mutex> m(waitingCallsMutex_);
    waitingCalls_.erase(id);
}

void
579
Manager::ManagerPimpl::loadAccount(const YAML::Node &node, int &errorCount)
580 581 582 583 584 585 586 587 588
{
    using yaml_utils::parseValue;

    std::string accountType;
    parseValue(node, "type", accountType);

    std::string accountid;
    parseValue(node, "id", accountid);

589
    if (!accountid.empty()) {
590
        if (base_.accountFactory.isSupportedType(accountType.c_str())) {
591 592 593
            if (auto a = base_.accountFactory.createAccount(accountType.c_str(), accountid)) {
                a->unserialize(node);
            } else {
Adrien Béraud's avatar
Adrien Béraud committed
594
                JAMI_ERR("Failed to create account type \"%s\"", accountType.c_str());
595 596 597
                ++errorCount;
            }
        } else {
Adrien Béraud's avatar
Adrien Béraud committed
598
            JAMI_WARN("Ignoring unknown account type \"%s\"", accountType.c_str());
599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616
        }
    }
}

//THREAD=VoIP
void
Manager::ManagerPimpl::sendTextMessageToConference(const Conference& conf,
                                     const std::map<std::string, std::string>& messages,
                                     const std::string& from) const noexcept
{
    ParticipantSet participants(conf.getParticipantList());
    for (const auto& call_id: participants) {
        try {
            auto call = base_.getCallFromCallID(call_id);
            if (not call)
                throw std::runtime_error("no associated call");
            call->sendTextMessage(messages, from);
        } catch (const std::exception& e) {
Adrien Béraud's avatar
Adrien Béraud committed
617
            JAMI_ERR("Failed to send message to conference participant %s: %s",
618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633
                     call_id.c_str(), e.what());
        }
    }
}

void
Manager::ManagerPimpl::bindCallToConference(Call& call, Conference& conf)
{
    const auto& call_id = call.getCallId();
    const auto& conf_id = conf.getConfID();
    const auto& state = call.getStateStr();

    // ensure that calls are only in one conference at a time
    if (base_.isConferenceParticipant(call_id))
        base_.detachParticipant(call_id);

Adrien Béraud's avatar
Adrien Béraud committed
634
    JAMI_DBG("[call:%s] bind to conference %s (callState=%s)",
635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653
             call_id.c_str(), conf_id.c_str(), state.c_str());

    base_.getRingBufferPool().unBindAll(call_id);

    conf.add(call_id);
    call.setConfId(conf_id);

    if (state == "HOLD") {
        conf.bindParticipant(call_id);
        base_.offHoldCall(call_id);
    } else if (state == "INCOMING") {
        conf.bindParticipant(call_id);
        base_.answerCall(call_id);
    } else if (state == "CURRENT") {
        conf.bindParticipant(call_id);
    } else if (state == "INACTIVE") {
        conf.bindParticipant(call_id);
        base_.answerCall(call_id);
    } else
Adrien Béraud's avatar
Adrien Béraud committed
654
        JAMI_WARN("[call:%s] call state %s not recognized for conference",
655
                  call_id.c_str(), state.c_str());
656 657 658 659 660 661 662 663 664 665 666 667 668
}

//==============================================================================

Manager&
Manager::instance()
{
    // Meyers singleton
    static Manager instance;

    // This will give a warning that can be ignored the first time instance()
    // is called...subsequent warnings are more serious
    if (not Manager::initialized)
Adrien Béraud's avatar
Adrien Béraud committed
669
        JAMI_DBG("Not initialized");
670 671 672 673 674 675 676 677 678 679

    return instance;
}

Manager::Manager()
    : preferences()
    , voipPreferences()
    , hookPreference()
    , audioPreference()
    , shortcutPreferences()
Adrien Béraud's avatar
Adrien Béraud committed
680
#ifdef ENABLE_VIDEO
681 682 683 684
    , videoPreferences()
#endif
    , callFactory()
    , accountFactory()
685
    , dataTransfers(std::make_unique<DataTransferFacade>())
686 687 688 689 690 691 692 693 694 695
    , pimpl_ (new ManagerPimpl(*this))
{}

Manager::~Manager()
{}

void
Manager::setAutoAnswer(bool enable)
{
    pimpl_->autoAnswer_ = enable;
696 697 698 699 700 701 702 703
}

void
Manager::init(const std::string &config_file)
{
    // FIXME: this is no good
    initialized = true;

704 705 706 707 708 709 710 711 712 713 714
#ifndef WIN32
    // Set the max number of open files.
    struct rlimit nofiles;
    if (getrlimit(RLIMIT_NOFILE, &nofiles) == 0) {
        if (nofiles.rlim_cur < nofiles.rlim_max && nofiles.rlim_cur < 1024u) {
            nofiles.rlim_cur = std::min<rlim_t>(nofiles.rlim_max, 8192u);
            setrlimit(RLIMIT_NOFILE, &nofiles);
        }
    }
#endif

715
#define PJSIP_TRY(ret) do {                                  \
Adrien Béraud's avatar
Adrien Béraud committed
716
        if ((ret) != PJ_SUCCESS)                               \
717 718 719
            throw std::runtime_error(#ret " failed");        \
    } while (0)

720
    srand(time(nullptr)); // to get random number for RANDOM_PORT
721 722 723 724 725 726

    // Initialize PJSIP (SIP and ICE implementation)
    PJSIP_TRY(pj_init());
    setSipLogLevel();
    PJSIP_TRY(pjlib_util_init());
    PJSIP_TRY(pjnath_init());
727
#undef PJSIP_TRY
728 729

    setGnuTlsLogLevel();
Adrien Béraud's avatar
Adrien Béraud committed
730 731 732 733

    JAMI_DBG("Using PJSIP version %s for %s", pj_get_version(), PJ_OS_NAME);
    JAMI_DBG("Using GnuTLS version %s", gnutls_check_version(nullptr));
    JAMI_DBG("Using OpenDHT version %s", dht::version());
734

735 736
    setDhtLogLevel();

737 738 739 740
    check_rename(fileutils::get_cache_dir(PACKAGE_OLD), fileutils::get_cache_dir());
    check_rename(fileutils::get_data_dir(PACKAGE_OLD), fileutils::get_data_dir());
    check_rename(fileutils::get_config_dir(PACKAGE_OLD), fileutils::get_config_dir());

741
    pimpl_->ice_tf_.reset(new IceTransportFactory());
742

743
    pimpl_->path_ = config_file.empty() ? pimpl_->retrieveConfigPath() : config_file;
Adrien Béraud's avatar
Adrien Béraud committed
744
    JAMI_DBG("Configuration file path: %s", pimpl_->path_.c_str());
745 746 747 748

    bool no_errors = true;

    // manager can restart without being recreated (android)
749
    pimpl_->finished_ = false;
750 751

    try {
752
        no_errors = pimpl_->parseConfiguration();
753
    } catch (const YAML::Exception &e) {
Adrien Béraud's avatar
Adrien Béraud committed
754
        JAMI_ERR("%s", e.what());
755 756 757 758 759
        no_errors = false;
    }

    // always back up last error-free configuration
    if (no_errors) {
760
        make_backup(pimpl_->path_);
761 762
    } else {
        // restore previous configuration
Adrien Béraud's avatar
Adrien Béraud committed
763
        JAMI_WARN("Restoring last working configuration");
764

765 766 767
        // keep a reference to sipvoiplink while destroying the accounts
        const auto sipvoiplink = getSIPVoIPLink();

768 769 770
        try {
            // remove accounts from broken configuration
            removeAccounts();
771 772
            restore_backup(pimpl_->path_);
            pimpl_->parseConfiguration();
773
        } catch (const YAML::Exception &e) {
Adrien Béraud's avatar
Adrien Béraud committed
774 775
            JAMI_ERR("%s", e.what());
            JAMI_WARN("Restoring backup failed");
776 777 778 779 780 781
        }
    }

    initAudioDriver();

    {
782
        std::lock_guard<std::mutex> lock(pimpl_->audioLayerMutex_);
783

784 785 786
        if (pimpl_->audiodriver_) {
            pimpl_->toneCtrl_.setSampleRate(pimpl_->audiodriver_->getSampleRate());
            pimpl_->dtmfKey_.reset(new DTMF(getRingBufferPool().getInternalSamplingRate()));
787 788 789 790 791 792 793 794 795 796
        }
    }

    registerAccounts();
}

void
Manager::finish() noexcept
{
    bool expected = false;
797
    if (not pimpl_->finished_.compare_exchange_strong(expected, true))
798 799 800 801 802 803 804
        return;

    try {
        // Forbid call creation
        callFactory.forbid();

        // Hangup all remaining active calls
Adrien Béraud's avatar
Adrien Béraud committed
805
        JAMI_DBG("Hangup %zu remaining call(s)", callFactory.callCount());
806 807 808 809
        for (const auto call : callFactory.getAllCalls())
            hangupCall(call->getCallId());
        callFactory.clear();

810
        for (const auto &account : getAllAccounts<JamiAccount>()) {
811 812 813 814
            if (account->getRegistrationState() == RegistrationState::INITIALIZING)
                removeAccount(account->getAccountID());
        }

815 816 817 818
        saveConfig();

        // Disconnect accounts, close link stacks and free allocated ressources
        unregisterAccounts();
819
        accountFactory.clear();
820 821

        {
822
            std::lock_guard<std::mutex> lock(pimpl_->audioLayerMutex_);
823

824
            pimpl_->audiodriver_.reset();
825 826
        }

827
        pimpl_->ice_tf_.reset();
828 829

        // Flush remaining tasks (free lambda' with capture)
830
        pimpl_->scheduler_.stop();
Adrien Béraud's avatar
Adrien Béraud committed
831 832
        dht::ThreadPool::io().join();
        dht::ThreadPool::computation().join();
833

834 835
        pj_shutdown();
    } catch (const VoipLinkException &err) {
Adrien Béraud's avatar
Adrien Béraud committed
836
        JAMI_ERR("%s", err.what());
837 838 839 840 841 842
    }
}

bool
Manager::isCurrentCall(const Call& call) const
{
843
    return pimpl_->currentCall_ == call.getCallId();
844 845 846 847 848
}

bool
Manager::hasCurrentCall() const
{
849
    return not pimpl_->currentCall_.empty();
850 851 852 853 854
}

std::shared_ptr<Call>
Manager::getCurrentCall() const
{
855
    return getCallFromCallID(pimpl_->currentCall_);
856 857
}

858
const std::string&
859 860
Manager::getCurrentCallId() const
{
861
    return pimpl_->currentCall_;
862 863 864
}

void
865
Manager::unregisterAccounts()
866
{
867 868 869
    for (const auto& account : getAllAccounts()) {
        if (account->isEnabled())
            account->doUnregister();
870 871 872
    }
}

873 874 875 876 877 878
///////////////////////////////////////////////////////////////////////////////
// Management of events' IP-phone user
///////////////////////////////////////////////////////////////////////////////
/* Main Thread */

std::string
879
Manager::outgoingCall(const std::string& account_id,
880
                          const std::string& to,
881 882
                          const std::string& conf_id,
                          const std::map<std::string, std::string>& volatileCallDetails)
883
{
884
    if (not conf_id.empty() and not isConference(conf_id)) {
Adrien Béraud's avatar
Adrien Béraud committed
885
        JAMI_ERR("outgoingCall() failed, invalid conference id");
886 887 888
        return {};
    }

Adrien Béraud's avatar
Adrien Béraud committed
889
    JAMI_DBG() << "try outgoing call to '" << to << "'"
890
               << " with account '" << account_id << "'";
891

892
    const auto& current_call_id(getCurrentCallId());
Adrien Béraud's avatar
Adrien Béraud committed
893
    std::string to_cleaned = hookPreference.getNumberAddPrefix() + trim(to);
894 895 896
    std::shared_ptr<Call> call;

    try {
897
        call = newOutgoingCall(to_cleaned, account_id, volatileCallDetails);
898
    } catch (const std::exception &e) {
Adrien Béraud's avatar
Adrien Béraud committed
899
        JAMI_ERR("%s", e.what());
900 901 902 903 904 905 906 907 908 909 910 911
        return {};
    }

    if (not call)
        return {};

    auto call_id = call->getCallId();

    stopTone();

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

914
        bool isConf = isConference(current_call_id);
915
        // if this is not a conference and this and is not a conference participant
916
        if (not isConf and not isConferenceParticipant(current_call_id))
917
            onHoldCall(current_call_id);
918
        else if (isConf and not isConferenceParticipant(call_id))
919
            detachLocalParticipant();
920 921
    }

922
    pimpl_->switchCall(call);
923 924 925 926 927 928 929 930 931 932 933 934 935
    call->setConfId(conf_id);

    return call_id;
}

//THREAD=Main : for outgoing Call
bool
Manager::answerCall(const std::string& call_id)
{
    bool result = true;

    auto call = getCallFromCallID(call_id);
    if (!call) {
Adrien Béraud's avatar
Adrien Béraud committed
936
        JAMI_ERR("Call %s is NULL", call_id.c_str());
937 938 939 940 941 942 943
        return false;
    }

    // If ring is ringing
    stopTone();

    // store the current call id
944
    const auto& current_call_id(getCurrentCallId());
945 946

    // in any cases we have to detach from current communication
947
    if (hasCurrentCall() and call_id != current_call_id) {
948

Adrien Béraud's avatar
Adrien Béraud committed
949
        JAMI_DBG("Currently conversing with %s", current_call_id.c_str());
950

951 952
        bool isConf = isConference(current_call_id);
        if (not isConf and not isConferenceParticipant(current_call_id)) {
Adrien Béraud's avatar
Adrien Béraud committed
953
            JAMI_DBG("Answer call: Put the current call (%s) on hold", current_call_id.c_str());
954
            onHoldCall(current_call_id);
955
        } else if (isConf and not isConferenceParticipant(call_id)) {
956
            // if we are talking to a conference and we are answering an incoming call
Adrien Béraud's avatar
Adrien Béraud committed
957
            JAMI_DBG("Detach main participant from conference");
958
            detachLocalParticipant();
959 960 961 962 963 964
        }
    }

    try {
        call->answer();
    } catch (const std::runtime_error &e) {
Adrien Béraud's avatar
Adrien Béraud committed
965
        JAMI_ERR("%s", e.what());
966 967 968 969
        result = false;
    }

    // if it was waiting, it's waiting no more
970
    pimpl_->removeWaitingCall(call_id);
971

972 973 974 975 976
    if (!result) {
        // do not switch to this call if it was not properly started
        return false;
    }

977 978
    // if we dragged this call into a conference already
    if (isConferenceParticipant(call_id))
979
        pimpl_->switchCall(call->getConfId());
980
    else
981
        pimpl_->switchCall(call);
982

983
    addAudio(*call);
984 985 986 987 988 989 990 991 992 993 994

    // Start recording if set in preference
    if (audioPreference.getIsAlwaysRecording())
        toggleRecordingCall(call_id);

    return result;
}

void
Manager::checkAudio()
{
995
    // FIXME dirty, the manager should not need to be aware of local recorders
996
    if (getCallList().empty()
Adrien Béraud's avatar
Adrien Béraud committed
997
#ifdef ENABLE_VIDEO
998 999
        and not getVideoManager().audioPreview
#endif
Adrien Béraud's avatar
Adrien Béraud committed
1000
        and not jami::LocalRecorderManager::instance().hasRunningRecorders()) {
1001 1002 1003
        std::lock_guard<std::mutex> lock(pimpl_->audioLayerMutex_);
        if (pimpl_->audiodriver_)
            pimpl_->audiodriver_->stopStream();
1004 1005 1006 1007 1008 1009 1010 1011
    }
}

//THREAD=Main
bool
Manager::hangupCall(const std::string& callId)
{
    // store the current call id
1012
    const auto& currentCallId(getCurrentCallId());
1013 1014 1015 1016 1017 1018

    stopTone();

    /* We often get here when the call was hungup before being created */
    auto call = getCallFromCallID(callId);
    if (not call) {
Adrien Béraud's avatar
Adrien Béraud committed
1019
        JAMI_WARN("Could not hang up non-existant call %s", callId.c_str());
1020 1021 1022 1023 1024
        checkAudio();
        return false;
    }

    // Disconnect streams
1025
    removeAudio(*call);
1026 1027 1028 1029 1030

    if (isConferenceParticipant(callId)) {
        removeParticipant(callId);
    } else {
        // we are not participating in a conference, current call switched to ""
1031
        if (not isConference(currentCallId) and isCurrentCall(*call))
1032
            pimpl_->unsetCurrentCall();
1033 1034 1035 1036 1037 1038
    }

    try {
        call->hangup(0);
        checkAudio();
    } catch (const VoipLinkException &e) {
Adrien Béraud's avatar
Adrien Béraud committed
1039
        JAMI_ERR("%s", e.what());
1040 1041 1042 1043 1044 1045 1046 1047 1048
        return false;
    }

    return true;
}

bool
Manager::hangupConference(const std::string& id)
{
Adrien Béraud's avatar
Adrien Béraud committed
1049
    JAMI_DBG("Hangup conference %s", id.c_str());
1050

1051
    ConferenceMap::iterator iter_conf = pimpl_->conferenceMap_.find(id);
1052

1053
    if (iter_conf != pimpl_->conferenceMap_.end()) {
1054 1055 1056 1057 1058 1059 1060 1061
        auto conf = iter_conf->second;

        if (conf) {
            ParticipantSet participants(conf->getParticipantList());

            for (const auto &item : participants)
                hangupCall(item);
        } else {
Adrien Béraud's avatar
Adrien Béraud committed
1062
            JAMI_ERR("No such conference %s", id.c_str());
1063 1064 1065 1066
            return false;
        }
    }

1067
    pimpl_->unsetCurrentCall();
1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083

    return true;
}

//THREAD=Main
bool
Manager::onHoldCall(const std::string& callId)
{
    bool result = true;

    stopTone();

    std::string current_call_id(getCurrentCallId());

    if (auto call = getCallFromCallID(callId)) {
        try {
1084 1085
            result = call->onhold();
            if (result)
1086
                removeAudio(*call); // Unbind calls in main buffer
1087
        } catch (const VoipLinkException &e) {
Adrien Béraud's avatar
Adrien Béraud committed
1088
            JAMI_ERR("%s", e.what());
1089 1090
            result = false;
        }
1091

1092
    } else {
Adrien Béraud's avatar
Adrien Béraud committed
1093
        JAMI_DBG("CallID %s doesn't exist in call onHold", callId.c_str());
1094 1095 1096
        return false;
    }

1097 1098
    if (result) {
        // Remove call from the queue if it was still there
1099
        pimpl_->removeWaitingCall(callId);
1100

1101 1102 1103
        // keeps current call id if the action is not holding this call
        // or a new outgoing call. This could happen in case of a conference
        if (current_call_id == callId)
1104
            pimpl_->unsetCurrentCall();
1105
    }
1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117

    return result;
}

//THREAD=Main
bool
Manager::offHoldCall(const std::string& callId)
{
    bool result = true;

    stopTone();

1118
    const auto& currentCallId = getCurrentCallId();
1119
    // Place current call on hold if it isn't
1120
    if (hasCurrentCall() and currentCallId != callId) {
1121 1122
        bool isConf = isConference(currentCallId);
        if (not isConf and not isConferenceParticipant(currentCallId)) {
Adrien Béraud's avatar
Adrien Béraud committed
1123
            JAMI_DBG("Has current call (%s), put on hold", currentCallId.c_str());
1124
            onHoldCall(currentCallId);
1125
        } else if (isConf and not isConferenceParticipant(callId)) {
1126
            holdConference(currentCallId);
1127
            detachLocalParticipant();
1128
        }
1129 1130
    }

Guillaume Roguez's avatar
Guillaume Roguez committed
1131 1132 1133 1134
    std::shared_ptr<Call> call = getCallFromCallID(callId);
    if (!call)
        return false;

1135
    try {
Guillaume Roguez's avatar
Guillaume Roguez committed
1136
        result = call->offhold();
1137
    } catch (const VoipLinkException &e) {
Adrien Béraud's avatar
Adrien Béraud committed
1138
        JAMI_ERR("%s", e.what());
1139 1140 1141
        return false;
    }

1142 1143
    if (result) {
        if (isConferenceParticipant(callId))
1144
            pimpl_->switchCall(call->getConfId());
1145
        else
1146
            pimpl_->switchCall(call);
1147