From 812e7730378ae387e365f16166c34c3f28bc703c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Blin?= <sebastien.blin@savoirfairelinux.com> Date: Thu, 3 May 2018 11:18:55 -0400 Subject: [PATCH] conversationmodel: add the ability to retry to send interactions Change-Id: If208ca55d5a710cce3e9404a65aa8fb3f176ba86 Reviewed-by: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> --- src/api/conversationmodel.h | 6 + src/conversationmodel.cpp | 49 +++++++ test/conversationmodeltester.cpp | 177 ++++++++++++++++++++++++- test/conversationmodeltester.h | 30 +++++ test/mocks/callmanager_mock.h | 4 + test/mocks/configurationmanager_mock.h | 4 + 6 files changed, 269 insertions(+), 1 deletion(-) diff --git a/src/api/conversationmodel.h b/src/api/conversationmodel.h index 7a96dcf1..29face79 100644 --- a/src/api/conversationmodel.h +++ b/src/api/conversationmodel.h @@ -167,6 +167,12 @@ public: * @param interactionId */ void clearInteractionFromConversation(const std::string& convId, const uint64_t& interactionId); + /** + * Retry to send a message. In fact, will delete the previous interaction and resend a new one. + * @param convId + * @param interactionId + */ + void retryInteraction(const std::string& convId, const uint64_t& interactionId); /** * delete obsolete history from the database * @param days, number of days from today. Below this date, interactions will be deleted diff --git a/src/conversationmodel.cpp b/src/conversationmodel.cpp index 5e2b3748..805fd8a6 100644 --- a/src/conversationmodel.cpp +++ b/src/conversationmodel.cpp @@ -21,6 +21,7 @@ //Qt #include <QtCore/QTimer> +#include <QFileInfo> // daemon #include <account_const.h> @@ -756,6 +757,54 @@ ConversationModel::clearInteractionFromConversation(const std::string& convId, c } } +void +ConversationModel::retryInteraction(const std::string& convId, const uint64_t& interactionId) +{ + auto conversationIdx = pimpl_->indexOf(convId); + if (conversationIdx == -1) + return; + + auto interactionType = interaction::Type::INVALID; + auto body = std::string(); + { + std::lock_guard<std::mutex> lk(pimpl_->interactionsLocks[convId]); + try { + auto& conversation = pimpl_->conversations.at(conversationIdx); + + auto& interactions = conversation.interactions; + auto it = interactions.find(interactionId); + if (it == interactions.end()) + return; + + if (!interaction::isOutgoing(it->second)) + return; // Do not retry non outgoing info + + if (it->second.type == interaction::Type::TEXT + || it->second.type == interaction::Type::OUTGOING_DATA_TRANSFER) { + body = it->second.body; + interactionType = it->second.type; + } else + return; + + database::clearInteractionFromConversation(pimpl_->db, convId, interactionId); + conversation.interactions.erase(interactionId); + } catch (const std::out_of_range& e) { + qDebug() << "can't find interaction from conversation: " << e.what(); + return; + } + } + emit interactionRemoved(convId, interactionId); + + // Send a new interaction like the previous one + if (interactionType == interaction::Type::TEXT) { + sendMessage(convId, body); + } else { + // send file + QFileInfo f(body.c_str()); + sendFile(convId, body, f.fileName().toStdString()); + } +} + void ConversationModel::clearAllHistory() { diff --git a/test/conversationmodeltester.cpp b/test/conversationmodeltester.cpp index 81216a86..996a8a92 100644 --- a/test/conversationmodeltester.cpp +++ b/test/conversationmodeltester.cpp @@ -30,6 +30,7 @@ #include <api/contactmodel.h> #include <api/newcallmodel.h> #include <dbus/configurationmanager.h> +#include <dbus/callmanager.h> #include <namedirectory.h> namespace ring @@ -393,7 +394,7 @@ ConversationModelTester::testSendMessagesAndClearInteraction() CPPUNIT_ASSERT(conversationExists); accInfo_.conversationModel->clearInteractionFromConversation(firstConversationUid, secondInterId); - auto unreadMessage = WaitForSignalHelper(*accInfo_.conversationModel, + WaitForSignalHelper(*accInfo_.conversationModel, SIGNAL(interactionRemoved(const std::string& convUid, uint64_t interactionId))).wait(1000); conversations = accInfo_.conversationModel->allFilteredConversations(); conversationExists = false; @@ -463,6 +464,180 @@ ConversationModelTester::testSendMessagesAndClearLastInteraction() CPPUNIT_ASSERT(conversationExists); } +void +ConversationModelTester::testRetryToSendTextInteraction() +{ + accInfo_.conversationModel->setFilter(""); + auto conversations = accInfo_.conversationModel->allFilteredConversations(); + CPPUNIT_ASSERT(conversations.size() != 0); + auto firstConversation = accInfo_.conversationModel->filteredConversation(0).uid; + accInfo_.conversationModel->sendMessage(firstConversation, "Hello World!"); + accInfo_.conversationModel->sendMessage(firstConversation, "It's been a long time"); + accInfo_.conversationModel->sendMessage(firstConversation, "How have you been?"); + auto conversation = accInfo_.conversationModel->filteredConversation(0); + const auto& interactions = conversation.interactions; + auto it = interactions.begin(); + it++; + auto secondId = it->first; + + // set failure on one interaction + ConfigurationManager::instance().emitAccountMessageStatusChanged( + "ring0", secondId, + conversation.participants.front().c_str(), + static_cast<int>(DRing::Account::MessageStates::FAILURE)); + // retry sending + accInfo_.conversationModel->retryInteraction(conversation.uid, secondId); + // no more failure, no more secondId, and second message should be the last + conversation = accInfo_.conversationModel->filteredConversation(0); + bool hasFailedInteraction = false; + bool hasOldSecondInteraction = false; + bool secondBodyPresent = false; conversation.interactions.begin()->second.body == "It's been a long time"; + for (const auto& interaction : conversation.interactions) { + if (interaction.second.status == lrc::api::interaction::Status::FAILED) + hasFailedInteraction = true; + if (interaction.second.body == "It's been a long time") + secondBodyPresent = true; + if (interaction.first == secondId) + hasOldSecondInteraction = true; + } + CPPUNIT_ASSERT(!hasFailedInteraction); + CPPUNIT_ASSERT(!hasOldSecondInteraction); + CPPUNIT_ASSERT(secondBodyPresent); +} + +void +ConversationModelTester::testRetryToSendFileInteraction() +{ + accInfo_.conversationModel->setFilter(""); + auto conversations = accInfo_.conversationModel->allFilteredConversations(); + CPPUNIT_ASSERT(conversations.size() != 0); + auto firstConversation = accInfo_.conversationModel->filteredConversation(0).uid; + // send file + +} + +void +ConversationModelTester::testRetryInvalidInteraction() +{ + accInfo_.conversationModel->setFilter(""); + auto conversations = accInfo_.conversationModel->allFilteredConversations(); + CPPUNIT_ASSERT(conversations.size() != 0); + auto firstConversation = accInfo_.conversationModel->filteredConversation(0).uid; + accInfo_.conversationModel->sendMessage(firstConversation, "Hello World!"); + accInfo_.conversationModel->sendMessage(firstConversation, "It's been a long time"); + accInfo_.conversationModel->sendMessage(firstConversation, "How have you been?"); + auto conversation = accInfo_.conversationModel->filteredConversation(0); + const auto& interactions = conversation.interactions; + auto it = interactions.begin(); + it++; + auto secondId = it->first; + + // set failure on one interaction + ConfigurationManager::instance().emitAccountMessageStatusChanged( + "ring0", secondId, + conversation.participants.front().c_str(), + static_cast<int>(DRing::Account::MessageStates::FAILURE)); + auto firstConv = accInfo_.conversationModel->filteredConversation(0); + // retry sending (should do nothing) + accInfo_.conversationModel->retryInteraction(conversation.uid, 1412); + + conversation = accInfo_.conversationModel->filteredConversation(0); + auto bIt = firstConv.interactions.begin(); + auto nIt = conversation.interactions.begin(); + for (size_t i = 0 ; i < firstConv.interactions.size(); ++i) { + CPPUNIT_ASSERT(bIt->second.body == nIt->second.body); + CPPUNIT_ASSERT(bIt->second.status == nIt->second.status); + CPPUNIT_ASSERT(bIt->second.type == nIt->second.type); + bIt++; + nIt++; + } +} + +void +ConversationModelTester::testRetryIncomingInteraction() +{ + // Add a new message for the first conversation + auto conversations = accInfo_.conversationModel->allFilteredConversations(); + CPPUNIT_ASSERT(conversations.size() != 0); + auto firstConversation = accInfo_.conversationModel->filteredConversation(0); + QMap<QString, QString> payloads; + payloads["text/plain"] = "You're a monster"; + ConfigurationManager::instance().emitIncomingAccountMessage(accInfo_.id.c_str(), + firstConversation.participants.front().c_str(), payloads); + auto unreadMessage = WaitForSignalHelper(*accInfo_.conversationModel, + SIGNAL(newUnreadMessage(const std::string&, uint64_t, const interaction::Info&))).wait(1000); + CPPUNIT_ASSERT_EQUAL(unreadMessage, true); + + // Retry incoming message + conversations = accInfo_.conversationModel->allFilteredConversations(); + CPPUNIT_ASSERT(conversations.size() != 0); + firstConversation = accInfo_.conversationModel->filteredConversation(0); + auto lastInteraction = *firstConversation.interactions.rbegin(); + accInfo_.conversationModel->retryInteraction(firstConversation.uid, lastInteraction.first); + // Should do nothing + firstConversation = accInfo_.conversationModel->filteredConversation(0); + auto newLastInteraction = *firstConversation.interactions.rbegin(); + CPPUNIT_ASSERT(newLastInteraction.second.status == lrc::api::interaction::Status::UNREAD); +} + +void +ConversationModelTester::testRetryContactInteraction() +{ + // Add a new message for the first conversation. The first message will be "contact added" + auto conversations = accInfo_.conversationModel->allFilteredConversations(); + CPPUNIT_ASSERT(conversations.size() != 0); + auto firstConversation = accInfo_.conversationModel->filteredConversation(0); + QMap<QString, QString> payloads; + payloads["text/plain"] = "You're a monster"; + ConfigurationManager::instance().emitIncomingAccountMessage(accInfo_.id.c_str(), + firstConversation.participants.front().c_str(), payloads); + auto unreadMessage = WaitForSignalHelper(*accInfo_.conversationModel, + SIGNAL(newUnreadMessage(const std::string&, uint64_t, const interaction::Info&))).wait(1000); + CPPUNIT_ASSERT_EQUAL(unreadMessage, true); + + // The first message is "Contact added" + conversations = accInfo_.conversationModel->allFilteredConversations(); + CPPUNIT_ASSERT(conversations.size() != 0); + firstConversation = accInfo_.conversationModel->filteredConversation(0); + auto firstInteraction = *firstConversation.interactions.begin(); + CPPUNIT_ASSERT(firstInteraction.second.type == lrc::api::interaction::Type::CONTACT); + + // Retry contact + accInfo_.conversationModel->retryInteraction(firstConversation.uid, firstInteraction.first); + // Should do nothings + firstConversation = accInfo_.conversationModel->filteredConversation(0); + auto newLastInteraction = *firstConversation.interactions.begin(); + CPPUNIT_ASSERT(newLastInteraction.second.type == lrc::api::interaction::Type::CONTACT); +} + +void +ConversationModelTester::testRetryCallInteraction() +{ + // Place call + auto conversations = accInfo_.conversationModel->allFilteredConversations(); + CPPUNIT_ASSERT(conversations.size() != 0); + auto firstConversation = accInfo_.conversationModel->filteredConversation(0); + accInfo_.conversationModel->placeCall(firstConversation.uid); + CallManager::instance().emitCallStateChanged( + accInfo_.conversationModel->filteredConversation(0).callId.c_str(), "CURRENT", 0); + WaitForSignalHelper(*accInfo_.conversationModel, SIGNAL(modelSorted())).wait(1000); + + // Last interaction is a CALL + conversations = accInfo_.conversationModel->allFilteredConversations(); + CPPUNIT_ASSERT(conversations.size() != 0); + firstConversation = accInfo_.conversationModel->filteredConversation(0); + auto lastInteraction = *firstConversation.interactions.rbegin(); + CPPUNIT_ASSERT(lastInteraction.second.type == lrc::api::interaction::Type::CALL); + // Retry, should do nothing + accInfo_.conversationModel->retryInteraction(firstConversation.uid, lastInteraction.first); + + // Should do nothing + firstConversation = accInfo_.conversationModel->filteredConversation(0); + auto newLastInteraction = *firstConversation.interactions.rbegin(); + CPPUNIT_ASSERT(newLastInteraction.second.type == lrc::api::interaction::Type::CALL); +} + + void ConversationModelTester::testReceiveMessageAndSetRead() { diff --git a/test/conversationmodeltester.h b/test/conversationmodeltester.h index 2bab3ee1..64e787a4 100644 --- a/test/conversationmodeltester.h +++ b/test/conversationmodeltester.h @@ -48,6 +48,12 @@ class ConversationModelTester : public CppUnit::TestFixture { CPPUNIT_TEST(testSendMessageAndClearHistory); CPPUNIT_TEST(testSendMessagesAndClearInteraction); CPPUNIT_TEST(testSendMessagesAndClearLastInteraction); + CPPUNIT_TEST(testRetryToSendTextInteraction); + CPPUNIT_TEST(testRetryToSendFileInteraction); + CPPUNIT_TEST(testRetryInvalidInteraction); + CPPUNIT_TEST(testRetryIncomingInteraction); + CPPUNIT_TEST(testRetryContactInteraction); + CPPUNIT_TEST(testRetryCallInteraction); CPPUNIT_TEST(testReceiveMessageAndSetRead); CPPUNIT_TEST(testPlaceCall); CPPUNIT_TEST(testCreateConference); @@ -105,6 +111,30 @@ public: * lastMessageUid should be updated */ void testSendMessagesAndClearLastInteraction(); + /** + * Send an old failed outgoing text interaction + */ + void testRetryToSendTextInteraction(); + /** + * Send an old failed outgoing file interaction + */ + void testRetryToSendFileInteraction(); + /** + * Retry an unexistant interaction + */ + void testRetryInvalidInteraction(); + /** + * Retry an incoming interaction + */ + void testRetryIncomingInteraction(); + /** + * Retry a contact interaction + */ + void testRetryContactInteraction(); + /** + * Retry a call interaction + */ + void testRetryCallInteraction(); /** * Receives a message from a conversation and set this message READ */ diff --git a/test/mocks/callmanager_mock.h b/test/mocks/callmanager_mock.h index 5b174fc9..23d56995 100644 --- a/test/mocks/callmanager_mock.h +++ b/test/mocks/callmanager_mock.h @@ -54,6 +54,10 @@ public: bool isValid() { return true; } + void emitCallStateChanged(const QString &callID, const QString &state, int code) { + emit callStateChanged(callID, state, code); + } + public Q_SLOTS: // METHODS bool accept(const QString &callID) { diff --git a/test/mocks/configurationmanager_mock.h b/test/mocks/configurationmanager_mock.h index 76abe21b..90ce44b8 100644 --- a/test/mocks/configurationmanager_mock.h +++ b/test/mocks/configurationmanager_mock.h @@ -108,6 +108,10 @@ public: emit incomingAccountMessage(accountId, from, payloads); } + void emitAccountMessageStatusChanged(const QString& accountId, const uint64_t id, const QString& to, int status) + { + emit accountMessageStatusChanged(accountId, id, to, status); + } std::map<long long, lrc::api::datatransfer::Info> transferInfos_; std::map<long long, uint32_t> transferInfosEvent_; -- GitLab