diff --git a/src/api/contactmodel.h b/src/api/contactmodel.h index 6f54378301845beaf1856a0cced686ba35a9189b..bec615e50a66c6ca49a1e9c9acde030d896b55ae 100644 --- a/src/api/contactmodel.h +++ b/src/api/contactmodel.h @@ -75,6 +75,7 @@ public: * @throws out_of_range exception if can't find the contact */ const contact::Info getContact(const QString& contactUri) const; + ContactInfoMap getSearchResults() const; /** * get list of banned contacts. * @return list of banned contacts uris as string diff --git a/src/api/conversationmodel.h b/src/api/conversationmodel.h index 0b36314a3f05d5fa8081770a6206429300f4ce0d..307b7444358e51567d15a523c6962cbdb0e995e5 100644 --- a/src/api/conversationmodel.h +++ b/src/api/conversationmodel.h @@ -122,6 +122,26 @@ public: * @return a copy of the conversation */ Q_INVOKABLE conversation::Info filteredConversation(unsigned int row) const; + + /** + * Get the search results + * @return a searchResult + */ + const ConversationQueue& getAllSearchResults() const; + + /** + * Get the conversation at row in the search results + * @param row + * @return a copy of the conversation + */ + conversation::Info searchResultForRow(unsigned int row) const; + + /** + * Update the searchResults + * @param new status + */ + void updateSearchStatus(const QString& status) const; + /** * Emit a filterChanged signal to force the client to refresh the filter. For instance * this is required when a contact was banned or un-banned. @@ -324,6 +344,16 @@ Q_SIGNALS: void displayedInteractionChanged(const QString& uid, const QString& participantURI, const uint64_t previousUid, const uint64_t newdUid) const; + /** + * Emitted when search status changed + * @param status + */ + void searchStatusChanged(const QString& status) const; + /** + * Emitted when search result has been updated + */ + void searchResultUpdated() const; + private: std::unique_ptr<ConversationModelPimpl> pimpl_; }; diff --git a/src/contactmodel.cpp b/src/contactmodel.cpp index 707b30c11945cf5df8d4d5b35e24175da3fddff3..3728a040bdba165c0a4bd6516e08f474fe04c4d4 100644 --- a/src/contactmodel.cpp +++ b/src/contactmodel.cpp @@ -41,6 +41,7 @@ #include "callbackshandler.h" #include "uri.h" #include "vcard.h" +#include "typedefs.h" #include "authority/daemon.h" #include "authority/storagehelper.h" @@ -91,10 +92,12 @@ public: */ 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, const QString& uri); + void updateTemporaryMessage(const QString& mes); /** * Check if equivalent uri exist in contact @@ -109,7 +112,9 @@ public: // Containers ContactModel::ContactInfoMap contacts; + ContactModel::ContactInfoMap searchResult; QList<QString> bannedContacts; + QString query; std::mutex contactsMtx_; std::mutex bannedContactsMtx_; @@ -189,6 +194,15 @@ public Q_SLOTS: * @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; @@ -348,10 +362,12 @@ const contact::Info ContactModel::getContact(const QString& contactUri) const { std::lock_guard<std::mutex> lk(pimpl_->contactsMtx_); - if (!pimpl_->contacts.contains(contactUri)) { - throw std::out_of_range("Contact out of range"); + if (pimpl_->contacts.contains(contactUri)) { + return pimpl_->contacts.value(contactUri); + } else if (pimpl_->searchResult.contains(contactUri)) { + return pimpl_->searchResult.value(contactUri); } - return pimpl_->contacts.value(contactUri); + throw std::out_of_range("Contact out of range"); } const QList<QString>& @@ -360,13 +376,20 @@ 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_->contacts[""] = {}; + pimpl_->searchResult.clear(); auto uri = URI(query); + pimpl_->query = query; auto uriScheme = uri.schemeType(); if (static_cast<int>(uriScheme) > 2 && owner.profileInfo.type == profile::Type::SIP) { @@ -379,20 +402,20 @@ ContactModel::searchContact(const QString& query) 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) { - pimpl_->searchRingContact(uri); + bool isJamsAccount = !owner.confProperties.managerUri.isEmpty(); + if (isJamsAccount) + pimpl_->jamsSearch(uri); + else + pimpl_->searchRingContact(uri); } else { - pimpl_->updateTemporaryMessage(tr("Bad URI scheme"), uri.full()); + pimpl_->updateTemporaryMessage(tr("Bad URI scheme")); } } void -ContactModelPimpl::updateTemporaryMessage(const QString& mes, const QString& uri) +ContactModelPimpl::updateTemporaryMessage(const QString& mes) { - std::lock_guard<std::mutex> lk(contactsMtx_); - auto& temporaryContact = contacts[""]; - temporaryContact.profileInfo.alias = mes; - temporaryContact.profileInfo.type = profile::Type::TEMPORARY; - temporaryContact.registeredName = uri; + linked.owner.conversationModel->updateSearchStatus(mes); } void @@ -402,6 +425,7 @@ ContactModelPimpl::searchRingContact(const URI& query) if (query.isEmpty()) { // This will remove the temporary item emit linked.modelUpdated(uriID); + updateTemporaryMessage(""); return; } @@ -412,17 +436,30 @@ ContactModelPimpl::searchRingContact(const URI& query) return; } } - auto& temporaryContact = contacts[""]; + 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…"), uriID); + updateTemporaryMessage(tr("Searching…")); // Default searching ConfigurationManager::instance().lookupName(linked.owner.id, "", uriID); } - emit linked.modelUpdated(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 @@ -432,10 +469,11 @@ ContactModelPimpl::searchSipContact(const URI& query) if (query.isEmpty()) { // This will remove the temporary item emit linked.modelUpdated(uriID); + updateTemporaryMessage(""); return; } - auto& temporaryContact = contacts[""]; + auto& temporaryContact = searchResult[query]; { std::lock_guard<std::mutex> lk(contactsMtx_); if (contacts.find(uriID) == contacts.end()) { @@ -469,6 +507,7 @@ ContactModelPimpl::ContactModelPimpl(const ContactModel& linked, , behaviorController(behaviorController) , callbacksHandler(callbacksHandler) { + qRegisterMetaType<VectorMapStringString>("VectorMapStringString"); // Init contacts map if (linked.owner.profileInfo.type == profile::Type::SIP) fillWithSIPContacts(); @@ -494,6 +533,8 @@ ContactModelPimpl::ContactModelPimpl(const ContactModel& linked, this, &ContactModelPimpl::slotNewAccountTransfer); connect(&ConfigurationManager::instance(), &ConfigurationManagerInterface::profileReceived, this, &ContactModelPimpl::slotProfileReceived); + connect(&ConfigurationManager::instance(), &ConfigurationManagerInterface::userSearchEnded, + this, &ContactModelPimpl::slotUserSearchEnded); } ContactModelPimpl::~ContactModelPimpl() @@ -516,6 +557,8 @@ ContactModelPimpl::~ContactModelPimpl() this, &ContactModelPimpl::slotNewAccountTransfer); disconnect(&ConfigurationManager::instance(), &ConfigurationManagerInterface::profileReceived, this, &ContactModelPimpl::slotProfileReceived); + disconnect(&ConfigurationManager::instance(), &ConfigurationManagerInterface::userSearchEnded, + this, &ContactModelPimpl::slotUserSearchEnded); } bool @@ -638,6 +681,14 @@ ContactModelPimpl::slotContactAdded(const QString& accountId, const QString& con 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; @@ -660,7 +711,6 @@ ContactModelPimpl::slotContactAdded(const QString& accountId, const QString& con addToContacts(contactUri, linked.owner.profileInfo.type, "", false); } } - if (isBanned) { // Update the smartlist linked.owner.conversationModel->refreshFilter(); @@ -760,43 +810,48 @@ ContactModelPimpl::slotRegisteredNameFound(const QString& accountId, { if (accountId != linked.owner.id) return; - auto& temporaryContact = contacts[""]; 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; - temporaryContact = {}; + searchResult.clear(); } else { - if (temporaryContact.registeredName != uri && temporaryContact.registeredName != registeredName) { + if ((query != uri && query != registeredName) || query.isEmpty()) { // we are notified that a previous lookup ended return; } - - // update temporary item + auto& temporaryContact = searchResult[uri]; lrc::api::profile::Info profileInfo = {uri, "", "", profile::Type::TEMPORARY}; temporaryContact = {profileInfo, registeredName, false, false}; } } else { - if (temporaryContact.registeredName != uri && temporaryContact.registeredName != registeredName) { + { + std::lock_guard<std::mutex> lk(contactsMtx_); + if (contacts.find(uri) != contacts.end()) { + // it was lookup for contact + return; + } + } + if ((query != uri && query != registeredName) || query.isEmpty()) { // we are notified that a previous lookup ended return; } - switch (status) { case 1 /* INVALID */: - updateTemporaryMessage(tr("Invalid ID"), registeredName); + updateTemporaryMessage(tr("Invalid ID")); break; case 2 /* NOT FOUND */: - updateTemporaryMessage(tr("Registered name not found"), registeredName); + updateTemporaryMessage(tr("Registered name not found")); break; case 3 /* ERROR */: - updateTemporaryMessage(tr("Couldn't lookup…"), registeredName); + updateTemporaryMessage(tr("Couldn't lookup…")); break; } + return; } - + updateTemporaryMessage(""); emit linked.modelUpdated(uri); } @@ -1009,6 +1064,38 @@ ContactModelPimpl::slotProfileReceived(const QString& accountId, const QString& linked.owner.contactModel->addContact(contactInfo); } +void +ContactModelPimpl::slotUserSearchEnded(const QString& accountId, int status, const QString& query, const VectorMapStringString& result) +{ + if (query != 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"); + return; + default: + return; + } + updateTemporaryMessage(""); + for (auto& resultInfo : result) { + if (contacts.find(resultInfo.value("jamiId")) != contacts.end()) { + continue; + } + profile::Info profileInfo; + profileInfo.uri = resultInfo.value("jamiId"); + 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 diff --git a/src/conversationmodel.cpp b/src/conversationmodel.cpp index fcb1135a261988121c7faee401dbf5ce2ccd8dfb..19b7c8e6d651a7843b41e86c77d1bc8015bee8ff 100644 --- a/src/conversationmodel.cpp +++ b/src/conversationmodel.cpp @@ -76,12 +76,27 @@ public: * @return an int. */ int indexOf(const QString& uid) const; + /** + * return a reference to a conversation with given uid. + * @param conversation uid. + * @param searchResultIncluded if need to search in contacts and userSearch. + * @return a reference to a conversation with given uid. + */ + conversation::Info& getConversation(const QString& uid, const bool searchResultIncluded = false); + /** * return a conversation index from conversations or -1 if no index is found. * @param uri of the contact to search. * @return an int. */ int indexOfContact(const QString& uri) const; + /** + * return a reference to a conversation with participant. + * @param participant uri. + * @param searchResultIncluded if need to search in contacts and userSearch. + * @return a reference to a conversation with participant. + */ + conversation::Info& getConversationForContact(const QString& uid, const bool searchResultIncluded = false); /** * Initialize conversations_ and filteredConversations_ */ @@ -169,6 +184,7 @@ public: ConversationModel::ConversationQueue conversations; ///< non-filtered conversations ConversationModel::ConversationQueue filteredConversations; + ConversationModel::ConversationQueue searchResults; ConversationModel::ConversationQueue customFilteredConversations; QString filter; profile::Type typeFilter; @@ -504,6 +520,12 @@ ConversationModel::getConferenceableConversations(const QString& convId, const Q return result; } +const ConversationModel::ConversationQueue& +ConversationModel::getAllSearchResults() const +{ + return pimpl_->searchResults; +} + const ConversationModel::ConversationQueue& ConversationModel::getFilteredConversations(const profile::Type& filter, bool forceUpdate, const bool includeBanned) const { @@ -529,13 +551,10 @@ ConversationModel::getFilteredConversations(const profile::Type& filter, bool fo conversation::Info ConversationModel::getConversationForUID(const QString& uid) const { - auto conversationIdx = pimpl_->indexOf(uid); - if (conversationIdx == -1 || !owner.enabled) { - return {}; - } try { - return pimpl_->conversations.at(conversationIdx); - } catch (...) { + return pimpl_->getConversation(uid, true); + } + catch (const std::out_of_range& e) { return {}; } } @@ -552,87 +571,92 @@ ConversationModel::filteredConversation(const unsigned int row) const return conversationInfo; } +conversation::Info +ConversationModel::searchResultForRow(const unsigned int row) const +{ + const auto& results = pimpl_->searchResults; + if (row >= results.size()) + return conversation::Info(); + + return results.at(row); +} + void ConversationModel::makePermanent(const QString& uid) { - auto conversationIdx = pimpl_->indexOf(uid); - if (conversationIdx == -1 || !owner.enabled) - return; + try { + auto& conversation = pimpl_->getConversation(uid, true); - auto& conversation = pimpl_->conversations.at(conversationIdx); - if (conversation.participants.empty()) { - // Should not - qDebug() << "ConversationModel::addConversation can't add a conversation with no participant"; - return; - } + if (conversation.participants.empty()) { + // Should not + qDebug() << "ConversationModel::addConversation can't add a conversation with no participant"; + return; + } - // Send contact request if non used - pimpl_->sendContactRequest(conversation.participants.front()); + // Send contact request if non used + pimpl_->sendContactRequest(conversation.participants.front()); + } catch (const std::out_of_range& e) { + qDebug() << "make permanent failed. conversation not found"; + } } void ConversationModel::selectConversation(const QString& uid) const { - // Get conversation - auto conversationIdx = pimpl_->indexOf(uid); - - if (conversationIdx == -1) - return; - - if (uid.isEmpty() && owner.contactModel->getContact("").profileInfo.uri.isEmpty()) { - // if we select the temporary contact, check if its a valid contact. - return; - } + try { + auto& conversation = pimpl_->getConversation(uid, true); - auto& conversation = pimpl_->conversations.at(conversationIdx); - bool callEnded = true; - if (!conversation.callId.isEmpty()) { - try { - auto call = owner.callModel->getCall(conversation.callId); - callEnded = call.status == call::Status::ENDED; - } catch (...) {} - } + bool callEnded = true; + if (!conversation.callId.isEmpty()) { + try { + auto call = owner.callModel->getCall(conversation.callId); + callEnded = call.status == call::Status::ENDED; + } catch (...) {} + } - if (not callEnded and not conversation.confId.isEmpty()) { - emit pimpl_->behaviorController.showCallView(owner.id, conversation); - } else if (callEnded) { - emit pimpl_->behaviorController.showChatView(owner.id, conversation); - } else { - try { - auto call = owner.callModel->getCall(conversation.callId); - switch (call.status) { - case call::Status::INCOMING_RINGING: - case call::Status::OUTGOING_RINGING: - case call::Status::CONNECTING: - case call::Status::SEARCHING: - // We are currently in a call - emit pimpl_->behaviorController.showIncomingCallView(owner.id, conversation); - break; - case call::Status::PAUSED: - case call::Status::CONNECTED: - case call::Status::IN_PROGRESS: - // We are currently receiving a call - emit pimpl_->behaviorController.showCallView(owner.id, conversation); - break; - case call::Status::PEER_BUSY: - emit pimpl_->behaviorController.showLeaveMessageView(owner.id, conversation); - break; - case call::Status::TIMEOUT: - case call::Status::TERMINATING: - case call::Status::INVALID: - case call::Status::INACTIVE: - // call just ended + if (not callEnded and not conversation.confId.isEmpty()) { + emit pimpl_->behaviorController.showCallView(owner.id, conversation); + } else if (callEnded) { + emit pimpl_->behaviorController.showChatView(owner.id, conversation); + } else { + try { + auto call = owner.callModel->getCall(conversation.callId); + switch (call.status) { + case call::Status::INCOMING_RINGING: + case call::Status::OUTGOING_RINGING: + case call::Status::CONNECTING: + case call::Status::SEARCHING: + // We are currently in a call + emit pimpl_->behaviorController.showIncomingCallView(owner.id, conversation); + break; + case call::Status::PAUSED: + case call::Status::CONNECTED: + case call::Status::IN_PROGRESS: + // We are currently receiving a call + emit pimpl_->behaviorController.showCallView(owner.id, conversation); + break; + case call::Status::PEER_BUSY: + emit pimpl_->behaviorController.showLeaveMessageView(owner.id, conversation); + break; + case call::Status::TIMEOUT: + case call::Status::TERMINATING: + case call::Status::INVALID: + case call::Status::INACTIVE: + // call just ended + emit pimpl_->behaviorController.showChatView(owner.id, conversation); + break; + case call::Status::ENDED: + default: // ENDED + // nothing to do + break; + } + } catch (const std::out_of_range&) { + // Should not happen emit pimpl_->behaviorController.showChatView(owner.id, conversation); - break; - case call::Status::ENDED: - default: // ENDED - // nothing to do - break; } - } catch (const std::out_of_range&) { - // Should not happen - emit pimpl_->behaviorController.showChatView(owner.id, conversation); } + } catch (const std::out_of_range& e) { + qDebug() << "select conversation failed. conversation not exists"; } } @@ -672,66 +696,62 @@ ConversationModel::deleteObsoleteHistory(int days) void ConversationModelPimpl::placeCall(const QString& uid, bool isAudioOnly) { - auto conversationIdx = indexOf(uid); - - if (conversationIdx == -1 || !linked.owner.enabled) - return; - - auto& conversation = conversations.at(conversationIdx); - if (conversation.participants.empty()) { - // Should not - qDebug() << "ConversationModel::placeCall can't call a conversation without participant"; - return; - } + try { + auto& conversation = getConversation(uid, true); + if (conversation.participants.empty()) { + // Should not + qDebug() << "ConversationModel::placeCall can't call a conversation without participant"; + return; + } - // Disallow multiple call - if (!conversation.callId.isEmpty()) { - try { - auto call = linked.owner.callModel->getCall(conversation.callId); - switch (call.status) { - case call::Status::INCOMING_RINGING: - case call::Status::OUTGOING_RINGING: - case call::Status::CONNECTING: - case call::Status::SEARCHING: - case call::Status::PAUSED: - case call::Status::IN_PROGRESS: - case call::Status::CONNECTED: - return; - case call::Status::INVALID: - case call::Status::INACTIVE: - case call::Status::ENDED: - case call::Status::PEER_BUSY: - case call::Status::TIMEOUT: - case call::Status::TERMINATING: - default: - break; + // Disallow multiple call + if (!conversation.callId.isEmpty()) { + try { + auto call = linked.owner.callModel->getCall(conversation.callId); + switch (call.status) { + case call::Status::INCOMING_RINGING: + case call::Status::OUTGOING_RINGING: + case call::Status::CONNECTING: + case call::Status::SEARCHING: + case call::Status::PAUSED: + case call::Status::IN_PROGRESS: + case call::Status::CONNECTED: + return; + case call::Status::INVALID: + case call::Status::INACTIVE: + case call::Status::ENDED: + case call::Status::PEER_BUSY: + case call::Status::TIMEOUT: + case call::Status::TERMINATING: + default: + break; + } + } catch (const std::out_of_range&) { } - } catch (const std::out_of_range&) { } - } - auto convId = uid; + auto convId = uid; - auto participant = conversation.participants.front(); - bool isTemporary = participant.isEmpty(); - auto contactInfo = linked.owner.contactModel->getContact(participant); - auto uri = contactInfo.profileInfo.uri; + auto participant = conversation.participants.front(); + bool isTemporary = participant == convId; + auto contactInfo = linked.owner.contactModel->getContact(participant); + auto uri = contactInfo.profileInfo.uri; - if (uri.isEmpty()) - return; // Incorrect item + if (uri.isEmpty()) + return; // Incorrect item - // Don't call banned contact - if (contactInfo.isBanned) { - qDebug() << "ContactModel::placeCall: denied, contact is banned"; - return; - } + // Don't call banned contact + if (contactInfo.isBanned) { + qDebug() << "ContactModel::placeCall: denied, contact is banned"; + return; + } - if (linked.owner.profileInfo.type != profile::Type::SIP) { - uri = "ring:" + uri; // Add the ring: before or it will fail. - } + if (linked.owner.profileInfo.type != profile::Type::SIP) { + uri = "ring:" + uri; // Add the ring: before or it will fail. + } - auto cb = std::function<void(QString)>( - [this, isTemporary, uri, isAudioOnly, &conversation](QString convId) { + auto cb = std::function<void(QString)>( + [this, isTemporary, uri, isAudioOnly, &conversation](QString convId) { int contactIndex; if (isTemporary && (contactIndex = indexOfContact(convId)) < 0) { qDebug() << "Can't place call: Other participant is not a contact (removed while placing call ?)"; @@ -751,22 +771,25 @@ ConversationModelPimpl::placeCall(const QString& uid, bool isAudioOnly) emit behaviorController.showIncomingCallView(linked.owner.id, newConv); }); - if (isTemporary) { - QMetaObject::Connection* const connection = new QMetaObject::Connection; - *connection = connect(&this->linked, &ConversationModel::conversationReady, - [cb, connection](QString convId) { + if (isTemporary) { + QMetaObject::Connection* const connection = new QMetaObject::Connection; + *connection = connect(&this->linked, &ConversationModel::conversationReady, + [cb, connection](QString convId) { cb(convId); QObject::disconnect(*connection); if (connection) { delete connection; } }); - } + } - sendContactRequest(participant); + sendContactRequest(participant); - if (!isTemporary) { - cb(convId); + if (!isTemporary) { + cb(convId); + } + } catch (const std::out_of_range& e) { + qDebug() << "could not place call to not existing conversation"; } } @@ -785,31 +808,28 @@ ConversationModel::placeCall(const QString& uid) void ConversationModel::sendMessage(const QString& uid, const QString& body) { - // FIXME potential race condition between index check and at() call - auto conversationIdx = pimpl_->indexOf(uid); - if (conversationIdx == -1 || !owner.enabled) - return; - - auto& conversation = pimpl_->conversations.at(conversationIdx); + try { + auto& conversation = pimpl_->getConversation(uid, true); - if (conversation.participants.empty()) { - // Should not - qDebug() << "ConversationModel::sendMessage can't send a interaction to a conversation with no participant"; - return; - } + if (conversation.participants.empty()) { + // Should not + qDebug() << "ConversationModel::sendMessage can't send a interaction to a conversation with no participant"; + return; + } - auto convId = uid; - bool isTemporary = conversation.participants.front() == ""; + auto convId = uid; + //for temporary contact conversation id is the same as participant uri + bool isTemporary = conversation.participants.front() == uid; - /* Make a copy of participants list: if current conversation is temporary, - it might me destroyed while we are reading it */ - const auto participants = conversation.participants; + /* Make a copy of participants list: if current conversation is temporary, + it might me destroyed while we are reading it */ + const auto participants = conversation.participants; - auto cb = std::function<void(QString)>( - [this, isTemporary, body, &conversation](QString convId) { + auto cb = std::function<void(QString)>( + [this, isTemporary, body, &conversation](QString convId) { /* Now we should be able to retrieve the final conversation, in case the previous - one was temporary */ - // FIXME potential race condition between index check and at() call + one was temporary */ + // FIXME potential race condition between index check and at() call int contactIndex; if (isTemporary && (contactIndex = pimpl_->indexOfContact(convId)) < 0) { qDebug() << "Can't send message: Other participant is not a contact"; @@ -882,33 +902,36 @@ ConversationModel::sendMessage(const QString& uid, const QString& body) emit modelSorted(); }); - if (isTemporary) { - QMetaObject::Connection* const connection = new QMetaObject::Connection; - *connection = connect(this, &ConversationModel::conversationReady, - [cb, connection](QString convId) { + if (isTemporary) { + QMetaObject::Connection* const connection = new QMetaObject::Connection; + *connection = connect(this, &ConversationModel::conversationReady, + [cb, connection](QString convId) { cb(convId); QObject::disconnect(*connection); if (connection) { delete connection; } }); - } + } - /* Check participants list, send contact request if needed. - NOTE: conferences are not implemented yet, so we have only one participant */ - for (const auto& participant: participants) { - auto contactInfo = owner.contactModel->getContact(participant); + /* Check participants list, send contact request if needed. + NOTE: conferences are not implemented yet, so we have only one participant */ + for (const auto& participant: participants) { + auto contactInfo = owner.contactModel->getContact(participant); - if (contactInfo.isBanned) { - qDebug() << "ContactModel::sendMessage: denied, contact is banned"; - return; - } + if (contactInfo.isBanned) { + qDebug() << "ContactModel::sendMessage: denied, contact is banned"; + return; + } - pimpl_->sendContactRequest(participant); - } + pimpl_->sendContactRequest(participant); + } - if (!isTemporary) { - cb(convId); + if (!isTemporary) { + cb(convId); + } + }catch (const std::out_of_range& e) { + qDebug() << "could not send message to not existing conversation"; } } @@ -919,12 +942,19 @@ ConversationModel::refreshFilter() emit filterChanged(); } +void +ConversationModel::updateSearchStatus(const QString& status) const +{ + emit searchStatusChanged(status); +} + void ConversationModel::setFilter(const QString& filter) { pimpl_->filter = filter; pimpl_->dirtyConversations = {true, true}; - // Will update the temporary contact in the contactModel + pimpl_->searchResults.clear(); + emit searchResultUpdated(); owner.contactModel->searchContact(filter); emit filterChanged(); } @@ -1503,6 +1533,10 @@ ConversationModelPimpl::slotContactAdded(const QString& contactUri) if (indexOf(profileInfo.uri) >= 0) { conversations.erase(conversations.begin() + indexOf(profileInfo.uri)); } + for (unsigned int i = 0; i < searchResults.size(); ++i) { + if (searchResults.at(i).uid == profileInfo.uri) + searchResults.erase(searchResults.begin() + i); + } sortConversations(); emit linked.conversationReady(profileInfo.uri); @@ -1564,74 +1598,36 @@ ConversationModelPimpl::slotContactRemoved(const QString& uri) void ConversationModelPimpl::slotContactModelUpdated(const QString& uri, bool needsSorted) { - // We don't create newConversationItem if we already filter on pending - conversation::Info newConversationItem; - if (!filter.isEmpty()) { - // Create a conversation with the temporary item - conversation::Info conversationInfo; - auto& temporaryContact = linked.owner.contactModel->getContact(""); - conversationInfo.uid = temporaryContact.profileInfo.uri; - conversationInfo.participants.push_back(""); - conversationInfo.accountId = linked.owner.id; - - // if temporary contact is already present, its alias is not empty (namely "Searching ..."), - // or its registeredName is set because it was found on the nameservice. - if (not temporaryContact.profileInfo.alias.isEmpty() || not temporaryContact.registeredName.isEmpty()) { - if (!conversations.empty()) { - auto firstContactUri = conversations.front().participants.front(); - //if first conversation has uri it is already a contact - // then we must add temporary item - if (not firstContactUri.isEmpty()) { - conversations.emplace_front(conversationInfo); - } else if (not conversationInfo.uid.isEmpty()) { - // If firstContactUri is empty it means that we have to update - // this element as it is the temporary. - // Only when we have found an uri. - conversations.front() = conversationInfo; - } else if (not conversations.front().uid.isEmpty()) { - //update conversation when uri not found - //but conversation have uri from previous search - conversations.front() = conversationInfo; - } - } else { - // no conversation, add temporaryItem - conversations.emplace_front(conversationInfo); - } + //presence updated + if (!needsSorted) { + try { + auto& conversation = getConversationForContact(uri, true); dirtyConversations = {true, true}; - if (needsSorted) { - emit linked.modelSorted(); - } else { - emit linked.conversationUpdated(conversations.front().uid); - } - return; + emit linked.conversationUpdated(conversation.uid); + } catch (std::out_of_range&) { + qDebug() << "contact updated for not existing conversation"; } - } else { - // No filter, so we can remove the newConversationItem - if (!conversations.empty()) { - auto firstContactUri = conversations.front().participants.front(); + return; + } - if (firstContactUri.isEmpty() && needsSorted) { - conversations.pop_front(); - dirtyConversations = {true, true}; - emit linked.modelSorted(); - return; - } + if (filter.isEmpty()) { + if (searchResults.empty()) { + return; } + searchResults.clear(); + emit linked.searchResultUpdated(); + return; } - // trigger dirtyConversation in all cases to flush emptied temporary element due to filtered contact present in list - // TL:DR : avoid duplicates and empty elements - dirtyConversations = {true, true}; - int index = indexOfContact(uri); - if (index != -1) { - if (!conversations.empty() && conversations.front().participants.front().isEmpty() && - needsSorted) { - // In this case, contact is present in list, so temporary item does not longer exists - emit linked.modelSorted(); - } else { - // In this case, a presence is updated - emit linked.conversationUpdated(conversations.at(index).uid); - } + searchResults.clear(); + auto users = linked.owner.contactModel->getSearchResults(); + for (auto& user : users) { + conversation::Info conversationInfo; + conversationInfo.uid = user.profileInfo.uri; + conversationInfo.participants.push_back(user.profileInfo.uri); + conversationInfo.accountId = linked.owner.id; + searchResults.emplace_front(conversationInfo); } + emit linked.searchResultUpdated(); } void @@ -1695,6 +1691,38 @@ ConversationModelPimpl::indexOf(const QString& uid) const return -1; } +conversation::Info& +ConversationModelPimpl::getConversation(const QString& uid, const bool searchResultIncluded) +{ + for (unsigned int i = 0; i < conversations.size(); ++i) { + if (conversations.at(i).uid == uid) return conversations.at(i); + } + + if (searchResultIncluded) { + for (unsigned int i = 0; i < searchResults.size(); ++i) { + if (searchResults.at(i).uid == uid) return searchResults.at(i); + } + } + throw std::out_of_range("Conversation out of range"); +} + +conversation::Info& +ConversationModelPimpl::getConversationForContact(const QString& uri, const bool searchResultIncluded) +{ + for (unsigned int i = 0; i < conversations.size(); ++i) { + if (conversations.at(i).participants.front() == uri) + return conversations.at(i); + } + + if (searchResultIncluded) { + for (unsigned int i = 0; i < searchResults.size(); ++i) { + if (searchResults.at(i).participants.front() == uri) + return searchResults.at(i); + } + } + throw std::out_of_range("Conversation out of range"); +} + int ConversationModelPimpl::indexOfContact(const QString& uri) const { @@ -2045,23 +2073,22 @@ ConversationModel::sendFile(const QString& convUid, const QString& path, const QString& filename) { - auto conversationIdx = pimpl_->indexOf(convUid); - if (conversationIdx == -1 || !owner.enabled) - return; + try { + auto& conversation = pimpl_->getConversation(convUid, true); - const auto peerUri = pimpl_->conversations[conversationIdx].participants.front(); - bool isTemporary = peerUri.isEmpty(); + const auto peerUri = conversation.participants.front(); + bool isTemporary = peerUri == convUid; - /* It is necessary to make a copy of convUid since it may very well point to - a field in the temporary conversation, which is going to be destroyed by - slotContactAdded() (indirectly triggered by sendContactrequest(). Not doing - so may result in use after free/crash. */ - auto convUidCopy = convUid; + /* It is necessary to make a copy of convUid since it may very well point to + a field in the temporary conversation, which is going to be destroyed by + slotContactAdded() (indirectly triggered by sendContactrequest(). Not doing + so may result in use after free/crash. */ + auto convUidCopy = convUid; - pimpl_->sendContactRequest(peerUri); + pimpl_->sendContactRequest(peerUri); - auto cb = std::function<void(QString)>( - [this, isTemporary, peerUri, path, filename](QString convId) { + auto cb = std::function<void(QString)>( + [this, isTemporary, peerUri, path, filename](QString convId) { int contactIndex; if (isTemporary && (contactIndex = pimpl_->indexOfContact(convId)) < 0) { qDebug() << "Can't send file: Other participant is not a contact (removed while sending file ?)"; @@ -2083,18 +2110,21 @@ ConversationModel::sendFile(const QString& convUid, filename); }); - if (isTemporary) { - QMetaObject::Connection* const connection = new QMetaObject::Connection; - *connection = connect(this, &ConversationModel::conversationReady, - [cb, connection](QString convId) { + if (isTemporary) { + QMetaObject::Connection* const connection = new QMetaObject::Connection; + *connection = connect(this, &ConversationModel::conversationReady, + [cb, connection](QString convId) { cb(convId); QObject::disconnect(*connection); if (connection) { delete connection; } }); - } else { - cb(convUidCopy); + } else { + cb(convUidCopy); + } + } catch (const std::out_of_range& e) { + qDebug() << "could not send file to not existing conversation"; } } diff --git a/src/qtwrapper/configurationmanager_wrap.h b/src/qtwrapper/configurationmanager_wrap.h index 5fa19498fd6d1d2fc0424ba6bf9085ff88cb46a7..1d72acb848b437fafe72596a96bf2ba1e22d9c57 100644 --- a/src/qtwrapper/configurationmanager_wrap.h +++ b/src/qtwrapper/configurationmanager_wrap.h @@ -184,6 +184,10 @@ public: [this](const std::string& account_id, const std::string& from, int status) { Q_EMIT this->composingStatusChanged(QString(account_id.c_str()), QString(from.c_str()), status > 0 ? true : false); }), + exportable_callback<ConfigurationSignal::UserSearchEnded>( + [this](const std::string& account_id, int status, const std::string& query, const std::vector<std::map<std::string, std::string>>& results) { + Q_EMIT this->userSearchEnded(QString(account_id.c_str()), status, QString(query.c_str()), convertVecMap(results)); + }), }; dataXferHandlers = { @@ -725,6 +729,10 @@ public Q_SLOTS: // METHODS return DRing::setMessageDisplayed(accountId.toStdString(), contactId.toStdString(), messageId.toStdString(), status); } + bool searchUser(const QString& accountId, const QString& query) { + return DRing::searchUser(accountId.toStdString(), query.toStdString()); + } + Q_SIGNALS: // SIGNALS void volumeChanged(const QString& device, double value); void accountsChanged(); @@ -758,6 +766,7 @@ Q_SIGNALS: // SIGNALS void accountProfileReceived(const QString& accountId, const QString& displayName, const QString& userPhoto); void debugMessageReceived(const QString& message); void composingStatusChanged(const QString& accountId, const QString& contactId, bool isComposing); + void userSearchEnded(const QString& accountId, int status, const QString& query, VectorMapStringString results); }; namespace org { namespace ring { namespace Ring {