diff --git a/src/authority/databasehelper.cpp b/src/authority/databasehelper.cpp index 9a4687489442bafcd39fede62e74101ef1c0360c..b385da4e9bc6505e9390cc551acd898b1c4b11a4 100644 --- a/src/authority/databasehelper.cpp +++ b/src/authority/databasehelper.cpp @@ -2,6 +2,7 @@ * Copyright (C) 2017-2018 Savoir-faire Linux * * Author: Nicolas Jäger <nicolas.jager@savoirfairelinux.com> * * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com> * + * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com> * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * @@ -19,6 +20,7 @@ #include "databasehelper.h" #include "api/profile.h" #include "api/datatransfer.h" +#include <account_const.h> #include <datatransfer_interface.h> @@ -32,52 +34,106 @@ namespace database { std::string -getProfileId(Database& db, const std::string& uri) -{ - auto ids = db.select("id", "profiles","uri=:uri", {{":uri", uri}}).payloads; - return ids.empty() ? "" : ids[0]; +getProfileId(Database& db, + const std::string& accountId, + const std::string& isAccount, + const std::string& uri) +{ + auto accountProfiles = db.select("profile_id", "profiles_accounts", + "account_id=:account_id AND is_account=:is_account", + {{":account_id", accountId}, + {":is_account", isAccount}}).payloads; + if (accountProfiles.empty() && (isAccount == "true")) { + return ""; + } + if (isAccount == "true") return accountProfiles[0]; + + // we may have many contacts profiles for one account id, + // and need to check uri in addition to account id + auto profiles = db.select("id", "profiles", "uri=:uri", {{":uri", uri}}).payloads; + + if (profiles.empty()) return ""; + std::sort(accountProfiles.begin(), accountProfiles.end()); + std::sort(profiles.begin(), profiles.end()); + + std::vector<std::string> common; + std::set_intersection(accountProfiles.begin(), accountProfiles.end(), + profiles.begin(), profiles.end(), + std::back_inserter(common)); + //if profile exists but not linked with account id, + // update profiles_accounts. Except empty uri for SIP accounts + if(common.empty()) { + if(!uri.empty()) { + db.insertInto("profiles_accounts", + {{":profile_id", "profile_id"}, {":account_id", "account_id"}, + {":is_account", "is_account"}}, + {{":profile_id", profiles[0]}, {":account_id", accountId}, + {":is_account", isAccount}}); + } + return profiles[0]; + } + return common[0]; } std::string getOrInsertProfile(Database& db, const std::string& contactUri, + const std::string& accountId, + bool isAccount, + const std::string& type, const std::string& alias, - const std::string& avatar, - const std::string& type) + const std::string& avatar) { // Check if profile is already present. - auto profileAlreadyExists = db.select("id", - "profiles", - "uri=:uri", - {{":uri", contactUri}}); - if (profileAlreadyExists.payloads.empty()) { - // Doesn't exists, add contact to the database + const std::string isAccountStr = isAccount ? "true" : "false"; + auto profileAlreadyExists = getProfileId(db, accountId, isAccountStr, contactUri); + if (profileAlreadyExists.empty()) { + // Doesn't exists, add profile to the database auto row = db.insertInto("profiles", {{":uri", "uri"}, {":alias", "alias"}, {":photo", "photo"}, {":type", "type"}, {":status", "status"}}, {{":uri", contactUri}, {":alias", alias}, {":photo", avatar}, {":type", type}, {":status", "TRUSTED"}}); - if (row == -1) { - qDebug() << "contact not added to the database"; - return ""; + if (row == -1) { + qDebug() << "contact not added to the database"; + return ""; + } + // link profile id to account id + auto profiles = db.select("profile_id", "profiles_accounts", + "profile_id=:profile_id AND \ + account_id=:account_id AND \ + is_account=:is_account", + {{":profile_id", std::to_string(row)}, + {":account_id", accountId}, + {":is_account", isAccountStr}}) + .payloads; + + if (profiles.empty()) { + db.insertInto("profiles_accounts", + {{":profile_id", "profile_id"}, + {":account_id", "account_id"}, + {":is_account", "is_account"}}, + {{":profile_id", std::to_string(row)}, + {":account_id", accountId}, + {":is_account", isAccountStr}}); } - return std::to_string(row); + return std::to_string(row); } else { // Exists, update and retrieve it. if (!avatar.empty() && !alias.empty()) { db.update("profiles", "alias=:alias, photo=:photo", {{":alias", alias}, {":photo", avatar}}, - "uri=:uri", {{":uri", contactUri}}); + "id=:id", {{":id", profileAlreadyExists}}); } else if (!avatar.empty()) { db.update("profiles", "photo=:photo", {{":photo", avatar}}, - "uri=:uri", {{":uri", contactUri}}); + "id=:id", {{":id", profileAlreadyExists}}); } - return profileAlreadyExists.payloads[0]; + return profileAlreadyExists; } } @@ -127,6 +183,19 @@ getAliasForProfileId(Database& db, const std::string& profileId) return ""; } +bool +profileCouldBeRemoved(Database& db, const std::string& profileId) +{ + auto returnFromDb = db.select("account_id", + "profiles_accounts", + "profile_id=:profile_id", + {{":profile_id", profileId}}); + if (returnFromDb.nbrOfCols == 1 && returnFromDb.payloads.size() >= 1) { + return false; + } + return true; +} + void setAliasForProfileId(Database& db, const std::string& profileId, const std::string& alias) { @@ -211,10 +280,11 @@ beginConversationsBetween(Database& db, const std::string& accountProfile, const void getHistory(Database& db, api::conversation::Info& conversation) { + auto accountProfile = getProfileId(db, conversation.accountId, "true"); auto interactionsResult = db.select("id, author_id, body, timestamp, type, status", "interactions", - "conversation_id=:conversation_id", - {{":conversation_id", conversation.uid}}); + "conversation_id=:conversation_id AND account_id=:account_id", + {{":conversation_id", conversation.uid}, {":account_id", accountProfile}}); if (interactionsResult.nbrOfCols == 6) { auto payloads = interactionsResult.payloads; for (decltype(payloads.size()) i = 0; i < payloads.size(); i += 6) { @@ -252,7 +322,8 @@ addDataTransferToConversation(Database& db, const std::string& conversationId, const api::datatransfer::Info& infoFromDaemon) { - auto peerProfileId = getProfileId(db, infoFromDaemon.peerUri); + auto peerProfileId = getProfileId(db, infoFromDaemon.accountId, "false", + infoFromDaemon.peerUri); return db.insertInto("interactions", { {":account_id", "account_id"}, @@ -376,25 +447,25 @@ void clearInteractionFromConversation(Database& db, {{":conv_id", conversationId}, {":int_id", std::to_string(interactionId)}}); } -void clearAllHistoryFor(Database& db, const std::string& accountUri) +void clearAllHistoryFor(Database& db, const std::string& accountId) { - auto accountId = db.select("id", "profiles","uri=:uri", {{":uri", accountUri}}).payloads; + auto profileId = getProfileId(db, accountId, "true"); - if (accountId.empty()) + if (profileId.empty()) return; - db.deleteFrom("interactions", "account_id=:account_id", {{":account_id", accountId[0]}}); + db.deleteFrom("interactions", "account_id=:account_id", {{":account_id", profileId}}); } void -removeContact(Database& db, const std::string& accountUri, const std::string& contactUri) +removeContact(Database& db, const std::string& contactUri, const std::string& accountId) { // Get profile for contact - auto contactId = db.select("id", "profiles","uri=:uri", {{":uri", contactUri}}).payloads; + auto contactId = getProfileId(db, accountId, "false", contactUri); if (contactId.empty()) return; // No profile + auto accountProfileId = getProfileId(db, accountId, "true"); // Get common conversations - auto accountProfileId = getProfileId(db, accountUri); - auto conversations = getConversationsBetween(db, accountProfileId, contactId[0]); + auto conversations = getConversationsBetween(db, accountProfileId, contactId); // Remove conversations + interactions for (const auto& conversationId: conversations) { // Remove conversation @@ -403,17 +474,23 @@ removeContact(Database& db, const std::string& accountUri, const std::string& co db.deleteFrom("interactions", "conversation_id=:id", {{":id", conversationId}}); } // Get conversations for this contact. - conversations = getConversationsForProfile(db, contactId[0]); + conversations = getConversationsForProfile(db, contactId); if (conversations.empty()) { // Delete profile - db.deleteFrom("profiles", "id=:id", {{":id", contactId[0]}}); + db.deleteFrom("profiles_accounts", + "profile_id=:profile_id AND account_id=:account_id AND is_account=:is_account", + {{":profile_id", contactId}, + {":account_id", accountId}, + {":is_account", "false"}}); + if (profileCouldBeRemoved(db, contactId)) + db.deleteFrom("profiles", "id=:id", {{":id", contactId}}); } } void -removeAccount(Database& db, const std::string& accountUri) +removeAccount(Database& db, const std::string& accountId) { - auto accountProfileId = database::getProfileId(db, accountUri); + auto accountProfileId = database::getProfileId(db, accountId, "true"); auto conversationsForAccount = getConversationsForProfile(db, accountProfileId); for (const auto& convId: conversationsForAccount) { auto peers = getPeerParticipantsForConversation(db, accountProfileId, convId); @@ -422,24 +499,36 @@ removeAccount(Database& db, const std::string& accountUri) for (const auto& peerId: peers) { auto otherConversationsForProfile = getConversationsForProfile(db, peerId); if (otherConversationsForProfile.empty()) { - db.deleteFrom("profiles", "id=:id", {{":id", peerId}}); + db.deleteFrom("profiles_accounts", + "profile_id=:profile_id AND account_id=:account_id AND is_account=:is_account", + {{":profile_id", peerId}, + {":account_id", accountId}, + {":is_account", "false"}}); + if (profileCouldBeRemoved(db, peerId)) { + db.deleteFrom("profiles", "id=:id", {{":id", peerId}}); + } } } } + db.deleteFrom("profiles_accounts", + "profile_id=:profile_id AND account_id=:account_id AND is_account=:is_account", + {{":profile_id", accountProfileId}, + {":account_id", accountId}, + {":is_account", "true"}}); db.deleteFrom("profiles", "id=:id", {{":id", accountProfileId}}); } void -addContact(Database& db, const std::string& accountUri, const std::string& contactUri) +addContact(Database& db, const std::string& contactUri, const std::string& accountId) { // Get profile for contact - auto row = getOrInsertProfile(db, contactUri); + auto row = getOrInsertProfile(db, contactUri, accountId, false, "", ""); if (row.empty()) { qDebug() << "database::addContact, no profile for contact. abort"; return; } // Get profile of the account linked - auto accountProfileId = getProfileId(db, accountUri); + auto accountProfileId = getProfileId(db, accountId, "true"); // Get if conversation exists auto common = getConversationsBetween(db, accountProfileId, row); if (common.empty()) { diff --git a/src/authority/databasehelper.h b/src/authority/databasehelper.h index 5b7616fac8f71d99bebc515f42da43479a6e2bb3..2fa4ea85a32e2e0ddee930b4c3c2b4b1264a686f 100644 --- a/src/authority/databasehelper.h +++ b/src/authority/databasehelper.h @@ -1,7 +1,8 @@ /**************************************************************************** - * Copyright (C) 2017-2018 Savoir-faire Linux * + * Copyright (C) 2017-2018 Savoir-faire Linux * * Author: Nicolas Jäger <nicolas.jager@savoirfairelinux.com> * * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com> * + * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com> * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * @@ -42,25 +43,34 @@ namespace database /** * Get id from database for a given uri * @param db + * @param accountId + * @param isAccount * @param uri * @return the id * @note "" if no id */ -std::string getProfileId(Database& db, const std::string& uri); +std::string getProfileId(Database& db, + const std::string& accountId, + const std::string& isAccount, + const std::string& uri=""); -/** + /** * Get id for a profile. If the profile doesn't exist, create it. * @param db * @param contactUri + * @param accountId + * @param isAccount * @param alias * @param avatar * @return the id */ -std::string getOrInsertProfile(Database& db, - const std::string& contactUri, - const std::string& alias = "", - const std::string& avatar = "", - const std::string& type = "INVALID"); + std::string getOrInsertProfile(Database& db, + const std::string& contactUri, + const std::string& accountId, + bool isAccount, + const std::string& type, + const std::string& alias = "", + const std::string& avatar = ""); /** * Get conversations for a given profile. @@ -88,6 +98,13 @@ std::vector<std::string> getPeerParticipantsForConversation(Database& db, */ std::string getAvatarForProfileId(Database& db, const std::string& profileId); +/** + * Check if the profile could be removed + * @param db + * @param profileId + */ +bool profileCouldBeRemoved(Database& db, const std::string& profileId); + /** * @param db * @param profileId @@ -234,9 +251,9 @@ void clearInteractionFromConversation(Database& db, /** * Clear all history stored in the database for the account uri * @param db - * @param accountUri + * @param accountId */ -void clearAllHistoryFor(Database& db, const std::string& accountUri); +void clearAllHistoryFor(Database& db, const std::string& accountId); /** * delete obsolete histori from the database @@ -250,23 +267,24 @@ void deleteObsoleteHistory(Database& db, long int date); * the conversations table and profiles if the profile is not present in conversations. * @param db * @param contactUri + * @param accountId */ -void removeContact(Database& db, const std::string& accountUri, const std::string& contactUri); +void removeContact(Database& db, const std::string& contactUri, const std::string& accountId); /** * Remove from conversations and profiles linked to an account. * @param db - * @param accountUri + * @param accountId */ -void removeAccount(Database& db, const std::string& accountUri); +void removeAccount(Database& db, const std::string& accountId); /** * insert into profiles and conversations. * @param db - * @param accountUri * @param contactUri + * @param accountId */ -void addContact(Database& db, const std::string& accountUri, const std::string& contactUri); +void addContact(Database& db, const std::string& contactUri, const std::string& accountId); /** * count number of 'UNREAD' from 'interactions' table. diff --git a/src/call.cpp b/src/call.cpp index 9463be891a406c3632780778e6671e2a7296153e..87bb67f6307a617887bdc6f841bd1732dfaf12a1 100644 --- a/src/call.cpp +++ b/src/call.cpp @@ -1713,7 +1713,9 @@ void CallPrivate::sendProfile() std::string alias = ""; if (dbfile.good()) { lrc::Database db; - auto accountProfileId = lrc::authority::database::getOrInsertProfile(db, uri); + auto accountProfileId = lrc::authority::database::getOrInsertProfile(db, uri, + m_Account->id().toStdString(), true, + m_Account->protocol() == Account::Protocol::RING ? "RING" : "SIP"); // Retrieve avatar from database photo = lrc::authority::database::getAvatarForProfileId(db, accountProfileId); alias = lrc::authority::database::getAliasForProfileId(db, accountProfileId); diff --git a/src/contactmodel.cpp b/src/contactmodel.cpp index 16e84bbe6a9912622a951ed4abf49cb5b3023eee..58ecb1c5feb37e103f85ac9add5972c89cce7fc0 100644 --- a/src/contactmodel.cpp +++ b/src/contactmodel.cpp @@ -4,6 +4,7 @@ * 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> * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * @@ -262,9 +263,8 @@ ContactModel::addContact(contact::Info contactInfo) return; } - database::getOrInsertProfile(pimpl_->db, - profile.uri, profile.alias, profile.avatar, - to_string(owner.profileInfo.type)); + database::getOrInsertProfile(pimpl_->db, profile.uri, owner.id, false, + to_string(owner.profileInfo.type),profile.alias, profile.avatar); { std::lock_guard<std::mutex> lk(pimpl_->contactsMtx_); @@ -299,13 +299,13 @@ ContactModel::removeContact(const std::string& contactUri, bool banned) return; } pimpl_->contacts.erase(contactUri); - database::removeContact(pimpl_->db, owner.profileInfo.uri, contactUri); + database::removeContact(pimpl_->db, contactUri, owner.id); emitContactRemoved = true; } else if (owner.profileInfo.type == profile::Type::SIP) { // Remove contact from db pimpl_->contacts.erase(contactUri); - database::removeContact(pimpl_->db, owner.profileInfo.uri, contactUri); + database::removeContact(pimpl_->db, contactUri, owner.id); emitContactRemoved = true; } } @@ -334,7 +334,7 @@ ContactModel::getBannedContacts() const const std::string ContactModel::getContactProfileId(const std::string& contactUri) const { - return database::getProfileId(pimpl_->db, contactUri); + return database::getProfileId(pimpl_->db, pimpl_->linked.owner.id, "false", contactUri); } void @@ -488,7 +488,7 @@ ContactModelPimpl::~ContactModelPimpl() bool ContactModelPimpl::fillsWithSIPContacts() { - auto accountProfileId = database::getProfileId(db, linked.owner.profileInfo.uri); + auto accountProfileId = database::getProfileId(db, linked.owner.id, "true", linked.owner.profileInfo.uri); auto conversationsForAccount = database::getConversationsForProfile(db, accountProfileId); for (const auto& c : conversationsForAccount) { auto otherParticipants = database::getPeerParticipantsForConversation(db, accountProfileId, c); @@ -544,9 +544,8 @@ ContactModelPimpl::fillsWithRINGContacts() { contacts.emplace(contactUri.toStdString(), contactInfo); } - database::getOrInsertProfile(db, contactUri.toStdString(), - alias.toStdString(), photo.toStdString(), - profile::to_string(profile::Type::RING)); + database::getOrInsertProfile(db, contactUri.toStdString(), linked.owner.id, false, + profile::to_string(profile::Type::RING), alias.toStdString(), photo.toStdString()); } return true; @@ -642,7 +641,7 @@ ContactModelPimpl::slotContactRemoved(const std::string& accountId, const std::s bannedContacts.erase(it); } } - database::removeContact(db, linked.owner.profileInfo.uri, contactUri); + database::removeContact(db, contactUri, accountId); contacts.erase(contactUri); } } @@ -659,8 +658,8 @@ ContactModelPimpl::slotContactRemoved(const std::string& accountId, const std::s void ContactModelPimpl::addToContacts(const std::string& contactId, const profile::Type& type, bool banned) { - auto profileId = database::getOrInsertProfile(db, contactId, "", "", - to_string(linked.owner.profileInfo.type)); + auto profileId = database::getOrInsertProfile(db, contactId, linked.owner.id, + false, to_string(linked.owner.profileInfo.type),"", ""); auto contactInfo = database::buildContactFromProfileId(db, profileId); contactInfo.isBanned = banned; @@ -755,9 +754,8 @@ ContactModelPimpl::slotIncomingContactRequest(const std::string& accountId, auto contactInfo = contact::Info {profileInfo, "", false, false, false}; contacts.emplace(contactUri, contactInfo); emitTrust = true; - database::getOrInsertProfile(db, contactUri, alias.toStdString(), - photo.toStdString(), - profile::to_string(profile::Type::RING)); + database::getOrInsertProfile(db, contactUri, accountId, false, + profile::to_string(profile::Type::RING), alias.toStdString(), photo.toStdString()); } } diff --git a/src/conversationmodel.cpp b/src/conversationmodel.cpp index 25537b8d17dcfe1371c011841850e8661a61aab9..febedf8d32cdbd60a4c7cc39e30a5686da46363f 100644 --- a/src/conversationmodel.cpp +++ b/src/conversationmodel.cpp @@ -3,6 +3,7 @@ * Author: Nicolas Jäger <nicolas.jager@savoirfairelinux.com> * * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com> * * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> * + * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com> * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * @@ -593,10 +594,10 @@ ConversationModelPimpl::placeCall(const std::string& uid, bool isAudioOnly) *connection = connect(&this->linked, &ConversationModel::conversationReady, [cb, connection](std::string convId) { cb(convId); - QObject::disconnect(*connection); - if (connection) { - delete connection; - } + QObject::disconnect(*connection); + if (connection) { + delete connection; + } }); } else { cb(convId); @@ -728,10 +729,10 @@ ConversationModel::sendMessage(const std::string& uid, const std::string& body) *connection = connect(this, &ConversationModel::conversationReady, [cb, connection](std::string convId) { cb(convId); - QObject::disconnect(*connection); - if (connection) { - delete connection; - } + QObject::disconnect(*connection); + if (connection) { + delete connection; + } }); } else { cb(convId); @@ -908,7 +909,7 @@ ConversationModel::retryInteraction(const std::string& convId, const uint64_t& i void ConversationModel::clearAllHistory() { - database::clearAllHistoryFor(pimpl_->db, owner.profileInfo.uri); + database::clearAllHistoryFor(pimpl_->db, owner.id); for (auto& conversation : pimpl_->conversations) { { @@ -986,7 +987,7 @@ ConversationModelPimpl::ConversationModelPimpl(const ConversationModel& linked, , callbacksHandler(callbacksHandler) , typeFilter(profile::Type::INVALID) , customTypeFilter(profile::Type::INVALID) -, accountProfileId(database::getProfileId(db, linked.owner.profileInfo.uri)) +, accountProfileId(database::getProfileId(db, linked.owner.id, "true", linked.owner.profileInfo.uri)) , behaviorController(behaviorController) { initConversations(); @@ -1147,7 +1148,8 @@ ConversationModelPimpl::initConversations() } for (auto const& c : linked.owner.contactModel->getAllContacts()) { - auto contactProfileId = database::getProfileId(db, c.second.profileInfo.uri); + auto contactProfileId = database::getProfileId(db, linked.owner.id, "false", + c.second.profileInfo.uri); if (contactProfileId.empty()) { // Should not, ContactModel must create profiles before. qDebug() << "ConversationModelPimpl::initConversations(), contact not in db"; @@ -1254,24 +1256,25 @@ ConversationModelPimpl::sendContactRequest(const std::string& contactUri) void ConversationModelPimpl::slotContactAdded(const std::string& uri) { - auto contactProfileId = database::getOrInsertProfile(db, uri); - auto conv = database::getConversationsBetween(db, accountProfileId, contactProfileId); - if (conv.empty()) { - std::string interaction = ""; - try { - auto contact = linked.owner.contactModel->getContact(uri); - interaction = contact.profileInfo.type == profile::Type::PENDING ? + auto type = linked.owner.profileInfo.type; + std::string interaction = ""; + try { + auto contact = linked.owner.contactModel->getContact(uri); + type = contact.profileInfo.type; + interaction = type == profile::Type::PENDING ? QObject::tr("Invitation received").toStdString() : QObject::tr("Contact added").toStdString(); - } catch (...) {} - + } catch (...) {} + auto contactProfileId = database::getOrInsertProfile(db, uri, + linked.owner.id, false, to_string(type)); + auto conv = database::getConversationsBetween(db, accountProfileId, contactProfileId); + if (conv.empty()) { // pass conversation UID through only element conv.emplace_back( database::beginConversationsBetween(db, accountProfileId, contactProfileId, interaction ) ); - } // Add the conversation if not already here if (indexOf(conv[0]) == -1) { @@ -1292,7 +1295,12 @@ ConversationModelPimpl::slotContactAdded(const std::string& uri) void ConversationModelPimpl::slotPendingContactAccepted(const std::string& uri) { - auto contactProfileId = database::getOrInsertProfile(db, uri); + auto type = linked.owner.profileInfo.type; + try { + type = linked.owner.contactModel->getContact(uri).profileInfo.type; + } catch (std::out_of_range& e) {} + auto contactProfileId = database::getOrInsertProfile(db, uri, linked.owner.id, + false, to_string(type)); auto conv = database::getConversationsBetween(db, accountProfileId, contactProfileId); if (conv.empty()) { conv.emplace_back( @@ -1605,7 +1613,12 @@ ConversationModelPimpl::slotIncomingCallMessage(const std::string& callId, const for (const auto& conversation: conversations) { if (conversation.confId == callId) { if (conversation.participants.empty()) continue; - auto authorProfileId = database::getOrInsertProfile(db, from); + auto type = linked.owner.profileInfo.type; + try { + type = linked.owner.contactModel->getContact(from).profileInfo.type; + } catch (std::out_of_range& e) {} + auto authorProfileId = database::getOrInsertProfile(db, from, linked.owner.id, + false, to_string(type)); addIncomingMessage(conversation.participants.front(), body, authorProfileId); } } @@ -1621,8 +1634,14 @@ ConversationModelPimpl::addIncomingMessage(const std::string& from, const std::string& authorProfileId, const uint64_t& timestamp) { - auto contactProfileId = database::getOrInsertProfile(db, from); - auto accountProfileId = database::getProfileId(db, linked.owner.profileInfo.uri); + auto type = linked.owner.profileInfo.type; + try { + type = linked.owner.contactModel->getContact(from).profileInfo.type; + } catch (std::out_of_range& e) {} + auto contactProfileId = database::getOrInsertProfile(db, from, linked.owner.id, + false, to_string(type)); + auto accountProfileId = database::getProfileId(db, linked.owner.id, "true", + linked.owner.profileInfo.uri); auto conv = database::getConversationsBetween(db, accountProfileId, contactProfileId); if (conv.empty()) { conv.emplace_back(database::beginConversationsBetween( @@ -1699,8 +1718,9 @@ ConversationModelPimpl::slotUpdateInteractionStatus(const std::string& accountId auto msgId = std::stoull(interactionId); database::updateInteractionStatus(db, msgId, newStatus); // Update conversations - auto contactProfileId = database::getProfileId(db, to); - auto accountProfileId = database::getProfileId(db, linked.owner.profileInfo.uri); + auto contactProfileId = database::getProfileId(db, linked.owner.id, "false", to); + auto accountProfileId = database::getProfileId(db, linked.owner.id, "true", + linked.owner.profileInfo.uri); auto conv = database::getConversationsBetween(db, accountProfileId, contactProfileId); if (!conv.empty()) { auto conversationIdx = indexOf(conv[0]); @@ -1786,10 +1806,10 @@ ConversationModel::sendFile(const std::string& convUid, *connection = connect(this, &ConversationModel::conversationReady, [cb, connection](std::string convId) { cb(convId); - QObject::disconnect(*connection); - if (connection) { - delete connection; - } + QObject::disconnect(*connection); + if (connection) { + delete connection; + } }); } else { cb(convUidCopy); @@ -1872,9 +1892,14 @@ ConversationModelPimpl::slotTransferStatusCreated(long long dringId, datatransfe const auto* account = AccountModel::instance().getById(info.accountId.c_str()); if (not account) return; - - auto contactProfileId = database::getOrInsertProfile(db, info.peerUri); - auto accountProfileId = database::getProfileId(db, linked.owner.profileInfo.uri); + auto type = linked.owner.profileInfo.type; + try { + type = linked.owner.contactModel->getContact(info.peerUri).profileInfo.type; + } catch (std::out_of_range& e) {} + auto contactProfileId = database::getOrInsertProfile(db, info.peerUri, info.accountId, + false, to_string(type)); + auto accountProfileId = database::getProfileId(db, info.accountId, "true", + linked.owner.profileInfo.uri); // create a new conversation if needed auto conversation_list = database::getConversationsBetween(db, accountProfileId, contactProfileId); diff --git a/src/database.cpp b/src/database.cpp index f5e3f857db13b0ccb988299446bbbfb6dbfbaf22..02bd6b7bcb820ce77748c4e410064f07a59eecb9 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -1,8 +1,9 @@ /**************************************************************************** - * Copyright (C) 2017-2018 Savoir-faire Linux * + * Copyright (C) 2017-2018 Savoir-faire Linux * * Author: Nicolas Jäger <nicolas.jager@savoirfairelinux.com> * * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com> * * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> * + * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com> * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * @@ -45,6 +46,7 @@ #include "account.h" #include "accountmodel.h" #include "private/vcardutils.h" +#include <account_const.h> namespace lrc { @@ -89,6 +91,8 @@ Database::Database() } // NOTE: the migration can take some time. migrateOldFiles(); + } else { + migrateIfNeeded(); } } @@ -102,11 +106,11 @@ Database::createTables() { QSqlQuery query; - auto tableProfiles = "CREATE TABLE profiles (id INTEGER PRIMARY KEY,\ - uri TEXT NOT NULL, \ - alias TEXT, \ - photo TEXT, \ - type TEXT, \ + auto tableProfiles = "CREATE TABLE profiles (id INTEGER PRIMARY KEY, \ + uri TEXT NOT NULL, \ + alias TEXT, \ + photo TEXT, \ + type TEXT, \ status TEXT)"; auto tableConversations = "CREATE TABLE conversations (id INTEGER,\ @@ -125,6 +129,11 @@ Database::createTables() FOREIGN KEY(account_id) REFERENCES profiles(id), \ FOREIGN KEY(author_id) REFERENCES profiles(id), \ FOREIGN KEY(conversation_id) REFERENCES conversations(id))"; + + auto tableProfileAccounts = "CREATE TABLE profiles_accounts (profile_id INTEGER NOT NULL, \ + account_id TEXT NOT NULL, \ + is_account TEXT, \ + FOREIGN KEY(profile_id) REFERENCES profiles(id))"; // add profiles table if (not db_.tables().contains("profiles", Qt::CaseInsensitive) and not query.exec(tableProfiles)) { @@ -143,9 +152,57 @@ Database::createTables() throw QueryError(query); } + // add profiles accounts table + if (not db_.tables().contains("profiles_accounts", Qt::CaseInsensitive) + and not query.exec(tableProfileAccounts)) { + throw QueryError(query); + } + storeVersion(VERSION); } +void +Database::migrateIfNeeded() +{ + try { + std::string currentVersion = getVersion(); + if (currentVersion == VERSION) { + return; + } + QSqlDatabase::database().transaction(); + migrateFromVersion(currentVersion); + storeVersion(VERSION); + QSqlDatabase::database().commit(); + } catch (QueryError& e) { + QSqlDatabase::database().rollback(); + throw std::runtime_error("Could not correctly migrate the database"); + } +} + +void +Database::migrateFromVersion(const std::string& currentVersion) +{ + if (currentVersion == "1") { + migrateSchemaFromVersion1(); + } +} + +void +Database::migrateSchemaFromVersion1() +{ + QSqlQuery query; + auto tableProfileAccounts = "CREATE TABLE profiles_accounts (profile_id INTEGER NOT NULL, \ + account_id TEXT NOT NULL, \ + is_account TEXT, \ + FOREIGN KEY(profile_id) REFERENCES profiles(id))"; + // add profiles accounts table + if (not db_.tables().contains("profiles_accounts", Qt::CaseInsensitive) + and not query.exec(tableProfileAccounts)) { + throw QueryError(query); + } + linkRingProfilesWithAccounts(false); +} + void Database::storeVersion(const std::string& version) { @@ -157,6 +214,17 @@ Database::storeVersion(const std::string& version) throw QueryError(query); } +std::string +Database::getVersion() +{ + QSqlQuery query; + auto getVersionQuery = std::string("pragma user_version"); + if (not query.exec(getVersionQuery.c_str())) + throw QueryError(query); + query.first(); + return query.value(0).toString().toStdString(); +} + int Database::insertInto(const std::string& table, // "tests" const std::map<std::string, std::string>& bindCol, // {{":id", "id"}, {":forename", "colforname"}, {":name", "colname"}} @@ -387,6 +455,7 @@ Database::migrateOldFiles() migrateLocalProfiles(); migratePeerProfiles(); migrateTextHistory(); + linkRingProfilesWithAccounts(true); // NOTE we don't remove old files for now. } @@ -418,6 +487,7 @@ Database::migrateLocalProfiles() if (!account) continue; auto type = account->protocol() == Account::Protocol::RING ? "RING" : "SIP"; auto uri = account->username(); + auto accountId = account->id(); // Remove the ring: from the username because account uri is stored without "ring:" in the database if (uri.startsWith("ring:")) { uri = uri.mid(std::string("ring:").size()); @@ -430,6 +500,20 @@ Database::migrateLocalProfiles() {{":uri", uri.toStdString()}, {":alias", alias.toStdString()}, {":photo", avatar.toStdString()}, {":type", type}, {":status", "TRUSTED"}}); + auto profileIds = select("id", "profiles","uri=:uri", + {{":uri", uri.toStdString()}}).payloads; + if (!profileIds.empty() && select("profile_id", "profiles_accounts", + "account_id=:account_id AND is_account=:is_account", + {{":account_id", accountId.toStdString()}, + {":is_account", "true"}}).payloads.empty()) { + insertInto("profiles_accounts", + {{":profile_id", "profile_id"}, + {":account_id", "account_id"}, + {":is_account", "is_account"}}, + {{":profile_id", profileIds[0]}, + {":account_id", accountId.toStdString()}, + {":is_account", "true"}}); + } } } } @@ -537,6 +621,25 @@ Database::migrateTextHistory() qDebug() << "Can't find profile for URI: " << peersObject["uri"].toString() << ". Ignore this file."; } else { auto contactId = contactIds[0]; + // link profile id to account id + auto profiles = select("profile_id", "profiles_accounts", + "profile_id=:profile_id AND \ + account_id=:account_id AND \ + is_account=:is_account", + {{":profile_id", contactId}, + {":account_id", peersObject["accountId"].toString().toStdString()}, + {":is_account", "false"}}) + .payloads; + + if (profiles.empty()) { + insertInto("profiles_accounts", + {{":profile_id", "profile_id"}, + {":account_id", "account_id"}, + {":is_account", "is_account"}}, + {{":profile_id", contactId}, + {":account_id", peersObject["accountId"].toString().toStdString()}, + {":is_account", "false"}}); + } auto accountId = accountIds[0]; auto newConversationsId = select("IFNULL(MAX(id), 0) + 1", "conversations", @@ -585,4 +688,104 @@ Database::migrateTextHistory() } } +void +Database::updateProfileAccountForContact(const std::string& contactURI, + const std::string& accountId) +{ + auto profileIds = select("id", "profiles","uri=:uri", + {{":uri", contactURI}}) + .payloads; + if (profileIds.empty()) { + return; + } + auto rows = select("profile_id", "profiles_accounts", + "account_id=:account_id AND is_account=:is_account", {{":account_id", accountId}, + {":is_account", "false"}}).payloads; + if (std::find(rows.begin(), rows.end(), profileIds[0]) == rows.end()) { + insertInto("profiles_accounts", + {{":profile_id", "profile_id"}, {":account_id", "account_id"}, + {":is_account", "is_account"}}, + {{":profile_id", profileIds[0]}, {":account_id", accountId}, + {":is_account", "false"}}); + } +} + +void +Database::linkRingProfilesWithAccounts(bool contactsOnly) +{ + const QStringList accountIds = + ConfigurationManager::instance().getAccountList(); + for (auto accountId : accountIds) { + MapStringString account = ConfigurationManager::instance(). + getAccountDetails(accountId.toStdString().c_str()); + auto accountURI = account[DRing::Account::ConfProperties::USERNAME].contains("ring:") ? + account[DRing::Account::ConfProperties::USERNAME] + .toStdString().substr(std::string("ring:").size()) : + account[DRing::Account::ConfProperties::USERNAME].toStdString(); + auto profileIds = select("id", "profiles","uri=:uri", {{":uri", accountURI}}).payloads; + if(profileIds.empty()) { + continue; + } + if(!contactsOnly) { + //if is_account is true we should have only one profile id for account id + if (select("profile_id", "profiles_accounts", + "account_id=:account_id AND is_account=:is_account", + {{":account_id", accountId.toStdString()}, + {":is_account", "true"}}).payloads.empty()) { + insertInto("profiles_accounts", + {{":profile_id", "profile_id"}, {":account_id", "account_id"}, + {":is_account", "is_account"}}, + {{":profile_id", profileIds[0]}, {":account_id", accountId.toStdString()}, + {":is_account", "true"}}); + } + } + + if (account[DRing::Account::ConfProperties::TYPE] == DRing::Account::ProtocolNames::RING) { + + // update RING contacts + const VectorMapStringString& contacts_vector = ConfigurationManager::instance() + .getContacts(accountId.toStdString().c_str()); + //update contacts profiles + for (auto contact_info : contacts_vector) { + auto contactURI = contact_info["id"]; + updateProfileAccountForContact(contactURI.toStdString(), accountId.toStdString()); + } + //update pending contacts profiles + const VectorMapStringString& pending_tr = ConfigurationManager::instance() + .getTrustRequests(accountId.toStdString().c_str()); + for (auto tr_info : pending_tr) { + auto contactURI = tr_info[DRing::Account::TrustRequest::FROM]; + updateProfileAccountForContact(contactURI.toStdString(), accountId.toStdString()); + } + } else if (account[DRing::Account::ConfProperties::TYPE] == DRing::Account::ProtocolNames::SIP) { + // update SIP contacts + auto conversations = select("id", "conversations", + "participant_id=:participant_id", + {{":participant_id", profileIds[0]}}).payloads; + for (const auto& c : conversations) { + auto otherParticipants = select("participant_id","conversations", + "id=:id AND participant_id!=:participant_id", + {{":id", c}, {":participant_id", profileIds[0]}}) + .payloads; + for (const auto& participant: otherParticipants) { + auto rows = select("profile_id", "profiles_accounts", + "profile_id=:profile_id AND \ + account_id=:account_id AND \ + is_account=:is_account", + {{":profile_id", participant}, + {":account_id", accountId.toStdString()}, + {":is_account", "false"}}).payloads; + if (rows.empty()) { + insertInto("profiles_accounts", + {{":profile_id", "profile_id"}, {":account_id", "account_id"}, + {":is_account", "is_account"}}, + {{":profile_id", participant}, {":account_id", accountId.toStdString()}, + {":is_account", "false"}}); + } + } + } + } + } +} + } // namespace lrc diff --git a/src/database.h b/src/database.h index 6d3be139605a33108c8e219a6631380eb4d308da..1d399ce587d79fded26569b338dbee23cb7e99c9 100644 --- a/src/database.h +++ b/src/database.h @@ -1,8 +1,9 @@ /**************************************************************************** - * Copyright (C) 2017-2018 Savoir-faire Linux * + * Copyright (C) 2017-2018 Savoir-faire Linux * * Author: Nicolas Jäger <nicolas.jager@savoirfairelinux.com> * * Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com> * * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> * + * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com> * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * @@ -33,7 +34,7 @@ namespace lrc { -static constexpr auto VERSION = "1"; +static constexpr auto VERSION = "1.1"; static constexpr auto NAME = "ring.db"; /** @@ -235,6 +236,13 @@ private: void migrateLocalProfiles(); void migratePeerProfiles(); void migrateTextHistory(); + void linkRingProfilesWithAccounts(bool contactsOnly); + void migrateIfNeeded(); + std::string getVersion(); + void migrateFromVersion(const std::string& version); + void migrateSchemaFromVersion1(); + void updateProfileAccountForContact(const std::string& contactURI, + const std::string& accountID); QSqlDatabase db_; }; diff --git a/src/newaccountmodel.cpp b/src/newaccountmodel.cpp index a4d2222ebd59ad7b0ecd46889230d50870899748..f4cfdbc3dcc2d9406329b06ff90a6f55450a9077 100644 --- a/src/newaccountmodel.cpp +++ b/src/newaccountmodel.cpp @@ -243,7 +243,9 @@ NewAccountModel::setAlias(const std::string& accountId, const std::string& alias throw std::out_of_range("NewAccountModel::setAlias, can't find " + accountId); } accountInfo->second.profileInfo.alias = alias; - auto accountProfileId = authority::database::getOrInsertProfile(pimpl_->database, accountInfo->second.profileInfo.uri); + auto accountProfileId = authority::database::getOrInsertProfile(pimpl_->database, + accountInfo->second.profileInfo.uri, accountId, true, + to_string(accountInfo->second.profileInfo.type)); if (!accountProfileId.empty()) { authority::database::setAliasForProfileId(pimpl_->database, accountProfileId, alias); } @@ -258,7 +260,9 @@ NewAccountModel::setAvatar(const std::string& accountId, const std::string& avat throw std::out_of_range("NewAccountModel::setAvatar, can't find " + accountId); } accountInfo->second.profileInfo.avatar = avatar; - auto accountProfileId = authority::database::getOrInsertProfile(pimpl_->database, accountInfo->second.profileInfo.uri); + auto accountProfileId = authority::database::getOrInsertProfile(pimpl_->database, + accountInfo->second.profileInfo.uri, accountId, true, + to_string(accountInfo->second.profileInfo.type)); if (!accountProfileId.empty()) { authority::database::setAvatarForProfileId(pimpl_->database, accountProfileId, avatar); } @@ -543,9 +547,11 @@ NewAccountModelPimpl::addToAccounts(const std::string& accountId) if (accountType == DRing::Account::ProtocolNames::SIP || !newAcc.profileInfo.uri.empty()) { auto accountProfileId = authority::database::getOrInsertProfile(database, newAcc.profileInfo.uri, + accountId, + true, + accountType, newAcc.profileInfo.alias, - "", - accountType); + ""); // Retrieve avatar from database newAcc.profileInfo.avatar = authority::database::getAvatarForProfileId(database, accountProfileId); @@ -572,7 +578,11 @@ NewAccountModelPimpl::removeFromAccounts(const std::string& accountId) { /* Update db before waiting for the client to stop using the structs is fine as long as we don't free anything */ - authority::database::removeAccount(database, accounts[accountId].profileInfo.uri); + auto accountInfo = accounts.find(accountId); + if (accountInfo == accounts.end()) { + return; + } + authority::database::removeAccount(database, accountId); /* Inform client about account removal. Do *not* free account structures before we are sure that the client stopped using it, otherwise we might