managerimpl.cpp 81.5 KB
Newer Older
jpbl's avatar
jpbl committed
1
/*
2
 *  Copyright (C) 2004-2012 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
56
57
#include "iax/iaxvoiplink.h"
#endif

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
#include "conference.h"
81
#include "scoped_lock.h"
82

83
#include <cerrno>
84
#include <algorithm>
85
#include <ctime>
jpbl's avatar
jpbl committed
86
87
#include <cstdlib>
#include <iostream>
88
#include <tr1/functional>
Tristan Matthews's avatar
cleanup    
Tristan Matthews committed
89
#include <iterator>
jpbl's avatar
jpbl committed
90
#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

95
ManagerImpl::ManagerImpl() :
96
    preferences(), voipPreferences(),
97
    hookPreference(),  audioPreference(), shortcutPreferences(),
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
105
106
    pthread_mutex_init(&currentCallMutex_, NULL);
    pthread_mutex_init(&toneMutex_, NULL);
    pthread_mutex_init(&audioLayerMutex_, NULL);
107
    pthread_mutex_init(&waitingCallsMutex_, NULL);
108
    // initialize random generator for call id
109
    srand(time(NULL));
jpbl's avatar
jpbl committed
110
111
}

112
113
114
ManagerImpl::~ManagerImpl()
{
    // destroy in reverse order of initialization
115
    pthread_mutex_destroy(&waitingCallsMutex_);
116
117
118
119
120
    pthread_mutex_destroy(&audioLayerMutex_);
    pthread_mutex_destroy(&toneMutex_);
    pthread_mutex_destroy(&currentCallMutex_);
}

121
namespace {
122
123

    void copy_over(const std::string &srcPath, const std::string &destPath)
124
    {
125
126
        std::ifstream src(srcPath.c_str());
        std::ofstream dest(destPath.c_str());
127
128
129
130
        dest << src.rdbuf();
        src.close();
        dest.close();
    }
131
132
133
134
135
136
137
138
139
140
141
142
    // 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);
    }
143
144
}

145
bool ManagerImpl::parseConfiguration()
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
146
{
147
    bool result = true;
148

149
    FILE *file = fopen(path_.c_str(), "rb");
150

151
    try {
152
153
        if (file) {
            Conf::YamlParser parser(file);
154
155
156
            parser.serializeEvents();
            parser.composeEvents();
            parser.constructNativeData();
157
            const int error_count = loadAccountMap(parser);
158
            fclose(file);
159
            if (error_count > 0) {
160
                WARN("Errors while parsing %s", path_.c_str());
161
                result = false;
162
            }
163
        } else {
164
165
166
            WARN("Config file not found: creating default account map");
            loadDefaultAccountMap();
        }
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
    } 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();
185
    } catch (const Conf::YamlParserException &e) {
186
        ERROR("%s", e.what());
187
        no_errors = false;
188
189
    }

190
    // always back up last error-free configuration
191
    if (no_errors) {
192
        make_backup(path_);
193
194
195
196
197
198
199
200
201
202
203
204
    } 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();
        }
    }
205

206
    initAudioDriver();
207

Tristan Matthews's avatar
Tristan Matthews committed
208
    {
209
        sfl::ScopedLock lock(audioLayerMutex_);
Tristan Matthews's avatar
Tristan Matthews committed
210
        if (audiodriver_) {
211
            {
212
                sfl::ScopedLock toneLock(toneMutex_);
213
214
                telephoneTone_.reset(new TelephoneTone(preferences.getZoneToneChoice(), audiodriver_->getSampleRate()));
            }
215
            dtmfKey_.reset(new DTMF(getMainBuffer().getInternalSamplingRate()));
Tristan Matthews's avatar
Tristan Matthews committed
216
        }
217
    }
218

219
    history_.load(preferences.getHistoryLimit());
220
    registerAccounts();
jpbl's avatar
jpbl committed
221
222
}

Emeric Vigier's avatar
Emeric Vigier committed
223
224
225
226
void ManagerImpl::setPath(const std::string &path) {
	history_.setPath(path);
}

227
#if HAVE_DBUS
228
229
void ManagerImpl::run()
{
230
    DEBUG("Starting client event loop");
231
    client_.event_loop();
232
}
233
#endif
234
235
236

void ManagerImpl::finish()
{
237
238
239
240
241
242
243
244
    if (finished_)
        return;

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

246
    std::vector<std::string> callList(getCallList());
247
    DEBUG("Hangup %zu remaining call(s)", callList.size());
248

249
250
    for (const auto &item : callList)
        hangupCall(item);
251

252
253
    saveConfig();

254
    unregisterAllAccounts();
255

256
    SIPVoIPLink::destroy();
257
258
259
#if HAVE_IAX
    IAXVoIPLink::unloadAccountMap();
#endif
260

261
    {
262
        sfl::ScopedLock lock(audioLayerMutex_);
263
264
265
266

        delete audiodriver_;
        audiodriver_ = NULL;
    }
267

268
    client_.exit();
jpbl's avatar
jpbl committed
269
270
}

271
bool ManagerImpl::isCurrentCall(const std::string& callId) const
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
272
{
273
    return currentCallId_ == callId;
jpbl's avatar
jpbl committed
274
275
}

276
bool ManagerImpl::hasCurrentCall() const
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
277
{
278
    return not currentCallId_.empty();
jpbl's avatar
jpbl committed
279
280
}

281
std::string
282
ManagerImpl::getCurrentCallId() const
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
283
{
284
    return currentCallId_;
jpbl's avatar
jpbl committed
285
286
}

287
288
289
290
291
void ManagerImpl::unsetCurrentCall()
{
    switchCall("");
}

292
void ManagerImpl::switchCall(const std::string& id)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
293
{
294
    sfl::ScopedLock m(currentCallMutex_);
295
    DEBUG("----- Switch current call id to %s -----", id.c_str());
296
    currentCallId_ = id;
jpbl's avatar
jpbl committed
297
298
299
300
301
}

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

304
bool ManagerImpl::outgoingCall(const std::string& account_id,
305
306
307
                               const std::string& call_id,
                               const std::string& to,
                               const std::string& conf_id)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
308
{
309
    if (call_id.empty()) {
310
        DEBUG("New outgoing call abort, missing callid");
Emmanuel Lepage's avatar
Emmanuel Lepage committed
311
312
        return false;
    }
313

314
    // Call ID must be unique
315
    if (isValidCall(call_id)) {
316
        ERROR("Call id already exists in outgoing call");
317
318
319
        return false;
    }

320
    DEBUG("New outgoing call %s to %s", call_id.c_str(), to.c_str());
321

322
323
    stopTone();

324
    std::string current_call_id(getCurrentCallId());
325

326
    std::string prefix(hookPreference.getNumberAddPrefix());
327

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

330
331
    // in any cases we have to detach from current communication
    if (hasCurrentCall()) {
332
        DEBUG("Has current call (%s) put it onhold", current_call_id.c_str());
333

334
        // if this is not a conference and this and is not a conference participant
335
        if (not isConference(current_call_id) and not isConferenceParticipant(current_call_id))
336
            onHoldCall(current_call_id);
337
        else if (isConference(current_call_id) and not isConferenceParticipant(call_id))
338
            detachParticipant(MainBuffer::DEFAULT_ID);
339
    }
340

341
    DEBUG("Selecting account %s", account_id.c_str());
342

343
    // fallback using the default sip account if the specied doesn't exist
Tristan Matthews's avatar
Tristan Matthews committed
344
    std::string use_account_id;
345
    if (!accountExists(account_id)) {
346
        WARN("Account does not exist, trying with default SIP account");
347
348
349
350
        use_account_id = SIPAccount::IP2IP_PROFILE;
    }
    else {
        use_account_id = account_id;
351
    }
352

353
    try {
354
        Call *call = getAccountLink(account_id)->newOutgoingCall(call_id, to_cleaned, use_account_id);
355
356

        // try to reverse match the peer name using the cache
Tristan Matthews's avatar
Tristan Matthews committed
357
        if (call->getDisplayName().empty()) {
358
            const std::string pseudo_contact_name(HistoryNameCache::getInstance().getNameFromHistory(call->getPeerNumber(), call->getAccountId()));
Tristan Matthews's avatar
Tristan Matthews committed
359
            if (not pseudo_contact_name.empty())
360
361
                call->setDisplayName(pseudo_contact_name);
        }
362
        switchCall(call_id);
363
        call->setConfId(conf_id);
364
    } catch (const VoipLinkException &e) {
365
        callFailure(call_id);
366
        ERROR("%s", e.what());
367
        return false;
368
369
370
371
    } catch (ost::Socket *) {
        callFailure(call_id);
        ERROR("Could not bind socket");
        return false;
372
373
    }

374
    getMainBuffer().dumpInfo();
Alexandre Savard's avatar
Alexandre Savard committed
375

376
    return true;
jpbl's avatar
jpbl committed
377
378
}

yanmorin's avatar
   
yanmorin committed
379
//THREAD=Main : for outgoing Call
380
bool ManagerImpl::answerCall(const std::string& call_id)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
381
{
382
    bool result = true;
383
384
385
386
387
388
    Call *call = getCallFromCallID(call_id);

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

390
    // If sflphone is ringing
391
    stopTone();
392

393
    // set playback mode to VOICE
394
    if (audiodriver_) audiodriver_->setPlaybackMode(AudioLayer::VOICE);
395

396
    // store the current call id
397
    std::string current_call_id(getCurrentCallId());
398

399
400
    // in any cases we have to detach from current communication
    if (hasCurrentCall()) {
401

402
        DEBUG("Currently conversing with %s", current_call_id.c_str());
403

404
        if (not isConference(current_call_id) and not isConferenceParticipant(current_call_id)) {
405
            DEBUG("Answer call: Put the current call (%s) on hold", current_call_id.c_str());
406
            onHoldCall(current_call_id);
407
        } else if (isConference(current_call_id) and not isConferenceParticipant(call_id)) {
408
            // if we are talking to a conference and we are answering an incoming call
409
            DEBUG("Detach main participant from conference");
410
            detachParticipant(MainBuffer::DEFAULT_ID);
411
412
        }
    }
413

414
    try {
415
        VoIPLink *link = getAccountLink(call->getAccountId());
416
417
        if (link)
            link->answer(call);
418
    } catch (const std::runtime_error &e) {
419
        ERROR("%s", e.what());
420
        result = false;
421
    }
Alexandre Savard's avatar
Alexandre Savard committed
422

423
    // if it was waiting, it's waiting no more
424
    removeWaitingCall(call_id);
425

426
    // if we dragged this call into a conference already
427
    if (isConferenceParticipant(call_id))
428
        switchCall(call->getConfId());
429
    else
430
        switchCall(call_id);
431

432
    // Connect streams
433
    addStream(call_id);
434

435
    getMainBuffer().dumpInfo();
Alexandre Savard's avatar
Alexandre Savard committed
436

437
    // Start recording if set in preference
438
    if (audioPreference.getIsAlwaysRecording())
439
        toggleRecordingCall(call_id);
440

441
    client_.getCallManager()->callStateChanged(call_id, "CURRENT");
442

443
    return result;
jpbl's avatar
jpbl committed
444
445
}

446
447
448
449
450
451
452
void ManagerImpl::checkAudio()
{
    if (getCallList().empty()) {
        sfl::ScopedLock lock(audioLayerMutex_);
        if (audiodriver_)
            audiodriver_->stopStream();
    }
453

454
455
}

yanmorin's avatar
   
yanmorin committed
456
//THREAD=Main
457
bool ManagerImpl::hangupCall(const std::string& callId)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
458
{
459
    // store the current call id
460
    std::string currentCallId(getCurrentCallId());
yanmorin's avatar
   
yanmorin committed
461

462
    stopTone();
463

464
    // set playback mode to NONE
465
    if (audiodriver_) audiodriver_->setPlaybackMode(AudioLayer::NONE);
466

467
    DEBUG("Send call state change (HUNGUP) for id %s", callId.c_str());
468
    client_.getCallManager()->callStateChanged(callId, "HUNGUP");
469

470
    /* We often get here when the call was hungup before being created */
471
    if (not isValidCall(callId) and not isIPToIP(callId)) {
472
        DEBUG("Could not hang up call %s, call not valid", callId.c_str());
473
        checkAudio();
474
        return false;
475
476
    }

477
478
479
    // Disconnect streams
    removeStream(callId);

480
    if (isConferenceParticipant(callId)) {
481
        removeParticipant(callId);
482
    } else {
483
484
        // we are not participating in a conference, current call switched to ""
        if (not isConference(currentCallId))
485
            unsetCurrentCall();
486
    }
487

488
    if (isIPToIP(callId)) {
489
        /* Direct IP to IP call */
490
        try {
491
            Call * call = SIPVoIPLink::instance()->getSipCall(callId);
492
493
            if (call) {
                history_.addCall(call, preferences.getHistoryLimit());
494
                SIPVoIPLink::instance()->hangup(callId, 0);
495
                checkAudio();
496
497
                saveHistory();
            }
498
        } catch (const VoipLinkException &e) {
499
            ERROR("%s", e.what());
500
            return false;
501
        }
502
    } else {
503
        Call * call = getCallFromCallID(callId);
504
505
        if (call) {
            history_.addCall(call, preferences.getHistoryLimit());
506
            VoIPLink *link = getAccountLink(call->getAccountId());
507
            link->hangup(callId, 0);
508
            checkAudio();
509
510
            saveHistory();
        }
511
    }
512

513
    getMainBuffer().dumpInfo();
514
    return true;
jpbl's avatar
jpbl committed
515
516
}

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

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

523
    if (iter_conf != conferenceMap_.end()) {
524
        Conference *conf = iter_conf->second;
Alexandre Savard's avatar
Alexandre Savard committed
525

526
527
        if (conf) {
            ParticipantSet participants(conf->getParticipantList());
528

529
530
            for (const auto &item : participants)
                hangupCall(item);
531
        } else {
532
            ERROR("No such conference %s", id.c_str());
533
534
            return false;
        }
535
    }
Alexandre Savard's avatar
Alexandre Savard committed
536

537
    unsetCurrentCall();
538

539
    getMainBuffer().dumpInfo();
540

541
    return true;
Alexandre Savard's avatar
Alexandre Savard committed
542
543
}

jpbl's avatar
jpbl committed
544

yanmorin's avatar
   
yanmorin committed
545
//THREAD=Main
546
bool ManagerImpl::onHoldCall(const std::string& callId)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
547
{
548
    bool result = true;
549

550
    stopTone();
551

552
    std::string current_call_id(getCurrentCallId());
553

554
    try {
555
        if (isIPToIP(callId)) {
556
            SIPVoIPLink::instance()->onhold(callId);
557
558
559
        } else {
            /* Classic call, attached to an account */
            std::string account_id(getAccountFromCall(callId));
560

561
            if (account_id.empty()) {
562
                DEBUG("Account ID %s or callid %s doesn't exist in call onHold", account_id.c_str(), callId.c_str());
563
                return false;
564
            }
565
566
567
568

            getAccountLink(account_id)->onhold(callId);
        }
    } catch (const VoipLinkException &e) {
569
        ERROR("%s", e.what());
570
        result = false;
571
    }
572

573
    // Unbind calls in main buffer
574
    removeStream(callId);
575

576
    // Remove call from teh queue if it was still there
577
    removeWaitingCall(callId);
578

579
    // keeps current call id if the action is not holding this call or a new outgoing call
580
    // this could happen in case of a conference
581
    if (current_call_id == callId)
582
        unsetCurrentCall();
583

584
    client_.getCallManager()->callStateChanged(callId, "HOLD");
585

586
    getMainBuffer().dumpInfo();
587
    return result;
yanmorin's avatar
   
yanmorin committed
588
589
590
}

//THREAD=Main
591
bool ManagerImpl::offHoldCall(const std::string& callId)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
592
{
593
    bool result = true;
594

595
    stopTone();
596

Tristan Matthews's avatar
Tristan Matthews committed
597
    const std::string currentCallId(getCurrentCallId());
598

Tristan Matthews's avatar
Tristan Matthews committed
599
    // Place current call on hold if it isn't
600
    if (hasCurrentCall()) {
Tristan Matthews's avatar
Tristan Matthews committed
601
        if (not isConference(currentCallId) and not isConferenceParticipant(currentCallId)) {
602
            DEBUG("Has current call (%s), put on hold", currentCallId.c_str());
603
            onHoldCall(currentCallId);
604
605
        } else if (isConference(currentCallId) && callId != currentCallId) {
            holdConference(currentCallId);
606
        } else if (isConference(currentCallId) and not isConferenceParticipant(callId))
607
            detachParticipant(MainBuffer::DEFAULT_ID);
608
    }
alexandresavard's avatar
alexandresavard committed
609

610
    if (isIPToIP(callId))
Tristan Matthews's avatar
Tristan Matthews committed
611
        SIPVoIPLink::instance()->offhold(callId);
612
    else {
613
        /* Classic call, attached to an account */
614
        Call * call = getCallFromCallID(callId);
615

616
        if (call)
617
            getAccountLink(call->getAccountId())->offhold(callId);
618
619
        else
            result = false;
620
    }
621

622
    client_.getCallManager()->callStateChanged(callId, "UNHOLD");
623

624
    if (isConferenceParticipant(callId)) {
625
        Call *call = getCallFromCallID(callId);
626
        if (call)
627
            switchCall(call->getConfId());
628
629
        else
            result = false;
630
    } else
631
        switchCall(callId);
632

633
634
    addStream(callId);

635
    getMainBuffer().dumpInfo();
636
    return result;
jpbl's avatar
jpbl committed
637
638
}

yanmorin's avatar
   
yanmorin committed
639
//THREAD=Main
640
bool ManagerImpl::transferCall(const std::string& callId, const std::string& to)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
641
{
642
    if (isConferenceParticipant(callId)) {
643
        removeParticipant(callId);
Tristan Matthews's avatar
cleanup    
Tristan Matthews committed
644
    } else if (not isConference(getCurrentCallId()))
645
        unsetCurrentCall();
646

647
    // Direct IP to IP call
648
    if (isIPToIP(callId)) {
649
        SIPVoIPLink::instance()->transfer(callId, to);
650
651
    } else {
        std::string accountID(getAccountFromCall(callId));
652

653
        if (accountID.empty())
654
            return false;
655

656
657
        VoIPLink *link = getAccountLink(accountID);
        link->transfer(callId, to);
658
    }
659

660
    // remove waiting call in case we make transfer without even answer
661
    removeWaitingCall(callId);
662

663
    getMainBuffer().dumpInfo();
Alexandre Savard's avatar
Alexandre Savard committed
664

665
    return true;
jpbl's avatar
jpbl committed
666
667
}

668
void ManagerImpl::transferFailed()
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
669
{
670
    client_.getCallManager()->transferFailed();