managerimpl.cpp 82.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

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

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

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

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

72
#ifndef __ANDROID__
73
#include "client/configurationmanager.h"
74
#include "client/callmanager.h"
75
#else
76
77
#include "client/android/configurationmanager.h"
#include "client/android/callmanager.h"
78
79
#endif

80
#ifdef SFL_VIDEO
81
#include "client/video_controls.h"
82
#endif
83

84
#include "conference.h"
85
#include "scoped_lock.h"
86

87
#include <cerrno>
88
#include <algorithm>
89
#include <ctime>
jpbl's avatar
jpbl committed
90
91
#include <cstdlib>
#include <iostream>
92
#include <tr1/functional>
Tristan Matthews's avatar
cleanup    
Tristan Matthews committed
93
#include <iterator>
jpbl's avatar
jpbl committed
94
#include <fstream>
95
#include <sstream>
jpbl's avatar
jpbl committed
96
#include <sys/types.h> // mkdir(2)
97
#include <sys/stat.h>  // mkdir(2)
98

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

116
117
118
ManagerImpl::~ManagerImpl()
{
    // destroy in reverse order of initialization
119
    pthread_mutex_destroy(&waitingCallsMutex_);
120
121
122
    pthread_mutex_destroy(&audioLayerMutex_);
    pthread_mutex_destroy(&toneMutex_);
    pthread_mutex_destroy(&currentCallMutex_);
123
124
}

125
126
127
128
129
130
131
132
133
134
135
namespace {
    // 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");
        std::ifstream src(path.c_str());
        std::ofstream dest(backup_path.c_str());
        dest << src.rdbuf();
        src.close();
        dest.close();
    }
136
137
}

138
void ManagerImpl::init(const std::string &config_file)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
139
{
140
    path_ = config_file.empty() ? retrieveConfigPath() : config_file;
141
    DEBUG("Configuration file path: %s", path_.c_str());
142

143
144
    bool no_errors = true;

145
    try {
146
        FILE *file = fopen(path_.c_str(), "rb");
147

148
149
        if (file) {
            Conf::YamlParser parser(file);
150
151
152
            parser.serializeEvents();
            parser.composeEvents();
            parser.constructNativeData();
153
            const int error_count = loadAccountMap(parser);
154
            fclose(file);
155
            if (error_count > 0) {
156
157
                WARN("Errors while parsing %s", path_.c_str());
                no_errors = false;
158
            }
159
        } else {
160
161
162
            WARN("Config file not found: creating default account map");
            loadDefaultAccountMap();
        }
163
    } catch (const Conf::YamlParserException &e) {
164
        ERROR("%s", e.what());
165
        no_errors = false;
166
167
    }

168
169
170
171
    // always back up last error-free configuration
    if (no_errors)
        make_backup(path_);

172
    initAudioDriver();
173

Tristan Matthews's avatar
Tristan Matthews committed
174
    {
175
        sfl::ScopedLock lock(audioLayerMutex_);
Tristan Matthews's avatar
Tristan Matthews committed
176
        if (audiodriver_) {
177
            {
178
                sfl::ScopedLock toneLock(toneMutex_);
179
180
                telephoneTone_.reset(new TelephoneTone(preferences.getZoneToneChoice(), audiodriver_->getSampleRate()));
            }
181
            dtmfKey_.reset(new DTMF(getMainBuffer().getInternalSamplingRate()));
Tristan Matthews's avatar
Tristan Matthews committed
182
        }
183
    }
184

185
    history_.load(preferences.getHistoryLimit());
186
    registerAccounts();
jpbl's avatar
jpbl committed
187
188
}

Emeric Vigier's avatar
Emeric Vigier committed
189
190
191
192
void ManagerImpl::setPath(const std::string &path) {
	history_.setPath(path);
}

193
#if HAVE_DBUS
194
195
void ManagerImpl::run()
{
196
    DEBUG("Starting client event loop");
197
    client_.event_loop();
198
}
199
#endif
200
201
202

void ManagerImpl::finish()
{
203
204
205
206
207
208
209
210
    if (finished_)
        return;

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

212
    std::vector<std::string> callList(getCallList());
213
    DEBUG("Hangup %zu remaining call(s)", callList.size());
214

215
    for (std::vector<std::string>::iterator iter = callList.begin();
216
            iter != callList.end(); ++iter)
217
        hangupCall(*iter);
218

219
220
    saveConfig();

221
    unregisterAllAccounts();
222

223
    SIPVoIPLink::destroy();
224
225
226
#if HAVE_IAX
    IAXVoIPLink::unloadAccountMap();
#endif
227

228
    {
229
        sfl::ScopedLock lock(audioLayerMutex_);
230
231
232
233

        delete audiodriver_;
        audiodriver_ = NULL;
    }
234

235
    client_.exit();
jpbl's avatar
jpbl committed
236
237
}

238
bool ManagerImpl::isCurrentCall(const std::string& callId) const
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
239
{
240
    return currentCallId_ == callId;
jpbl's avatar
jpbl committed
241
242
}

243
bool ManagerImpl::hasCurrentCall() const
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
244
{
245
    return not currentCallId_.empty();
jpbl's avatar
jpbl committed
246
247
}

248
std::string
249
ManagerImpl::getCurrentCallId() const
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
250
{
251
    return currentCallId_;
jpbl's avatar
jpbl committed
252
253
}

254
255
256
257
258
void ManagerImpl::unsetCurrentCall()
{
    switchCall("");
}

259
void ManagerImpl::switchCall(const std::string& id)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
260
{
261
    sfl::ScopedLock m(currentCallMutex_);
262
    DEBUG("----- Switch current call id to %s -----", id.c_str());
263
    currentCallId_ = id;
jpbl's avatar
jpbl committed
264
265
266
267
268
}

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

271
bool ManagerImpl::outgoingCall(const std::string& account_id,
272
273
274
                               const std::string& call_id,
                               const std::string& to,
                               const std::string& conf_id)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
275
{
276
    if (call_id.empty()) {
277
        DEBUG("New outgoing call abort, missing callid");
Emmanuel Lepage's avatar
Emmanuel Lepage committed
278
279
        return false;
    }
280

281
    // Call ID must be unique
282
    if (isValidCall(call_id)) {
283
        ERROR("Call id already exists in outgoing call");
284
285
286
        return false;
    }

287
    DEBUG("New outgoing call %s to %s", call_id.c_str(), to.c_str());
288

289
290
    stopTone();

291
    std::string current_call_id(getCurrentCallId());
292

293
    std::string prefix(hookPreference.getNumberAddPrefix());
294

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

297
298
    // in any cases we have to detach from current communication
    if (hasCurrentCall()) {
299
        DEBUG("Has current call (%s) put it onhold", current_call_id.c_str());
300

301
        // if this is not a conference and this and is not a conference participant
302
        if (not isConference(current_call_id) and not isConferenceParticipant(current_call_id))
303
            onHoldCall(current_call_id);
304
        else if (isConference(current_call_id) and not isConferenceParticipant(call_id))
305
            detachParticipant(MainBuffer::DEFAULT_ID);
306
    }
307

308
    DEBUG("Selecting account %s", account_id.c_str());
309

310
    // fallback using the default sip account if the specied doesn't exist
Tristan Matthews's avatar
Tristan Matthews committed
311
    std::string use_account_id;
312
    if (!accountExists(account_id)) {
313
        WARN("Account does not exist, trying with default SIP account");
314
315
316
317
        use_account_id = SIPAccount::IP2IP_PROFILE;
    }
    else {
        use_account_id = account_id;
318
    }
319

320
    try {
321
        Call *call = getAccountLink(account_id)->newOutgoingCall(call_id, to_cleaned, use_account_id);
322
323

        // try to reverse match the peer name using the cache
Tristan Matthews's avatar
Tristan Matthews committed
324
        if (call->getDisplayName().empty()) {
325
            const std::string pseudo_contact_name(HistoryNameCache::getInstance().getNameFromHistory(call->getPeerNumber(), call->getAccountId()));
Tristan Matthews's avatar
Tristan Matthews committed
326
            if (not pseudo_contact_name.empty())
327
328
                call->setDisplayName(pseudo_contact_name);
        }
329
        switchCall(call_id);
330
        call->setConfId(conf_id);
331
    } catch (const VoipLinkException &e) {
332
        callFailure(call_id);
333
        ERROR("%s", e.what());
334
        return false;
335
336
337
338
    } catch (ost::Socket *) {
        callFailure(call_id);
        ERROR("Could not bind socket");
        return false;
339
340
    }

341
    getMainBuffer().dumpInfo();
Alexandre Savard's avatar
Alexandre Savard committed
342

343
    return true;
jpbl's avatar
jpbl committed
344
345
}

yanmorin's avatar
   
yanmorin committed
346
//THREAD=Main : for outgoing Call
347
bool ManagerImpl::answerCall(const std::string& call_id)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
348
{
349
    bool result = true;
350
351
352
353
354
355
    Call *call = getCallFromCallID(call_id);

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

357
    // If sflphone is ringing
358
    stopTone();
359

360
    // set playback mode to VOICE
361
    AudioLayer *al = getAudioDriver();
362
363
    if(al) al->setPlaybackMode(AudioLayer::VOICE);

364
    // store the current call id
365
    std::string current_call_id(getCurrentCallId());
366

367
368
    // in any cases we have to detach from current communication
    if (hasCurrentCall()) {
369

370
        DEBUG("Currently conversing with %s", current_call_id.c_str());
371

372
        if (not isConference(current_call_id) and not isConferenceParticipant(current_call_id)) {
373
            DEBUG("Answer call: Put the current call (%s) on hold", current_call_id.c_str());
374
            onHoldCall(current_call_id);
375
        } else if (isConference(current_call_id) and not isConferenceParticipant(call_id)) {
376
            // if we are talking to a conference and we are answering an incoming call
377
            DEBUG("Detach main participant from conference");
378
            detachParticipant(MainBuffer::DEFAULT_ID);
379
380
        }
    }
381

382
    try {
383
        VoIPLink *link = getAccountLink(call->getAccountId());
384
385
        if (link)
            link->answer(call);
386
    } catch (const std::runtime_error &e) {
387
        ERROR("%s", e.what());
388
        result = false;
389
    }
Alexandre Savard's avatar
Alexandre Savard committed
390

391
    // if it was waiting, it's waiting no more
392
    removeWaitingCall(call_id);
393

394
    // if we dragged this call into a conference already
395
    if (isConferenceParticipant(call_id))
396
        switchCall(call->getConfId());
397
    else
398
        switchCall(call_id);
399

400
    // Connect streams
401
    addStream(call_id);
402

403
    getMainBuffer().dumpInfo();
Alexandre Savard's avatar
Alexandre Savard committed
404

405
    // Start recording if set in preference
406
    if (audioPreference.getIsAlwaysRecording())
407
        toggleRecordingCall(call_id);
408

409
    client_.getCallManager()->callStateChanged(call_id, "CURRENT");
410

411
    return result;
jpbl's avatar
jpbl committed
412
413
}

414
415
416
417
418
419
420
void ManagerImpl::checkAudio()
{
    if (getCallList().empty()) {
        sfl::ScopedLock lock(audioLayerMutex_);
        if (audiodriver_)
            audiodriver_->stopStream();
    }
421

jpbl's avatar
jpbl committed
422
423
}

yanmorin's avatar
   
yanmorin committed
424
//THREAD=Main
425
bool ManagerImpl::hangupCall(const std::string& callId)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
426
{
427
    // store the current call id
428
    std::string currentCallId(getCurrentCallId());
yanmorin's avatar
   
yanmorin committed
429

430
    stopTone();
431

432
    // set playback mode to NONE
433
    AudioLayer *al = getAudioDriver();
434
435
    if(al) al->setPlaybackMode(AudioLayer::NONE);

436
    DEBUG("Send call state change (HUNGUP) for id %s", callId.c_str());
437
    client_.getCallManager()->callStateChanged(callId, "HUNGUP");
438

439
    /* We often get here when the call was hungup before being created */
440
    if (not isValidCall(callId) and not isIPToIP(callId)) {
441
        DEBUG("Could not hang up call %s, call not valid", callId.c_str());
442
        checkAudio();
443
        return false;
444
445
    }

446
447
448
    // Disconnect streams
    removeStream(callId);

449
    if (isConferenceParticipant(callId)) {
450
        removeParticipant(callId);
451
    } else {
452
453
        // we are not participating in a conference, current call switched to ""
        if (not isConference(currentCallId))
454
            unsetCurrentCall();
455
    }
456

457
    if (isIPToIP(callId)) {
458
        /* Direct IP to IP call */
459
        try {
460
            Call * call = SIPVoIPLink::instance()->getSipCall(callId);
461
462
            if (call) {
                history_.addCall(call, preferences.getHistoryLimit());
463
                SIPVoIPLink::instance()->hangup(callId, 0);
464
                checkAudio();
465
466
                saveHistory();
            }
467
        } catch (const VoipLinkException &e) {
468
            ERROR("%s", e.what());
469
            return false;
470
        }
471
    } else {
472
        Call * call = getCallFromCallID(callId);
473
474
        if (call) {
            history_.addCall(call, preferences.getHistoryLimit());
475
            VoIPLink *link = getAccountLink(call->getAccountId());
476
            link->hangup(callId, 0);
477
            checkAudio();
478
479
            saveHistory();
        }
480
    }
481

482
    getMainBuffer().dumpInfo();
483
    return true;
jpbl's avatar
jpbl committed
484
485
}

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

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

492
    if (iter_conf != conferenceMap_.end()) {
493
        Conference *conf = iter_conf->second;
Alexandre Savard's avatar
Alexandre Savard committed
494

495
496
        if (conf) {
            ParticipantSet participants(conf->getParticipantList());
497

498
499
500
            for (ParticipantSet::const_iterator iter = participants.begin();
                    iter != participants.end(); ++iter)
                hangupCall(*iter);
501
        } else {
502
            ERROR("No such conference %s", id.c_str());
503
504
            return false;
        }
505
    }
Alexandre Savard's avatar
Alexandre Savard committed
506

507
    unsetCurrentCall();
508

509
    getMainBuffer().dumpInfo();
510

511
    return true;
Alexandre Savard's avatar
Alexandre Savard committed
512
513
}

jpbl's avatar
jpbl committed
514

yanmorin's avatar
   
yanmorin committed
515
//THREAD=Main
516
bool ManagerImpl::onHoldCall(const std::string& callId)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
517
{
518
    bool result = true;
519

520
    stopTone();
521

522
    std::string current_call_id(getCurrentCallId());
523

524
    try {
525
        if (isIPToIP(callId)) {
526
            SIPVoIPLink::instance()->onhold(callId);
527
528
529
        } else {
            /* Classic call, attached to an account */
            std::string account_id(getAccountFromCall(callId));
530

531
            if (account_id.empty()) {
532
                DEBUG("Account ID %s or callid %s doesn't exist in call onHold", account_id.c_str(), callId.c_str());
533
                return false;
534
            }
535
536
537
538

            getAccountLink(account_id)->onhold(callId);
        }
    } catch (const VoipLinkException &e) {
539
        ERROR("%s", e.what());
540
        result = false;
541
    }
542

543
    // Unbind calls in main buffer
544
    removeStream(callId);
545

546
    // Remove call from teh queue if it was still there
547
    removeWaitingCall(callId);
548

549
    // keeps current call id if the action is not holding this call or a new outgoing call
550
    // this could happen in case of a conference
551
    if (current_call_id == callId)
552
        unsetCurrentCall();
553

554
    client_.getCallManager()->callStateChanged(callId, "HOLD");
555

556
    getMainBuffer().dumpInfo();
557
    return result;
yanmorin's avatar
   
yanmorin committed
558
559
560
}

//THREAD=Main
561
bool ManagerImpl::offHoldCall(const std::string& callId)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
562
{
563
    bool result = true;
564

565
    stopTone();
566

Tristan Matthews's avatar
Tristan Matthews committed
567
    const std::string currentCallId(getCurrentCallId());
568

Tristan Matthews's avatar
Tristan Matthews committed
569
    // Place current call on hold if it isn't
570
    if (hasCurrentCall()) {
Tristan Matthews's avatar
Tristan Matthews committed
571
        if (not isConference(currentCallId) and not isConferenceParticipant(currentCallId)) {
572
            DEBUG("Has current call (%s), put on hold", currentCallId.c_str());
573
            onHoldCall(currentCallId);
574
575
        } else if (isConference(currentCallId) && callId != currentCallId) {
            holdConference(currentCallId);
576
        } else if (isConference(currentCallId) and not isConferenceParticipant(callId))
577
            detachParticipant(MainBuffer::DEFAULT_ID);
578
    }
alexandresavard's avatar
alexandresavard committed
579

580
    if (isIPToIP(callId))
Tristan Matthews's avatar
Tristan Matthews committed
581
        SIPVoIPLink::instance()->offhold(callId);
582
    else {
583
        /* Classic call, attached to an account */
584
        Call * call = getCallFromCallID(callId);
585

586
        if (call)
587
            getAccountLink(call->getAccountId())->offhold(callId);
588
589
        else
            result = false;
590
    }
591

592
    client_.getCallManager()->callStateChanged(callId, "UNHOLD");
593

594
    if (isConferenceParticipant(callId)) {
595
        Call *call = getCallFromCallID(callId);
596
        if (call)
597
            switchCall(call->getConfId());
598
599
        else
            result = false;
600
    } else
601
        switchCall(callId);
602

603
604
    addStream(callId);

605
    getMainBuffer().dumpInfo();
606
    return result;
jpbl's avatar
jpbl committed
607
608
}

yanmorin's avatar
   
yanmorin committed
609
//THREAD=Main
610
bool ManagerImpl::transferCall(const std::string& callId, const std::string& to)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
611
{
612
    if (isConferenceParticipant(callId)) {
613
        removeParticipant(callId);
Tristan Matthews's avatar
cleanup    
Tristan Matthews committed
614
    } else if (not isConference(getCurrentCallId()))
615
        unsetCurrentCall();
616

617
    // Direct IP to IP call
618
    if (isIPToIP(callId)) {
619
        SIPVoIPLink::instance()->transfer(callId, to);
620
621
    } else {
        std::string accountID(getAccountFromCall(callId));
622

623
        if (accountID.empty())
624
            return false;
625

626
627
        VoIPLink *link = getAccountLink(accountID);
        link->transfer(callId, to);
628
    }
629

630
    // remove waiting call in case we make transfer without even answer
631
    removeWaitingCall(callId);
632

633
    getMainBuffer().dumpInfo();
Alexandre Savard's avatar
Alexandre Savard committed
634

635
    return true;
jpbl's avatar
jpbl committed
636
637
}

638
void ManagerImpl::transferFailed()
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
639
{
640
    client_.getCallManager()->transferFailed();
641
642
}

643
void ManagerImpl::transferSucceeded()
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
644
{
645
    client_.getCallManager()->transferSucceeded();
646
647
}

648
bool ManagerImpl::attendedTransfer(const std::string& transferID, const std::string& targetID)