diff --git a/src/conversationmodel.cpp b/src/conversationmodel.cpp index e75f37502eeb5a3f24ec6da3dafa91482da9c6cb..cc23613dbf891c569e1bac7b3add3d1cf31fe1a4 100644 --- a/src/conversationmodel.cpp +++ b/src/conversationmodel.cpp @@ -173,6 +173,10 @@ public: std::pair<bool, bool> dirtyConversations {true, true}; ///< true if filteredConversations/customFilteredConversations must be regenerated std::map<std::string, std::mutex> interactionsLocks; ///< {convId, mutex} + // Synchronization tools for account creation synchronization + std::mutex m_mutex_conversations; + std::condition_variable m_condVar_conversation_creation; + public Q_SLOTS: /** * Listen from contactModel when updated (like new alias, avatar, etc.) @@ -560,11 +564,13 @@ ConversationModel::placeCall(const std::string& uid) void ConversationModel::sendMessage(const std::string& uid, const std::string& body) { + // FIXME potential race condition between index check and at() call auto conversationIdx = pimpl_->indexOf(uid); if (conversationIdx == -1) return; auto& conversation = pimpl_->conversations.at(conversationIdx); + if (conversation.participants.empty()) { // Should not qDebug() << "ConversationModel::sendMessage can't send a interaction to a conversation with no participant"; @@ -575,11 +581,16 @@ ConversationModel::sendMessage(const std::string& uid, const std::string& body) auto accountId = pimpl_->accountProfileId; bool isTemporary = conversation.participants.front() == ""; - // Send interaction to all participants - // NOTE: conferences are not implemented yet, so we have only one participant uint64_t daemonMsgId = 0; auto status = interaction::Status::SENDING; - for (const auto& participant: 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; + + /* 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) { @@ -588,25 +599,27 @@ ConversationModel::sendMessage(const std::string& uid, const std::string& body) } pimpl_->sendContactRequest(participant); + } - QStringList callLists = CallManager::instance().getCallList(); // no auto - // workaround: sometimes, it may happen that the daemon delete a call, but lrc don't. We check if the call is - // still valid every time the user want to send a message. - if (not conversation.callId.empty() and not callLists.contains(conversation.callId.c_str())) - conversation.callId.clear(); - - if (not conversation.callId.empty() - and call::canSendSIPMessage(owner.callModel->getCall(conversation.callId))) { - status = interaction::Status::UNKNOWN; - owner.callModel->sendSipMessage(conversation.callId, body); - - } else - daemonMsgId = owner.contactModel->sendDhtMessage(contactInfo.profileInfo.uri, body); + if (isTemporary) { + /* Block until we are sure that the final conversation was created by + slotContactAdded(). If adding contact failed we should not process + any further */ + std::unique_lock<std::mutex> lock(pimpl_->m_mutex_conversations); + auto res = pimpl_->m_condVar_conversation_creation.wait_for(lock, std::chrono::seconds(2), [&]() { + return pimpl_->indexOfContact(convId) >= 0; + }); + lock.unlock(); + if (!res) { + qDebug() << "ConversationModel::sendMessage reached timeout while waiting for contact to be added. Couldn't send message."; + return; + } } - // If first interaction with temporary contact, we have to update the conversations info - // at this stage + /* 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 int contactIndex; if (isTemporary && (contactIndex = pimpl_->indexOfContact(convId)) < 0) { qDebug() << "Can't send message: Other participant is not a contact"; @@ -616,19 +629,50 @@ ConversationModel::sendMessage(const std::string& uid, const std::string& body) auto& newConv = isTemporary ? pimpl_->conversations.at(contactIndex) : conversation; convId = newConv.uid; + // Send interaction to each participant + for (const auto& participant: newConv.participants) { + auto contactInfo = owner.contactModel->getContact(participant); + + QStringList callLists = CallManager::instance().getCallList(); // no auto + // workaround: sometimes, it may happen that the daemon delete a call, but lrc don't. We check if the call is + // still valid every time the user want to send a message. + if (not newConv.callId.empty() and not callLists.contains(newConv.callId.c_str())) + newConv.callId.clear(); + + if (not newConv.callId.empty() + and call::canSendSIPMessage(owner.callModel->getCall(newConv.callId))) { + status = interaction::Status::UNKNOWN; + owner.callModel->sendSipMessage(newConv.callId, body); + + } else { + daemonMsgId = owner.contactModel->sendDhtMessage(contactInfo.profileInfo.uri, body); + } + + } + // Add interaction to database auto msg = interaction::Info {accountId, body, std::time(nullptr), interaction::Type::TEXT, status}; int msgId = database::addMessageToConversation(pimpl_->db, accountId, convId, msg); + // Update conversation if (status == interaction::Status::SENDING) { // Because the daemon already give an id for the message, we need to store it. database::addDaemonMsgId(pimpl_->db, std::to_string(msgId), std::to_string(daemonMsgId)); } + + bool ret = false; + { std::lock_guard<std::mutex> lk(pimpl_->interactionsLocks[convId]); - newConv.interactions.insert(std::pair<uint64_t, interaction::Info>(msgId, msg)); + ret = newConv.interactions.insert(std::pair<uint64_t, interaction::Info>(msgId, msg)).second; } + + if (!ret) { + qDebug("ConversationModel::sendMessage failed to send message because an existing key was already present in the database (key = %d)", msgId); + return; + } + newConv.lastMessageUid = msgId; pimpl_->dirtyConversations = {true, true}; // Emit this signal for chatview in the client @@ -1173,6 +1217,7 @@ ConversationModelPimpl::slotContactAdded(const std::string& uri) } sortConversations(); + m_condVar_conversation_creation.notify_all(); emit linked.modelSorted(); }