-
Sébastien Blin authored
Change-Id: I007aeaa520d4647d53f2ab590c57cfbacf5b6cee
Sébastien Blin authoredChange-Id: I007aeaa520d4647d53f2ab590c57cfbacf5b6cee
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
contactmodel.cpp 42.16 KiB
/****************************************************************************
* Copyright (C) 2017-2020 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"
// Std
#include <algorithm>
#include <mutex>
// Daemon
#include <account_const.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"
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
*/
void addToContacts(const QString& contactId,
const profile::Type& type,
const QString& displayName = "",
bool banned = false);
/**
* Helpers for searchContact. Search for a given RING or SIP contact.
*/
void searchRingContact(const URI& query);
void searchSipContact(const URI& query);
void jamsSearch(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_;
public Q_SLOTS:
/**
* Listen CallbacksHandler when a presence update occurs
* @param contactUri
* @param status
*/
void slotNewBuddySubscription(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& 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 from
* @param payloads
*/
void slotNewAccountMessage(const QString& accountId,
const QString& msgId,
const QString& from,
const MapStringString& payloads);
/**
* 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);
/**
* 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;
}
bool
ContactModel::hasPendingRequests() const
{
return pendingRequestCount() > 0;
}
int
ContactModel::pendingRequestCount() const
{
if (!pimpl_)
return 0;
std::lock_guard<std::mutex> lk(pimpl_->contactsMtx_);
int pendingRequestCount = 0;
std::for_each(pimpl_->contacts.begin(),
pimpl_->contacts.end(),
[&pendingRequestCount](const auto& c) {
if (!c.isBanned)
pendingRequestCount += static_cast<int>(c.profileInfo.type
== profile::Type::PENDING);
});
return pendingRequestCount;
}
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::RING or profile.type == profile::Type::SIP)) {
qDebug() << "ContactModel::addContact, types invalids.";
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;
QByteArray vCard = owner.accountModel->accountVCard(owner.id).toUtf8();
switch (profile.type) {
case profile::Type::TEMPORARY:
ConfigurationManager::instance().addContact(owner.id, profile.uri);
ConfigurationManager::instance().sendTrustRequest(owner.id, profile.uri, vCard);
// will be saved after receiving contact added signal
return;
case profile::Type::PENDING:
if (daemon::addContactFromPending(owner, profile.uri)) {
emit pendingContactAccepted(profile.uri);
} else {
return;
}
break;
case profile::Type::RING:
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 contactAdded(profile.uri);
}
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::removeContact(pimpl_->db, contactUri);
emitContactRemoved = true;
} else if (owner.profileInfo.type == profile::Type::SIP) {
// Remove contact from db
pimpl_->contacts.remove(contactUri);
storage::removeContact(pimpl_->db, 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::RING) {
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::RING) {
bool isJamsAccount = !owner.confProperties.managerUri.isEmpty();
if (isJamsAccount)
pimpl_->jamsSearch(uri);
else
pimpl_->searchRingContact(uri);
} else {
pimpl_->updateTemporaryMessage(tr("Bad URI scheme"));
}
}
void
ContactModelPimpl::updateTemporaryMessage(const QString& mes)
{
linked.owner.conversationModel->updateSearchStatus(mes);
}
void
ContactModelPimpl::searchRingContact(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…"));
// Default searching
ConfigurationManager::instance().lookupName(linked.owner.id, "", uriID);
}
}
void
ContactModelPimpl::jamsSearch(const URI& query)
{
QString uriID = query.format(URI::Section::USER_INFO | URI::Section::HOSTNAME
| URI::Section::PORT);
if (query.isEmpty()) {
emit linked.modelUpdated(uriID);
updateTemporaryMessage("");
return;
}
updateTemporaryMessage(tr("Searching…"));
ConfigurationManager::instance().searchUser(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;
}
auto& temporaryContact = searchResult[query];
{
std::lock_guard<std::mutex> lk(contactsMtx_);
if (contacts.find(uriID) == contacts.end()) {
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;
}
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);
}
// 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 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, false);
}
}
break;
}
}
}
return true;
}
void
ContactModelPimpl::slotNewBuddySubscription(const QString& contactUri, bool status)
{
{
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, false);
}
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::RING && !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);
}
addToContacts(contactUri, linked.owner.profileInfo.type, "", false);
}
}
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::removeContact(db, contactUri);
contacts.remove(contactUri);
}
}
if (banned) {
// Update the smartlist
linked.owner.conversationModel->refreshFilter();
emit linked.bannedStatusChanged(contactUri, true);
} else {
emit linked.contactRemoved(contactUri);
}
}
void
ContactModelPimpl::addToContacts(const QString& contactUri,
const profile::Type& type,
const QString& displayName,
bool banned)
{
// create a vcard if necessary
profile::Info profileInfo {contactUri, {}, displayName, linked.owner.profileInfo.type};
storage::createOrUpdateProfile(linked.owner.id, profileInfo, true);
auto contactInfo = storage::buildContactFromProfile(linked.owner.id, contactUri, type);
contactInfo.isBanned = banned;
// lookup address in case of RING contact
if (type == profile::Type::RING) {
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("Registered name 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& 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);
}
}
if (emitTrust) {
emit linked.contactAdded(contactUri);
emit behaviorController.newTrustRequest(linked.owner.id, 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::RING)
? 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) {
emit linked.contactAdded(fromId);
if (linked.owner.profileInfo.type == profile::Type::RING) {
emit behaviorController.newTrustRequest(linked.owner.id, fromId);
}
}
emit linked.incomingCall(fromId, callId);
}
void
ContactModelPimpl::slotNewAccountMessage(const QString& accountId,
const QString& msgId,
const QString& from,
const MapStringString& payloads)
{
if (accountId != linked.owner.id)
return;
QString from2(from);
auto emitNewTrust = false;
{
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.
if (linked.owner.profileInfo.type == profile::Type::SIP) {
QString potentialContact = sipUriReceivedFilter(from);
if (potentialContact.isEmpty()) {
addToContacts(from, profile::Type::SIP, "", false);
} else {
// equivalent uri exist, use that uri
from2 = potentialContact;
}
} else {
addToContacts(from, profile::Type::PENDING, "", false);
emitNewTrust = true;
}
}
}
if (emitNewTrust) {
emit behaviorController.newTrustRequest(linked.owner.id, from);
}
emit linked.newAccountMessage(accountId, msgId, from2, 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(long long dringId, datatransfer::Info info)
{
if (info.accountId != linked.owner.id)
return;
bool emitNewTrust = false;
{
std::lock_guard<std::mutex> lk(contactsMtx_);
if (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::RING)
? profile::Type::PENDING
: profile::Type::SIP;
addToContacts(info.peerUri, type, "", false);
emitNewTrust = (linked.owner.profileInfo.type == profile::Type::RING);
}
}
if (emitNewTrust) {
emit behaviorController.newTrustRequest(linked.owner.id, info.peerUri);
}
emit linked.newAccountTransfer(dringId, 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);
auto vCard = in.readAll();
vCardFile.remove();
profile::Info profileInfo;
profileInfo.uri = peer;
profileInfo.type = profile::Type::RING;
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];
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 */
break;
case 3: /* ERROR */
updateTemporaryMessage("could not find contact matching search");
emit linked.modelUpdated(query);
return;
default:
emit linked.modelUpdated(query);
return;
}
updateTemporaryMessage("");
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);
}
emit linked.modelUpdated(query);
}
} // namespace lrc
#include "api/moc_contactmodel.cpp"
#include "contactmodel.moc"