contactmodel.cpp 35.2 KB
Newer Older
Nicolas Jager's avatar
Nicolas Jager committed
1
/****************************************************************************
2
 *    Copyright (C) 2017-2019 Savoir-faire Linux Inc.                       *
3 4 5
 *   Author: Nicolas Jäger <nicolas.jager@savoirfairelinux.com>             *
 *   Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>           *
 *   Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com>       *
6
 *   Author: Hugo Lefeuvre <hugo.lefeuvre@savoirfairelinux.com>             *
7
 *   Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>       *
8
 *   Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>         *
Nicolas Jager's avatar
Nicolas Jager committed
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 *                                                                          *
 *   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/>.  *
 ***************************************************************************/

24 25
#include "api/contactmodel.h"

26 27
// Std
#include <algorithm>
28
#include <mutex>
29 30 31 32

// Daemon
#include <account_const.h>

Nicolas Jager's avatar
Nicolas Jager committed
33
// LRC
34
#include "api/account.h"
Nicolas Jager's avatar
Nicolas Jager committed
35
#include "api/contact.h"
36
#include "api/interaction.h"
37
#include "api/newaccountmodel.h"
38
#include "api/newcallmodel.h"
39
#include "api/conversationmodel.h"
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
40
#include "api/newaccountmodel.h"
41
#include "callbackshandler.h"
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
42
#include "uri.h"
43
#include "vcard.h"
Nicolas Jager's avatar
Nicolas Jager committed
44

45
#include "authority/daemon.h"
46
#include "authority/storagehelper.h"
47 48 49

// Dbus
#include "dbus/configurationmanager.h"
50
#include "dbus/presencemanager.h"
Nicolas Jager's avatar
Nicolas Jager committed
51 52 53 54 55 56 57 58 59 60

namespace lrc
{

using namespace api;

class ContactModelPimpl : public QObject
{
    Q_OBJECT
public:
61 62
    ContactModelPimpl(const ContactModel& linked,
                      Database& db,
63 64
                      const CallbacksHandler& callbacksHandler,
                      const BehaviorController& behaviorController);
Nicolas Jager's avatar
Nicolas Jager committed
65

66
    ~ContactModelPimpl();
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
67

68
    /**
69
     * Fills the contacts based on database's conversations
70 71
     * @return if the method succeeds
     */
72
    bool fillWithSIPContacts();
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
73

74
    /**
75
     * Fills the contacts based on database's conversations
76 77
     * @return if the method succeeds
     */
78
    bool fillWithJamiContacts();
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
79

80 81
    /**
     * Add a contact::Info to contacts.
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
82 83
     * @note: the contactId must corresponds to a profile in the database.
     * @param contactId
84
     * @param type
85
     * @param banned whether contact is banned or not
86
     */
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
87
    void addToContacts(const std::string& contactId, const profile::Type& type, bool banned = false);
88 89 90
    /**
     * Helpers for searchContact. Search for a given RING or SIP contact.
     */
91 92
    void searchRingContact(const URI& query);
    void searchSipContact(const URI& query);
93 94 95 96
    /**
     * Update temporary item to display a given message about a given uri.
     */
    void updateTemporaryMessage(const std::string& mes, const std::string& uri);
97

98 99 100 101 102
    /**
     * Check if equivalent uri exist in contact
     */
    std::string sipUriReceivedFilter(const std::string& uri);

103
    // Helpers
104
    const BehaviorController& behaviorController;
105
    const ContactModel& linked;
106 107 108 109
    Database& db;
    const CallbacksHandler& callbacksHandler;

    // Containers
Nicolas Jager's avatar
Nicolas Jager committed
110
    ContactModel::ContactInfoMap contacts;
111
    std::list<std::string> bannedContacts;
112
    std::mutex contactsMtx_;
113
    std::mutex bannedContactsMtx_;
Nicolas Jager's avatar
Nicolas Jager committed
114 115

public Q_SLOTS:
116 117 118 119 120 121
    /**
     * Listen CallbacksHandler when a presence update occurs
     * @param contactUri
     * @param status
     */
    void slotNewBuddySubscription(const std::string& uri, bool status);
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
122

123 124 125 126 127 128 129
    /**
     * Listen CallbacksHandler when a contact is added
     * @param accountId
     * @param contactUri
     * @param confirmed
     */
    void slotContactAdded(const std::string& accountId, const std::string& contactUri, bool confirmed);
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
130

131 132 133 134 135 136 137
    /**
     * Listen CallbacksHandler when a contact is removed
     * @param accountId
     * @param contactUri
     * @param banned
     */
    void slotContactRemoved(const std::string& accountId, const std::string& contactUri, bool banned);
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
138

139 140 141
    /**
     * Listen CallbacksHandler when a registeredName is found
     * @param accountId account linked
142
     * @param status (0 = SUCCESS, 1 = Not found, 2 = Network error)
143 144 145
     * @param uri of the contact found
     * @param registeredName of the contact found
     */
146
    void slotRegisteredNameFound(const std::string& accountId, int status, const std::string& uri, const std::string& registeredName);
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
147

148 149 150 151 152 153 154 155 156 157
    /**
     * Listen CallbacksHandler when an incoming request arrives
     * @param accountId account linked
     * @param contactUri
     * @param payload VCard of the contact
     */
    void slotIncomingContactRequest(const std::string& accountId,
                                    const std::string& contactUri,
                                    const std::string& payload);
    /**
158
     * Listen from callModel when an incoming call arrives.
159 160 161 162
     * @param fromId
     * @param callId
     */
    void slotIncomingCall(const std::string& fromId, const std::string& callId);
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
163

164 165 166 167 168 169 170 171 172
    /**
     * Listen from callbacksHandler for new account interaction and add pending contact if not present
     * @param accountId
     * @param from
     * @param payloads
     */
    void slotNewAccountMessage(std::string& accountId,
                               std::string& from,
                               std::map<std::string,std::string> payloads);
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
173

174 175 176 177 178 179
    /**
     * Listen from callbacksHandler to know when a file transfer interaction is incoming
     * @param dringId Daemon's ID for incoming transfer
     * @param transferInfo DataTransferInfo structure from daemon
     */
    void slotNewAccountTransfer(long long dringId, datatransfer::Info info);
Nicolas Jager's avatar
Nicolas Jager committed
180 181
};

182
using namespace authority;
Nicolas Jager's avatar
Nicolas Jager committed
183

184 185 186 187 188 189
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))
Nicolas Jager's avatar
Nicolas Jager committed
190 191 192 193 194 195 196 197 198 199 200 201 202 203
{
}

ContactModel::~ContactModel()
{

}

const ContactModel::ContactInfoMap&
ContactModel::getAllContacts() const
{
    return pimpl_->contacts;
}

204 205
bool
ContactModel::hasPendingRequests() const
206 207 208 209 210 211
{
    return pendingRequestCount() > 0;
}

int
ContactModel::pendingRequestCount() const
Nicolas Jager's avatar
Nicolas Jager committed
212
{
213
    std::lock_guard<std::mutex> lk(pimpl_->contactsMtx_);
214
    int pendingRequestCount = 0;
215
    std::for_each(pimpl_->contacts.begin(), pimpl_->contacts.end(),
216
        [&pendingRequestCount] (const auto& c) {
217 218
            if (!c.second.isBanned)
                pendingRequestCount += static_cast<int>(c.second.profileInfo.type == profile::Type::PENDING);
219
        });
220
    return pendingRequestCount;
Nicolas Jager's avatar
Nicolas Jager committed
221 222 223
}

void
224
ContactModel::addContact(contact::Info contactInfo)
Nicolas Jager's avatar
Nicolas Jager committed
225
{
226 227
    auto& profile = contactInfo.profileInfo;

228 229 230 231 232 233 234 235 236
    // 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("Unban-ing contact %s", profile.uri.c_str());
        ConfigurationManager::instance().addContact(owner.id.c_str(), profile.uri.c_str());
        // bannedContacts will be updated in slotContactAdded
        return;
    }

237
    if ((owner.profileInfo.type != profile.type) and
238 239 240
       (profile.type == profile::Type::RING or profile.type == profile::Type::SIP)) {
        qDebug() << "ContactModel::addContact, types invalids.";
        return;
241 242 243 244 245
    }

    MapStringString details = ConfigurationManager::instance().getContactDetails(
        owner.id.c_str(), contactInfo.profileInfo.uri.c_str());

246 247 248 249 250 251
    // 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;
252

253
    QByteArray vCard = owner.accountModel->accountVCard(owner.id).c_str();
254 255
    switch (profile.type) {
    case profile::Type::TEMPORARY:
256 257
        ConfigurationManager::instance().addContact(owner.id.c_str(), profile.uri.c_str());
        ConfigurationManager::instance().sendTrustRequest(owner.id.c_str(), profile.uri.c_str(), vCard);
258 259
        break;
    case profile::Type::PENDING:
260 261 262 263 264
        if (daemon::addContactFromPending(owner, profile.uri)) {
            emit pendingContactAccepted(profile.uri);
        } else {
            return;
        }
265 266 267 268 269
        break;
    case profile::Type::RING:
    case profile::Type::SIP:
        break;
    case profile::Type::INVALID:
270
    case profile::Type::COUNT__:
271 272 273 274 275
    default:
        qDebug() << "ContactModel::addContact, cannot add contact with invalid type.";
        return;
    }

276
    storage::createOrUpdateProfile(owner.id, profile, true);
277

278 279 280 281 282 283 284 285 286 287 288 289
    {
        std::lock_guard<std::mutex> lk(pimpl_->contactsMtx_);
        auto iter = pimpl_->contacts.find(contactInfo.profileInfo.uri);
        if (iter == pimpl_->contacts.end())
            pimpl_->contacts.emplace_hint(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->second.profileInfo.type;
            iter->second.profileInfo = contactInfo.profileInfo;
        }
290
    }
291 292
    if (profile.type == profile::Type::TEMPORARY)
        return;
293
    emit contactAdded(profile.uri);
Nicolas Jager's avatar
Nicolas Jager committed
294 295 296
}

void
297 298
ContactModel::removeContact(const std::string& contactUri, bool banned)
{
299 300 301 302 303 304 305
    bool emitContactRemoved = false;
    {
        std::lock_guard<std::mutex> lk(pimpl_->contactsMtx_);
        auto contact = pimpl_->contacts.find(contactUri);
        if (!banned && contact != pimpl_->contacts.end()
            && contact->second.profileInfo.type == profile::Type::PENDING) {
            // Discard the pending request and remove profile from db if necessary
306 307 308 309
            if(!daemon::discardFromPending(owner, contactUri)) {
                qDebug() << "Discard request for account " << owner.id.c_str() << " failed (" << contactUri.c_str() << ")";
                return;
            }
310
            pimpl_->contacts.erase(contactUri);
311
            storage::removeContact(pimpl_->db, contactUri);
312 313 314 315 316
            emitContactRemoved = true;
        }
        else if (owner.profileInfo.type == profile::Type::SIP) {
            // Remove contact from db
            pimpl_->contacts.erase(contactUri);
317
            storage::removeContact(pimpl_->db, contactUri);
318 319 320
            emitContactRemoved = true;
        }
    }
321 322 323 324 325
    // 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){}
326
    if (emitContactRemoved) {
327 328
        emit contactRemoved(contactUri);
    } else {
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
329 330
        // NOTE: this method is asynchronous, the model will be updated
        // in slotContactRemoved
331 332 333 334 335 336
        daemon::removeContact(owner, contactUri, banned);
    }
}

const contact::Info
ContactModel::getContact(const std::string& contactUri) const
Nicolas Jager's avatar
Nicolas Jager committed
337
{
338
    std::lock_guard<std::mutex> lk(pimpl_->contactsMtx_);
339 340
    return pimpl_->contacts.at(contactUri);
}
Nicolas Jager's avatar
Nicolas Jager committed
341

342 343 344 345 346 347
const std::list<std::string>&
ContactModel::getBannedContacts() const
{
    return pimpl_->bannedContacts;
}

Nicolas Jager's avatar
Nicolas Jager committed
348
void
349
ContactModel::searchContact(const std::string& query)
Nicolas Jager's avatar
Nicolas Jager committed
350
{
351 352
    // always reset temporary contact
    pimpl_->contacts[""] = {};
353

Olivier Soldano's avatar
Olivier Soldano committed
354 355
    auto uri = URI(QString(query.c_str()));

356 357 358 359 360 361 362
    auto uriScheme = uri.schemeType();
    if (uri.schemeType() == URI::SchemeType::NONE) {
        // uri has no scheme, default to current account scheme
        if (owner.profileInfo.type == profile::Type::SIP) {
            uriScheme = URI::SchemeType::SIP;
        } else if (owner.profileInfo.type == profile::Type::RING) {
            uriScheme = URI::SchemeType::RING;
363
        }
364 365 366
    }

    if (uriScheme == URI::SchemeType::SIP && owner.profileInfo.type == profile::Type::SIP) {
367
        pimpl_->searchSipContact(uri);
368
    } else if (uriScheme == URI::SchemeType::RING && owner.profileInfo.type == profile::Type::RING) {
369
        pimpl_->searchRingContact(uri);
Olivier Soldano's avatar
Olivier Soldano committed
370
    } else {
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385
        pimpl_->updateTemporaryMessage(tr("Bad URI scheme").toStdString(), uri.full().toStdString());
    }
}

void
ContactModelPimpl::updateTemporaryMessage(const std::string& mes, const std::string& uri)
{
    std::lock_guard<std::mutex> lk(contactsMtx_);
    auto& temporaryContact = contacts[""];
    temporaryContact.profileInfo.alias = mes;
    temporaryContact.profileInfo.type = profile::Type::TEMPORARY;
    temporaryContact.registeredName = uri;
}

void
386
ContactModelPimpl::searchRingContact(const URI& query)
387
{
388
    std::string uriID = query.format(URI::Section::USER_INFO | URI::Section::HOSTNAME | URI::Section::PORT).toStdString();
389
    if (query.isEmpty()) {
390 391
        // This will remove the temporary item
        emit linked.modelUpdated(uriID);
392
        return;
393 394
    }

395 396
    if (query.protocolHint() == URI::ProtocolHint::RING) {
        // no lookup, this is a ring infoHash
397 398 399 400 401
        for (auto &i : contacts) {
            if (i.second.profileInfo.uri == uriID) {
                return;
            }
        }
402 403 404 405 406 407
        auto& temporaryContact = contacts[""];
        temporaryContact.profileInfo.uri = uriID;
        temporaryContact.profileInfo.alias = uriID;
        temporaryContact.profileInfo.type = profile::Type::TEMPORARY;
    } else {
        updateTemporaryMessage(tr("Searching…").toStdString(), uriID);
408

409
        // Default searching
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
410
        ConfigurationManager::instance().lookupName(QString::fromStdString(linked.owner.id), "", QString::fromStdString(uriID));
411
    }
412
    emit linked.modelUpdated(uriID);
413 414 415
}

void
416
ContactModelPimpl::searchSipContact(const URI& query)
417
{
418
    std::string uriID = query.format(URI::Section::USER_INFO | URI::Section::HOSTNAME | URI::Section::PORT).toStdString();
419
    if (query.isEmpty()) {
420 421
        // This will remove the temporary item
        emit linked.modelUpdated(uriID);
422 423 424 425 426 427
        return;
    }

    auto& temporaryContact = contacts[""];
    {
        std::lock_guard<std::mutex> lk(contactsMtx_);
428 429 430
        if (contacts.find(uriID) == contacts.end()) {
            temporaryContact.profileInfo.uri = uriID;
            temporaryContact.profileInfo.alias = uriID;
431 432 433
            temporaryContact.profileInfo.type = profile::Type::TEMPORARY;
        }
    }
434
    emit linked.modelUpdated(uriID);
435
}
Nicolas Jager's avatar
Nicolas Jager committed
436

437
uint64_t
438 439 440 441 442
ContactModel::sendDhtMessage(const std::string& contactUri, const std::string& body) const
{
    // Send interaction
    QMap<QString, QString> payloads;
    payloads["text/plain"] = body.c_str();
443
    auto msgId = ConfigurationManager::instance().sendTextMessage(QString(owner.id.c_str()),
444 445 446
                                                     QString(contactUri.c_str()),
                                                     payloads);
    // NOTE: ConversationModel should store the interaction into the database
447
    return msgId;
Nicolas Jager's avatar
Nicolas Jager committed
448 449
}

450 451
ContactModelPimpl::ContactModelPimpl(const ContactModel& linked,
                                     Database& db,
452 453
                                     const CallbacksHandler& callbacksHandler,
                                     const BehaviorController& behaviorController)
454 455
: linked(linked)
, db(db)
456
, behaviorController(behaviorController)
457
, callbacksHandler(callbacksHandler)
Nicolas Jager's avatar
Nicolas Jager committed
458
{
459 460
    // Init contacts map
    if (linked.owner.profileInfo.type == profile::Type::SIP)
461
        fillWithSIPContacts();
462
    else
463
        fillWithJamiContacts();
464 465 466

    // connect the signals
    connect(&callbacksHandler, &CallbacksHandler::newBuddySubscription,
467
            this, &ContactModelPimpl::slotNewBuddySubscription);
468 469 470 471 472 473 474 475
    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);
476 477
    connect(&*linked.owner.callModel, &NewCallModel::newIncomingCall,
            this, &ContactModelPimpl::slotIncomingCall);
478 479
    connect(&callbacksHandler, &lrc::CallbacksHandler::newAccountMessage,
            this, &ContactModelPimpl::slotNewAccountMessage);
480 481
    connect(&callbacksHandler, &CallbacksHandler::transferStatusCreated,
            this, &ContactModelPimpl::slotNewAccountTransfer);
Nicolas Jager's avatar
Nicolas Jager committed
482 483 484 485
}

ContactModelPimpl::~ContactModelPimpl()
{
486 487 488 489 490 491 492 493 494 495 496 497 498 499
    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);
500 501
    disconnect(&callbacksHandler, &CallbacksHandler::transferStatusCreated,
               this, &ContactModelPimpl::slotNewAccountTransfer);
Nicolas Jager's avatar
Nicolas Jager committed
502 503
}

504
bool
505
ContactModelPimpl::fillWithSIPContacts()
Nicolas Jager's avatar
Nicolas Jager committed
506
{
507 508 509
    auto conversationsForAccount = storage::getAllConversations(db);
    for (const auto& convId : conversationsForAccount) {
        auto otherParticipants = storage::getPeerParticipantsForConversation(db, convId);
510 511
        for (const auto& participant: otherParticipants) {
            // for each conversations get the other profile id
512 513 514
            auto contactInfo = storage::buildContactFromProfile(linked.owner.id,
                                                                participant,
                                                                profile::Type::SIP);
515 516 517 518
            {
                std::lock_guard<std::mutex> lk(contactsMtx_);
                contacts.emplace(contactInfo.profileInfo.uri, contactInfo);
            }
519 520
        }
    }
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
521

522 523
    return true;
}
Nicolas Jager's avatar
Nicolas Jager committed
524

525
bool
526
ContactModelPimpl::fillWithJamiContacts() {
527

528
    // Add contacts from daemon
529 530
    const VectorMapStringString& contacts_vector = ConfigurationManager::instance().getContacts(linked.owner.id.c_str());
    for (auto contact_info : contacts_vector) {
531
        std::lock_guard<std::mutex> lk(contactsMtx_);
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
532 533
        bool banned = contact_info["banned"] == "true" ? true : false;
        addToContacts(contact_info["id"].toStdString(), linked.owner.profileInfo.type, banned);
534 535 536
    }

    // Add pending contacts
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
537
    const VectorMapStringString& pending_tr {ConfigurationManager::instance().getTrustRequests(linked.owner.id.c_str())};
538 539 540 541 542 543
    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];

544 545 546 547
        auto contactInfo = storage::buildContactFromProfile(linked.owner.id,
                                                            contactUri.toStdString(),
                                                            profile::Type::PENDING);

548
        const auto vCard = lrc::vCard::utils::toHashMap(payload);
549
        const auto alias = vCard["FN"];
550 551 552 553 554 555 556
        const auto photo = (vCard.find("PHOTO;ENCODING=BASE64;TYPE=PNG") != vCard.end()) ?
            vCard["PHOTO;ENCODING=BASE64;TYPE=PNG"] :
            vCard["PHOTO;ENCODING=BASE64;TYPE=JPEG"];

        contactInfo.profileInfo.type = profile::Type::PENDING;
        if (!alias.isEmpty()) contactInfo.profileInfo.alias = alias.constData();
        if (!photo.isEmpty()) contactInfo.profileInfo.avatar = photo.constData();
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
557 558
        contactInfo.registeredName = "";
        contactInfo.isBanned = false;
559

560 561 562 563 564
        {
            std::lock_guard<std::mutex> lk(contactsMtx_);
            contacts.emplace(contactUri.toStdString(), contactInfo);
        }

565 566
        // create profile vcard for contact
        storage::createOrUpdateProfile(linked.owner.id, contactInfo.profileInfo, true);
567 568
    }

569 570
    // Update presence
    // TODO fix this map. This is dumb for now. The map contains values as keys, and empty values.
571 572 573
    const VectorMapStringString& subscriptions {
        PresenceManager::instance().getSubscriptions(linked.owner.id.c_str())
    };
574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593
    for (const auto& subscription : subscriptions) {
        auto first = true;
        std::string uri = "";
        for (const auto& key : subscription) {
            if (first) {
                first = false;
                uri = key.toStdString();
            } else {
                {
                    std::lock_guard<std::mutex> lk(contactsMtx_);
                    auto it = contacts.find(uri);
                    if (it != contacts.end()) {
                        it->second.isPresent = key == "Online";
                        linked.modelUpdated(uri, false);
                    }
                }
                break;
            }
        }
    }
594
    return true;
Nicolas Jager's avatar
Nicolas Jager committed
595 596 597
}

void
598
ContactModelPimpl::slotNewBuddySubscription(const std::string& contactUri, bool status)
Nicolas Jager's avatar
Nicolas Jager committed
599
{
600 601 602 603 604 605 606
    {
        std::lock_guard<std::mutex> lk(contactsMtx_);
        auto it = contacts.find(contactUri);
        if (it != contacts.end()) {
            it->second.isPresent = status;
        } else
            return;
607
    }
608
    emit linked.modelUpdated(contactUri, false);
Nicolas Jager's avatar
Nicolas Jager committed
609 610
}

611 612
void
ContactModelPimpl::slotContactAdded(const std::string& accountId, const std::string& contactUri, bool confirmed)
Nicolas Jager's avatar
Nicolas Jager committed
613
{
614 615
    Q_UNUSED(confirmed)
    if (accountId != linked.owner.id) return;
616 617
    auto contact = contacts.find(contactUri);

618 619 620 621
    if (contact->second.profileInfo.type == profile::Type::PENDING) {
        emit behaviorController.trustRequestTreated(linked.owner.id, contactUri);
    }

622 623
    bool isBanned = false;

624
    {
625
        // Always get contactsMtx_ lock before bannedContactsMtx_.
626
        std::lock_guard<std::mutex> lk(contactsMtx_);
627 628 629 630 631 632 633 634 635 636 637 638 639

        {
            // Check whether contact is banned or not
            std::lock_guard<std::mutex> lk(bannedContactsMtx_);
            auto it = std::find(bannedContacts.begin(), bannedContacts.end(), contact->second.profileInfo.uri);

            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);
            }

Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
640
            addToContacts(contactUri, linked.owner.profileInfo.type, false);
641 642 643 644 645 646
        }
    }

    if (isBanned) {
        // Update the smartlist
        linked.owner.conversationModel->refreshFilter();
647
        emit linked.bannedStatusChanged(contactUri, false);
648 649
    } else {
        emit linked.contactAdded(contactUri);
650
    }
Nicolas Jager's avatar
Nicolas Jager committed
651 652 653
}

void
654
ContactModelPimpl::slotContactRemoved(const std::string& accountId, const std::string& contactUri, bool banned)
Nicolas Jager's avatar
Nicolas Jager committed
655
{
656 657
    if (accountId != linked.owner.id)
        return;
658

659
    {
660
        // Always get contactsMtx_ lock before bannedContactsMtx_.
661
        std::lock_guard<std::mutex> lk(contactsMtx_);
662 663 664 665

        auto contact = contacts.find(contactUri);
        if (contact == contacts.end()) return;

666 667 668 669
        if (contact->second.profileInfo.type == profile::Type::PENDING) {
            emit behaviorController.trustRequestTreated(linked.owner.id, contactUri);
        }

670 671 672
        if (contact->second.profileInfo.type != profile::Type::SIP)
            PresenceManager::instance().subscribeBuddy(linked.owner.id.c_str(), contactUri.c_str(), false);

673 674
        if (banned) {
            contact->second.isBanned = true;
675 676
            // Update bannedContacts index
            bannedContacts.emplace_back(contact->second.profileInfo.uri);
677
        } else {
678 679 680 681 682 683 684 685 686 687 688
            if (contact->second.isBanned) {
                // Contact was banned, update bannedContacts
                std::lock_guard<std::mutex> lk(bannedContactsMtx_);
                auto it = std::find(bannedContacts.begin(), bannedContacts.end(), contact->second.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);
                }
            }
689
            storage::removeContact(db, contactUri);
690
            contacts.erase(contactUri);
691 692 693 694 695 696
        }
    }

    if (banned) {
        // Update the smartlist
        linked.owner.conversationModel->refreshFilter();
697
        emit linked.bannedStatusChanged(contactUri, true);
698 699
    } else {
        emit linked.contactRemoved(contactUri);
700 701
    }
}
Nicolas Jager's avatar
Nicolas Jager committed
702

703
void
704
ContactModelPimpl::addToContacts(const std::string& contactUri, const profile::Type& type, bool banned)
705
{
706 707 708
    // create a vcard if necessary
    profile::Info profileInfo{ contactUri, {}, {}, linked.owner.profileInfo.type };
    storage::createOrUpdateProfile(linked.owner.id, profileInfo, true);
709

710
    auto contactInfo = storage::buildContactFromProfile(linked.owner.id, contactUri, type);
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
711 712 713 714 715
    contactInfo.isBanned = banned;

    // lookup address in case of RING contact
    if (type == profile::Type::RING) {
        ConfigurationManager::instance().lookupAddress(QString::fromStdString(linked.owner.id),
716 717
                                                       "", QString::fromStdString(contactUri));
        PresenceManager::instance().subscribeBuddy(linked.owner.id.c_str(), contactUri.c_str(), !banned);
718
    }
719

720 721
    contactInfo.profileInfo.type = type; // Because PENDING should not be stored in the database
    auto iter = contacts.find(contactInfo.profileInfo.uri);
722 723 724
    if (iter != contacts.end()) {
        auto info = iter->second;
        contactInfo.registeredName = info.registeredName;
725
        iter->second = contactInfo;
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
726
    } else
727
        contacts.emplace_hint(iter, contactInfo.profileInfo.uri, contactInfo);
728 729

    if (banned) {
730
        bannedContacts.emplace_back(contactUri);
731
    }
Nicolas Jager's avatar
Nicolas Jager committed
732 733 734
}

void
735
ContactModelPimpl::slotRegisteredNameFound(const std::string& accountId,
736
                                           int status,
737 738
                                           const std::string& uri,
                                           const std::string& registeredName)
Nicolas Jager's avatar
Nicolas Jager committed
739
{
740 741 742
    if (accountId != linked.owner.id) return;

    auto& temporaryContact = contacts[""];
743
    if (status == 0 /* SUCCESS */) {
744 745 746 747 748 749 750 751 752 753
        std::lock_guard<std::mutex> lk(contactsMtx_);

        if (contacts.find(uri) != contacts.end()) {
            // update contact and remove temporary item
            contacts[uri].registeredName = registeredName;
            temporaryContact = {};
        } else {
            if (temporaryContact.registeredName != uri && temporaryContact.registeredName != registeredName) {
                // we are notified that a previous lookup ended
                return;
754
            }
755 756 757 758

            // update temporary item
            lrc::api::profile::Info profileInfo = {uri, "", "", profile::Type::TEMPORARY};
            temporaryContact = {profileInfo, registeredName, false, false};
759
        }
760
    } else {
761
        if (temporaryContact.registeredName != uri && temporaryContact.registeredName != registeredName) {
762
            // we are notified that a previous lookup ended
763 764
            return;
        }
765 766 767

        switch (status) {
        case 1 /* INVALID */:
768
            updateTemporaryMessage(tr("Invalid ID").toStdString(), registeredName);
769 770
            break;
        case 2 /* NOT FOUND */:
771
            updateTemporaryMessage(tr("Registered name not found").toStdString(), registeredName);
772 773 774 775
            break;
        case 3 /* ERROR */:
            updateTemporaryMessage(tr("Couldn't lookup…").toStdString(), registeredName);
            break;
776
        }
777
    }
778 779

    emit linked.modelUpdated(uri);
780 781
}

782 783 784 785 786 787 788 789
void
ContactModelPimpl::slotIncomingContactRequest(const std::string& accountId,
                                              const std::string& contactUri,
                                              const std::string& payload)
{
    if (linked.owner.id != accountId)
        return;

790
    auto emitTrust = false;
791 792 793
    {
        std::lock_guard<std::mutex> lk(contactsMtx_);
        if (contacts.find(contactUri) == contacts.end()) {
794
            const auto vCard = lrc::vCard::utils::toHashMap(payload.c_str());
795
            const auto alias = vCard["FN"];
796 797
            const auto photo = (vCard.find("PHOTO;ENCODING=BASE64;TYPE=PNG") == vCard.end()) ?
            vCard["PHOTO;ENCODING=BASE64;TYPE=JPEG"] : vCard["PHOTO;ENCODING=BASE64;TYPE=PNG"];
798 799

            auto profileInfo = profile::Info {contactUri, photo.toStdString(), alias.toStdString(), profile::Type::PENDING};
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
800
            auto contactInfo = contact::Info {profileInfo, "", false, false, false};
801
            contacts.emplace(contactUri, contactInfo);
802
            emitTrust = true;
803
            storage::createOrUpdateProfile(accountId, profileInfo, true);
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
804
        }
805
    }
806

807
    if (emitTrust) {
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
808
        emit linked.contactAdded(contactUri);
809 810
        emit behaviorController.newTrustRequest(linked.owner.id, contactUri);
    }
Nicolas Jager's avatar
Nicolas Jager committed
811 812 813
}

void
814
ContactModelPimpl::slotIncomingCall(const std::string& fromId, const std::string& callId)
Nicolas Jager's avatar
Nicolas Jager committed
815
{
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
816
    bool emitContactAdded = false;
817
    {
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
818 819 820 821 822 823 824
        std::lock_guard<std::mutex> lk(contactsMtx_);
        if (contacts.find(fromId) == 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::RING) ? profile::Type::PENDING : profile::Type::SIP;
            addToContacts(fromId, type, false);
            emitContactAdded = true;
825
        }
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
826 827 828 829 830
    }
    if (emitContactAdded) {
        emit linked.contactAdded(fromId);
        if (linked.owner.profileInfo.type == profile::Type::RING) {
            emit behaviorController.newTrustRequest(linked.owner.id, fromId);
831
        }
832
    }
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
833 834

    emit linked.incomingCallFromPending(fromId, callId);
835
}
Nicolas Jager's avatar
Nicolas Jager committed
836

837 838 839 840 841 842
void
ContactModelPimpl::slotNewAccountMessage(std::string& accountId,
                                         std::string& from,
                                         std::map<std::string,std::string> payloads)
{
    if (accountId != linked.owner.id) return;
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
843

844
    auto emitNewTrust = false;
845 846 847 848 849
    {
        std::lock_guard<std::mutex> lk(contactsMtx_);
        if (contacts.find(from) == contacts.end()) {
            // Contact not found, load profile from database.
            // The conversation model will create an entry and link the incomingCall.
850 851 852 853 854 855 856 857 858 859 860 861 862

            if (linked.owner.profileInfo.type == profile::Type::SIP) {
                std::string potentialContact = sipUriReceivedFilter(from);
                if (potentialContact.empty()) {
                    addToContacts(from, profile::Type::SIP, false);
                } else {
                    // equivalent uri exist, use that uri
                    from = potentialContact;
                }
            } else {
                addToContacts(from, profile::Type::PENDING, false);
                emitNewTrust = true;
            }
863
        }
864
    }
865 866 867
    if (emitNewTrust) {
        emit behaviorController.newTrustRequest(linked.owner.id, from);
    }
868
    emit linked.newAccountMessage(accountId, from, payloads);
Nicolas Jager's avatar
Nicolas Jager committed
869 870
}

871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925
std::string
ContactModelPimpl::sipUriReceivedFilter(const std::string& 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;

    auto pos = uriCopy.find("@");
    auto ownerHostName = linked.owner.confProperties.hostname;

    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) {

            if (contacts.find(remoteUser) != contacts.end()) {
                return remoteUser;
            }
            if (remoteUser.at(0) == '+') {
                // "+" - country dial-in codes
                // maximum 3 digits
                for (int i = 2; i <= 4; i++) {
                    std::string tempUserName = 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& i : contacts) {
                    if (!i.first.empty()) {
                        for (int j = 2; j <= 4; j++) {
                            std::string tempUserName = i.first.substr(j);
                            if (tempUserName == remoteUser) {
                                return i.first;
                            }
                        }
                    }
                }
                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 "";
}

926 927 928 929
void
ContactModelPimpl::slotNewAccountTransfer(long long dringId, datatransfer::Info info)
{
    if (info.accountId != linked.owner.id) return;
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
930

931
    bool emitNewTrust = false;