Skip to content
Snippets Groups Projects
Select Git revision
  • 5e9090175fa2e6e96fa208cea8c50b4549e1b7e5
  • master default protected
  • release/202005
  • release/202001
  • release/201912
  • release/201911
  • release/201910
  • release/201908
  • release/201906
  • release/201905
  • release/201904
  • release/201903
  • release/201902
  • release/201901
  • release/201812
  • release/201811
  • release/201808
  • releases/beta1
  • packaging
  • native
  • release-0.2.x
  • 1.0.0
  • 0.2.0
  • 0.1.1
  • 0.1.0
25 results

banneditemwidget.h

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    contactmodel.cpp 46.54 KiB
    /****************************************************************************
     *    Copyright (C) 2017-2021 Savoir-faire Linux Inc.                       *
     *   Author: Nicolas Jäger <nicolas.jager@savoirfairelinux.com>             *
     *   Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>           *
     *   Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>       *
     *   Author: Hugo Lefeuvre <hugo.lefeuvre@savoirfairelinux.com>             *
     *   Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>       *
     *   Author: Andreas Traczyk <andreas.traczyk@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/contactmodel.h"
    
    // LRC
    #include "api/account.h"
    #include "api/contact.h"
    #include "api/interaction.h"
    #include "api/newaccountmodel.h"
    #include "api/newcallmodel.h"
    #include "api/conversationmodel.h"
    #include "api/newaccountmodel.h"
    #include "callbackshandler.h"
    #include "uri.h"
    #include "vcard.h"
    #include "typedefs.h"
    
    #include "authority/daemon.h"
    #include "authority/storagehelper.h"
    
    // Dbus
    #include "dbus/configurationmanager.h"
    #include "dbus/presencemanager.h"
    
    #include "account_const.h"
    
    // Std
    #include <algorithm>
    #include <mutex>
    
    namespace lrc {
    
    using namespace api;
    
    class ContactModelPimpl : public QObject
    {
        Q_OBJECT
    public:
        ContactModelPimpl(const ContactModel& linked,
                          Database& db,
                          const CallbacksHandler& callbacksHandler,
                          const BehaviorController& behaviorController);
    
        ~ContactModelPimpl();
    
        /**
         * Fills the contacts based on database's conversations
         * @return if the method succeeds
         */
        bool fillWithSIPContacts();
    
        /**
         * Fills the contacts based on database's conversations
         * @return if the method succeeds
         */
        bool fillWithJamiContacts();
    
        /**
         * Add a contact::Info to contacts.
         * @note: the contactId must corresponds to a profile in the database.
         * @param contactId
         * @param type
         * @param displayName
         * @param banned whether contact is banned or not
         * @param conversationId linked swarm if one
         */
        void addToContacts(const QString& contactId,
                           const profile::Type& type,
                           const QString& displayName = "",
                           bool banned = false,
                           const QString& conversationId = "");
        /**
         * Helpers for searchContact. Search for a given classic or SIP contact.
         */
        void searchContact(const URI& query);
        void searchSipContact(const URI& query);
    
        /**
         * Update temporary item to display a given message about a given uri.
         */
        void updateTemporaryMessage(const QString& mes);
    
        /**
         * Check if equivalent uri exist in contact
         */
        QString sipUriReceivedFilter(const QString& uri);
    
        // Helpers
        const BehaviorController& behaviorController;
        const ContactModel& linked;
        Database& db;
        const CallbacksHandler& callbacksHandler;
    
        // Containers
        ContactModel::ContactInfoMap contacts;
        ContactModel::ContactInfoMap searchResult;
        QList<QString> bannedContacts;
        QString searchQuery;
        std::mutex contactsMtx_;
        std::mutex bannedContactsMtx_;
        QString searchStatus_ {};
    
    public Q_SLOTS:
        /**
         * Listen CallbacksHandler when a presence update occurs
         * @param accountId
         * @param contactUri
         * @param status
         */
        void slotNewBuddySubscription(const QString& accountId, const QString& uri, bool status);
    
        /**
         * Listen CallbacksHandler when a contact is added
         * @param accountId
         * @param contactUri
         * @param confirmed
         */
        void slotContactAdded(const QString& accountId, const QString& contactUri, bool confirmed);
    
        /**
         * Listen CallbacksHandler when a contact is removed
         * @param accountId
         * @param contactUri
         * @param banned
         */
        void slotContactRemoved(const QString& accountId, const QString& contactUri, bool banned);
    
        /**
         * Listen CallbacksHandler when a registeredName is found
         * @param accountId account linked
         * @param status (0 = SUCCESS, 1 = Not found, 2 = Network error)
         * @param uri of the contact found
         * @param registeredName of the contact found
         */
        void slotRegisteredNameFound(const QString& accountId,
                                     int status,
                                     const QString& uri,
                                     const QString& registeredName);
    
        /**
         * Listen CallbacksHandler when an incoming request arrives
         * @param accountId account linked
         * @param contactUri
         * @param payload VCard of the contact
         */
        void slotIncomingContactRequest(const QString& accountId,
                                        const QString& conversationId,
                                        const QString& contactUri,
                                        const QString& payload);
        /**
         * Listen from callModel when an incoming call arrives.
         * @param fromId
         * @param callId
         * @param displayName
         */
        void slotIncomingCall(const QString& fromId, const QString& callId, const QString& displayname);
    
        /**
         * Listen from callbacksHandler for new account interaction and add pending contact if not present
         * @param accountId
         * @param msgId
         * @param peerId
         * @param payloads
         */
        void slotNewAccountMessage(const QString& accountId,
                                   const QString& peerId,
                                   const QString& msgId,
                                   const MapStringString& payloads);
    
        /**
         * Listen from callbacksHandler to know when a file transfer interaction is incoming
         * @param fileId Daemon's ID for incoming transfer
         * @param transferInfo DataTransferInfo structure from daemon
         */
        void slotNewAccountTransfer(const QString& fileId, datatransfer::Info info);
    
        /**
         * Listen from daemon to know when a VCard is received
         * @param accountId
         * @param peer
         * @param vCard
         */
        void slotProfileReceived(const QString& accountId, const QString& peer, const QString& vCard);
    
        /**
         * Listen from daemon to know when a user search completed
         * @param accountId
         * @param status
         * @param query
         * @param result
         */
        void slotUserSearchEnded(const QString& accountId,
                                 int status,
                                 const QString& query,
                                 const VectorMapStringString& result);
    };
    
    using namespace authority;
    
    ContactModel::ContactModel(const account::Info& owner,
                               Database& db,
                               const CallbacksHandler& callbacksHandler,
                               const BehaviorController& behaviorController)
        : owner(owner)
        , pimpl_(std::make_unique<ContactModelPimpl>(*this, db, callbacksHandler, behaviorController))
    {}
    
    ContactModel::~ContactModel() {}
    
    const ContactModel::ContactInfoMap&
    ContactModel::getAllContacts() const
    {
        return pimpl_->contacts;
    }
    
    time_t
    ContactModel::getAddedTs(const QString& contactUri) const
    {
        MapStringString details = ConfigurationManager::instance().getContactDetails(owner.id,
                                                                                     contactUri);
        auto itAdded = details.find("added");
        if (itAdded == details.end())
            return 0;
        return itAdded.value().toUInt();
    }
    
    void
    ContactModel::addContact(contact::Info contactInfo)
    {
        auto& profile = contactInfo.profileInfo;
    
        // If passed contact is a banned contact, call the daemon to unban it
        auto it = std::find(pimpl_->bannedContacts.begin(), pimpl_->bannedContacts.end(), profile.uri);
        if (it != pimpl_->bannedContacts.end()) {
            qDebug() << QString("Unban-ing contact %1").arg(profile.uri);
            ConfigurationManager::instance().addContact(owner.id, profile.uri);
            // bannedContacts will be updated in slotContactAdded
            return;
        }
    
        if ((owner.profileInfo.type != profile.type)
            and (profile.type == profile::Type::JAMI or profile.type == profile::Type::SIP)) {
            qDebug() << "ContactModel::addContact, types invalid.";
            return;
        }
    
        MapStringString details = ConfigurationManager::instance()
                                      .getContactDetails(owner.id, contactInfo.profileInfo.uri);
    
        // if contactInfo is already a contact for the daemon, type should be equals to RING
        // if the user add a temporary item for a SIP account, should be directly transformed
        if (!details.empty()
            || (profile.type == profile::Type::TEMPORARY
                && owner.profileInfo.type == profile::Type::SIP))
            profile.type = owner.profileInfo.type;
    
        switch (profile.type) {
        case profile::Type::TEMPORARY: {
            // make a temporary contact available for UI elements, it will be upgraded to
            // its corresponding type after receiving contact added signal
            std::lock_guard<std::mutex> lk(pimpl_->contactsMtx_);
            contactInfo.profileInfo.type = profile::Type::PENDING;
            pimpl_->contacts.insert(contactInfo.profileInfo.uri, contactInfo);
            ConfigurationManager::instance().addContact(owner.id, profile.uri);
            ConfigurationManager::instance()
                .sendTrustRequest(owner.id,
                                  profile.uri,
                                  owner.accountModel->accountVCard(owner.id).toUtf8());
            return;
        }
        case profile::Type::PENDING:
            if (daemon::addContactFromPending(owner, profile.uri)) {
                emit pendingContactAccepted(profile.uri);
            } else {
                return;
            }
            break;
        case profile::Type::JAMI:
        case profile::Type::SIP:
            break;
        case profile::Type::INVALID:
        case profile::Type::COUNT__:
        default:
            qDebug() << "ContactModel::addContact, cannot add contact with invalid type.";
            return;
        }
    
        storage::createOrUpdateProfile(owner.id, profile, true);
    
        {
            std::lock_guard<std::mutex> lk(pimpl_->contactsMtx_);
            auto iter = pimpl_->contacts.find(contactInfo.profileInfo.uri);
            if (iter == pimpl_->contacts.end())
                pimpl_->contacts.insert(iter, contactInfo.profileInfo.uri, contactInfo);
            else {
                // On non-DBus platform, contactInfo.profileInfo.type may be wrong as the contact
                // may be trusted already. We must use Profile::Type from pimpl_->contacts
                // and not from contactInfo so we cannot revert a contact back to PENDING.
                contactInfo.profileInfo.type = iter->profileInfo.type;
                iter->profileInfo = contactInfo.profileInfo;
            }
        }
        emit profileUpdated(profile.uri);
        if (profile.type == profile::Type::SIP)
            emit contactAdded(profile.uri);
    }
    
    void
    ContactModel::addToContacts(const QString& contactUri)
    {
        std::lock_guard<std::mutex> lk(pimpl_->contactsMtx_);
        auto iter = pimpl_->contacts.find(contactUri);
        if (iter != pimpl_->contacts.end())
            return;
    
        auto contactInfo = storage::buildContactFromProfile(owner.id,
                                                            contactUri,
                                                            profile::Type::PENDING);
        pimpl_->contacts.insert(iter, contactUri, contactInfo);
        ConfigurationManager::instance().lookupAddress(owner.id, "", contactUri);
    }
    
    void
    ContactModel::removeContact(const QString& contactUri, bool banned)
    {
        bool emitContactRemoved = false;
        {
            std::lock_guard<std::mutex> lk(pimpl_->contactsMtx_);
            auto contact = pimpl_->contacts.find(contactUri);
            if (!banned && contact != pimpl_->contacts.end()
                && contact->profileInfo.type == profile::Type::PENDING) {
                // Discard the pending request and remove profile from db if necessary
                if (!daemon::discardFromPending(owner, contactUri)) {
                    qDebug() << "Discard request for account " << owner.id << " failed (" << contactUri
                             << ")";
                    return;
                }
                pimpl_->contacts.remove(contactUri);
                storage::removeContactConversations(pimpl_->db, contactUri);
                storage::removeProfile(owner.id, contactUri);
                emitContactRemoved = true;
            } else if (owner.profileInfo.type == profile::Type::SIP) {
                // Remove contact from db
                pimpl_->contacts.remove(contactUri);
                storage::removeContactConversations(pimpl_->db, contactUri);
                storage::removeProfile(owner.id, contactUri);
                emitContactRemoved = true;
            }
        }
        // hang up calls with the removed contact as peer
        try {
            auto callinfo = owner.callModel->getCallFromURI(contactUri, true);
            owner.callModel->hangUp(callinfo.id);
        } catch (std::out_of_range& e) {
        }
        if (emitContactRemoved) {
            emit contactRemoved(contactUri);
        } else {
            // NOTE: this method is asynchronous, the model will be updated
            // in slotContactRemoved
            daemon::removeContact(owner, contactUri, banned);
        }
    }
    
    const contact::Info
    ContactModel::getContact(const QString& contactUri) const
    {
        std::lock_guard<std::mutex> lk(pimpl_->contactsMtx_);
        if (pimpl_->contacts.contains(contactUri)) {
            return pimpl_->contacts.value(contactUri);
        } else if (pimpl_->searchResult.contains(contactUri)) {
            return pimpl_->searchResult.value(contactUri);
        }
        throw std::out_of_range("Contact out of range");
    }
    
    const QList<QString>&
    ContactModel::getBannedContacts() const
    {
        return pimpl_->bannedContacts;
    }
    
    ContactModel::ContactInfoMap
    ContactModel::getSearchResults() const
    {
        return pimpl_->searchResult;
    }
    
    void
    ContactModel::searchContact(const QString& query)
    {
        // always reset temporary contact
        pimpl_->searchResult.clear();
    
        auto uri = URI(query);
        QString uriID = uri.format(URI::Section::USER_INFO | URI::Section::HOSTNAME
                                   | URI::Section::PORT);
        pimpl_->searchQuery = uriID;
    
        auto uriScheme = uri.schemeType();
        if (static_cast<int>(uriScheme) > 2 && owner.profileInfo.type == profile::Type::SIP) {
            // sip account do not care if schemeType is NONE, or UNRECOGNIZED (enum value > 2)
            uriScheme = URI::SchemeType::SIP;
        } else if (uriScheme == URI::SchemeType::NONE && owner.profileInfo.type == profile::Type::JAMI) {
            uriScheme = URI::SchemeType::RING;
        }
    
        if ((uriScheme == URI::SchemeType::SIP || uriScheme == URI::SchemeType::SIPS)
            && owner.profileInfo.type == profile::Type::SIP) {
            pimpl_->searchSipContact(uri);
        } else if (uriScheme == URI::SchemeType::RING && owner.profileInfo.type == profile::Type::JAMI) {
            pimpl_->searchContact(uri);
        } else {
            pimpl_->updateTemporaryMessage(tr("Bad URI scheme"));
        }
    }
    
    void
    ContactModelPimpl::updateTemporaryMessage(const QString& mes)
    {
        if (searchStatus_ != mes) {
            searchStatus_ = mes;
            linked.owner.conversationModel->updateSearchStatus(mes);
        }
    }
    
    void
    ContactModelPimpl::searchContact(const URI& query)
    {
        QString uriID = query.format(URI::Section::USER_INFO | URI::Section::HOSTNAME
                                     | URI::Section::PORT);
        if (query.isEmpty()) {
            // This will remove the temporary item
            emit linked.modelUpdated(uriID);
            updateTemporaryMessage("");
            return;
        }
    
        if (query.protocolHint() == URI::ProtocolHint::RING) {
            updateTemporaryMessage("");
            // no lookup, this is a ring infoHash
            for (auto& i : contacts) {
                if (i.profileInfo.uri == uriID) {
                    return;
                }
            }
            auto& temporaryContact = searchResult[uriID];
            temporaryContact.profileInfo.uri = uriID;
            temporaryContact.profileInfo.alias = uriID;
            temporaryContact.profileInfo.type = profile::Type::TEMPORARY;
            emit linked.modelUpdated(uriID);
        } else {
            updateTemporaryMessage(tr("Searching…"));
    
            // If the username contains an @ it's an exact match
            bool isJamsAccount = !linked.owner.confProperties.managerUri.isEmpty();
            if (isJamsAccount and not query.hasHostname())
                ConfigurationManager::instance().searchUser(linked.owner.id, uriID);
            else
                ConfigurationManager::instance().lookupName(linked.owner.id, "", uriID);
        }
    }
    
    void
    ContactModelPimpl::searchSipContact(const URI& query)
    {
        QString uriID = query.format(URI::Section::USER_INFO | URI::Section::HOSTNAME
                                     | URI::Section::PORT);
        if (query.isEmpty()) {
            // This will remove the temporary item
            emit linked.modelUpdated(uriID);
            updateTemporaryMessage("");
            return;
        }
    
        {
            std::lock_guard<std::mutex> lk(contactsMtx_);
            if (contacts.find(uriID) == contacts.end()) {
                auto& temporaryContact = searchResult[query];
    
                temporaryContact.profileInfo.uri = uriID;
                temporaryContact.profileInfo.alias = uriID;
                temporaryContact.profileInfo.type = profile::Type::TEMPORARY;
            }
        }
        emit linked.modelUpdated(uriID);
    }
    
    uint64_t
    ContactModel::sendDhtMessage(const QString& contactUri, const QString& body) const
    {
        // Send interaction
        QMap<QString, QString> payloads;
        payloads["text/plain"] = body;
        auto msgId = ConfigurationManager::instance().sendTextMessage(QString(owner.id),
                                                                      QString(contactUri),
                                                                      payloads);
        // NOTE: ConversationModel should store the interaction into the database
        return msgId;
    }
    
    const QString
    ContactModel::bestNameForContact(const QString& contactUri) const
    {
        try {
            auto contact = getContact(contactUri);
            auto alias = contact.profileInfo.alias.simplified();
    
            if (alias.isEmpty()) {
                return bestIdFromContactInfo(contact);
            }
            return alias;
        } catch (const std::out_of_range& e) {
            qDebug() << "ContactModel::bestNameForContact" << e.what();
        }
    
        return contactUri;
    }
    
    const QString
    ContactModel::bestIdForContact(const QString& contactUri) const
    {
        try {
            auto contact = getContact(contactUri);
            auto alias = contact.profileInfo.alias.simplified();
            if (alias.isEmpty()) {
                return {};
            }
            return bestIdFromContactInfo(contact);
        } catch (const std::out_of_range& e) {
            qDebug() << "ContactModel::bestIdForContact" << e.what();
        }
    
        return contactUri;
    }
    
    const QString
    ContactModel::bestIdFromContactInfo(const contact::Info& contactInfo) const
    {
        auto registeredName = contactInfo.registeredName.simplified();
        auto infoHash = contactInfo.profileInfo.uri.simplified();
    
        if (!registeredName.isEmpty()) {
            return registeredName;
        }
        return infoHash;
    }
    
    ContactModelPimpl::ContactModelPimpl(const ContactModel& linked,
                                         Database& db,
                                         const CallbacksHandler& callbacksHandler,
                                         const BehaviorController& behaviorController)
        : linked(linked)
        , db(db)
        , behaviorController(behaviorController)
        , callbacksHandler(callbacksHandler)
    {
        qRegisterMetaType<VectorMapStringString>("VectorMapStringString");
        // Init contacts map
        if (linked.owner.profileInfo.type == profile::Type::SIP)
            fillWithSIPContacts();
        else
            fillWithJamiContacts();
    
        // connect the signals
        connect(&callbacksHandler,
                &CallbacksHandler::newBuddySubscription,
                this,
                &ContactModelPimpl::slotNewBuddySubscription);
        connect(&callbacksHandler,
                &CallbacksHandler::contactAdded,
                this,
                &ContactModelPimpl::slotContactAdded);
        connect(&callbacksHandler,
                &CallbacksHandler::contactRemoved,
                this,
                &ContactModelPimpl::slotContactRemoved);
        connect(&callbacksHandler,
                &CallbacksHandler::incomingContactRequest,
                this,
                &ContactModelPimpl::slotIncomingContactRequest);
        connect(&callbacksHandler,
                &CallbacksHandler::registeredNameFound,
                this,
                &ContactModelPimpl::slotRegisteredNameFound);
        connect(&*linked.owner.callModel,
                &NewCallModel::newIncomingCall,
                this,
                &ContactModelPimpl::slotIncomingCall);
        connect(&callbacksHandler,
                &lrc::CallbacksHandler::newAccountMessage,
                this,
                &ContactModelPimpl::slotNewAccountMessage);
        connect(&callbacksHandler,
                &CallbacksHandler::transferStatusCreated,
                this,
                &ContactModelPimpl::slotNewAccountTransfer);
        connect(&ConfigurationManager::instance(),
                &ConfigurationManagerInterface::profileReceived,
                this,
                &ContactModelPimpl::slotProfileReceived);
        connect(&ConfigurationManager::instance(),
                &ConfigurationManagerInterface::userSearchEnded,
                this,
                &ContactModelPimpl::slotUserSearchEnded);
    }
    
    ContactModelPimpl::~ContactModelPimpl()
    {
        disconnect(&callbacksHandler,
                   &CallbacksHandler::newBuddySubscription,
                   this,
                   &ContactModelPimpl::slotNewBuddySubscription);
        disconnect(&callbacksHandler,
                   &CallbacksHandler::contactAdded,
                   this,
                   &ContactModelPimpl::slotContactAdded);
        disconnect(&callbacksHandler,
                   &CallbacksHandler::contactRemoved,
                   this,
                   &ContactModelPimpl::slotContactRemoved);
        disconnect(&callbacksHandler,
                   &CallbacksHandler::incomingContactRequest,
                   this,
                   &ContactModelPimpl::slotIncomingContactRequest);
        disconnect(&callbacksHandler,
                   &CallbacksHandler::registeredNameFound,
                   this,
                   &ContactModelPimpl::slotRegisteredNameFound);
        disconnect(&*linked.owner.callModel,
                   &NewCallModel::newIncomingCall,
                   this,
                   &ContactModelPimpl::slotIncomingCall);
        disconnect(&callbacksHandler,
                   &lrc::CallbacksHandler::newAccountMessage,
                   this,
                   &ContactModelPimpl::slotNewAccountMessage);
        disconnect(&callbacksHandler,
                   &CallbacksHandler::transferStatusCreated,
                   this,
                   &ContactModelPimpl::slotNewAccountTransfer);
        disconnect(&ConfigurationManager::instance(),
                   &ConfigurationManagerInterface::profileReceived,
                   this,
                   &ContactModelPimpl::slotProfileReceived);
        disconnect(&ConfigurationManager::instance(),
                   &ConfigurationManagerInterface::userSearchEnded,
                   this,
                   &ContactModelPimpl::slotUserSearchEnded);
    }
    
    bool
    ContactModelPimpl::fillWithSIPContacts()
    {
        auto conversationsForAccount = storage::getAllConversations(db);
        for (const auto& convId : conversationsForAccount) {
            auto otherParticipants = storage::getPeerParticipantsForConversation(db, convId);
            for (const auto& participant : otherParticipants) {
                // for each conversations get the other profile id
                auto contactInfo = storage::buildContactFromProfile(linked.owner.id,
                                                                    participant,
                                                                    profile::Type::SIP);
                {
                    std::lock_guard<std::mutex> lk(contactsMtx_);
                    contacts.insert(contactInfo.profileInfo.uri, contactInfo);
                }
            }
        }
    
        return true;
    }
    
    bool
    ContactModelPimpl::fillWithJamiContacts()
    {
        // Add contacts from daemon
        const VectorMapStringString& contacts_vector = ConfigurationManager::instance().getContacts(
            linked.owner.id);
        for (auto contact_info : contacts_vector) {
            std::lock_guard<std::mutex> lk(contactsMtx_);
            bool banned = contact_info["banned"] == "true" ? true : false;
            addToContacts(contact_info["id"],
                          linked.owner.profileInfo.type,
                          "",
                          banned,
                          contact_info["conversationId"]);
        }
    
        // Add pending contacts
        const VectorMapStringString& pending_tr {
            ConfigurationManager::instance().getTrustRequests(linked.owner.id)};
        for (const auto& tr_info : pending_tr) {
            // Get pending requests.
            auto payload = tr_info[DRing::Account::TrustRequest::PAYLOAD].toUtf8();
            auto contactUri = tr_info[DRing::Account::TrustRequest::FROM];
            auto convId = tr_info[DRing::Account::TrustRequest::CONVERSATIONID];
            if (!convId.isEmpty())
                continue; // This will be added via getConversationsRequests
    
            auto contactInfo = storage::buildContactFromProfile(linked.owner.id,
                                                                contactUri,
                                                                profile::Type::PENDING);
    
            const auto vCard = lrc::vCard::utils::toHashMap(payload);
            const auto alias = vCard["FN"];
            QByteArray photo;
            for (const auto& key : vCard.keys()) {
                if (key.contains("PHOTO"))
                    photo = vCard[key];
            }
            contactInfo.profileInfo.type = profile::Type::PENDING;
            if (!alias.isEmpty())
                contactInfo.profileInfo.alias = alias.constData();
            if (!photo.isEmpty())
                contactInfo.profileInfo.avatar = photo.constData();
            contactInfo.registeredName = "";
            contactInfo.isBanned = false;
    
            {
                std::lock_guard<std::mutex> lk(contactsMtx_);
                contacts.insert(contactUri, contactInfo);
            }
    
            // create profile vcard for contact
            storage::createOrUpdateProfile(linked.owner.id, contactInfo.profileInfo, true);
        }
    
        // Update presence
        // TODO fix this map. This is dumb for now. The map contains values as keys, and empty values.
        const VectorMapStringString& subscriptions {
            PresenceManager::instance().getSubscriptions(linked.owner.id)};
        for (const auto& subscription : subscriptions) {
            auto first = true;
            QString uri = "";
            for (const auto& key : subscription) {
                if (first) {
                    first = false;
                    uri = key;
                } else {
                    {
                        std::lock_guard<std::mutex> lk(contactsMtx_);
                        auto it = contacts.find(uri);
                        if (it != contacts.end()) {
                            it->isPresent = key == "Online";
                            linked.modelUpdated(uri);
                        }
                    }
                    break;
                }
            }
        }
        return true;
    }
    
    void
    ContactModelPimpl::slotNewBuddySubscription(const QString& accountId,
                                                const QString& contactUri,
                                                bool status)
    {
        if (accountId != linked.owner.id)
            return;
        {
            std::lock_guard<std::mutex> lk(contactsMtx_);
            auto it = contacts.find(contactUri);
            if (it != contacts.end()) {
                it->isPresent = status;
            } else
                return;
        }
        emit linked.modelUpdated(contactUri);
    }
    
    void
    ContactModelPimpl::slotContactAdded(const QString& accountId,
                                        const QString& contactUri,
                                        bool confirmed)
    {
        if (accountId != linked.owner.id)
            return;
        auto contact = contacts.find(contactUri);
        if (contact != contacts.end()) {
            if (contact->profileInfo.type == profile::Type::PENDING) {
                emit behaviorController.trustRequestTreated(linked.owner.id, contactUri);
            } else if (contact->profileInfo.type == profile::Type::JAMI && !contact->isBanned
                       && confirmed) {
                // This means that the peer accepted the trust request. We don't need to re-add the
                // contact a second time (and this reset the presence to false).
                return;
            }
        }
        // for jams account we already have profile with avatar, use it to save to vCard
        bool isJamsAccount = !linked.owner.confProperties.managerUri.isEmpty();
        if (isJamsAccount) {
            auto result = searchResult.find(contactUri);
            if (result != searchResult.end()) {
                storage::createOrUpdateProfile(linked.owner.id, result->profileInfo, true);
            }
        }
    
        bool isBanned = false;
    
        {
            // Always get contactsMtx_ lock before bannedContactsMtx_.
            std::lock_guard<std::mutex> lk(contactsMtx_);
    
            {
                // Check whether contact is banned or not
                std::lock_guard<std::mutex> lk(bannedContactsMtx_);
                auto it = std::find(bannedContacts.begin(), bannedContacts.end(), contactUri);
    
                isBanned = (it != bannedContacts.end());
    
                // If contact is banned, do not re-add it, simply update its flag and the banned contacts list
                if (isBanned) {
                    bannedContacts.erase(it);
                }
    
                MapStringString details = ConfigurationManager::instance()
                                              .getContactDetails(linked.owner.id, contactUri);
                addToContacts(contactUri,
                              linked.owner.profileInfo.type,
                              "",
                              false,
                              details["conversationId"]);
            }
        }
        if (isBanned) {
            // Update the smartlist
            linked.owner.conversationModel->refreshFilter();
            emit linked.bannedStatusChanged(contactUri, false);
        } else {
            emit linked.contactAdded(contactUri);
        }
    }
    
    void
    ContactModelPimpl::slotContactRemoved(const QString& accountId,
                                          const QString& contactUri,
                                          bool banned)
    {
        if (accountId != linked.owner.id)
            return;
    
        {
            // Always get contactsMtx_ lock before bannedContactsMtx_.
            std::lock_guard<std::mutex> lk(contactsMtx_);
    
            auto contact = contacts.find(contactUri);
            if (contact == contacts.end())
                return;
    
            if (contact->profileInfo.type == profile::Type::PENDING) {
                emit behaviorController.trustRequestTreated(linked.owner.id, contactUri);
            }
    
            if (contact->profileInfo.type != profile::Type::SIP)
                PresenceManager::instance().subscribeBuddy(linked.owner.id, contactUri, false);
    
            if (banned) {
                contact->isBanned = true;
                // Update bannedContacts index
                bannedContacts.append(contact->profileInfo.uri);
            } else {
                if (contact->isBanned) {
                    // Contact was banned, update bannedContacts
                    std::lock_guard<std::mutex> lk(bannedContactsMtx_);
                    auto it = std::find(bannedContacts.begin(),
                                        bannedContacts.end(),
                                        contact->profileInfo.uri);
                    if (it == bannedContacts.end()) {
                        // should not happen
                        qDebug("ContactModel::slotContactsRemoved(): Contact is banned but not present "
                               "in bannedContacts. This is most likely the result of an earlier bug.");
                    } else {
                        bannedContacts.erase(it);
                    }
                }
                storage::removeContactConversations(db, contactUri);
                storage::removeProfile(linked.owner.id, contactUri);
                contacts.remove(contactUri);
            }
        }
    
        // Update the smartlist
        linked.owner.conversationModel->refreshFilter();
        if (banned) {
            emit linked.bannedStatusChanged(contactUri, true);
        }
        emit linked.contactRemoved(contactUri);
    }
    
    void
    ContactModelPimpl::addToContacts(const QString& contactUri,
                                     const profile::Type& type,
                                     const QString& displayName,
                                     bool banned,
                                     const QString& conversationId)
    {
        // create a vcard if necessary
        profile::Info profileInfo {contactUri, {}, displayName, linked.owner.profileInfo.type};
        auto contactInfo = storage::buildContactFromProfile(linked.owner.id, contactUri, type);
        auto updateProfile = false;
        if (!profileInfo.alias.isEmpty() && contactInfo.profileInfo.alias != profileInfo.alias) {
            updateProfile = true;
            contactInfo.profileInfo.alias = profileInfo.alias;
        }
        if (!profileInfo.avatar.isEmpty() && contactInfo.profileInfo.avatar != profileInfo.avatar) {
            updateProfile = true;
            contactInfo.profileInfo.avatar = profileInfo.avatar;
        }
        if (updateProfile)
            storage::vcard::setProfile(linked.owner.id, contactInfo.profileInfo, true);
    
        contactInfo.isBanned = banned;
        contactInfo.conversationId = conversationId;
    
        // lookup address in case of RING contact
        if (type == profile::Type::JAMI) {
            ConfigurationManager::instance().lookupAddress(linked.owner.id, "", contactUri);
            PresenceManager::instance().subscribeBuddy(linked.owner.id, contactUri, !banned);
        } else {
            contactInfo.profileInfo.alias = displayName;
        }
    
        contactInfo.profileInfo.type = type; // Because PENDING should not be stored in the database
        auto iter = contacts.find(contactInfo.profileInfo.uri);
        if (iter != contacts.end()) {
            auto info = iter.value();
            contactInfo.registeredName = info.registeredName;
            iter.value() = contactInfo;
        } else
            contacts.insert(iter, contactInfo.profileInfo.uri, contactInfo);
    
        if (banned) {
            bannedContacts.append(contactUri);
        }
    }
    
    void
    ContactModelPimpl::slotRegisteredNameFound(const QString& accountId,
                                               int status,
                                               const QString& uri,
                                               const QString& registeredName)
    {
        if (accountId != linked.owner.id)
            return;
    
        if (status == 0 /* SUCCESS */) {
            std::lock_guard<std::mutex> lk(contactsMtx_);
    
            if (contacts.find(uri) != contacts.end()) {
                // update contact and remove temporary item
                contacts[uri].registeredName = registeredName;
                searchResult.clear();
            } else {
                if ((searchQuery != uri && searchQuery != registeredName) || searchQuery.isEmpty()) {
                    // we are notified that a previous lookup ended
                    return;
                }
                auto& temporaryContact = searchResult[uri];
                lrc::api::profile::Info profileInfo = {uri, "", "", profile::Type::TEMPORARY};
                temporaryContact = {profileInfo, registeredName, false, false};
            }
        } else {
            {
                std::lock_guard<std::mutex> lk(contactsMtx_);
                if (contacts.find(uri) != contacts.end()) {
                    // it was lookup for contact
                    return;
                }
            }
            if ((searchQuery != uri && searchQuery != registeredName) || searchQuery.isEmpty()) {
                // we are notified that a previous lookup ended
                return;
            }
            switch (status) {
            case 1 /* INVALID */:
                updateTemporaryMessage(tr("Invalid ID"));
                break;
            case 2 /* NOT FOUND */:
                updateTemporaryMessage(tr("Username not found"));
                break;
            case 3 /* ERROR */:
                updateTemporaryMessage(tr("Couldn't lookup…"));
                break;
            }
            return;
        }
        updateTemporaryMessage("");
        emit linked.modelUpdated(uri);
    }
    
    void
    ContactModelPimpl::slotIncomingContactRequest(const QString& accountId,
                                                  const QString& conversationId,
                                                  const QString& contactUri,
                                                  const QString& payload)
    {
        if (linked.owner.id != accountId)
            return;
    
        auto emitTrust = false;
        {
            std::lock_guard<std::mutex> lk(contactsMtx_);
            if (contacts.find(contactUri) == contacts.end()) {
                const auto vCard = lrc::vCard::utils::toHashMap(payload.toUtf8());
                const auto alias = vCard["FN"];
                QByteArray photo;
                for (const auto& key : vCard.keys()) {
                    if (key.contains("PHOTO"))
                        photo = vCard[key];
                }
                auto profileInfo = profile::Info {contactUri, photo, alias, profile::Type::PENDING};
                auto contactInfo = contact::Info {profileInfo, "", false, false, false};
                contacts.insert(contactUri, contactInfo);
                emitTrust = true;
                storage::createOrUpdateProfile(accountId, profileInfo, true);
                ConfigurationManager::instance().lookupAddress(linked.owner.id, "", contactUri);
            }
        }
        emit linked.incomingContactRequest(contactUri);
        if (emitTrust) {
            emit behaviorController.newTrustRequest(linked.owner.id, conversationId, contactUri);
        }
    }
    
    void
    ContactModelPimpl::slotIncomingCall(const QString& fromId,
                                        const QString& callId,
                                        const QString& displayname)
    {
        bool emitContactAdded = false;
        {
            std::lock_guard<std::mutex> lk(contactsMtx_);
            auto it = contacts.find(fromId);
            if (it == contacts.end()) {
                // Contact not found, load profile from database.
                // The conversation model will create an entry and link the incomingCall.
                auto type = (linked.owner.profileInfo.type == profile::Type::JAMI)
                                ? profile::Type::PENDING
                                : profile::Type::SIP;
                addToContacts(fromId, type, displayname, false);
                emitContactAdded = true;
            } else {
                // Update the display name
                if (!displayname.isEmpty()) {
                    it->profileInfo.alias = displayname;
                    storage::createOrUpdateProfile(linked.owner.id, it->profileInfo, true);
                }
            }
        }
        if (emitContactAdded) {
            if (linked.owner.profileInfo.type == profile::Type::SIP)
                emit linked.contactAdded(fromId);
            else if (linked.owner.profileInfo.type == profile::Type::JAMI)
                emit behaviorController.newTrustRequest(linked.owner.id, "", fromId);
        } else
            emit linked.profileUpdated(fromId);
    
        emit linked.incomingCall(fromId, callId);
    }
    
    void
    ContactModelPimpl::slotNewAccountMessage(const QString& accountId,
                                             const QString& peerId,
                                             const QString& msgId,
                                             const MapStringString& payloads)
    {
        if (accountId != linked.owner.id)
            return;
    
        QString peerId2(peerId);
    
        auto emitNewTrust = false;
        {
            std::lock_guard<std::mutex> lk(contactsMtx_);
            if (contacts.find(peerId) == contacts.end()) {
                // Contact not found, load profile from database.
                // The conversation model will create an entry and link the incomingCall.
    
                if (linked.owner.profileInfo.type == profile::Type::SIP) {
                    QString potentialContact = sipUriReceivedFilter(peerId);
                    if (potentialContact.isEmpty()) {
                        addToContacts(peerId, profile::Type::SIP, "", false);
                    } else {
                        // equivalent uri exist, use that uri
                        peerId2 = potentialContact;
                    }
                } else {
                    addToContacts(peerId, profile::Type::PENDING, "", false);
                    emitNewTrust = true;
                }
            }
        }
        if (emitNewTrust) {
            emit behaviorController.newTrustRequest(linked.owner.id, "", peerId);
        }
        emit linked.newAccountMessage(accountId, peerId2, msgId, payloads);
    }
    
    QString
    ContactModelPimpl::sipUriReceivedFilter(const QString& uri)
    {
        // this function serves when the uri is not found in the contact list
        // return "" means need to add new contact, else means equivalent uri exist
        std::string uriCopy = uri.toStdString();
    
        auto pos = uriCopy.find("@");
        auto ownerHostName = linked.owner.confProperties.hostname.toStdString();
    
        if (pos != std::string::npos) {
            // "@" is found, separate username and hostname
            std::string hostName = uriCopy.substr(pos + 1);
            uriCopy.erase(uriCopy.begin() + pos, uriCopy.end());
            std::string remoteUser = std::move(uriCopy);
    
            if (hostName.compare(ownerHostName) == 0) {
                auto remoteUserQStr = QString::fromStdString(remoteUser);
                if (contacts.find(remoteUserQStr) != contacts.end()) {
                    return remoteUserQStr;
                }
                if (remoteUser.at(0) == '+') {
                    // "+" - country dial-in codes
                    // maximum 3 digits
                    for (int i = 2; i <= 4; i++) {
                        QString tempUserName = QString::fromStdString(remoteUser.substr(i));
                        if (contacts.find(tempUserName) != contacts.end()) {
                            return tempUserName;
                        }
                    }
                    return "";
                } else {
                    // if not "+"  from incoming
                    // sub "+" char from contacts to see if user exit
                    for (auto& contactUri : contacts.keys()) {
                        if (!contactUri.isEmpty()) {
                            for (int j = 2; j <= 4; j++) {
                                if (QString(contactUri).remove(0, j) == remoteUserQStr) {
                                    return contactUri;
                                }
                            }
                        }
                    }
                    return "";
                }
            }
            // different hostname means not a phone number
            // no need to check country dial-in codes
            return "";
        }
        // "@" is not found -> not possible since all response uri has one
        return "";
    }
    
    void
    ContactModelPimpl::slotNewAccountTransfer(const QString& fileId, datatransfer::Info info)
    {
        if (info.accountId != linked.owner.id)
            return;
    
        bool emitNewTrust = false;
        {
            std::lock_guard<std::mutex> lk(contactsMtx_);
            // Note: just add a contact for compatibility (so not for swarm).
            if (info.conversationId.isEmpty() && !info.peerUri.isEmpty()
                && contacts.find(info.peerUri) == contacts.end()) {
                // Contact not found, load profile from database.
                // The conversation model will create an entry and link the incomingCall.
                auto type = (linked.owner.profileInfo.type == profile::Type::JAMI)
                                ? profile::Type::PENDING
                                : profile::Type::SIP;
                addToContacts(info.peerUri, type, "", false);
                emitNewTrust = (linked.owner.profileInfo.type == profile::Type::JAMI);
            }
        }
        if (emitNewTrust) {
            emit behaviorController.newTrustRequest(linked.owner.id, "", info.peerUri);
        }
    
        emit linked.newAccountTransfer(fileId, info);
    }
    
    void
    ContactModelPimpl::slotProfileReceived(const QString& accountId,
                                           const QString& peer,
                                           const QString& path)
    {
        if (accountId != linked.owner.id)
            return;
    
        QFile vCardFile(path);
        if (!vCardFile.open(QIODevice::ReadOnly | QIODevice::Text))
            return;
        QTextStream in(&vCardFile);
        in.setCodec("UTF-8");
    
        auto vCard = in.readAll();
    
        profile::Info profileInfo;
        profileInfo.uri = peer;
        profileInfo.type = profile::Type::JAMI;
    
        for (auto& e : QString(vCard).split("\n"))
            if (e.contains("PHOTO"))
                profileInfo.avatar = e.split(":")[1];
            else if (e.contains("FN"))
                profileInfo.alias = e.split(":")[1];
    
        if (peer == linked.owner.profileInfo.uri) {
            if (linked.owner.profileInfo.avatar.isEmpty() && !profileInfo.avatar.isEmpty()) {
                auto dest = storage::getPath() + accountId + "/profile.vcf";
                QFile oldvCard(dest);
                if (oldvCard.exists())
                    oldvCard.remove();
                vCardFile.rename(dest);
                linked.owner.accountModel->setAlias(linked.owner.id, profileInfo.alias);
                linked.owner.accountModel->setAvatar(linked.owner.id, profileInfo.avatar);
                emit linked.profileUpdated(peer);
            }
            return;
        }
        vCardFile.remove();
    
        contact::Info contactInfo;
        contactInfo.profileInfo = profileInfo;
    
        linked.owner.contactModel->addContact(contactInfo);
    }
    
    void
    ContactModelPimpl::slotUserSearchEnded(const QString& accountId,
                                           int status,
                                           const QString& query,
                                           const VectorMapStringString& result)
    {
        if (searchQuery != query)
            return;
        if (accountId != linked.owner.id)
            return;
        searchResult.clear();
        switch (status) {
        case 0: /* SUCCESS */
            for (auto& resultInfo : result) {
                if (contacts.find(resultInfo.value("id")) != contacts.end()) {
                    continue;
                }
                profile::Info profileInfo;
                profileInfo.uri = resultInfo.value("id");
                profileInfo.type = profile::Type::TEMPORARY;
                profileInfo.avatar = resultInfo.value("profilePicture");
                profileInfo.alias = resultInfo.value("firstName") + " " + resultInfo.value("lastName");
                contact::Info contactInfo;
                contactInfo.profileInfo = profileInfo;
                contactInfo.registeredName = resultInfo.value("username");
                searchResult.insert(profileInfo.uri, contactInfo);
            }
            updateTemporaryMessage("");
            break;
        case 3: /* ERROR */
            updateTemporaryMessage("could not find contact matching search");
            break;
        default:
            break;
        }
        emit linked.modelUpdated(query);
    }
    
    } // namespace lrc
    
    #include "api/moc_contactmodel.cpp"
    #include "contactmodel.moc"