managerimpl.cpp 81.8 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 "dbus/callmanager.h"
44
#include "global.h"
45
#include "fileutils.h"
46
#include "map_utils.h"
47
#include "sip/sipvoiplink.h"
48
#include "sip/sipaccount.h"
49
#include "sip/sipcall.h"
50
#include "im/instant_messaging.h"
51
#include "sip/sippresence.h"
52
53

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

59
#include "numbercleaner.h"
60
61
#include "config/yamlparser.h"
#include "config/yamlemitter.h"
62
63
#include "audio/alsa/alsalayer.h"
#include "audio/sound/tonelist.h"
64
65
#include "audio/sound/audiofile.h"
#include "audio/sound/dtmf.h"
66
#include "history/historynamecache.h"
67
#include "manager.h"
68

69
#include "dbus/configurationmanager.h"
70
71
72
#ifdef SFL_VIDEO
#include "dbus/video_controls.h"
#endif
73

74
#include "conference.h"
75
#include "scoped_lock.h"
76

77
#include <cerrno>
78
#include <algorithm>
79
#include <ctime>
jpbl's avatar
jpbl committed
80
81
#include <cstdlib>
#include <iostream>
82
#include <tr1/functional>
Tristan Matthews's avatar
cleanup    
Tristan Matthews committed
83
#include <iterator>
jpbl's avatar
jpbl committed
84
#include <fstream>
85
#include <sstream>
jpbl's avatar
jpbl committed
86
#include <sys/types.h> // mkdir(2)
87
#include <sys/stat.h>  // mkdir(2)
88

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

106
107
108
ManagerImpl::~ManagerImpl()
{
    // destroy in reverse order of initialization
109
    pthread_mutex_destroy(&waitingCallsMutex_);
110
111
112
113
114
    pthread_mutex_destroy(&audioLayerMutex_);
    pthread_mutex_destroy(&toneMutex_);
    pthread_mutex_destroy(&currentCallMutex_);
}

115
116
117
118
119
120
121
122
123
124
125
126
127
128
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();
    }
}


129
void ManagerImpl::init(const std::string &config_file)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
130
{
131
    path_ = config_file.empty() ? createConfigFile() : config_file;
132
    DEBUG("Configuration file path: %s", path_.c_str());
133

134
135
    bool no_errors = true;

136
    try {
137
        FILE *file = fopen(path_.c_str(), "rb");
138

139
140
        if (file) {
            Conf::YamlParser parser(file);
141
142
143
            parser.serializeEvents();
            parser.composeEvents();
            parser.constructNativeData();
144
            const int error_count = loadAccountMap(parser);
145
            fclose(file);
146
            if (error_count > 0) {
147
148
                WARN("Errors while parsing %s", path_.c_str());
                no_errors = false;
149
            }
150
        } else {
151
152
153
            WARN("Config file not found: creating default account map");
            loadDefaultAccountMap();
        }
154
    } catch (const Conf::YamlParserException &e) {
155
        ERROR("%s", e.what());
156
        no_errors = false;
157
158
    }

159
160
161
162
    // always back up last error-free configuration
    if (no_errors)
        make_backup(path_);

163
    initAudioDriver();
164

Tristan Matthews's avatar
Tristan Matthews committed
165
    {
166
        sfl::ScopedLock lock(audioLayerMutex_);
Tristan Matthews's avatar
Tristan Matthews committed
167
        if (audiodriver_) {
168
            {
169
                sfl::ScopedLock toneLock(toneMutex_);
170
171
                telephoneTone_.reset(new TelephoneTone(preferences.getZoneToneChoice(), audiodriver_->getSampleRate()));
            }
172
            dtmfKey_.reset(new DTMF(getMainBuffer().getInternalSamplingRate()));
Tristan Matthews's avatar
Tristan Matthews committed
173
        }
174
    }
175

176
    history_.load(preferences.getHistoryLimit());
177
    registerAccounts();
jpbl's avatar
jpbl committed
178
179
}

180
181
182
183
184
185
186
187
void ManagerImpl::run()
{
    DEBUG("Starting DBus event loop");
    dbus_.exec();
}

void ManagerImpl::finish()
{
188
189
190
191
192
193
194
195
    if (finished_)
        return;

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

197
    std::vector<std::string> callList(getCallList());
198
    DEBUG("Hangup %zu remaining call(s)", callList.size());
199

200
    for (std::vector<std::string>::iterator iter = callList.begin();
201
            iter != callList.end(); ++iter)
202
        hangupCall(*iter);
203

204
205
    saveConfig();

206
    unregisterAllAccounts();
207

208
    SIPVoIPLink::destroy();
209
210
211
#if HAVE_IAX
    IAXVoIPLink::unloadAccountMap();
#endif
212

213
    {
214
        sfl::ScopedLock lock(audioLayerMutex_);
215
216
217
218

        delete audiodriver_;
        audiodriver_ = NULL;
    }
219

220
    dbus_.exit();
jpbl's avatar
jpbl committed
221
222
}

223
bool ManagerImpl::isCurrentCall(const std::string& callId) const
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
224
{
225
    return currentCallId_ == callId;
jpbl's avatar
jpbl committed
226
227
}

228
bool ManagerImpl::hasCurrentCall() const
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
229
{
230
    return not currentCallId_.empty();
jpbl's avatar
jpbl committed
231
232
}

233
std::string
234
ManagerImpl::getCurrentCallId() const
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
235
{
236
    return currentCallId_;
jpbl's avatar
jpbl committed
237
238
}

239
240
241
242
243
void ManagerImpl::unsetCurrentCall()
{
    switchCall("");
}

244
void ManagerImpl::switchCall(const std::string& id)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
245
{
246
    sfl::ScopedLock m(currentCallMutex_);
247
    DEBUG("----- Switch current call id to %s -----", id.c_str());
248
    currentCallId_ = id;
jpbl's avatar
jpbl committed
249
250
251
252
253
}

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

256
bool ManagerImpl::outgoingCall(const std::string& account_id,
257
258
259
                               const std::string& call_id,
                               const std::string& to,
                               const std::string& conf_id)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
260
{
261
    if (call_id.empty()) {
262
        DEBUG("New outgoing call abort, missing callid");
Emmanuel Lepage's avatar
Emmanuel Lepage committed
263
264
        return false;
    }
265

266
    // Call ID must be unique
267
    if (isValidCall(call_id)) {
268
        ERROR("Call id already exists in outgoing call");
269
270
271
        return false;
    }

272
    DEBUG("New outgoing call %s to %s", call_id.c_str(), to.c_str());
273

274
275
    stopTone();

276
    std::string current_call_id(getCurrentCallId());
277

278
    std::string prefix(hookPreference.getNumberAddPrefix());
279

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

282
283
    // in any cases we have to detach from current communication
    if (hasCurrentCall()) {
284
        DEBUG("Has current call (%s) put it onhold", current_call_id.c_str());
285

286
        // if this is not a conference and this and is not a conference participant
287
        if (not isConference(current_call_id) and not isConferenceParticipant(current_call_id))
288
            onHoldCall(current_call_id);
289
        else if (isConference(current_call_id) and not isConferenceParticipant(call_id))
290
            detachParticipant(MainBuffer::DEFAULT_ID);
291
    }
292

293
    DEBUG("Selecting account %s", account_id.c_str());
294

295
    // fallback using the default sip account if the specied doesn't exist
Tristan Matthews's avatar
Tristan Matthews committed
296
    std::string use_account_id;
297
    if (!accountExists(account_id)) {
298
        WARN("Account does not exist, trying with default SIP account");
299
300
301
302
        use_account_id = SIPAccount::IP2IP_PROFILE;
    }
    else {
        use_account_id = account_id;
303
    }
304

305
    try {
306
        Call *call = getAccountLink(account_id)->newOutgoingCall(call_id, to_cleaned, use_account_id);
307
308

        // try to reverse match the peer name using the cache
Tristan Matthews's avatar
Tristan Matthews committed
309
        if (call->getDisplayName().empty()) {
310
            const std::string pseudo_contact_name(HistoryNameCache::getInstance().getNameFromHistory(call->getPeerNumber(), call->getAccountId()));
Tristan Matthews's avatar
Tristan Matthews committed
311
            if (not pseudo_contact_name.empty())
312
313
                call->setDisplayName(pseudo_contact_name);
        }
314
        switchCall(call_id);
315
        call->setConfId(conf_id);
316
    } catch (const VoipLinkException &e) {
317
        callFailure(call_id);
318
        ERROR("%s", e.what());
319
        return false;
320
321
322
323
    } catch (ost::Socket *) {
        callFailure(call_id);
        ERROR("Could not bind socket");
        return false;
324
325
    }

326
    getMainBuffer().dumpInfo();
Alexandre Savard's avatar
Alexandre Savard committed
327

328
    return true;
jpbl's avatar
jpbl committed
329
330
}

yanmorin's avatar
   
yanmorin committed
331
//THREAD=Main : for outgoing Call
332
bool ManagerImpl::answerCall(const std::string& call_id)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
333
{
334
    bool result = true;
335
336
337
338
339
340
    Call *call = getCallFromCallID(call_id);

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

342
    // If sflphone is ringing
343
    stopTone();
344

345
    // set playback mode to VOICE
346
    AudioLayer *al = getAudioDriver();
347
348
    if(al) al->setPlaybackMode(AudioLayer::VOICE);

349
    // store the current call id
350
    std::string current_call_id(getCurrentCallId());
351

352
353
    // in any cases we have to detach from current communication
    if (hasCurrentCall()) {
354

355
        DEBUG("Currently conversing with %s", current_call_id.c_str());
356

357
        if (not isConference(current_call_id) and not isConferenceParticipant(current_call_id)) {
358
            DEBUG("Answer call: Put the current call (%s) on hold", current_call_id.c_str());
359
            onHoldCall(current_call_id);
360
        } else if (isConference(current_call_id) and not isConferenceParticipant(call_id)) {
361
            // if we are talking to a conference and we are answering an incoming call
362
            DEBUG("Detach main participant from conference");
363
            detachParticipant(MainBuffer::DEFAULT_ID);
364
365
        }
    }
366

367
    try {
368
        VoIPLink *link = getAccountLink(call->getAccountId());
369
370
        if (link)
            link->answer(call);
371
    } catch (const std::runtime_error &e) {
372
        ERROR("%s", e.what());
373
        result = false;
374
    }
Alexandre Savard's avatar
Alexandre Savard committed
375

376
    // if it was waiting, it's waiting no more
377
    removeWaitingCall(call_id);
378

379
    // if we dragged this call into a conference already
380
    if (isConferenceParticipant(call_id))
381
        switchCall(call->getConfId());
382
    else
383
        switchCall(call_id);
384

385
    // Connect streams
386
    addStream(call_id);
387

388
    getMainBuffer().dumpInfo();
Alexandre Savard's avatar
Alexandre Savard committed
389

390
    // Start recording if set in preference
391
    if (audioPreference.getIsAlwaysRecording())
392
        toggleRecordingCall(call_id);
393
394

    // update call state on client side
395
    dbus_.getCallManager()->callStateChanged(call_id, "CURRENT");
396
    return result;
jpbl's avatar
jpbl committed
397
398
}

399
400
401
402
403
404
405
406
407
void ManagerImpl::checkAudio()
{
    if (getCallList().empty()) {
        sfl::ScopedLock lock(audioLayerMutex_);
        if (audiodriver_)
            audiodriver_->stopStream();
    }
}

yanmorin's avatar
   
yanmorin committed
408
//THREAD=Main
409
bool ManagerImpl::hangupCall(const std::string& callId)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
410
{
411
    // store the current call id
412
    std::string currentCallId(getCurrentCallId());
yanmorin's avatar
   
yanmorin committed
413

414
    stopTone();
415

416
    // set playback mode to NONE
417
    AudioLayer *al = getAudioDriver();
418
419
    if(al) al->setPlaybackMode(AudioLayer::NONE);

420
    /* Broadcast a signal over DBus */
421
    DEBUG("Send DBUS call state change (HUNGUP) for id %s", callId.c_str());
422
    dbus_.getCallManager()->callStateChanged(callId, "HUNGUP");
423

424
    /* We often get here when the call was hungup before being created */
425
    if (not isValidCall(callId) and not isIPToIP(callId)) {
426
        DEBUG("Could not hang up call %s, call not valid", callId.c_str());
427
        checkAudio();
428
        return false;
429
430
    }

431
432
433
    // Disconnect streams
    removeStream(callId);

434
    if (isConferenceParticipant(callId)) {
435
        removeParticipant(callId);
436
    } else {
437
438
        // we are not participating in a conference, current call switched to ""
        if (not isConference(currentCallId))
439
            unsetCurrentCall();
440
    }
441

442
    if (isIPToIP(callId)) {
443
        /* Direct IP to IP call */
444
        try {
445
            Call * call = SIPVoIPLink::instance()->getSipCall(callId);
446
447
            if (call) {
                history_.addCall(call, preferences.getHistoryLimit());
448
                SIPVoIPLink::instance()->hangup(callId, 0);
449
                checkAudio();
450
451
                saveHistory();
            }
452
        } catch (const VoipLinkException &e) {
453
            ERROR("%s", e.what());
454
            return false;
455
        }
456
    } else {
457
        Call * call = getCallFromCallID(callId);
458
459
        if (call) {
            history_.addCall(call, preferences.getHistoryLimit());
460
            VoIPLink *link = getAccountLink(call->getAccountId());
461
            link->hangup(callId, 0);
462
            checkAudio();
463
464
            saveHistory();
        }
465
    }
466

467
    getMainBuffer().dumpInfo();
468
    return true;
jpbl's avatar
jpbl committed
469
470
}

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

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

477
    if (iter_conf != conferenceMap_.end()) {
478
        Conference *conf = iter_conf->second;
Alexandre Savard's avatar
Alexandre Savard committed
479

480
481
        if (conf) {
            ParticipantSet participants(conf->getParticipantList());
482

483
484
485
            for (ParticipantSet::const_iterator iter = participants.begin();
                    iter != participants.end(); ++iter)
                hangupCall(*iter);
486
        } else {
487
            ERROR("No such conference %s", id.c_str());
488
489
            return false;
        }
490
    }
Alexandre Savard's avatar
Alexandre Savard committed
491

492
    unsetCurrentCall();
493

494
    getMainBuffer().dumpInfo();
495

496
    return true;
Alexandre Savard's avatar
Alexandre Savard committed
497
498
}

jpbl's avatar
jpbl committed
499

yanmorin's avatar
   
yanmorin committed
500
//THREAD=Main
501
bool ManagerImpl::onHoldCall(const std::string& callId)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
502
{
503
    bool result = true;
504

505
    stopTone();
506

507
    std::string current_call_id(getCurrentCallId());
508

509
    try {
510
        if (isIPToIP(callId)) {
511
            SIPVoIPLink::instance()->onhold(callId);
512
513
514
        } else {
            /* Classic call, attached to an account */
            std::string account_id(getAccountFromCall(callId));
515

516
            if (account_id.empty()) {
517
                DEBUG("Account ID %s or callid %s doesn't exist in call onHold", account_id.c_str(), callId.c_str());
518
                return false;
519
            }
520
521
522
523

            getAccountLink(account_id)->onhold(callId);
        }
    } catch (const VoipLinkException &e) {
524
        ERROR("%s", e.what());
525
        result = false;
526
    }
527

528
    // Unbind calls in main buffer
529
    removeStream(callId);
530

531
    // Remove call from teh queue if it was still there
532
    removeWaitingCall(callId);
533

534
    // keeps current call id if the action is not holding this call or a new outgoing call
535
    // this could happen in case of a conference
536
    if (current_call_id == callId)
537
        unsetCurrentCall();
538

539
    dbus_.getCallManager()->callStateChanged(callId, "HOLD");
540

541
    getMainBuffer().dumpInfo();
542
    return result;
yanmorin's avatar
   
yanmorin committed
543
544
545
}

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

550
    stopTone();
551

Tristan Matthews's avatar
Tristan Matthews committed
552
    const std::string currentCallId(getCurrentCallId());
553

Tristan Matthews's avatar
Tristan Matthews committed
554
    // Place current call on hold if it isn't
555
    if (hasCurrentCall()) {
Tristan Matthews's avatar
Tristan Matthews committed
556
        if (not isConference(currentCallId) and not isConferenceParticipant(currentCallId)) {
557
            DEBUG("Has current call (%s), put on hold", currentCallId.c_str());
558
            onHoldCall(currentCallId);
559
560
        } else if (isConference(currentCallId) && callId != currentCallId) {
            holdConference(currentCallId);
561
        } else if (isConference(currentCallId) and not isConferenceParticipant(callId))
562
            detachParticipant(MainBuffer::DEFAULT_ID);
563
    }
alexandresavard's avatar
alexandresavard committed
564

565
    if (isIPToIP(callId))
Tristan Matthews's avatar
Tristan Matthews committed
566
        SIPVoIPLink::instance()->offhold(callId);
567
    else {
568
        /* Classic call, attached to an account */
569
        Call * call = getCallFromCallID(callId);
570

571
        if (call)
572
            getAccountLink(call->getAccountId())->offhold(callId);
573
574
        else
            result = false;
575
    }
576

577
    dbus_.getCallManager()->callStateChanged(callId, "UNHOLD");
578

579
    if (isConferenceParticipant(callId)) {
580
        Call *call = getCallFromCallID(callId);
581
        if (call)
582
            switchCall(call->getConfId());
583
584
        else
            result = false;
585
    } else
586
        switchCall(callId);
587

588
589
    addStream(callId);

590
    getMainBuffer().dumpInfo();
591
    return result;
jpbl's avatar
jpbl committed
592
593
}

yanmorin's avatar
   
yanmorin committed
594
//THREAD=Main
595
bool ManagerImpl::transferCall(const std::string& callId, const std::string& to)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
596
{
597
    if (isConferenceParticipant(callId)) {
598
        removeParticipant(callId);
Tristan Matthews's avatar
cleanup    
Tristan Matthews committed
599
    } else if (not isConference(getCurrentCallId()))
600
        unsetCurrentCall();
601

602
    // Direct IP to IP call
603
    if (isIPToIP(callId)) {
604
        SIPVoIPLink::instance()->transfer(callId, to);
605
606
    } else {
        std::string accountID(getAccountFromCall(callId));
607

608
        if (accountID.empty())
609
            return false;
610

611
612
        VoIPLink *link = getAccountLink(accountID);
        link->transfer(callId, to);
613
    }
614

615
    // remove waiting call in case we make transfer without even answer
616
    removeWaitingCall(callId);
617

618
    getMainBuffer().dumpInfo();
Alexandre Savard's avatar
Alexandre Savard committed
619

620
    return true;
jpbl's avatar
jpbl committed
621
622
}

623
void ManagerImpl::transferFailed()
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
624
{
625
    dbus_.getCallManager()->transferFailed();
626
627
}

628
void ManagerImpl::transferSucceeded()
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
629
{
630
    dbus_.getCallManager()->transferSucceeded();
631
632
}

633
bool ManagerImpl::attendedTransfer(const std::string& transferID, const std::string& targetID)
634
{
635
    if (isIPToIP(transferID))
Rafaël Carré's avatar
Rafaël Carré committed
636
        return SIPVoIPLink::instance()->attendedTransfer(transferID, targetID);
637

Rafaël Carré's avatar
Rafaël Carré committed
638
    // Classic call, attached to an account
639
    std::string accountid(getAccountFromCall(transferID));
640

641
642
643
644
    if (accountid.empty())
        return false;

    return getAccountLink(accountid)->attendedTransfer(transferID, targetID);
645
646
}

yanmorin's avatar
   
yanmorin committed
647
//THREAD=Main : Call:Incoming
648
bool ManagerImpl::refuseCall(const std::string& id)
Emmanuel Milou's avatar
[#2402]    
Emmanuel Milou committed
649
{
650
651
652
    if (!isValidCall(id))
        return false;

653
    stopTone();
654

Rafaël Carré's avatar
Rafaël Carré committed
655
    if (getCallList().size() <= 1) {
656
        sfl::ScopedLock lock(audioLayerMutex_);
657
        audiodriver_->stopStream();
658
    }
Emmanuel Milou's avatar
Emmanuel Milou committed
659

660
    /* Direct IP to IP call */
661

662
    if (isIPToIP(id))