/* * Copyright (C) 2004-2012 Savoir-Faire Linux Inc. * Author: Alexandre Bourget * Author: Yan Morin * Author: Laurielle Lea * Author: Emmanuel Milou * Author: Alexandre Savard * Author: Guillaume Carmel-Archambault * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * 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. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "logger.h" #include "managerimpl.h" #include "account_schema.h" #include "global.h" #include "fileutils.h" #include "map_utils.h" #include "sip/sipvoiplink.h" #include "sip/sipaccount.h" #include "sip/sipcall.h" #include "im/instant_messaging.h" #if HAVE_IAX #include "iax/iaxaccount.h" #include "iax/iaxcall.h" #include "iax/iaxvoiplink.h" #endif #include "numbercleaner.h" #include "config/yamlparser.h" #include "config/yamlemitter.h" #ifndef __ANDROID__ #include "audio/alsa/alsalayer.h" #endif #include "audio/sound/tonelist.h" #include "audio/sound/audiofile.h" #include "audio/sound/dtmf.h" #include "history/historynamecache.h" #include "history/history.h" #include "manager.h" #include "client/configurationmanager.h" #include "client/callmanager.h" #ifdef SFL_VIDEO #include "client/video_controls.h" #endif #include "conference.h" #include "scoped_lock.h" #include #include #include #include #include #include #include #include #include #include // mkdir(2) #include // mkdir(2) ManagerImpl::ManagerImpl() : preferences(), voipPreferences(), hookPreference(), audioPreference(), shortcutPreferences(), hasTriedToRegister_(false), audioCodecFactory(), client_(), config_(), currentCallId_(), currentCallMutex_(), audiodriver_(0), dtmfKey_(), toneMutex_(), telephoneTone_(), audiofile_(), audioLayerMutex_(), waitingCalls_(), waitingCallsMutex_(), path_(), IPToIPMap_(), mainBuffer_(), conferenceMap_(), history_(), finished_(false) { pthread_mutex_init(¤tCallMutex_, NULL); pthread_mutex_init(&toneMutex_, NULL); pthread_mutex_init(&audioLayerMutex_, NULL); pthread_mutex_init(&waitingCallsMutex_, NULL); // initialize random generator for call id srand(time(NULL)); } ManagerImpl::~ManagerImpl() { // destroy in reverse order of initialization pthread_mutex_destroy(&waitingCallsMutex_); pthread_mutex_destroy(&audioLayerMutex_); pthread_mutex_destroy(&toneMutex_); pthread_mutex_destroy(¤tCallMutex_); } namespace { void copy_over(const std::string &srcPath, const std::string &destPath) { std::ifstream src(srcPath.c_str()); std::ofstream dest(destPath.c_str()); dest << src.rdbuf(); src.close(); dest.close(); } // Creates a backup of the file at "path" with a .bak suffix appended 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); } } bool ManagerImpl::parseConfiguration() { bool result = true; FILE *file = fopen(path_.c_str(), "rb"); try { if (file) { Conf::YamlParser parser(file); parser.serializeEvents(); parser.composeEvents(); parser.constructNativeData(); const int error_count = loadAccountMap(parser); fclose(file); if (error_count > 0) { WARN("Errors while parsing %s", path_.c_str()); result = false; } } else { WARN("Config file not found: creating default account map"); loadDefaultAccountMap(); } } 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(); } catch (const Conf::YamlParserException &e) { ERROR("%s", e.what()); no_errors = false; } // always back up last error-free configuration if (no_errors) { make_backup(path_); } 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(); } } initAudioDriver(); { sfl::ScopedLock lock(audioLayerMutex_); if (audiodriver_) { { sfl::ScopedLock toneLock(toneMutex_); telephoneTone_.reset(new TelephoneTone(preferences.getZoneToneChoice(), audiodriver_->getSampleRate())); } dtmfKey_.reset(new DTMF(getMainBuffer().getInternalSamplingRate())); } } history_.load(preferences.getHistoryLimit()); registerAccounts(); } void ManagerImpl::setPath(const std::string &path) { history_.setPath(path); } #if HAVE_DBUS void ManagerImpl::run() { DEBUG("Starting client event loop"); client_.event_loop(); } #endif void ManagerImpl::finish() { if (finished_) return; finished_ = true; // Unset signal handlers signal(SIGHUP, SIG_DFL); signal(SIGINT, SIG_DFL); signal(SIGTERM, SIG_DFL); std::vector callList(getCallList()); DEBUG("Hangup %zu remaining call(s)", callList.size()); for (const auto &item : callList) hangupCall(item); saveConfig(); unregisterAllAccounts(); SIPVoIPLink::destroy(); #if HAVE_IAX IAXVoIPLink::unloadAccountMap(); #endif { sfl::ScopedLock lock(audioLayerMutex_); delete audiodriver_; audiodriver_ = NULL; } client_.exit(); } bool ManagerImpl::isCurrentCall(const std::string& callId) const { return currentCallId_ == callId; } bool ManagerImpl::hasCurrentCall() const { return not currentCallId_.empty(); } std::string ManagerImpl::getCurrentCallId() const { return currentCallId_; } void ManagerImpl::unsetCurrentCall() { switchCall(""); } void ManagerImpl::switchCall(const std::string& id) { sfl::ScopedLock m(currentCallMutex_); DEBUG("----- Switch current call id to %s -----", id.c_str()); currentCallId_ = id; } /////////////////////////////////////////////////////////////////////////////// // Management of events' IP-phone user /////////////////////////////////////////////////////////////////////////////// /* Main Thread */ bool ManagerImpl::outgoingCall(const std::string& account_id, const std::string& call_id, const std::string& to, const std::string& conf_id) { if (call_id.empty()) { DEBUG("New outgoing call abort, missing callid"); return false; } // Call ID must be unique if (isValidCall(call_id)) { ERROR("Call id already exists in outgoing call"); return false; } DEBUG("New outgoing call %s to %s", call_id.c_str(), to.c_str()); stopTone(); std::string current_call_id(getCurrentCallId()); std::string prefix(hookPreference.getNumberAddPrefix()); std::string to_cleaned(NumberCleaner::clean(to, prefix)); // in any cases we have to detach from current communication if (hasCurrentCall()) { DEBUG("Has current call (%s) put it onhold", current_call_id.c_str()); // if this is not a conference and this and is not a conference participant if (not isConference(current_call_id) and not isConferenceParticipant(current_call_id)) onHoldCall(current_call_id); else if (isConference(current_call_id) and not isConferenceParticipant(call_id)) detachParticipant(MainBuffer::DEFAULT_ID); } DEBUG("Selecting account %s", account_id.c_str()); // fallback using the default sip account if the specied doesn't exist std::string use_account_id; if (!accountExists(account_id)) { WARN("Account does not exist, trying with default SIP account"); use_account_id = SIPAccount::IP2IP_PROFILE; } else { use_account_id = account_id; } try { Call *call = getAccountLink(account_id)->newOutgoingCall(call_id, to_cleaned, use_account_id); // try to reverse match the peer name using the cache if (call->getDisplayName().empty()) { const std::string pseudo_contact_name(HistoryNameCache::getInstance().getNameFromHistory(call->getPeerNumber(), call->getAccountId())); if (not pseudo_contact_name.empty()) call->setDisplayName(pseudo_contact_name); } switchCall(call_id); call->setConfId(conf_id); } catch (const VoipLinkException &e) { callFailure(call_id); ERROR("%s", e.what()); return false; } catch (ost::Socket *) { callFailure(call_id); ERROR("Could not bind socket"); return false; } getMainBuffer().dumpInfo(); return true; } //THREAD=Main : for outgoing Call bool ManagerImpl::answerCall(const std::string& call_id) { bool result = true; Call *call = getCallFromCallID(call_id); if (call == NULL) { ERROR("Call %s is NULL", call_id.c_str()); return false; } // If sflphone is ringing stopTone(); // set playback mode to VOICE if (audiodriver_) audiodriver_->setPlaybackMode(AudioLayer::VOICE); // store the current call id std::string current_call_id(getCurrentCallId()); // in any cases we have to detach from current communication if (hasCurrentCall()) { DEBUG("Currently conversing with %s", current_call_id.c_str()); if (not isConference(current_call_id) and not isConferenceParticipant(current_call_id)) { DEBUG("Answer call: Put the current call (%s) on hold", current_call_id.c_str()); onHoldCall(current_call_id); } else if (isConference(current_call_id) and not isConferenceParticipant(call_id)) { // if we are talking to a conference and we are answering an incoming call DEBUG("Detach main participant from conference"); detachParticipant(MainBuffer::DEFAULT_ID); } } try { VoIPLink *link = getAccountLink(call->getAccountId()); if (link) link->answer(call); } catch (const std::runtime_error &e) { ERROR("%s", e.what()); result = false; } // if it was waiting, it's waiting no more removeWaitingCall(call_id); // if we dragged this call into a conference already if (isConferenceParticipant(call_id)) switchCall(call->getConfId()); else switchCall(call_id); // Connect streams addStream(call_id); getMainBuffer().dumpInfo(); // Start recording if set in preference if (audioPreference.getIsAlwaysRecording()) toggleRecordingCall(call_id); client_.getCallManager()->callStateChanged(call_id, "CURRENT"); return result; } void ManagerImpl::checkAudio() { if (getCallList().empty()) { sfl::ScopedLock lock(audioLayerMutex_); if (audiodriver_) audiodriver_->stopStream(); } } //THREAD=Main bool ManagerImpl::hangupCall(const std::string& callId) { // store the current call id std::string currentCallId(getCurrentCallId()); stopTone(); // set playback mode to NONE if (audiodriver_) audiodriver_->setPlaybackMode(AudioLayer::NONE); DEBUG("Send call state change (HUNGUP) for id %s", callId.c_str()); client_.getCallManager()->callStateChanged(callId, "HUNGUP"); /* We often get here when the call was hungup before being created */ if (not isValidCall(callId) and not isIPToIP(callId)) { DEBUG("Could not hang up call %s, call not valid", callId.c_str()); checkAudio(); return false; } // Disconnect streams removeStream(callId); if (isConferenceParticipant(callId)) { removeParticipant(callId); } else { // we are not participating in a conference, current call switched to "" if (not isConference(currentCallId)) unsetCurrentCall(); } if (isIPToIP(callId)) { /* Direct IP to IP call */ try { Call * call = SIPVoIPLink::instance()->getSipCall(callId); if (call) { history_.addCall(call, preferences.getHistoryLimit()); SIPVoIPLink::instance()->hangup(callId, 0); checkAudio(); saveHistory(); } } catch (const VoipLinkException &e) { ERROR("%s", e.what()); return false; } } else { Call * call = getCallFromCallID(callId); if (call) { history_.addCall(call, preferences.getHistoryLimit()); VoIPLink *link = getAccountLink(call->getAccountId()); link->hangup(callId, 0); checkAudio(); saveHistory(); } } getMainBuffer().dumpInfo(); return true; } bool ManagerImpl::hangupConference(const std::string& id) { DEBUG("Hangup conference %s", id.c_str()); ConferenceMap::iterator iter_conf = conferenceMap_.find(id); if (iter_conf != conferenceMap_.end()) { Conference *conf = iter_conf->second; if (conf) { ParticipantSet participants(conf->getParticipantList()); for (const auto &item : participants) hangupCall(item); } else { ERROR("No such conference %s", id.c_str()); return false; } } unsetCurrentCall(); getMainBuffer().dumpInfo(); return true; } //THREAD=Main bool ManagerImpl::onHoldCall(const std::string& callId) { bool result = true; stopTone(); std::string current_call_id(getCurrentCallId()); try { if (isIPToIP(callId)) { SIPVoIPLink::instance()->onhold(callId); } else { /* Classic call, attached to an account */ std::string account_id(getAccountFromCall(callId)); if (account_id.empty()) { DEBUG("Account ID %s or callid %s doesn't exist in call onHold", account_id.c_str(), callId.c_str()); return false; } getAccountLink(account_id)->onhold(callId); } } catch (const VoipLinkException &e) { ERROR("%s", e.what()); result = false; } // Unbind calls in main buffer removeStream(callId); // Remove call from teh queue if it was still there removeWaitingCall(callId); // keeps current call id if the action is not holding this call or a new outgoing call // this could happen in case of a conference if (current_call_id == callId) unsetCurrentCall(); client_.getCallManager()->callStateChanged(callId, "HOLD"); getMainBuffer().dumpInfo(); return result; } //THREAD=Main bool ManagerImpl::offHoldCall(const std::string& callId) { bool result = true; stopTone(); const std::string currentCallId(getCurrentCallId()); // Place current call on hold if it isn't if (hasCurrentCall()) { if (not isConference(currentCallId) and not isConferenceParticipant(currentCallId)) { DEBUG("Has current call (%s), put on hold", currentCallId.c_str()); onHoldCall(currentCallId); } else if (isConference(currentCallId) && callId != currentCallId) { holdConference(currentCallId); } else if (isConference(currentCallId) and not isConferenceParticipant(callId)) detachParticipant(MainBuffer::DEFAULT_ID); } if (isIPToIP(callId)) SIPVoIPLink::instance()->offhold(callId); else { /* Classic call, attached to an account */ Call * call = getCallFromCallID(callId); if (call) getAccountLink(call->getAccountId())->offhold(callId); else result = false; } client_.getCallManager()->callStateChanged(callId, "UNHOLD"); if (isConferenceParticipant(callId)) { Call *call = getCallFromCallID(callId); if (call) switchCall(call->getConfId()); else result = false; } else switchCall(callId); addStream(callId); getMainBuffer().dumpInfo(); return result; } //THREAD=Main bool ManagerImpl::transferCall(const std::string& callId, const std::string& to) { if (isConferenceParticipant(callId)) { removeParticipant(callId); } else if (not isConference(getCurrentCallId())) unsetCurrentCall(); // Direct IP to IP call if (isIPToIP(callId)) { SIPVoIPLink::instance()->transfer(callId, to); } else { std::string accountID(getAccountFromCall(callId)); if (accountID.empty()) return false; VoIPLink *link = getAccountLink(accountID); link->transfer(callId, to); } // remove waiting call in case we make transfer without even answer removeWaitingCall(callId); getMainBuffer().dumpInfo(); return true; } void ManagerImpl::transferFailed() { client_.getCallManager()->transferFailed(); } void ManagerImpl::transferSucceeded() { client_.getCallManager()->transferSucceeded(); } bool ManagerImpl::attendedTransfer(const std::string& transferID, const std::string& targetID) { if (isIPToIP(transferID)) return SIPVoIPLink::instance()->attendedTransfer(transferID, targetID); // Classic call, attached to an account std::string accountid(getAccountFromCall(transferID)); if (accountid.empty()) return false; return getAccountLink(accountid)->attendedTransfer(transferID, targetID); } //THREAD=Main : Call:Incoming bool ManagerImpl::refuseCall(const std::string& id) { if (!isValidCall(id)) return false; stopTone(); if (getCallList().size() <= 1) { sfl::ScopedLock lock(audioLayerMutex_); audiodriver_->stopStream(); } /* Direct IP to IP call */ if (isIPToIP(id)) SIPVoIPLink::instance()->refuse(id); else { /* Classic call, attached to an account */ std::string accountid = getAccountFromCall(id); if (accountid.empty()) return false; getAccountLink(accountid)->refuse(id); checkAudio(); } removeWaitingCall(id); client_.getCallManager()->callStateChanged(id, "HUNGUP"); // Disconnect streams removeStream(id); getMainBuffer().dumpInfo(); return true; } Conference* ManagerImpl::createConference(const std::string& id1, const std::string& id2) { DEBUG("Create conference with call %s and %s", id1.c_str(), id2.c_str()); Conference* conf = new Conference; conf->add(id1); conf->add(id2); // Add conference to map conferenceMap_.insert(std::make_pair(conf->getConfID(), conf)); client_.getCallManager()->conferenceCreated(conf->getConfID()); return conf; } void ManagerImpl::removeConference(const std::string& conference_id) { DEBUG("Remove conference %s", conference_id.c_str()); DEBUG("number of participants: %u", conferenceMap_.size()); ConferenceMap::iterator iter = conferenceMap_.find(conference_id); Conference* conf = 0; if (iter != conferenceMap_.end()) conf = iter->second; if (conf == 0) { ERROR("Conference not found"); return; } client_.getCallManager()->conferenceRemoved(conference_id); // We now need to bind the audio to the remain participant // Unbind main participant audio from conference getMainBuffer().unBindAll(MainBuffer::DEFAULT_ID); ParticipantSet participants(conf->getParticipantList()); // bind main participant audio to remaining conference call ParticipantSet::iterator iter_p = participants.begin(); if (iter_p != participants.end()) getMainBuffer().bindCallID(*iter_p, MainBuffer::DEFAULT_ID); // Then remove the conference from the conference map if (conferenceMap_.erase(conference_id)) DEBUG("Conference %s removed successfully", conference_id.c_str()); else ERROR("Cannot remove conference: %s", conference_id.c_str()); delete conf; } Conference* ManagerImpl::getConferenceFromCallID(const std::string& call_id) { Call *call = getCallFromCallID(call_id); if (!call) return NULL; ConferenceMap::const_iterator iter(conferenceMap_.find(call->getConfId())); if (iter != conferenceMap_.end()) return iter->second; else return NULL; } bool ManagerImpl::holdConference(const std::string& id) { ConferenceMap::iterator iter_conf = conferenceMap_.find(id); if (iter_conf == conferenceMap_.end()) return false; Conference *conf = iter_conf->second; bool isRec = conf->getState() == Conference::ACTIVE_ATTACHED_REC or conf->getState() == Conference::ACTIVE_DETACHED_REC or conf->getState() == Conference::HOLD_REC; ParticipantSet participants(conf->getParticipantList()); for (const auto &item : participants) { switchCall(item); onHoldCall(item); } conf->setState(isRec ? Conference::HOLD_REC : Conference::HOLD); client_.getCallManager()->conferenceChanged(conf->getConfID(), conf->getStateStr()); return true; } bool ManagerImpl::unHoldConference(const std::string& id) { ConferenceMap::iterator iter_conf = conferenceMap_.find(id); if (iter_conf == conferenceMap_.end() or iter_conf->second == 0) return false; Conference *conf = iter_conf->second; bool isRec = conf->getState() == Conference::ACTIVE_ATTACHED_REC or conf->getState() == Conference::ACTIVE_DETACHED_REC or conf->getState() == Conference::HOLD_REC; ParticipantSet participants(conf->getParticipantList()); for (const auto &item : participants) { Call *call = getCallFromCallID(item); if (call) { // if one call is currently recording, the conference is in state recording isRec |= call->isRecording(); switchCall(item); offHoldCall(item); } } conf->setState(isRec ? Conference::ACTIVE_ATTACHED_REC : Conference::ACTIVE_ATTACHED); client_.getCallManager()->conferenceChanged(conf->getConfID(), conf->getStateStr()); return true; } bool ManagerImpl::isConference(const std::string& id) const { return conferenceMap_.find(id) != conferenceMap_.end(); } bool ManagerImpl::isConferenceParticipant(const std::string& call_id) { Call *call(getCallFromCallID(call_id)); return call and not call->getConfId().empty(); } bool ManagerImpl::addParticipant(const std::string& callId, const std::string& conferenceId) { DEBUG("Add participant %s to %s", callId.c_str(), conferenceId.c_str()); ConferenceMap::iterator iter = conferenceMap_.find(conferenceId); if (iter == conferenceMap_.end()) { ERROR("Conference id is not valid"); return false; } Call *call = getCallFromCallID(callId); if (call == NULL) { ERROR("Call id %s is not valid", callId.c_str()); return false; } // ensure that calls are only in one conference at a time if (isConferenceParticipant(callId)) detachParticipant(callId); // store the current call id (it will change in offHoldCall or in answerCall) std::string current_call_id(getCurrentCallId()); // detach from prior communication and switch to this conference if (current_call_id != callId) { if (isConference(current_call_id)) detachParticipant(MainBuffer::DEFAULT_ID); else onHoldCall(current_call_id); } // TODO: remove this ugly hack => There should be different calls when double clicking // a conference to add main participant to it, or (in this case) adding a participant // toconference unsetCurrentCall(); // Add main participant addMainParticipant(conferenceId); Conference* conf = iter->second; switchCall(conf->getConfID()); // Add coresponding IDs in conf and call call->setConfId(conf->getConfID()); conf->add(callId); // Connect new audio streams together getMainBuffer().unBindAll(callId); std::map callDetails(getCallDetails(callId)); std::string callState(callDetails.find("CALL_STATE")->second); if (callState == "HOLD") { conf->bindParticipant(callId); offHoldCall(callId); } else if (callState == "INCOMING") { conf->bindParticipant(callId); answerCall(callId); } else if (callState == "CURRENT") conf->bindParticipant(callId); ParticipantSet participants(conf->getParticipantList()); if (participants.empty()) ERROR("Participant list is empty for this conference"); // reset ring buffer for all conference participant // flush conference participants only for (const auto &p : participants) getMainBuffer().flush(p); getMainBuffer().flush(MainBuffer::DEFAULT_ID); // Connect stream addStream(callId); return true; } bool ManagerImpl::addMainParticipant(const std::string& conference_id) { if (hasCurrentCall()) { std::string current_call_id(getCurrentCallId()); if (isConference(current_call_id)) detachParticipant(MainBuffer::DEFAULT_ID); else onHoldCall(current_call_id); } { sfl::ScopedLock lock(audioLayerMutex_); ConferenceMap::const_iterator iter = conferenceMap_.find(conference_id); if (iter == conferenceMap_.end() or iter->second == 0) return false; Conference *conf = iter->second; ParticipantSet participants(conf->getParticipantList()); for (const auto &item_p : participants) { getMainBuffer().bindCallID(item_p, MainBuffer::DEFAULT_ID); // Reset ringbuffer's readpointers getMainBuffer().flush(item_p); } getMainBuffer().flush(MainBuffer::DEFAULT_ID); if (conf->getState() == Conference::ACTIVE_DETACHED) conf->setState(Conference::ACTIVE_ATTACHED); else if (conf->getState() == Conference::ACTIVE_DETACHED_REC) conf->setState(Conference::ACTIVE_ATTACHED_REC); else WARN("Invalid conference state while adding main participant"); client_.getCallManager()->conferenceChanged(conference_id, conf->getStateStr()); } switchCall(conference_id); return true; } Call * ManagerImpl::getCallFromCallID(const std::string &callID) { Call *call = NULL; call = SIPVoIPLink::instance()->getSipCall(callID); #if HAVE_IAX if (call != NULL) return call; call = IAXVoIPLink::getIaxCall(callID); #endif return call; } bool ManagerImpl::joinParticipant(const std::string& callId1, const std::string& callId2) { if (callId1 == callId2) { ERROR("Cannot join participant %s to itself", callId1.c_str()); return false; } // Set corresponding conference ids for call 1 Call *call1 = getCallFromCallID(callId1); if (call1 == NULL) { ERROR("Could not find call %s", callId1.c_str()); return false; } // Set corresponding conderence details Call *call2 = getCallFromCallID(callId2); if (call2 == NULL) { ERROR("Could not find call %s", callId2.c_str()); return false; } // ensure that calls are only in one conference at a time if (isConferenceParticipant(callId1)) detachParticipant(callId1); if (isConferenceParticipant(callId2)) detachParticipant(callId2); std::map call1Details(getCallDetails(callId1)); std::map call2Details(getCallDetails(callId2)); std::string current_call_id(getCurrentCallId()); DEBUG("Current Call ID %s", current_call_id.c_str()); // detach from the conference and switch to this conference if ((current_call_id != callId1) and (current_call_id != callId2)) { // If currently in a conference if (isConference(current_call_id)) detachParticipant(MainBuffer::DEFAULT_ID); else onHoldCall(current_call_id); // currently in a call } Conference *conf = createConference(callId1, callId2); call1->setConfId(conf->getConfID()); getMainBuffer().unBindAll(callId1); call2->setConfId(conf->getConfID()); getMainBuffer().unBindAll(callId2); // Process call1 according to its state std::string call1_state_str(call1Details.find("CALL_STATE")->second); DEBUG("Process call %s state: %s", callId1.c_str(), call1_state_str.c_str()); if (call1_state_str == "HOLD") { conf->bindParticipant(callId1); offHoldCall(callId1); } else if (call1_state_str == "INCOMING") { conf->bindParticipant(callId1); answerCall(callId1); } else if (call1_state_str == "CURRENT") { conf->bindParticipant(callId1); } else if (call1_state_str == "INACTIVE") { conf->bindParticipant(callId1); answerCall(callId1); } else WARN("Call state not recognized"); // Process call2 according to its state std::string call2_state_str(call2Details.find("CALL_STATE")->second); DEBUG("Process call %s state: %s", callId2.c_str(), call2_state_str.c_str()); if (call2_state_str == "HOLD") { conf->bindParticipant(callId2); offHoldCall(callId2); } else if (call2_state_str == "INCOMING") { conf->bindParticipant(callId2); answerCall(callId2); } else if (call2_state_str == "CURRENT") { conf->bindParticipant(callId2); } else if (call2_state_str == "INACTIVE") { conf->bindParticipant(callId2); answerCall(callId2); } else WARN("Call state not recognized"); // Switch current call id to this conference switchCall(conf->getConfID()); conf->setState(Conference::ACTIVE_ATTACHED); // set recording sampling rate { sfl::ScopedLock lock(audioLayerMutex_); if (audiodriver_) conf->setRecordingSmplRate(audiodriver_->getSampleRate()); } getMainBuffer().dumpInfo(); return true; } void ManagerImpl::createConfFromParticipantList(const std::vector< std::string > &participantList) { // we must at least have 2 participant for a conference if (participantList.size() <= 1) { ERROR("Participant number must be higher or equal to 2"); return; } Conference *conf = new Conference; int successCounter = 0; for (const auto &p : participantList) { std::string numberaccount(p); std::string tostr(numberaccount.substr(0, numberaccount.find(","))); std::string account(numberaccount.substr(numberaccount.find(",") + 1, numberaccount.size())); std::string generatedCallID(getNewCallID()); // Manager methods may behave differently if the call id participates in a conference conf->add(generatedCallID); unsetCurrentCall(); // Create call bool callSuccess = outgoingCall(account, generatedCallID, tostr, conf->getConfID()); // If not able to create call remove this participant from the conference if (!callSuccess) conf->remove(generatedCallID); else { client_.getCallManager()->newCallCreated(account, generatedCallID, tostr); successCounter++; } } // Create the conference if and only if at least 2 calls have been successfully created if (successCounter >= 2) { conferenceMap_[conf->getConfID()] = conf; client_.getCallManager()->conferenceCreated(conf->getConfID()); { sfl::ScopedLock lock(audioLayerMutex_); if (audiodriver_) conf->setRecordingSmplRate(audiodriver_->getSampleRate()); } getMainBuffer().dumpInfo(); } else { delete conf; } } bool ManagerImpl::detachParticipant(const std::string& call_id) { const std::string current_call_id(getCurrentCallId()); if (call_id != MainBuffer::DEFAULT_ID) { Call *call = getCallFromCallID(call_id); if (call == NULL) { ERROR("Could not find call %s", call_id.c_str()); return false; } Conference *conf = getConferenceFromCallID(call_id); if (conf == NULL) { ERROR("Call is not conferencing, cannot detach"); return false; } std::map call_details(getCallDetails(call_id)); std::map::iterator iter_details(call_details.find("CALL_STATE")); if (iter_details == call_details.end()) { ERROR("Could not find CALL_STATE"); return false; } // Don't hold ringing calls when detaching them from conferences if (iter_details->second != "RINGING") onHoldCall(call_id); removeParticipant(call_id); } else { DEBUG("Unbind main participant from conference %d"); getMainBuffer().unBindAll(MainBuffer::DEFAULT_ID); if (not isConference(current_call_id)) { ERROR("Current call id (%s) is not a conference", current_call_id.c_str()); return false; } ConferenceMap::iterator iter = conferenceMap_.find(current_call_id); Conference *conf = iter->second; if (iter == conferenceMap_.end() or conf == 0) { DEBUG("Conference is NULL"); return false; } if (conf->getState() == Conference::ACTIVE_ATTACHED) conf->setState(Conference::ACTIVE_DETACHED); else if (conf->getState() == Conference::ACTIVE_ATTACHED_REC) conf->setState(Conference::ACTIVE_DETACHED_REC); else WARN("Undefined behavior, invalid conference state in detach participant"); client_.getCallManager()->conferenceChanged(conf->getConfID(), conf->getStateStr()); unsetCurrentCall(); } return true; } void ManagerImpl::removeParticipant(const std::string& call_id) { DEBUG("Remove participant %s", call_id.c_str()); // this call is no longer a conference participant Call *call(getCallFromCallID(call_id)); if (call == 0) { ERROR("Call not found"); return; } ConferenceMap::const_iterator iter = conferenceMap_.find(call->getConfId()); Conference *conf = iter->second; if (iter == conferenceMap_.end() or conf == 0) { ERROR("No conference with id %s, cannot remove participant", call->getConfId().c_str()); return; } conf->remove(call_id); call->setConfId(""); removeStream(call_id); getMainBuffer().dumpInfo(); client_.getCallManager()->conferenceChanged(conf->getConfID(), conf->getStateStr()); processRemainingParticipants(*conf); } void ManagerImpl::processRemainingParticipants(Conference &conf) { const std::string current_call_id(getCurrentCallId()); ParticipantSet participants(conf.getParticipantList()); const size_t n = participants.size(); DEBUG("Process remaining %d participant(s) from conference %s", n, conf.getConfID().c_str()); if (n > 1) { // Reset ringbuffer's readpointers for (const auto &p : participants) getMainBuffer().flush(p); getMainBuffer().flush(MainBuffer::DEFAULT_ID); } else if (n == 1) { // this call is the last participant, hence // the conference is over ParticipantSet::iterator p = participants.begin(); Call *call = getCallFromCallID(*p); if (call) { call->setConfId(""); // if we are not listening to this conference if (current_call_id != conf.getConfID()) onHoldCall(call->getCallId()); else switchCall(*p); } DEBUG("No remaining participants, remove conference"); removeConference(conf.getConfID()); } else { DEBUG("No remaining participants, remove conference"); removeConference(conf.getConfID()); unsetCurrentCall(); } } bool ManagerImpl::joinConference(const std::string& conf_id1, const std::string& conf_id2) { if (conferenceMap_.find(conf_id1) == conferenceMap_.end()) { ERROR("Not a valid conference ID: %s", conf_id1.c_str()); return false; } if (conferenceMap_.find(conf_id2) == conferenceMap_.end()) { ERROR("Not a valid conference ID: %s", conf_id2.c_str()); return false; } Conference *conf = conferenceMap_.find(conf_id1)->second; ParticipantSet participants(conf->getParticipantList()); for (const auto &p : participants) addParticipant(p, conf_id2); return true; } void ManagerImpl::addStream(const std::string& call_id) { DEBUG("Add audio stream %s", call_id.c_str()); Call *call = getCallFromCallID(call_id); if (call and isConferenceParticipant(call_id)) { DEBUG("Add stream to conference"); // bind to conference participant ConferenceMap::iterator iter = conferenceMap_.find(call->getConfId()); if (iter != conferenceMap_.end() and iter->second) { Conference* conf = iter->second; conf->bindParticipant(call_id); ParticipantSet participants(conf->getParticipantList()); // reset ring buffer for all conference participant for (const auto &participant : participants) getMainBuffer().flush(participant); getMainBuffer().flush(MainBuffer::DEFAULT_ID); } } else { DEBUG("Add stream to call"); // bind to main getMainBuffer().bindCallID(call_id, MainBuffer::DEFAULT_ID); sfl::ScopedLock lock(audioLayerMutex_); audiodriver_->flushUrgent(); audiodriver_->flushMain(); } getMainBuffer().dumpInfo(); } void ManagerImpl::removeStream(const std::string& call_id) { DEBUG("Remove audio stream %s", call_id.c_str()); getMainBuffer().unBindAll(call_id); getMainBuffer().dumpInfo(); } //THREAD=Main void ManagerImpl::saveConfig() { DEBUG("Saving Configuration to XDG directory %s", path_.c_str()); if (audiodriver_ != NULL) { audioPreference.setVolumemic(audiodriver_->getCaptureGain()); audioPreference.setVolumespkr(audiodriver_->getPlaybackGain()); } try { Conf::YamlEmitter emitter(path_.c_str()); for (auto &item : SIPVoIPLink::instance()->getAccounts()) item.second->serialize(emitter); #if HAVE_IAX for (auto &item : IAXVoIPLink::getAccounts()) item.second->serialize(emitter); #endif preferences.serialize(emitter); voipPreferences.serialize(emitter); hookPreference.serialize(emitter); audioPreference.serialize(emitter); #ifdef SFL_VIDEO getVideoControls()->getVideoPreferences().serialize(emitter); #endif shortcutPreferences.serialize(emitter); emitter.serializeData(); } catch (const Conf::YamlEmitterException &e) { ERROR("ConfigTree: %s", e.what()); } } //THREAD=Main void ManagerImpl::sendDtmf(const std::string& id, char code) { playDtmf(code); // return if we're not "in" a call if (id.empty()) return; std::string accountid(getAccountFromCall(id)); getAccountLink(accountid)->carryingDTMFdigits(id, code); } //THREAD=Main | VoIPLink void ManagerImpl::playDtmf(char code) { stopTone(); if (not voipPreferences.getPlayDtmf()) { DEBUG("Do not have to play a tone..."); return; } // length in milliseconds int pulselen = voipPreferences.getPulseLength(); if (pulselen == 0) { DEBUG("Pulse length is not set..."); return; } sfl::ScopedLock lock(audioLayerMutex_); // numbers of int = length in milliseconds / 1000 (number of seconds) // = number of seconds * SAMPLING_RATE by SECONDS // fast return, no sound, so no dtmf if (audiodriver_ == NULL || dtmfKey_.get() == 0) { DEBUG("No audio layer..."); return; } // number of data sampling in one pulselen depends on samplerate // size (n sampling) = time_ms * sampling/s // --------------------- // ms/s int size = (int)((pulselen * (float) audiodriver_->getSampleRate()) / 1000); // this buffer is for mono // TODO <-- this should be global and hide if same size //std::vector buf(size); AudioBuffer buf(size); // Handle dtmf dtmfKey_->startTone(code); // copy the sound if (dtmfKey_->generateDTMF(*buf.getChannel(0))) { // Put buffer to urgentRingBuffer // put the size in bytes... // so size * 1 channel (mono) * sizeof (bytes for the data) // audiolayer->flushUrgent(); audiodriver_->startStream(); audiodriver_->putUrgent(buf); } // TODO Cache the DTMF } // Multi-thread bool ManagerImpl::incomingCallsWaiting() { sfl::ScopedLock m(waitingCallsMutex_); return not waitingCalls_.empty(); } void ManagerImpl::addWaitingCall(const std::string& id) { sfl::ScopedLock m(waitingCallsMutex_); waitingCalls_.insert(id); } void ManagerImpl::removeWaitingCall(const std::string& id) { sfl::ScopedLock m(waitingCallsMutex_); waitingCalls_.erase(id); } /////////////////////////////////////////////////////////////////////////////// // Management of event peer IP-phone //////////////////////////////////////////////////////////////////////////////// // SipEvent Thread void ManagerImpl::incomingCall(Call &call, const std::string& accountId) { stopTone(); const std::string callID(call.getCallId()); if (accountId.empty()) setIPToIPForCall(callID, true); else { // strip sip: which is not required and bring confusion with ip to ip calls // when placing new call from history (if call is IAX, do nothing) std::string peerNumber(call.getPeerNumber()); const char SIP_PREFIX[] = "sip:"; size_t startIndex = peerNumber.find(SIP_PREFIX); if (startIndex != std::string::npos) call.setPeerNumber(peerNumber.substr(startIndex + sizeof(SIP_PREFIX) - 1)); } if (not hasCurrentCall()) { call.setConnectionState(Call::RINGING); playRingtone(accountId); } addWaitingCall(callID); std::string number(call.getPeerNumber()); std::string from("<" + number + ">"); client_.getCallManager()->incomingCall(accountId, callID, call.getDisplayName() + " " + from); } //THREAD=VoIP #if HAVE_INSTANT_MESSAGING void ManagerImpl::incomingMessage(const std::string& callID, const std::string& from, const std::string& message) { if (isConferenceParticipant(callID)) { Conference *conf = getConferenceFromCallID(callID); ParticipantSet participants(conf->getParticipantList()); for (const auto &item_p : participants) { if (item_p == callID) continue; std::string accountId(getAccountFromCall(item_p)); DEBUG("Send message to %s, (%s)", item_p.c_str(), accountId.c_str()); Account *account = getAccount(accountId); if (!account) { ERROR("Failed to get account while sending instant message"); return; } account->getVoIPLink()->sendTextMessage(callID, message, from); } // in case of a conference we must notify client using conference id client_.getCallManager()->incomingMessage(conf->getConfID(), from, message); } else client_.getCallManager()->incomingMessage(callID, from, message); } //THREAD=VoIP bool ManagerImpl::sendTextMessage(const std::string& callID, const std::string& message, const std::string& from) { if (isConference(callID)) { DEBUG("Is a conference, send instant message to everyone"); ConferenceMap::iterator it = conferenceMap_.find(callID); if (it == conferenceMap_.end()) return false; Conference *conf = it->second; if (!conf) return false; ParticipantSet participants(conf->getParticipantList()); for (const auto &participant : participants) { std::string accountId = getAccountFromCall(participant); Account *account = getAccount(accountId); if (!account) { DEBUG("Failed to get account while sending instant message"); return false; } account->getVoIPLink()->sendTextMessage(participant, message, from); } return true; } if (isConferenceParticipant(callID)) { DEBUG("Call is participant in a conference, send instant message to everyone"); Conference *conf = getConferenceFromCallID(callID); if (!conf) return false; ParticipantSet participants(conf->getParticipantList()); for (const auto &item_p : participants) { const std::string accountId(getAccountFromCall(item_p)); Account *account = getAccount(accountId); if (!account) { DEBUG("Failed to get account while sending instant message"); return false; } account->getVoIPLink()->sendTextMessage(item_p, message, from); } } else { Account *account = getAccount(getAccountFromCall(callID)); if (!account) { DEBUG("Failed to get account while sending instant message"); return false; } account->getVoIPLink()->sendTextMessage(callID, message, from); } return true; } #endif // HAVE_INSTANT_MESSAGING //THREAD=VoIP CALL=Outgoing void ManagerImpl::peerAnsweredCall(const std::string& id) { DEBUG("Peer answered call %s", id.c_str()); // The if statement is usefull only if we sent two calls at the same time. if (isCurrentCall(id)) { stopTone(); // set playback mode to VOICE if (audiodriver_) audiodriver_->setPlaybackMode(AudioLayer::VOICE); } // Connect audio streams addStream(id); { sfl::ScopedLock lock(audioLayerMutex_); audiodriver_->flushMain(); audiodriver_->flushUrgent(); } if (audioPreference.getIsAlwaysRecording()) toggleRecordingCall(id); client_.getCallManager()->callStateChanged(id, "CURRENT"); } //THREAD=VoIP Call=Outgoing void ManagerImpl::peerRingingCall(const std::string& id) { DEBUG("Peer call %s ringing", id.c_str()); if (isCurrentCall(id)) ringback(); client_.getCallManager()->callStateChanged(id, "RINGING"); } //THREAD=VoIP Call=Outgoing/Ingoing void ManagerImpl::peerHungupCall(const std::string& call_id) { DEBUG("Peer hungup call %s", call_id.c_str()); if (isConferenceParticipant(call_id)) { removeParticipant(call_id); } else if (isCurrentCall(call_id)) { stopTone(); unsetCurrentCall(); // set playback mode to NONE if (audiodriver_) audiodriver_->setPlaybackMode(AudioLayer::NONE); } /* Direct IP to IP call */ if (isIPToIP(call_id)) { Call * call = SIPVoIPLink::instance()->getSipCall(call_id); if (call) { history_.addCall(call, preferences.getHistoryLimit()); SIPVoIPLink::instance()->hangup(call_id, 0); saveHistory(); } } else { Call * call = getCallFromCallID(call_id); if (call) { VoIPLink *link = getAccountLink(call->getAccountId()); history_.addCall(call, preferences.getHistoryLimit()); link->peerHungup(call_id); saveHistory(); } } client_.getCallManager()->callStateChanged(call_id, "HUNGUP"); removeWaitingCall(call_id); if (not incomingCallsWaiting()) stopTone(); removeStream(call_id); } //THREAD=VoIP void ManagerImpl::callBusy(const std::string& id) { client_.getCallManager()->callStateChanged(id, "BUSY"); if (isCurrentCall(id)) { playATone(Tone::TONE_BUSY); unsetCurrentCall(); } checkAudio(); removeWaitingCall(id); } //THREAD=VoIP void ManagerImpl::callFailure(const std::string& call_id) { client_.getCallManager()->callStateChanged(call_id, "FAILURE"); if (isCurrentCall(call_id)) { playATone(Tone::TONE_BUSY); unsetCurrentCall(); } if (isConferenceParticipant(call_id)) { DEBUG("Call %s participating in a conference failed", call_id.c_str()); // remove this participant removeParticipant(call_id); } checkAudio(); removeWaitingCall(call_id); } //THREAD=VoIP void ManagerImpl::startVoiceMessageNotification(const std::string& accountId, int nb_msg) { client_.getCallManager()->voiceMailNotify(accountId, nb_msg); } /** * Multi Thread */ void ManagerImpl::playATone(Tone::TONEID toneId) { if (not voipPreferences.getPlayTones()) return; { sfl::ScopedLock lock(audioLayerMutex_); if (audiodriver_ == NULL) { ERROR("Audio layer not initialized"); return; } audiodriver_->flushUrgent(); audiodriver_->startStream(); } { sfl::ScopedLock lock(toneMutex_); if (telephoneTone_.get() != 0) telephoneTone_->setCurrentTone(toneId); } } /** * Multi Thread */ void ManagerImpl::stopTone() { if (not voipPreferences.getPlayTones()) return; sfl::ScopedLock lock(toneMutex_); if (telephoneTone_.get() != NULL) telephoneTone_->setCurrentTone(Tone::TONE_NULL); if (audiofile_.get()) { std::string filepath(audiofile_->getFilePath()); client_.getCallManager()->recordPlaybackStopped(filepath); audiofile_.reset(); } } /** * Multi Thread */ void ManagerImpl::playTone() { playATone(Tone::TONE_DIALTONE); } /** * Multi Thread */ void ManagerImpl::playToneWithMessage() { playATone(Tone::TONE_CONGESTION); } /** * Multi Thread */ void ManagerImpl::congestion() { playATone(Tone::TONE_CONGESTION); } /** * Multi Thread */ void ManagerImpl::ringback() { playATone(Tone::TONE_RINGTONE); } // Caller must hold toneMutex void ManagerImpl::updateAudioFile(const std::string &file, int sampleRate) { try { audiofile_.reset(new AudioFile(file, sampleRate)); } catch (const AudioFileException &e) { ERROR("Exception: %s", e.what()); } } /** * Multi Thread */ void ManagerImpl::playRingtone(const std::string& accountID) { Account *account = getAccount(accountID); if (!account) { WARN("Invalid account in ringtone"); return; } if (!account->getRingtoneEnabled()) { ringback(); return; } std::string ringchoice = account->getRingtonePath(); if (ringchoice.find(DIR_SEPARATOR_STR) == std::string::npos) { // check inside global share directory static const char * const RINGDIR = "ringtones"; ringchoice = std::string(PROGSHAREDIR) + DIR_SEPARATOR_STR + RINGDIR + DIR_SEPARATOR_STR + ringchoice; } int audioLayerSmplr = 8000; { sfl::ScopedLock lock(audioLayerMutex_); if (!audiodriver_) { ERROR("no audio layer in ringtone"); return; } audioLayerSmplr = audiodriver_->getSampleRate(); } { sfl::ScopedLock m(toneMutex_); if (audiofile_.get()) { client_.getCallManager()->recordPlaybackStopped(audiofile_->getFilePath()); audiofile_.reset(); } updateAudioFile(ringchoice, audioLayerSmplr); } // leave mutex sfl::ScopedLock lock(audioLayerMutex_); // start audio if not started AND flush all buffers (main and urgent) audiodriver_->startStream(); } AudioLoop* ManagerImpl::getTelephoneTone() { sfl::ScopedLock m(toneMutex_); if (telephoneTone_.get()) return telephoneTone_->getCurrentTone(); else return NULL; } AudioLoop* ManagerImpl::getTelephoneFile() { sfl::ScopedLock m(toneMutex_); return audiofile_.get(); } /////////////////////////////////////////////////////////////////////////////// // Private functions /////////////////////////////////////////////////////////////////////////////// /** * Initialization: Main Thread */ std::string ManagerImpl::retrieveConfigPath() const { #ifdef __ANDROID__ std::string configdir = "/data/data/com.savoirfairelinux.sflphone"; #else std::string configdir = fileutils::get_home_dir() + DIR_SEPARATOR_STR + ".config" + DIR_SEPARATOR_STR + PACKAGE; #endif const std::string xdg_env(XDG_CONFIG_HOME); if (not xdg_env.empty()) configdir = xdg_env + DIR_SEPARATOR_STR + PACKAGE; if (mkdir(configdir.data(), 0700) != 0) { // If directory creation failed if (errno != EEXIST) DEBUG("Cannot create directory: %s!", configdir.c_str()); } static const char * const PROGNAME = "sflphoned"; return configdir + DIR_SEPARATOR_STR + PROGNAME + ".yml"; } std::string ManagerImpl::getCurrentAudioCodecName(const std::string& id) { Call* call = getCallFromCallID(id); std::string codecName; if (call) { Call::CallState state = call->getState(); if (state == Call::ACTIVE or state == Call::CONFERENCING) { VoIPLink* link = getAccountLink(call->getAccountId()); codecName = link->getCurrentAudioCodecNames(call); } } return codecName; } std::string ManagerImpl::getCurrentVideoCodecName(const std::string& ID) { Call *call(getCallFromCallID(ID)); if (call) { VoIPLink* link = getAccountLink(call->getAccountId()); return link->getCurrentVideoCodecName(call); } else { return ""; } } /** * Set input audio plugin */ void ManagerImpl::setAudioPlugin(const std::string& audioPlugin) { sfl::ScopedLock lock(audioLayerMutex_); audioPreference.setAlsaPlugin(audioPlugin); bool wasStarted = audiodriver_->isStarted(); // Recreate audio driver with new settings delete audiodriver_; audiodriver_ = audioPreference.createAudioLayer(); if (wasStarted) audiodriver_->startStream(); } /** * Set audio output device */ void ManagerImpl::setAudioDevice(int index, AudioLayer::PCMType type) { sfl::ScopedLock lock(audioLayerMutex_); if (!audiodriver_) { ERROR("Audio driver not initialized"); return ; } const bool wasStarted = audiodriver_->isStarted(); audiodriver_->updatePreference(audioPreference, index, type); // Recreate audio driver with new settings delete audiodriver_; audiodriver_ = audioPreference.createAudioLayer(); if (wasStarted) audiodriver_->startStream(); } /** * Get list of supported audio output device */ std::vector ManagerImpl::getAudioOutputDeviceList() { sfl::ScopedLock lock(audioLayerMutex_); return audiodriver_->getPlaybackDeviceList(); } /** * Get list of supported audio input device */ std::vector ManagerImpl::getAudioInputDeviceList() { sfl::ScopedLock lock(audioLayerMutex_); return audiodriver_->getCaptureDeviceList(); } /** * Get string array representing integer indexes of output and input device */ std::vector ManagerImpl::getCurrentAudioDevicesIndex() { sfl::ScopedLock lock(audioLayerMutex_); std::vector v; std::stringstream ssi, sso, ssr; sso << audiodriver_->getIndexPlayback(); v.push_back(sso.str()); ssi << audiodriver_->getIndexCapture(); v.push_back(ssi.str()); ssr << audiodriver_->getIndexRingtone(); v.push_back(ssr.str()); return v; } int ManagerImpl::isRingtoneEnabled(const std::string& id) { Account *account = getAccount(id); if (!account) { WARN("Invalid account in ringtone enabled"); return 0; } return account->getRingtoneEnabled(); } void ManagerImpl::ringtoneEnabled(const std::string& id) { Account *account = getAccount(id); if (!account) { WARN("Invalid account in ringtone enabled"); return; } account->getRingtoneEnabled() ? account->setRingtoneEnabled(false) : account->setRingtoneEnabled(true); } bool ManagerImpl::getIsAlwaysRecording() const { return audioPreference.getIsAlwaysRecording(); } void ManagerImpl::setIsAlwaysRecording(bool isAlwaysRec) { return audioPreference.setIsAlwaysRecording(isAlwaysRec); } bool ManagerImpl::toggleRecordingCall(const std::string& id) { Recordable* rec = NULL; ConferenceMap::const_iterator it(conferenceMap_.find(id)); if (it == conferenceMap_.end()) { DEBUG("toggle recording for call %s", id.c_str()); rec = getCallFromCallID(id); } else { DEBUG("toggle recording for conference %s", id.c_str()); Conference *conf = it->second; if (conf) { rec = conf; if (rec->isRecording()) conf->setState(Conference::ACTIVE_ATTACHED); else conf->setState(Conference::ACTIVE_ATTACHED_REC); } } if (rec == NULL) { ERROR("Could not find recordable instance %s", id.c_str()); return false; } const bool result = rec->toggleRecording(); client_.getCallManager()->recordPlaybackFilepath(id, rec->getFilename()); return result; } bool ManagerImpl::isRecording(const std::string& id) { Recordable* rec = getCallFromCallID(id); return rec and rec->isRecording(); } bool ManagerImpl::startRecordedFilePlayback(const std::string& filepath) { DEBUG("Start recorded file playback %s", filepath.c_str()); int sampleRate; { sfl::ScopedLock lock(audioLayerMutex_); if (!audiodriver_) { ERROR("No audio layer in start recorded file playback"); return false; } sampleRate = audiodriver_->getSampleRate(); } { sfl::ScopedLock m(toneMutex_); if (audiofile_.get()) { client_.getCallManager()->recordPlaybackStopped(audiofile_->getFilePath()); audiofile_.reset(); } updateAudioFile(filepath, sampleRate); if (not audiofile_) return false; } // release toneMutex sfl::ScopedLock lock(audioLayerMutex_); audiodriver_->startStream(); return true; } void ManagerImpl::recordingPlaybackSeek(const double value) { sfl::ScopedLock m(toneMutex_); if (audiofile_.get()) audiofile_.get()->seek(value); } void ManagerImpl::stopRecordedFilePlayback(const std::string& filepath) { DEBUG("Stop recorded file playback %s", filepath.c_str()); { sfl::ScopedLock lock(audioLayerMutex_); audiodriver_->stopStream(); } { sfl::ScopedLock m(toneMutex_); audiofile_.reset(); } client_.getCallManager()->recordPlaybackStopped(filepath); } void ManagerImpl::setHistoryLimit(int days) { DEBUG("Set history limit"); preferences.setHistoryLimit(days); saveConfig(); } int ManagerImpl::getHistoryLimit() const { return preferences.getHistoryLimit(); } void ManagerImpl::setAudioManager(const std::string &api) { { sfl::ScopedLock lock(audioLayerMutex_); if (!audiodriver_) return; if (api == audioPreference.getAudioApi()) { DEBUG("Audio manager chosen already in use. No changes made. "); return; } } switchAudioManager(); saveConfig(); } std::string ManagerImpl::getAudioManager() const { return audioPreference.getAudioApi(); } int ManagerImpl::getAudioDeviceIndex(const std::string &name) { int soundCardIndex = 0; sfl::ScopedLock lock(audioLayerMutex_); if (audiodriver_ == NULL) { ERROR("Audio layer not initialized"); return soundCardIndex; } return audiodriver_->getAudioDeviceIndex(name); } std::string ManagerImpl::getCurrentAudioOutputPlugin() const { return audioPreference.getAlsaPlugin(); } std::string ManagerImpl::getNoiseSuppressState() const { return audioPreference.getNoiseReduce() ? "enabled" : "disabled"; } void ManagerImpl::setNoiseSuppressState(const std::string &state) { audioPreference.setNoiseReduce(state == "enabled"); } bool ManagerImpl::getEchoCancelState() const { return audioPreference.getEchoCancel(); } void ManagerImpl::setEchoCancelState(const std::string &state) { audioPreference.setEchoCancel(state == "enabled"); } /** * Initialization: Main Thread */ void ManagerImpl::initAudioDriver() { sfl::ScopedLock lock(audioLayerMutex_); audiodriver_ = audioPreference.createAudioLayer(); } void ManagerImpl::switchAudioManager() { sfl::ScopedLock lock(audioLayerMutex_); bool wasStarted = audiodriver_->isStarted(); delete audiodriver_; audiodriver_ = audioPreference.switchAndCreateAudioLayer(); if (wasStarted) audiodriver_->startStream(); } void ManagerImpl::audioSamplingRateChanged(int samplerate) { sfl::ScopedLock lock(audioLayerMutex_); if (!audiodriver_) { DEBUG("No Audio driver initialized"); return; } // Only modify internal sampling rate if new sampling rate is higher int currentSamplerate = mainBuffer_.getInternalSamplingRate(); if (currentSamplerate >= samplerate) { DEBUG("No need to update audio layer sampling rate"); return; } else DEBUG("Audio sampling rate changed: %d -> %d", currentSamplerate, samplerate); bool wasActive = audiodriver_->isStarted(); mainBuffer_.setInternalSamplingRate(samplerate); delete audiodriver_; audiodriver_ = audioPreference.createAudioLayer(); unsigned int sampleRate = audiodriver_->getSampleRate(); { sfl::ScopedLock toneLock(toneMutex_); telephoneTone_.reset(new TelephoneTone(preferences.getZoneToneChoice(), sampleRate)); } dtmfKey_.reset(new DTMF(sampleRate)); if (wasActive) audiodriver_->startStream(); } //THREAD=Main std::string ManagerImpl::getConfigString(const std::string& section, const std::string& name) const { return config_.getConfigTreeItemValue(section, name); } //THREAD=Main void ManagerImpl::setConfig(const std::string& section, const std::string& name, const std::string& value) { config_.setConfigTreeItem(section, name, value); } //THREAD=Main void ManagerImpl::setConfig(const std::string& section, const std::string& name, int value) { std::ostringstream valueStream; valueStream << value; config_.setConfigTreeItem(section, name, valueStream.str()); } void ManagerImpl::setAccountsOrder(const std::string& order) { DEBUG("Set accounts order : %s", order.c_str()); // Set the new config preferences.setAccountOrder(order); saveConfig(); } std::vector ManagerImpl::getAccountList() const { using std::vector; using std::string; vector account_order(loadAccountOrder()); // The IP2IP profile is always available, and first in the list Account *account = getIP2IPAccount(); vector v; if (account) v.push_back(account->getAccountID()); else ERROR("could not find IP2IP profile in getAccount list"); // Concatenate all account pointers in a single map AccountMap allAccounts(getAllAccounts()); // If no order has been set, load the default one ie according to the creation date. if (account_order.empty()) { for (const auto &item : allAccounts) { if (item.first == SIPAccount::IP2IP_PROFILE || item.first.empty()) continue; if (item.second) v.push_back(item.second->getAccountID()); } } else { for (const auto &item : account_order) { if (item == SIPAccount::IP2IP_PROFILE or item.empty()) continue; AccountMap::const_iterator account_iter = allAccounts.find(item); if (account_iter != allAccounts.end() and account_iter->second) v.push_back(account_iter->second->getAccountID()); } } return v; } std::map ManagerImpl::getAccountDetails( const std::string& accountID) const { // Default account used to get default parameters if requested by client (to build new account) static const SIPAccount DEFAULT_ACCOUNT("default"); if (accountID.empty()) { DEBUG("Returning default account settings"); return DEFAULT_ACCOUNT.getAccountDetails(); } Account * account = getAccount(accountID); if (account) return account->getAccountDetails(); else { ERROR("Get account details on a non-existing accountID %s. Returning default", accountID.c_str()); return DEFAULT_ACCOUNT.getAccountDetails(); } } // method to reduce the if/else mess. // Even better, switch to XML ! void ManagerImpl::setAccountDetails(const std::string& accountID, const std::map& details) { DEBUG("Set account details for %s", accountID.c_str()); Account* account = getAccount(accountID); if (account == NULL) { ERROR("Could not find account %s", accountID.c_str()); return; } account->setAccountDetails(details); // Serialize configuration to disk once it is done saveConfig(); if (account->isEnabled()) account->registerVoIPLink(); else account->unregisterVoIPLink(); // Update account details to the client side client_.getConfigurationManager()->accountsChanged(); } std::string ManagerImpl::addAccount(const std::map& details) { /** @todo Deal with both the accountMap_ and the Configuration */ std::stringstream accountID; accountID << "Account:" << time(NULL); std::string newAccountID(accountID.str()); // Get the type std::string accountType; if (details.find(CONFIG_ACCOUNT_TYPE) == details.end()) accountType = "SIP"; else accountType = ((*details.find(CONFIG_ACCOUNT_TYPE)).second); DEBUG("Adding account %s", newAccountID.c_str()); /** @todo Verify the uniqueness, in case a program adds accounts, two in a row. */ Account* newAccount = NULL; if (accountType == "SIP") { newAccount = new SIPAccount(newAccountID); SIPVoIPLink::instance()->getAccounts()[newAccountID] = newAccount; } #if HAVE_IAX else if (accountType == "IAX") { newAccount = new IAXAccount(newAccountID); IAXVoIPLink::getAccounts()[newAccountID] = newAccount; } #endif else { ERROR("Unknown %s param when calling addAccount(): %s", CONFIG_ACCOUNT_TYPE, accountType.c_str()); return ""; } newAccount->setAccountDetails(details); // Add the newly created account in the account order list std::string accountList(preferences.getAccountOrder()); newAccountID += "/"; if (not accountList.empty()) { // Prepend the new account accountList.insert(0, newAccountID); preferences.setAccountOrder(accountList); } else { accountList = newAccountID; preferences.setAccountOrder(accountList); } DEBUG("Getting accounts: %s", accountList.c_str()); newAccount->registerVoIPLink(); saveConfig(); client_.getConfigurationManager()->accountsChanged(); return accountID.str(); } void ManagerImpl::removeAccount(const std::string& accountID) { // Get it down and dying Account* remAccount = getAccount(accountID); if (remAccount != NULL) { remAccount->unregisterVoIPLink(); SIPVoIPLink::instance()->getAccounts().erase(accountID); #if HAVE_IAX IAXVoIPLink::getAccounts().erase(accountID); #endif // http://projects.savoirfairelinux.net/issues/show/2355 // delete remAccount; } config_.removeSection(accountID); saveConfig(); client_.getConfigurationManager()->accountsChanged(); } std::string ManagerImpl::getAccountFromCall(const std::string& callID) { Call *call = getCallFromCallID(callID); if (call) return call->getAccountId(); else return ""; } // FIXME: get rid of this, there's no guarantee that // a Call will still exist after this has been called. bool ManagerImpl::isValidCall(const std::string& callID) { Call *call = getCallFromCallID(callID); return call != 0; } std::string ManagerImpl::getNewCallID() { std::ostringstream random_id("s"); random_id << (unsigned) rand(); // when it's not found, it return "" // generate, something like s10000s20000s4394040 while (isValidCall(random_id.str())) { random_id.clear(); random_id << "s"; random_id << (unsigned) rand(); } return random_id.str(); } std::vector ManagerImpl::loadAccountOrder() const { return Account::split_string(preferences.getAccountOrder()); } namespace { bool isIP2IP(const Conf::YamlNode *node) { if (!node) return false; std::string id; node->getValue("id", &id); return id == "IP2IP"; } #if HAVE_IAX void loadAccount(const Conf::YamlNode *item, AccountMap &sipAccountMap, AccountMap &iaxAccountMap, int &errorCount) #else void loadAccount(const Conf::YamlNode *item, AccountMap &sipAccountMap, int &errorCount) #endif { if (!item) { ERROR("Could not load account"); ++errorCount; return; } std::string accountType; item->getValue("type", &accountType); std::string accountid; item->getValue("id", &accountid); std::string accountAlias; item->getValue("alias", &accountAlias); if (!accountid.empty() and !accountAlias.empty() and accountid != SIPAccount::IP2IP_PROFILE) { if (accountType == "SIP") { Account *a = new SIPAccount(accountid); sipAccountMap[accountid] = a; a->unserialize(*item); } else if (accountType == "IAX") { #if HAVE_IAX Account *a = new IAXAccount(accountid); iaxAccountMap[accountid] = a; a->unserialize(*item); #else ERROR("Ignoring IAX account"); ++errorCount; #endif } else { ERROR("Ignoring unknown account type \"%s\"", accountType.c_str()); ++errorCount; } } } void registerAccount(std::pair &item) { item.second->registerVoIPLink(); } void unregisterAccount(std::pair &item) { item.second->unregisterVoIPLink(); } SIPAccount *createIP2IPAccount() { SIPAccount *ip2ip = new SIPAccount(SIPAccount::IP2IP_PROFILE); try { SIPVoIPLink::instance()->sipTransport.createSipTransport(*ip2ip); } catch (const std::runtime_error &e) { ERROR("%s", e.what()); } return ip2ip; } } // end anonymous namespace void ManagerImpl::loadDefaultAccountMap() { // build a default IP2IP account with default parameters only if does not exist AccountMap::const_iterator iter = SIPVoIPLink::instance()->getAccounts().find(SIPAccount::IP2IP_PROFILE); if (iter == SIPVoIPLink::instance()->getAccounts().end()) SIPVoIPLink::instance()->getAccounts()[SIPAccount::IP2IP_PROFILE] = createIP2IPAccount(); SIPVoIPLink::instance()->getAccounts()[SIPAccount::IP2IP_PROFILE]->registerVoIPLink(); } int ManagerImpl::loadAccountMap(Conf::YamlParser &parser) { using namespace Conf; // build a default IP2IP account with default parameters AccountMap::const_iterator iter = SIPVoIPLink::instance()->getAccounts().find(SIPAccount::IP2IP_PROFILE); if (iter == SIPVoIPLink::instance()->getAccounts().end()) SIPVoIPLink::instance()->getAccounts()[SIPAccount::IP2IP_PROFILE] = createIP2IPAccount(); // load saved preferences for IP2IP account from configuration file Sequence *seq = parser.getAccountSequence()->getSequence(); Sequence::const_iterator ip2ip = std::find_if(seq->begin(), seq->end(), isIP2IP); if (ip2ip != seq->end()) { SIPVoIPLink::instance()->getAccounts()[SIPAccount::IP2IP_PROFILE]->unserialize(**ip2ip); } // Force IP2IP settings to be loaded // No registration in the sense of the REGISTER method is performed. SIPVoIPLink::instance()->getAccounts()[SIPAccount::IP2IP_PROFILE]->registerVoIPLink(); // build preferences preferences.unserialize(*parser.getPreferenceNode()); voipPreferences.unserialize(*parser.getVoipPreferenceNode()); hookPreference.unserialize(*parser.getHookNode()); audioPreference.unserialize(*parser.getAudioNode()); shortcutPreferences.unserialize(*parser.getShortcutNode()); int errorCount = 0; #ifdef SFL_VIDEO VideoControls *controls(getVideoControls()); try { MappingNode *videoNode = parser.getVideoNode(); if (videoNode) controls->getVideoPreferences().unserialize(*videoNode); } catch (const YamlParserException &e) { ERROR("No video node in config file"); ++errorCount; } #endif using std::tr1::placeholders::_1; #if HAVE_IAX std::for_each(seq->begin(), seq->end(), std::tr1::bind(loadAccount, _1, std::tr1::ref(SIPVoIPLink::instance()->getAccounts()), std::tr1::ref(IAXVoIPLink::getAccounts()), std::tr1::ref(errorCount))); #else std::for_each(seq->begin(), seq->end(), std::tr1::bind(loadAccount, _1, std::tr1::ref(SIPVoIPLink::instance()->getAccounts()), std::tr1::ref(errorCount))); #endif return errorCount; } void ManagerImpl::registerAllAccounts() { std::for_each(SIPVoIPLink::instance()->getAccounts().begin(), SIPVoIPLink::instance()->getAccounts().end(), registerAccount); #if HAVE_IAX std::for_each(IAXVoIPLink::getAccounts().begin(), IAXVoIPLink::getAccounts().end(), registerAccount); #endif } void ManagerImpl::unregisterAllAccounts() { std::for_each(SIPVoIPLink::instance()->getAccounts().begin(), SIPVoIPLink::instance()->getAccounts().end(), unregisterAccount); #if HAVE_IAX std::for_each(IAXVoIPLink::getAccounts().begin(), IAXVoIPLink::getAccounts().end(), unregisterAccount); #endif } bool ManagerImpl::accountExists(const std::string &accountID) { bool ret = false; ret = SIPVoIPLink::instance()->getAccounts().find(accountID) != SIPVoIPLink::instance()->getAccounts().end(); #if HAVE_IAX if(ret) return ret; ret = IAXVoIPLink::getAccounts().find(accountID) != IAXVoIPLink::getAccounts().end(); #endif return ret; } SIPAccount* ManagerImpl::getIP2IPAccount() const { AccountMap::const_iterator iter = SIPVoIPLink::instance()->getAccounts().find(SIPAccount::IP2IP_PROFILE); if(iter == SIPVoIPLink::instance()->getAccounts().end()) return NULL; return static_cast(iter->second); } Account* ManagerImpl::getAccount(const std::string& accountID) const { Account *account = NULL; account = getSipAccount(accountID); if(account != NULL) return account; #if HAVE_IAX account = getIaxAccount(accountID); if(account != NULL) return account; #endif return NULL; } SIPAccount * ManagerImpl::getSipAccount(const std::string& accountID) const { AccountMap::const_iterator iter = SIPVoIPLink::instance()->getAccounts().find(accountID); if(iter != SIPVoIPLink::instance()->getAccounts().end()) return static_cast(iter->second); return NULL; } #if HAVE_IAX IAXAccount * ManagerImpl::getIaxAccount(const std::string& accountID) const { AccountMap::const_iterator iter = IAXVoIPLink::getAccounts().find(accountID); if(iter != IAXVoIPLink::getAccounts().end()) return static_cast(iter->second); return NULL; } #endif AccountMap ManagerImpl::getAllAccounts() const { AccountMap all; all.insert(SIPVoIPLink::instance()->getAccounts().begin(), SIPVoIPLink::instance()->getAccounts().end()); #if HAVE_IAX all.insert(IAXVoIPLink::getAccounts().begin(), IAXVoIPLink::getAccounts().end()); #endif return all; } void ManagerImpl::setIPToIPForCall(const std::string& callID, bool IPToIP) { if (not isIPToIP(callID)) // no IPToIP calls with the same ID IPToIPMap_[callID] = IPToIP; } bool ManagerImpl::isIPToIP(const std::string& callID) const { std::map::const_iterator iter = IPToIPMap_.find(callID); return iter != IPToIPMap_.end() and iter->second; } std::map ManagerImpl::getCallDetails(const std::string &callID) { // We need here to retrieve the call information attached to the call ID // To achieve that, we need to get the voip link attached to the call // But to achieve that, we need to get the account the call was made with // Then the VoIP link this account is linked with (IAX2 or SIP) Call *call = getCallFromCallID(callID); if (call) { return call->getDetails(); } else { ERROR("Call is NULL"); // FIXME: is this even useful? return Call::getNullDetails(); } } std::vector > ManagerImpl::getHistory() { return history_.getSerialized(); } std::vector ManagerImpl::getCallList() const { std::vector v(SIPVoIPLink::instance()->getCallIDs()); #if HAVE_IAX const std::vector iaxCalls(IAXVoIPLink::getCallIDs()); v.insert(v.end(), iaxCalls.begin(), iaxCalls.end()); #endif return v; } std::map ManagerImpl::getConferenceDetails( const std::string& confID) const { std::map conf_details; ConferenceMap::const_iterator iter_conf = conferenceMap_.find(confID); if (iter_conf != conferenceMap_.end()) { conf_details["CONFID"] = confID; conf_details["CONF_STATE"] = iter_conf->second->getStateStr(); } return conf_details; } std::vector ManagerImpl::getConferenceList() const { std::vector v; map_utils::vectorFromMapKeys(conferenceMap_, v); return v; } std::vector ManagerImpl::getParticipantList(const std::string& confID) const { std::vector v; ConferenceMap::const_iterator iter_conf = conferenceMap_.find(confID); if (iter_conf != conferenceMap_.end()) { const ParticipantSet participants(iter_conf->second->getParticipantList()); std::copy(participants.begin(), participants.end(), std::back_inserter(v));; } else WARN("Did not find conference %s", confID.c_str()); return v; } std::string ManagerImpl::getConferenceId(const std::string& callID) { Call *call = getCallFromCallID(callID); if (call == NULL) { ERROR("Call is NULL"); return ""; } return call->getConfId(); } void ManagerImpl::saveHistory() { if (!history_.save()) ERROR("Could not save history!"); else client_.getConfigurationManager()->historyChanged(); } void ManagerImpl::clearHistory() { history_.clear(); } void ManagerImpl::startAudioDriverStream() { sfl::ScopedLock lock(audioLayerMutex_); audiodriver_->startStream(); } void ManagerImpl::registerAccounts() { AccountMap allAccounts(getAllAccounts()); for (auto &item : allAccounts) { Account *a = item.second; if (!a) continue; a->loadConfig(); if (a->isEnabled()) a->registerVoIPLink(); } } VoIPLink* ManagerImpl::getAccountLink(const std::string& accountID) { Account *account = getAccount(accountID); if (account == NULL) { DEBUG("Could not find account for account %s, returning sip voip", accountID.c_str()); return SIPVoIPLink::instance(); } if (not accountID.empty()) return account->getVoIPLink(); else { DEBUG("Account id is empty for voip link, returning sip voip"); return SIPVoIPLink::instance(); } } void ManagerImpl::sendRegister(const std::string& accountID, bool enable) { Account* acc = getAccount(accountID); if (!acc) return; acc->setEnabled(enable); acc->loadConfig(); Manager::instance().saveConfig(); if (acc->isEnabled()) acc->registerVoIPLink(); else acc->unregisterVoIPLink(); }