|
|
|
|
@ -26,6 +26,7 @@
@@ -26,6 +26,7 @@
|
|
|
|
|
#include <QMutexLocker> |
|
|
|
|
#include <QTimer> |
|
|
|
|
#include <QCoreApplication> |
|
|
|
|
#include <chrono> |
|
|
|
|
|
|
|
|
|
OfflineMsgEngine::OfflineMsgEngine(Friend* frnd) |
|
|
|
|
: mutex(QMutex::Recursive) |
|
|
|
|
@ -33,111 +34,144 @@ OfflineMsgEngine::OfflineMsgEngine(Friend* frnd)
@@ -33,111 +34,144 @@ OfflineMsgEngine::OfflineMsgEngine(Friend* frnd)
|
|
|
|
|
{ |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void OfflineMsgEngine::dischargeReceipt(ReceiptNum receipt) |
|
|
|
|
/**
|
|
|
|
|
* @brief Notification that the message is now delivered. |
|
|
|
|
* |
|
|
|
|
* @param[in] receipt Toxcore message ID which the receipt is for. |
|
|
|
|
*/ |
|
|
|
|
void OfflineMsgEngine::onReceiptReceived(ReceiptNum receipt) |
|
|
|
|
{ |
|
|
|
|
QMutexLocker ml(&mutex); |
|
|
|
|
|
|
|
|
|
auto it = receipts.find(receipt); |
|
|
|
|
if (it == receipts.end()) { |
|
|
|
|
it = receipts.insert(receipt, Receipt()); |
|
|
|
|
} else if (it->bRecepitReceived) { |
|
|
|
|
qWarning() << "Received duplicate receipt"; |
|
|
|
|
if (receivedReceipts.contains(receipt)) { |
|
|
|
|
qWarning() << "Receievd duplicate receipt" << receipt.get() << "from friend" << f->getId(); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
it->bRecepitReceived = true; |
|
|
|
|
processReceipt(receipt); |
|
|
|
|
receivedReceipts.append(receipt); |
|
|
|
|
checkForCompleteMessages(receipt); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void OfflineMsgEngine::registerReceipt(ReceiptNum receipt, RowId messageID, ChatMessage::Ptr msg) |
|
|
|
|
/**
|
|
|
|
|
* @brief Add a message which has been saved to history, but not sent yet to the peer. |
|
|
|
|
* |
|
|
|
|
* OfflineMsgEngine will send this message once the friend becomes online again, then track its |
|
|
|
|
* receipt, updating history and chatlog once received. |
|
|
|
|
* |
|
|
|
|
* @param[in] messageID database RowId of the message, used to eventually mark messages as received in history |
|
|
|
|
* @param[in] msg chat message line in the chatlog, used to eventually set the message's receieved timestamp |
|
|
|
|
*/ |
|
|
|
|
void OfflineMsgEngine::addSavedMessage(RowId messageID, ChatMessage::Ptr chatMessage) |
|
|
|
|
{ |
|
|
|
|
QMutexLocker ml(&mutex); |
|
|
|
|
|
|
|
|
|
auto it = receipts.find(receipt); |
|
|
|
|
if (it == receipts.end()) { |
|
|
|
|
it = receipts.insert(receipt, Receipt()); |
|
|
|
|
} else if (it->bRowValid && receipt.get() != 0 /* offline receipt */) { |
|
|
|
|
qWarning() << "Received duplicate registration of receipt"; |
|
|
|
|
assert([&](){ |
|
|
|
|
for (const auto& message : unsentSavedMessages) { |
|
|
|
|
if (message.rowId == messageID) { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
it->rowId = messageID; |
|
|
|
|
it->bRowValid = true; |
|
|
|
|
undeliveredMsgs[messageID] = {msg, receipt}; |
|
|
|
|
processReceipt(receipt); |
|
|
|
|
} |
|
|
|
|
return true; |
|
|
|
|
}()); |
|
|
|
|
unsentSavedMessages.append(Message{chatMessage, messageID, std::chrono::steady_clock::now()}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Add a message which has been saved to history, and which has been sent to the peer. |
|
|
|
|
* |
|
|
|
|
* OfflineMsgEngine will track this message's receipt. If the friend goes offline then comes back before the receipt |
|
|
|
|
* is received, OfflineMsgEngine will also resend the message, updating history and chatlog once received. |
|
|
|
|
* |
|
|
|
|
* @param[in] receipt the toxcore message ID, corresponding to expected receipt ID |
|
|
|
|
* @param[in] messageID database RowId of the message, used to eventually mark messages as received in history |
|
|
|
|
* @param[in] msg chat message line in the chatlog, used to eventually set the message's receieved timestamp |
|
|
|
|
*/ |
|
|
|
|
void OfflineMsgEngine::addSentSavedMessage(ReceiptNum receipt, RowId messageID, ChatMessage::Ptr chatMessage) |
|
|
|
|
{ |
|
|
|
|
QMutexLocker ml(&mutex); |
|
|
|
|
assert(!sentSavedMessages.contains(receipt)); |
|
|
|
|
sentSavedMessages.insert(receipt, {chatMessage, messageID, std::chrono::steady_clock::now()}); |
|
|
|
|
checkForCompleteMessages(receipt); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Deliver all messages, used when a friend comes online. |
|
|
|
|
*/ |
|
|
|
|
void OfflineMsgEngine::deliverOfflineMsgs() |
|
|
|
|
{ |
|
|
|
|
QMutexLocker ml(&mutex); |
|
|
|
|
|
|
|
|
|
if (!Settings::getInstance().getFauxOfflineMessaging()) |
|
|
|
|
if (!Settings::getInstance().getFauxOfflineMessaging()) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (f->getStatus() == Status::Offline) |
|
|
|
|
if (f->getStatus() == Status::Offline) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (undeliveredMsgs.size() == 0) |
|
|
|
|
if (sentSavedMessages.empty() && unsentSavedMessages.empty()) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
QMap<RowId, MsgPtr> msgs = undeliveredMsgs; |
|
|
|
|
removeAllReceipts(); |
|
|
|
|
undeliveredMsgs.clear(); |
|
|
|
|
|
|
|
|
|
for (auto iter = msgs.begin(); iter != msgs.end(); ++iter) { |
|
|
|
|
auto val = iter.value(); |
|
|
|
|
auto key = iter.key(); |
|
|
|
|
QString messageText = val.msg->toString(); |
|
|
|
|
ReceiptNum rec; |
|
|
|
|
if (val.msg->isAction()) { |
|
|
|
|
Core::getInstance()->sendAction(f->getId(), messageText, rec); |
|
|
|
|
QVector<Message> messages = sentSavedMessages.values().toVector() + unsentSavedMessages; |
|
|
|
|
// order messages by authorship time to resend in same order as they were written
|
|
|
|
|
qSort(messages.begin(), messages.end(), [](const Message& lhs, const Message& rhs){ return lhs.authorshipTime < rhs.authorshipTime; }); |
|
|
|
|
removeAllMessages(); |
|
|
|
|
|
|
|
|
|
for (const auto& message : messages) { |
|
|
|
|
QString messageText = message.chatMessage->toString(); |
|
|
|
|
ReceiptNum receipt; |
|
|
|
|
bool messageSent{false}; |
|
|
|
|
if (message.chatMessage->isAction()) { |
|
|
|
|
messageSent = Core::getInstance()->sendAction(f->getId(), messageText, receipt); |
|
|
|
|
} else { |
|
|
|
|
Core::getInstance()->sendMessage(f->getId(), messageText, rec); |
|
|
|
|
messageSent = Core::getInstance()->sendMessage(f->getId(), messageText, receipt); |
|
|
|
|
} |
|
|
|
|
if (messageSent) { |
|
|
|
|
addSentSavedMessage(receipt, message.rowId, message.chatMessage); |
|
|
|
|
} else { |
|
|
|
|
qCritical() << "deliverOfflineMsgs failed to send message"; |
|
|
|
|
addSavedMessage(message.rowId, message.chatMessage); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
registerReceipt(rec, key, val.msg); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void OfflineMsgEngine::removeAllReceipts() |
|
|
|
|
/**
|
|
|
|
|
* @brief Removes all messages which are being tracked. |
|
|
|
|
*/ |
|
|
|
|
void OfflineMsgEngine::removeAllMessages() |
|
|
|
|
{ |
|
|
|
|
QMutexLocker ml(&mutex); |
|
|
|
|
|
|
|
|
|
receipts.clear(); |
|
|
|
|
receivedReceipts.clear(); |
|
|
|
|
sentSavedMessages.clear(); |
|
|
|
|
unsentSavedMessages.clear(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void OfflineMsgEngine::updateTimestamp(ReceiptNum receiptId) |
|
|
|
|
void OfflineMsgEngine::completeMessage(QMap<ReceiptNum, Message>::iterator msgIt) |
|
|
|
|
{ |
|
|
|
|
QMutexLocker ml(&mutex); |
|
|
|
|
Profile* const profile = Nexus::getProfile(); |
|
|
|
|
if (profile->isHistoryEnabled()) { |
|
|
|
|
profile->getHistory()->markAsSent(msgIt->rowId); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
auto receipt = receipts.find(receiptId); |
|
|
|
|
const auto msg = undeliveredMsgs.constFind(receipt->rowId); |
|
|
|
|
if (msg == undeliveredMsgs.end()) { |
|
|
|
|
// this should never occur as registerReceipt adds the msg before processReceipt calls updateTimestamp
|
|
|
|
|
qCritical() << "Message was not in undeliveredMsgs map when attempting to update its timestamp!"; |
|
|
|
|
return; |
|
|
|
|
if (QThread::currentThread() == QCoreApplication::instance()->thread()) { |
|
|
|
|
updateTimestamp(msgIt->chatMessage); |
|
|
|
|
} else { |
|
|
|
|
QMetaObject::invokeMethod(this, "updateTimestamp", Qt::QueuedConnection, Q_ARG(ChatMessage::Ptr, msgIt->chatMessage)); |
|
|
|
|
} |
|
|
|
|
msg->msg->markAsSent(QDateTime::currentDateTime()); |
|
|
|
|
undeliveredMsgs.remove(receipt->rowId); |
|
|
|
|
receipts.erase(receipt); |
|
|
|
|
sentSavedMessages.erase(msgIt); |
|
|
|
|
receivedReceipts.removeOne(msgIt.key()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void OfflineMsgEngine::processReceipt(ReceiptNum receiptId) |
|
|
|
|
void OfflineMsgEngine::checkForCompleteMessages(ReceiptNum receipt) |
|
|
|
|
{ |
|
|
|
|
const auto receipt = receipts.constFind(receiptId); |
|
|
|
|
if (receipt == receipts.end()) { |
|
|
|
|
// this should never occur as callers ensure receipts contains receiptId
|
|
|
|
|
qCritical() << "Receipt was not added to map prior to attempting to process it!"; |
|
|
|
|
auto msgIt = sentSavedMessages.find(receipt); |
|
|
|
|
const bool receiptReceived = receivedReceipts.contains(receipt); |
|
|
|
|
if (!receiptReceived || msgIt == sentSavedMessages.end()) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
assert(!unsentSavedMessages.contains(*msgIt)); |
|
|
|
|
completeMessage(msgIt); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!receipt->bRecepitReceived || !receipt->bRowValid) |
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
Profile* const profile = Nexus::getProfile(); |
|
|
|
|
if (profile->isHistoryEnabled()) { |
|
|
|
|
profile->getHistory()->markAsSent(receipt->rowId); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (QThread::currentThread() == QCoreApplication::instance()->thread()) { |
|
|
|
|
updateTimestamp(receiptId); |
|
|
|
|
} else { |
|
|
|
|
QMetaObject::invokeMethod(this, "updateTimestamp", Qt::QueuedConnection, Q_ARG(ReceiptNum, receiptId)); |
|
|
|
|
} |
|
|
|
|
void OfflineMsgEngine::updateTimestamp(ChatMessage::Ptr msg) |
|
|
|
|
{ |
|
|
|
|
msg->markAsSent(QDateTime::currentDateTime()); |
|
|
|
|
} |
|
|
|
|
|