Skip to content
Snippets Groups Projects
Select Git revision
  • ef81c6679f5c7ffe0f3a354a356edf03e583d879
  • master default protected
  • release/202005
  • release/202001
  • release/201912
  • release/201911
  • release/releaseWindowsTestOne
  • release/releaseTest
  • release/releaseWindowsTest
  • release/windowsReleaseTest
  • release/201910
  • release/qt/201910
  • release/windows-test/201910
  • release/201908
  • release/201906
  • release/201905
  • release/201904
  • release/201903
  • release/201902
  • release/201901
  • release/201812
  • 1.0.0
  • 0.3.0
  • 0.2.1
  • 0.2.0
  • 0.1.0
26 results

newcallmodel.cpp

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    newcallmodel.cpp 16.89 KiB
    /****************************************************************************
     *   Copyright (C) 2017 Savoir-faire Linux                                  *
     *   Author : Nicolas Jäger <nicolas.jager@savoirfairelinux.com>            *
     *   Author : Sébastien Blin <sebastien.blin@savoirfairelinux.com>          *
     *                                                                          *
     *   This library is free software; you can redistribute it and/or          *
     *   modify it under the terms of the GNU Lesser General Public             *
     *   License as published by the Free Software Foundation; either           *
     *   version 2.1 of the License, or (at your option) any later version.     *
     *                                                                          *
     *   This library 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      *
     *   Lesser General Public License for more details.                        *
     *                                                                          *
     *   You should have received a copy of the GNU General Public License      *
     *   along with this program.  If not, see <http://www.gnu.org/licenses/>.  *
     ***************************************************************************/
    
    #include "api/newcallmodel.h"
    
    // std
    #include <chrono>
    
    // Lrc
    #include "callbackshandler.h"
    #include "api/conversationmodel.h"
    #include "api/contact.h"
    #include "api/contactmodel.h"
    #include "api/newaccountmodel.h"
    #include "dbus/callmanager.h"
    #include "private/videorenderermanager.h"
    #include "video/renderer.h"
    
    // Ring daemon
    #include <media_const.h>
    
    // Qt
    #include <QObject>
    #include <QString>
    
    namespace lrc
    {
    
    using namespace api;
    
    class NewCallModelPimpl: public QObject
    {
    public:
        NewCallModelPimpl(const NewCallModel& linked, const CallbacksHandler& callbacksHandler);
        ~NewCallModelPimpl();
    
        NewCallModel::CallInfoMap calls;
        const CallbacksHandler& callbacksHandler;
        const NewCallModel& linked;
    
        /**
         * key = peer's uri
         * vector = chunks
         * @note chunks are counted from 1 to number of parts. We use 0 to store the actual number of parts stored
         */
        std::map<std::string, std::vector<std::string>> vcardsChunks;
    
    public Q_SLOTS:
        /**
         * Listen from CallbacksHandler when a call is incoming
         * @param accountId account which receives the call
         * @param callId
         * @param fromId peer uri
         */
        void slotIncomingCall(const std::string& accountId, const std::string& callId, const std::string& fromId);
        /**
         * Listen from CallbacksHandler when a call got a new state
         * @param callId
         * @param state the new state
         * @param code unused
         */
        void slotCallStateChanged(const std::string& callId, const std::string &state, int code);
        /**
         * Listen from VideoRendererManager when a Renderer starts
         * @param callId
         * @param renderer
         */
        void slotRemotePreviewStarted(const std::string& callId, Video::Renderer* renderer);
        /**
         * Listen from CallbacksHandler when a VCard chunk is incoming
         * @param callId
         * @param from
         * @param part
         * @param numberOfParts
         * @param payload
         */
        void slotincomingVCardChunk(const std::string& callId, const std::string& from, int part, int numberOfParts, const std::string& payload);
        /**
         * Listen from CallbacksHandler when a conference is created.
         * @param callId
         */
        void slotConferenceCreated(const std::string& callId);
    };
    
    NewCallModel::NewCallModel(const account::Info& owner, const CallbacksHandler& callbacksHandler)
    : owner(owner)
    , pimpl_(std::make_unique<NewCallModelPimpl>(*this, callbacksHandler))
    {
    
    }
    
    NewCallModel::~NewCallModel()
    {
    }
    
    const call::Info&
    NewCallModel::getCallFromURI(const std::string& uri) const
    {
        for (const auto& call: pimpl_->calls) {
            if (call.second->peer == uri) {
                return *call.second;
            }
        }
        throw std::out_of_range("No call at URI " + uri);
    }
    
    const call::Info&
    NewCallModel::getCall(const std::string& uid) const
    {
        return *pimpl_->calls.at(uid);
    }
    
    std::string
    NewCallModel::createCall(const std::string& url, bool isAudioOnly)
    {
    #ifdef ENABLE_LIBWRAP
        auto callId = isAudioOnly ? CallManager::instance().placeCall(owner.id.c_str(),
                                                                      url.c_str(),
                                                                      {{"AUDIO_ONLY", "true"}})
                                      : CallManager::instance().placeCall(owner.id.c_str(), url.c_str());
    #else // dbus
        // do not use auto here (QDBusPendingReply<QString>)
        QString callId = isAudioOnly ? CallManager::instance().placeCallWithDetails(owner.id.c_str(),
                                                                                    url.c_str(),
                                                                                    {{"AUDIO_ONLY", "true"}})
                                     : CallManager::instance().placeCall(owner.id.c_str(), url.c_str());
    #endif // ENABLE_LIBWRAP
    
        if (callId.isEmpty())
            qDebug() << "no call placed between (account :" << owner.id.c_str() << ", contact :" << url.c_str() << ")";
    
        auto callInfo = std::make_shared<call::Info>();
        callInfo->id = callId.toStdString();
        callInfo->peer = url;
        callInfo->isOutgoing = true;
        callInfo->status =  call::Status::SEARCHING;
        callInfo->type =  call::Type::DIALOG;
        pimpl_->calls.emplace(callId.toStdString(), std::move(callInfo));
    
        return callId.toStdString();
    }
    
    void
    NewCallModel::accept(const std::string& callId) const
    {
        CallManager::instance().accept(callId.c_str());
    }
    
    void
    NewCallModel::hangUp(const std::string& callId) const
    {
        auto it = pimpl_->calls.find(callId);
        if (it == pimpl_->calls.end()) return;
        auto& call = it->second;
        switch(call->type)
        {
        case call::Type::DIALOG:
            CallManager::instance().hangUp(callId.c_str());
            break;
        case call::Type::CONFERENCE:
            CallManager::instance().hangUpConference(callId.c_str());
            break;
        case call::Type::INVALID:
        default:
            break;
        }
    }
    
    void
    NewCallModel::toggleAudioRecord(const std::string& callId) const
    {
        CallManager::instance().toggleRecording(callId.c_str());
    }
    
    void
    NewCallModel::playDTMF(const std::string& callId, const std::string& value) const
    {
        auto call = pimpl_->calls.find(callId);
        if (call == pimpl_->calls.end()) return;
        if (pimpl_->calls[callId]->status != call::Status::IN_PROGRESS) return;
        CallManager::instance().playDTMF(value.c_str());
    }
    
    void
    NewCallModel::togglePause(const std::string& callId) const
    {
        auto it = pimpl_->calls.find(callId);
        if (it == pimpl_->calls.end()) return;
        auto& call = it->second;
        switch(call->status)
        {
        case call::Status::PAUSED:
            if (call->type == call::Type::DIALOG)
                CallManager::instance().unhold(callId.c_str());
            else {
                CallManager::instance().unholdConference(callId.c_str());
                call->status =  call::Status::IN_PROGRESS;
                emit callStatusChanged(callId);
            }
            break;
        case call::Status::IN_PROGRESS:
            if (call->type == call::Type::DIALOG)
                CallManager::instance().hold(callId.c_str());
            else {
                CallManager::instance().holdConference(callId.c_str());
                call->status = call::Status::PAUSED;
                emit callStatusChanged(callId);
            }
            break;
        case call::Status::INVALID:
        case call::Status::OUTGOING_REQUESTED:
        case call::Status::INCOMING_RINGING:
        case call::Status::OUTGOING_RINGING:
        case call::Status::CONNECTING:
        case call::Status::SEARCHING:
        case call::Status::PEER_PAUSED:
        case call::Status::INACTIVE:
        case call::Status::ENDED:
        case call::Status::TERMINATING:
        case call::Status::CONNECTED:
        case call::Status::AUTO_ANSWERING:
            break;
        }
    }
    
    void
    NewCallModel::toggleMedia(const std::string& callId, const NewCallModel::Media media) const
    {
        auto it = pimpl_->calls.find(callId);
        if (it == pimpl_->calls.end()) return;
        auto& call = it->second;
        switch(media)
        {
        case NewCallModel::Media::AUDIO:
            CallManager::instance().muteLocalMedia(callId.c_str(),
                                                   DRing::Media::Details::MEDIA_TYPE_AUDIO,
                                                   !call->audioMuted);
            call->audioMuted = !call->audioMuted;
            break;
    
        case NewCallModel::Media::VIDEO:
            CallManager::instance().muteLocalMedia(callId.c_str(),
                                                   DRing::Media::Details::MEDIA_TYPE_VIDEO,
                                                   !call->videoMuted);
            call->videoMuted = !call->videoMuted;
            break;
    
        case NewCallModel::Media::NONE:
        default:
            break;
        }
    }
    
    void
    NewCallModel::setQuality(const std::string& callId, const double quality) const
    {
        qDebug() << "setQuality, isn't yet implemented";
    }
    
    void
    NewCallModel::transfer(const std::string& callId, const std::string& to) const
    {
        qDebug() << "transfer, isn't yet implemented";
    }
    
    void
    NewCallModel::joinCalls(const std::string& callIdA, const std::string& callIdB) const
    {
        if (pimpl_->calls.find(callIdA) == pimpl_->calls.end()) return;
        if (pimpl_->calls.find(callIdB) == pimpl_->calls.end()) return;
        auto& call1 = pimpl_->calls[callIdA];
        auto& call2 = pimpl_->calls[callIdB];
        if (call1->type == call::Type::CONFERENCE)
            CallManager::instance().addParticipant(callIdB.c_str(), callIdA.c_str());
        else if (call2->type == call::Type::CONFERENCE)
            CallManager::instance().addParticipant(callIdA.c_str(), callIdB.c_str());
        else if (call1->type == call::Type::CONFERENCE && call2->type == call::Type::CONFERENCE)
            CallManager::instance().joinConference(callIdA.c_str(), callIdB.c_str());
        else
            CallManager::instance().joinParticipant(callIdA.c_str(), callIdB.c_str());
    }
    
    void
    NewCallModel::removeParticipant(const std::string& callId, const std::string& participant) const
    {
    
    }
    
    Video::Renderer*
    NewCallModel::getRenderer(const std::string& callId) const
    {
       #ifdef ENABLE_VIDEO
       return VideoRendererManager::instance().getRenderer(callId);
       #else
       return nullptr;
       #endif
    }
    
    std::string
    NewCallModel::getFormattedCallDuration(const std::string& callId) const
    {
        if (pimpl_->calls.find(callId) == pimpl_->calls.end()) return "00:00";
        auto& startTime = pimpl_->calls[callId]->startTime;
        if (startTime.time_since_epoch().count() == 0) return "00:00";
        auto now = std::chrono::steady_clock::now();
        auto d = std::chrono::duration_cast<std::chrono::seconds>(
                 now.time_since_epoch() - startTime.time_since_epoch()).count();
    
        std::string formattedString;
        auto minutes = d / 60;
        auto seconds = d % 60;
        if (minutes > 0) {
            formattedString += std::to_string(minutes) + ":";
            if (formattedString.length() == 2) {
                formattedString = "0" + formattedString;
            }
        } else {
            formattedString += "00:";
        }
        if (seconds < 10) formattedString += "0";
        formattedString += std::to_string(seconds);
        return formattedString;
    }
    
    
    NewCallModelPimpl::NewCallModelPimpl(const NewCallModel& linked, const CallbacksHandler& callbacksHandler)
    : linked(linked)
    , callbacksHandler(callbacksHandler)
    {
        // TODO init call list and conferences if client crash but not the daemon in call.
        connect(&callbacksHandler, &CallbacksHandler::incomingCall, this, &NewCallModelPimpl::slotIncomingCall);
        connect(&callbacksHandler, &CallbacksHandler::callStateChanged, this, &NewCallModelPimpl::slotCallStateChanged);
        connect(&VideoRendererManager::instance(), &VideoRendererManager::remotePreviewStarted, this, &NewCallModelPimpl::slotRemotePreviewStarted);
        connect(&callbacksHandler, &CallbacksHandler::incomingVCardChunk, this, &NewCallModelPimpl::slotincomingVCardChunk);
        connect(&callbacksHandler, &CallbacksHandler::conferenceCreated, this , &NewCallModelPimpl::slotConferenceCreated);
    }
    
    NewCallModelPimpl::~NewCallModelPimpl()
    {
    
    }
    
    void
    NewCallModel::sendSipMessage(const std::string& callId, const std::string& body) const
    {
        QMap<QString, QString> payloads;
        payloads["text/plain"] = body.c_str();
    
        CallManager::instance().sendTextMessage(callId.c_str(), payloads, true /* not used */);
    }
    
    void
    NewCallModelPimpl::slotIncomingCall(const std::string& accountId, const std::string& callId, const std::string& fromId)
    {
        if (linked.owner.id != accountId) return;
    
        // do not use auto here (QDBusPendingReply<MapStringString>)
        MapStringString callDetails = CallManager::instance().getCallDetails(callId.c_str());
    
        auto callInfo = std::make_shared<call::Info>();
        callInfo->id = callId;
        callInfo->peer = fromId;
        callInfo->isOutgoing = false;
        callInfo->status =  call::Status::INCOMING_RINGING;
        callInfo->type =  call::Type::DIALOG;
        callInfo->isAudioOnly = callDetails["AUDIO_ONLY"] == "true" ? true : false;
        calls.emplace(callId, std::move(callInfo));
    
        emit linked.newIncomingCall(fromId, callId);
    }
    
    void
    NewCallModelPimpl::slotCallStateChanged(const std::string& callId, const std::string& state, int code)
    {
        Q_UNUSED(code)
        if (calls.find(callId) != calls.end()) {
            if (state == "CONNECTING") {
                calls[callId]->status = call::Status::CONNECTING;
            } else if (state == "RINGING") {
                calls[callId]->status = call::Status::OUTGOING_RINGING;
            } else if (state == "HUNGUP") {
                calls[callId]->status = call::Status::TERMINATING;
            } else if (state == "FAILURE" || state == "OVER") {
                emit linked.callEnded(callId);
                calls[callId]->status = call::Status::ENDED;
            } else if (state == "INACTIVE") {
                calls[callId]->status = call::Status::INACTIVE;
            } else if (state == "CURRENT") {
                if (calls[callId]->startTime.time_since_epoch().count() == 0) {
                    calls[callId]->startTime = std::chrono::steady_clock::now();
                    emit linked.callStarted(callId);
                }
                calls[callId]->status = call::Status::IN_PROGRESS;
            } else if (state == "HOLD") {
                calls[callId]->status = call::Status::PAUSED;
            }
            qDebug() << "slotCallStateChanged, call:" << callId.c_str() << " - state: " << state.c_str();
            emit linked.callStatusChanged(callId);
        }
    }
    
    void
    NewCallModelPimpl::slotRemotePreviewStarted(const std::string& callId, Video::Renderer* renderer)
    {
        emit linked.remotePreviewStarted(callId, renderer);
    }
    
    void
    NewCallModelPimpl::slotincomingVCardChunk(const std::string& callId,
                                              const std::string& from,
                                              int part,
                                              int numberOfParts,
                                              const std::string& payload)
    {
        auto it = calls.find(callId);
    
        if (it != calls.end()) {
            auto it_2 = vcardsChunks.find(from);
            if (it_2 != vcardsChunks.end()) {
                vcardsChunks[from][part-1] = payload;
    
                if ( not std::any_of(vcardsChunks[from].begin(), vcardsChunks[from].end(),
                    [](const auto& s) { return s.empty(); }) ) {
    
                    profile::Info profileInfo;
                    profileInfo.uri = from;
                    profileInfo.type = profile::Type::RING;
    
                    std::string vcardPhoto;
    
                    for (auto& chunk : vcardsChunks[from])
                        vcardPhoto += chunk;
    
                    for (auto& e : QString(vcardPhoto.c_str()).split( "\n" ))
                        if (e.contains("PHOTO"))
                            profileInfo.avatar = e.split( ":" )[1].toStdString();
                        else if (e.contains("FN"))
                            profileInfo.alias = e.split( ":" )[1].toStdString();
    
                    contact::Info contactInfo;
                    contactInfo.profileInfo = profileInfo;
    
                    linked.owner.contactModel->addContact(contactInfo);
                    vcardsChunks.erase(from); // Transfer is finish, we don't want to reuse this entry.
                }
            } else {
                vcardsChunks[from] = std::vector<std::string>(numberOfParts);
                vcardsChunks[from][part-1] = payload;
            }
    
        }
    }
    
    bool
    NewCallModel::hasCall(const std::string& callId)
    {
        return pimpl_->calls.find(callId) != pimpl_->calls.end();
    }
    
    void
    NewCallModelPimpl::slotConferenceCreated(const std::string& confId)
    {
        auto callInfo = std::make_shared<call::Info>();
        callInfo->id = confId;
        callInfo->status =  call::Status::IN_PROGRESS;
        callInfo->type =  call::Type::CONFERENCE;
        callInfo->startTime = std::chrono::steady_clock::now();
        calls[confId] = callInfo;
        QStringList callList = CallManager::instance().getParticipantList(confId.c_str());
        foreach(const auto& call, callList) {
            emit linked.callAddedToConference(call.toStdString(), confId);
        }
    }
    
    } // namespace lrc
    
    #include "api/moc_newcallmodel.cpp"