managerimpl.cpp 81.4 KB
Newer Older
jpbl's avatar
jpbl committed
1
/*
2
 *  Copyright (C) 2004-2013 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>
jpbl's avatar
jpbl committed
9 10
 *  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
11
 *  the Free Software Foundation; either version 3 of the License, or
jpbl's avatar
jpbl committed
12
 *  (at your option) any later version.
13
 *
jpbl's avatar
jpbl committed
14 15 16 17
 *  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.
18
 *
jpbl's avatar
jpbl committed
19 20
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
21
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
22 23 24 25 26 27 28 29 30 31 32
 *
 *  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
33 34
 */

35
#ifdef HAVE_CONFIG_H
36
#include "config.h"
37
#endif
38

39
#include "logger.h"
40
#include "managerimpl.h"
41
#include "account_schema.h"
42

43
#include "global.h"
44
#include "fileutils.h"
45
#include "map_utils.h"
46
#include "sip/sipvoiplink.h"
47
#include "sip/sipaccount.h"
48
#include "sip/sipcall.h"
49
#include "im/instant_messaging.h"
50
#include "sip/sippresence.h"
51

52
#if HAVE_IAX
53
#include "iax/iaxaccount.h"
54
#include "iax/iaxcall.h"
55
#include "iax/iaxvoiplink.h"
56
#endif
57

58
#include "numbercleaner.h"
59 60
#include "config/yamlparser.h"
#include "config/yamlemitter.h"
61

Adrien Béraud's avatar
Adrien Béraud committed
62
#ifndef __ANDROID__
63
#include "audio/alsa/alsalayer.h"
64
#endif
65

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

73
#include "client/configurationmanager.h"
74
#include "client/callmanager.h"
75

76
#ifdef SFL_VIDEO
77
#include "client/video_controls.h"
78
#endif
79

80 81
#include "conference.h"

82
#include <cerrno>
83
#include <algorithm>
84
#include <ctime>
jpbl's avatar
jpbl committed
85 86
#include <cstdlib>
#include <iostream>
Adrien Béraud's avatar
Adrien Béraud committed
87
#include <functional>
Tristan Matthews's avatar
cleanup  
Tristan Matthews committed
88
#include <iterator>
jpbl's avatar
jpbl committed
89
#include <fstream>
90
#include <sstream>
jpbl's avatar
jpbl committed
91
#include <sys/types.h> // mkdir(2)
92
#include <sys/stat.h>  // mkdir(2)
93

94
ManagerImpl::ManagerImpl() :
95
    preferences(), voipPreferences(),
96
    hookPreference(),  audioPreference(), shortcutPreferences(),
97 98
    hasTriedToRegister_(false), audioCodecFactory(), client_(),
	config_(),
99 100
    currentCallId_(), currentCallMutex_(), audiodriver_(0), dtmfKey_(),
    toneMutex_(), telephoneTone_(), audiofile_(), audioLayerMutex_(),
101
    waitingCalls_(), waitingCallsMutex_(), path_(),
102
    IPToIPMap_(), mainBuffer_(), conferenceMap_(), history_(), finished_(false)
Emmanuel Milou's avatar
[#2402]  
Emmanuel Milou committed
103
{
104
    // initialize random generator for call id
105
    srand(time(NULL));
jpbl's avatar
jpbl committed
106 107
}

108 109
ManagerImpl::~ManagerImpl()
{
110 111
}

112
namespace {
113 114

    void copy_over(const std::string &srcPath, const std::string &destPath)
115
    {
116 117
        std::ifstream src(srcPath.c_str());
        std::ofstream dest(destPath.c_str());
118 119 120 121
        dest << src.rdbuf();
        src.close();
        dest.close();
    }
122 123 124 125 126 127 128 129 130 131 132 133
    // Creates a backup of the file at "path" with a .bak suffix appended
    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
    void restore_backup(const std::string &path)
    {
        const std::string backup_path(path + ".bak");
        copy_over(backup_path, path);
    }
134 135
}

136
bool ManagerImpl::parseConfiguration()
Emmanuel Milou's avatar
[#2402]  
Emmanuel Milou committed
137
{
138
    bool result = true;
139

140
    FILE *file = fopen(path_.c_str(), "rb");
141

142
    try {
143 144
        if (file) {
            Conf::YamlParser parser(file);
145 146 147
            parser.serializeEvents();
            parser.composeEvents();
            parser.constructNativeData();
148
            const int error_count = loadAccountMap(parser);
149
            fclose(file);
150
            if (error_count > 0) {
151
                WARN("Errors while parsing %s", path_.c_str());
152
                result = false;
153
            }
154
        } else {
155 156 157
            WARN("Config file not found: creating default account map");
            loadDefaultAccountMap();
        }
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
    } catch (const Conf::YamlParserException &e) {
        // we only want to close the local file here and then rethrow the exception
        fclose(file);
        throw;
    }

    return result;
}

void ManagerImpl::init(const std::string &config_file)
{
    path_ = config_file.empty() ? retrieveConfigPath() : config_file;
    DEBUG("Configuration file path: %s", path_.c_str());

    bool no_errors = true;

    try {
        no_errors = parseConfiguration();
176
    } catch (const Conf::YamlParserException &e) {
177
        ERROR("%s", e.what());
178
        no_errors = false;
179 180
    }

181
    // always back up last error-free configuration
182
    if (no_errors) {
183
        make_backup(path_);
184 185 186 187 188 189 190 191 192 193 194 195
    } else {
        // restore previous configuration
        WARN("Restoring last working configuration");
        try {
            restore_backup(path_);
            parseConfiguration();
        } catch (const Conf::YamlParserException &e) {
            ERROR("%s", e.what());
            WARN("Restoring backup failed, creating default account map");
            loadDefaultAccountMap();
        }
    }
196

197
    initAudioDriver();
198

Tristan Matthews's avatar
Tristan Matthews committed
199
    {
Adrien Béraud's avatar
Adrien Béraud committed
200
        std::lock_guard<std::mutex> lock(audioLayerMutex_);
Tristan Matthews's avatar
Tristan Matthews committed
201
        if (audiodriver_) {
202
            {
Adrien Béraud's avatar
Adrien Béraud committed
203
                std::lock_guard<std::mutex> toneLock(toneMutex_);
204 205
                telephoneTone_.reset(new TelephoneTone(preferences.getZoneToneChoice(), audiodriver_->getSampleRate()));
            }
206
            dtmfKey_.reset(new DTMF(getMainBuffer().getInternalSamplingRate()));
Tristan Matthews's avatar
Tristan Matthews committed
207
        }
208
    }
209

210
    history_.load(preferences.getHistoryLimit());
211
    registerAccounts();
jpbl's avatar
jpbl committed
212 213
}

Emeric Vigier's avatar
Emeric Vigier committed
214 215 216 217
void ManagerImpl::setPath(const std::string &path) {
	history_.setPath(path);
}

218
#if HAVE_DBUS
219 220
void ManagerImpl::run()
{
221
    DEBUG("Starting client event loop");
222
    client_.event_loop();
223
}
224
#endif
225 226 227

void ManagerImpl::finish()
{
228 229 230 231 232 233 234 235
    if (finished_)
        return;

    finished_ = true;
    // Unset signal handlers
    signal(SIGHUP, SIG_DFL);
    signal(SIGINT, SIG_DFL);
    signal(SIGTERM, SIG_DFL);
236

237
    std::vector<std::string> callList(getCallList());
238
    DEBUG("Hangup %zu remaining call(s)", callList.size());
239

240 241
    for (const auto &item : callList)
        hangupCall(item);
242

243 244
    saveConfig();

245
    unregisterAllAccounts();
246

247
    SIPVoIPLink::destroy();
248 249 250
#if HAVE_IAX
    IAXVoIPLink::unloadAccountMap();
#endif
251

252
    {
Adrien Béraud's avatar
Adrien Béraud committed
253
        std::lock_guard<std::mutex> lock(audioLayerMutex_);
254 255 256 257

        delete audiodriver_;
        audiodriver_ = NULL;
    }
258

259
    client_.exit();
jpbl's avatar
jpbl committed
260 261
}

262
bool ManagerImpl::isCurrentCall(const std::string& callId) const
Emmanuel Milou's avatar
[#2402]  
Emmanuel Milou committed
263
{
264
    return currentCallId_ == callId;
jpbl's avatar
jpbl committed
265 266
}

267
bool ManagerImpl::hasCurrentCall() const
Emmanuel Milou's avatar
[#2402]  
Emmanuel Milou committed
268
{
269
    return not currentCallId_.empty();
jpbl's avatar
jpbl committed
270 271
}

272
std::string
273
ManagerImpl::getCurrentCallId() const
Emmanuel Milou's avatar
[#2402]  
Emmanuel Milou committed
274
{
275
    return currentCallId_;
jpbl's avatar
jpbl committed
276 277
}

278 279 280 281 282
void ManagerImpl::unsetCurrentCall()
{
    switchCall("");
}

283
void ManagerImpl::switchCall(const std::string& id)
Emmanuel Milou's avatar
[#2402]  
Emmanuel Milou committed
284
{
Adrien Béraud's avatar
Adrien Béraud committed
285
    std::lock_guard<std::mutex> m(currentCallMutex_);
286
    DEBUG("----- Switch current call id to %s -----", id.c_str());
287
    currentCallId_ = id;
jpbl's avatar
jpbl committed
288 289 290 291 292
}

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

295
bool ManagerImpl::outgoingCall(const std::string& account_id,
296 297 298
                               const std::string& call_id,
                               const std::string& to,
                               const std::string& conf_id)
Emmanuel Milou's avatar
[#2402]  
Emmanuel Milou committed
299
{
300
    if (call_id.empty()) {
301
        DEBUG("New outgoing call abort, missing callid");
Emmanuel Lepage's avatar
Emmanuel Lepage committed
302 303
        return false;
    }
304

305
    // Call ID must be unique
306
    if (isValidCall(call_id)) {
307
        ERROR("Call id already exists in outgoing call");
308 309 310
        return false;
    }

311
    DEBUG("New outgoing call %s to %s", call_id.c_str(), to.c_str());
312

313 314
    stopTone();

315
    std::string current_call_id(getCurrentCallId());
316

317
    std::string prefix(hookPreference.getNumberAddPrefix());
318

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

321 322
    // in any cases we have to detach from current communication
    if (hasCurrentCall()) {
323
        DEBUG("Has current call (%s) put it onhold", current_call_id.c_str());
324

325
        // if this is not a conference and this and is not a conference participant
326
        if (not isConference(current_call_id) and not isConferenceParticipant(current_call_id))
327
            onHoldCall(current_call_id);
328
        else if (isConference(current_call_id) and not isConferenceParticipant(call_id))
329
            detachParticipant(MainBuffer::DEFAULT_ID);
330
    }
331

332
    DEBUG("Selecting account %s", account_id.c_str());
333

334
    // fallback using the default sip account if the specied doesn't exist
Tristan Matthews's avatar
Tristan Matthews committed
335
    std::string use_account_id;
336
    if (!accountExists(account_id)) {
337
        WARN("Account does not exist, trying with default SIP account");
338 339 340 341
        use_account_id = SIPAccount::IP2IP_PROFILE;
    }
    else {
        use_account_id = account_id;
342
    }
343

344
    try {
345
        Call *call = getAccountLink(account_id)->newOutgoingCall(call_id, to_cleaned, use_account_id);
346 347

        // try to reverse match the peer name using the cache
Tristan Matthews's avatar
Tristan Matthews committed
348
        if (call->getDisplayName().empty()) {
349
            const std::string pseudo_contact_name(HistoryNameCache::getInstance().getNameFromHistory(call->getPeerNumber(), call->getAccountId()));
Tristan Matthews's avatar
Tristan Matthews committed
350
            if (not pseudo_contact_name.empty())
351 352
                call->setDisplayName(pseudo_contact_name);
        }
353
        switchCall(call_id);
354
        call->setConfId(conf_id);
355
    } catch (const VoipLinkException &e) {
356
        callFailure(call_id);
357
        ERROR("%s", e.what());
358
        return false;
359 360 361 362
    } catch (ost::Socket *) {
        callFailure(call_id);
        ERROR("Could not bind socket");
        return false;
363 364
    }

365
    getMainBuffer().dumpInfo();
Alexandre Savard's avatar
Alexandre Savard committed
366

367
    return true;
jpbl's avatar
jpbl committed
368 369
}

yanmorin's avatar
 
yanmorin committed
370
//THREAD=Main : for outgoing Call
371
bool ManagerImpl::answerCall(const std::string& call_id)
Emmanuel Milou's avatar
[#2402]  
Emmanuel Milou committed
372
{
373
    bool result = true;
374 375 376 377 378 379
    Call *call = getCallFromCallID(call_id);

    if (call == NULL) {
        ERROR("Call %s is NULL", call_id.c_str());
        return false;
    }
380

381
    // If sflphone is ringing
382
    stopTone();
383

384
    // set playback mode to VOICE
385
    if (audiodriver_) audiodriver_->setPlaybackMode(AudioLayer::VOICE);
386

387
    // store the current call id
388
    std::string current_call_id(getCurrentCallId());
389

390 391
    // in any cases we have to detach from current communication
    if (hasCurrentCall()) {
392

393
        DEBUG("Currently conversing with %s", current_call_id.c_str());
394

395
        if (not isConference(current_call_id) and not isConferenceParticipant(current_call_id)) {
396
            DEBUG("Answer call: Put the current call (%s) on hold", current_call_id.c_str());
397
            onHoldCall(current_call_id);
398
        } else if (isConference(current_call_id) and not isConferenceParticipant(call_id)) {
399
            // if we are talking to a conference and we are answering an incoming call
400
            DEBUG("Detach main participant from conference");
401
            detachParticipant(MainBuffer::DEFAULT_ID);
402 403
        }
    }
404

405
    try {
406
        VoIPLink *link = getAccountLink(call->getAccountId());
407 408
        if (link)
            link->answer(call);
409
    } catch (const std::runtime_error &e) {
410
        ERROR("%s", e.what());
411
        result = false;
412
    }
Alexandre Savard's avatar
Alexandre Savard committed
413

414
    // if it was waiting, it's waiting no more
415
    removeWaitingCall(call_id);
416

417
    // if we dragged this call into a conference already
418
    if (isConferenceParticipant(call_id))
419
        switchCall(call->getConfId());
420
    else
421
        switchCall(call_id);
422

423
    // Connect streams
424
    addStream(call_id);
425

426
    getMainBuffer().dumpInfo();
Alexandre Savard's avatar
Alexandre Savard committed
427

428
    // Start recording if set in preference
429
    if (audioPreference.getIsAlwaysRecording())
430
        toggleRecordingCall(call_id);
431

432
    client_.getCallManager()->callStateChanged(call_id, "CURRENT");
433

434
    return result;
jpbl's avatar
jpbl committed
435 436
}

437 438 439
void ManagerImpl::checkAudio()
{
    if (getCallList().empty()) {
Adrien Béraud's avatar
Adrien Béraud committed
440
        std::lock_guard<std::mutex> lock(audioLayerMutex_);
441 442 443
        if (audiodriver_)
            audiodriver_->stopStream();
    }
444

jpbl's avatar
jpbl committed
445 446
}

yanmorin's avatar
 
yanmorin committed
447
//THREAD=Main
448
bool ManagerImpl::hangupCall(const std::string& callId)
Emmanuel Milou's avatar
[#2402]  
Emmanuel Milou committed
449
{
450
    // store the current call id
451
    std::string currentCallId(getCurrentCallId());
yanmorin's avatar
 
yanmorin committed
452

453
    stopTone();
454

455
    // set playback mode to NONE
456
    if (audiodriver_) audiodriver_->setPlaybackMode(AudioLayer::NONE);
457

458
    DEBUG("Send call state change (HUNGUP) for id %s", callId.c_str());
459
    client_.getCallManager()->callStateChanged(callId, "HUNGUP");
460

461
    /* We often get here when the call was hungup before being created */
462
    if (not isValidCall(callId) and not isIPToIP(callId)) {
463
        DEBUG("Could not hang up call %s, call not valid", callId.c_str());
464
        checkAudio();
465
        return false;
466 467
    }

468 469 470
    // Disconnect streams
    removeStream(callId);

471
    if (isConferenceParticipant(callId)) {
472
        removeParticipant(callId);
473
    } else {
474 475
        // we are not participating in a conference, current call switched to ""
        if (not isConference(currentCallId))
476
            unsetCurrentCall();
477
    }
478

479
    if (isIPToIP(callId)) {
480
        /* Direct IP to IP call */
481
        try {
482
            Call * call = SIPVoIPLink::instance()->getSipCall(callId);
483 484
            if (call) {
                history_.addCall(call, preferences.getHistoryLimit());
485
                SIPVoIPLink::instance()->hangup(callId, 0);
486
                checkAudio();
487 488
                saveHistory();
            }
489
        } catch (const VoipLinkException &e) {
490
            ERROR("%s", e.what());
491
            return false;
492
        }
493
    } else {
494
        Call * call = getCallFromCallID(callId);
495 496
        if (call) {
            history_.addCall(call, preferences.getHistoryLimit());
497
            VoIPLink *link = getAccountLink(call->getAccountId());
498
            link->hangup(callId, 0);
499
            checkAudio();
500 501
            saveHistory();
        }
502
    }
503

504
    getMainBuffer().dumpInfo();
505
    return true;
jpbl's avatar
jpbl committed
506 507
}

508
bool ManagerImpl::hangupConference(const std::string& id)
Emmanuel Milou's avatar
[#2402]  
Emmanuel Milou committed
509
{
510
    DEBUG("Hangup conference %s", id.c_str());
Alexandre Savard's avatar
Alexandre Savard committed
511

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

514
    if (iter_conf != conferenceMap_.end()) {
515
        Conference *conf = iter_conf->second;
Alexandre Savard's avatar
Alexandre Savard committed
516

517 518
        if (conf) {
            ParticipantSet participants(conf->getParticipantList());
519

520 521
            for (const auto &item : participants)
                hangupCall(item);
522
        } else {
523
            ERROR("No such conference %s", id.c_str());
524 525
            return false;
        }
526
    }
Alexandre Savard's avatar
Alexandre Savard committed
527

528
    unsetCurrentCall();
529

530
    getMainBuffer().dumpInfo();
531

532
    return true;
Alexandre Savard's avatar
Alexandre Savard committed
533 534
}

jpbl's avatar
jpbl committed
535

yanmorin's avatar
 
yanmorin committed
536
//THREAD=Main
537
bool ManagerImpl::onHoldCall(const std::string& callId)
Emmanuel Milou's avatar
[#2402]  
Emmanuel Milou committed
538
{
539
    bool result = true;
540

541
    stopTone();
542

543
    std::string current_call_id(getCurrentCallId());
544

545
    try {
546
        if (isIPToIP(callId)) {
547
            SIPVoIPLink::instance()->onhold(callId);
548 549 550
        } else {
            /* Classic call, attached to an account */
            std::string account_id(getAccountFromCall(callId));
551

552
            if (account_id.empty()) {
553
                DEBUG("Account ID %s or callid %s doesn't exist in call onHold", account_id.c_str(), callId.c_str());
554
                return false;
555
            }
556 557 558 559

            getAccountLink(account_id)->onhold(callId);
        }
    } catch (const VoipLinkException &e) {
560
        ERROR("%s", e.what());
561
        result = false;
562
    }
563

564
    // Unbind calls in main buffer
565
    removeStream(callId);
566

567
    // Remove call from teh queue if it was still there
568
    removeWaitingCall(callId);
569

570
    // keeps current call id if the action is not holding this call or a new outgoing call
571
    // this could happen in case of a conference
572
    if (current_call_id == callId)
573
        unsetCurrentCall();
574

575
    client_.getCallManager()->callStateChanged(callId, "HOLD");
576

577
    getMainBuffer().dumpInfo();
578
    return result;
yanmorin's avatar
 
yanmorin committed
579 580 581
}

//THREAD=Main
582
bool ManagerImpl::offHoldCall(const std::string& callId)
Emmanuel Milou's avatar
[#2402]  
Emmanuel Milou committed
583
{
584
    bool result = true;
585

586
    stopTone();
587

Tristan Matthews's avatar
Tristan Matthews committed
588
    const std::string currentCallId(getCurrentCallId());
589

Tristan Matthews's avatar
Tristan Matthews committed
590
    // Place current call on hold if it isn't
591
    if (hasCurrentCall()) {
Tristan Matthews's avatar
Tristan Matthews committed
592
        if (not isConference(currentCallId) and not isConferenceParticipant(currentCallId)) {
593
            DEBUG("Has current call (%s), put on hold", currentCallId.c_str());
594
            onHoldCall(currentCallId);
595 596
        } else if (isConference(currentCallId) && callId != currentCallId) {
            holdConference(currentCallId);
597
        } else if (isConference(currentCallId) and not isConferenceParticipant(callId))
598
            detachParticipant(MainBuffer::DEFAULT_ID);
599
    }
alexandresavard's avatar
alexandresavard committed
600

601
    if (isIPToIP(callId))
Tristan Matthews's avatar
Tristan Matthews committed
602
        SIPVoIPLink::instance()->offhold(callId);
603
    else {
604
        /* Classic call, attached to an account */
605
        Call * call = getCallFromCallID(callId);
606

607
        if (call)
608
            getAccountLink(call->getAccountId())->offhold(callId);
609 610
        else
            result = false;
611
    }
612

613
    client_.getCallManager()->callStateChanged(callId, "UNHOLD");
614

615
    if (isConferenceParticipant(callId)) {
616
        Call *call = getCallFromCallID(callId);
617
        if (call)
618
            switchCall(call->getConfId());
619 620
        else
            result = false;
621
    } else
622
        switchCall(callId);
623

624 625
    addStream(callId);

626
    getMainBuffer().dumpInfo();
627
    return result;
jpbl's avatar
jpbl committed
628 629
}

yanmorin's avatar
 
yanmorin committed
630
//THREAD=Main