diff --git a/qtox.pro b/qtox.pro index 450978f61..50026e0df 100644 --- a/qtox.pro +++ b/qtox.pro @@ -234,7 +234,8 @@ HEADERS += src/widget/form/addfriendform.h \ src/widget/systemtrayicon_private.h \ src/nexus.h \ src/widget/gui.h \ - src/widget/androidgui.h + src/widget/androidgui.h \ + src/offlinemsgengine.h SOURCES += \ src/widget/form/addfriendform.cpp \ @@ -306,7 +307,8 @@ SOURCES += \ src/widget/systemtrayicon.cpp \ src/nexus.cpp \ src/widget/gui.cpp \ - src/widget/androidgui.cpp + src/widget/androidgui.cpp \ + src/offlinemsgengine.cpp contains(DEFINES, QTOX_FILTER_AUDIO) { HEADERS += src/audiofilterer.h diff --git a/src/offlinemsgengine.cpp b/src/offlinemsgengine.cpp new file mode 100644 index 000000000..bfb6bb04f --- /dev/null +++ b/src/offlinemsgengine.cpp @@ -0,0 +1,121 @@ +/* + Copyright (C) 2015 by Project Tox + + This file is part of qTox, a Qt-based graphical interface for Tox. + + This program is libre software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the COPYING file for more details. +*/ + +#include "offlinemsgengine.h" +#include "src/friend.h" +#include "src/historykeeper.h" +#include "src/misc/settings.h" +#include "src/core.h" +#include +#include + +const int OfflineMsgEngine::offlineTimeout = 2000; +QSet OfflineMsgEngine::engines; +QMutex OfflineMsgEngine::globalMutex; + +OfflineMsgEngine::OfflineMsgEngine(Friend *frnd) : + mutex(QMutex::Recursive), + f(frnd) +{ + engines.insert(this); +} + +OfflineMsgEngine::~OfflineMsgEngine() +{ + engines.remove(this); +} + +void OfflineMsgEngine::dischargeReceipt(int receipt) +{ + QMutexLocker ml(&mutex); + + auto it = receipts.find(receipt); + if (it != receipts.end()) + { + int mID = it.value(); + auto msgIt = undeliveredMsgs.find(mID); + if (msgIt != undeliveredMsgs.end()) + { + HistoryKeeper::getInstance()->markAsSent(mID); + msgIt.value().msg->markAsSent(); + msgIt.value().msg->featureUpdate(); + undeliveredMsgs.erase(msgIt); + } + receipts.erase(it); + } +} + +void OfflineMsgEngine::registerReceipt(int receipt, int messageID, MessageActionPtr msg, const QDateTime ×tamp) +{ + QMutexLocker ml(&mutex); + + receipts[receipt] = messageID; + undeliveredMsgs[messageID] = {msg, timestamp, receipt}; +} + +void OfflineMsgEngine::deliverOfflineMsgs() +{ + QMutexLocker ml(&mutex); + + if (!Settings::getInstance().getFauxOfflineMessaging()) + return; + + if (f->getStatus() == Status::Offline) + return; + + if (undeliveredMsgs.size() == 0) + return; + + QMap msgs = undeliveredMsgs; + removeAllReciepts(); + + for (auto iter = msgs.begin(); iter != msgs.end(); iter++) + { + if (iter.value().timestamp.msecsTo(QDateTime::currentDateTime()) < offlineTimeout) + { + registerReceipt(iter.value().receipt, iter.key(), iter.value().msg, iter.value().timestamp); + continue; + } + QString messageText = iter.value().msg->getRawMessage(); + int rec; + if (iter.value().msg->isAction()) + rec = Core::getInstance()->sendAction(f->getFriendID(), messageText); + else + rec = Core::getInstance()->sendMessage(f->getFriendID(), messageText); + registerReceipt(rec, iter.key(), iter.value().msg); + } +} + +void OfflineMsgEngine::removeAllReciepts() +{ + QMutexLocker ml(&mutex); + + receipts.clear(); + undeliveredMsgs.clear(); +} + +void OfflineMsgEngine::processAllMsgs() +{ + if (globalMutex.tryLock()) + { + for (auto &it : engines) + { + it->deliverOfflineMsgs(); + } + + globalMutex.unlock(); + } +} diff --git a/src/offlinemsgengine.h b/src/offlinemsgengine.h new file mode 100644 index 000000000..46e7639da --- /dev/null +++ b/src/offlinemsgengine.h @@ -0,0 +1,61 @@ +/* + Copyright (C) 2015 by Project Tox + + This file is part of qTox, a Qt-based graphical interface for Tox. + + This program is libre software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the COPYING file for more details. +*/ + +#ifndef OFFLINEMSGENGINE_H +#define OFFLINEMSGENGINE_H + +#include +#include +#include +#include +#include "src/widget/tool/chatactions/messageaction.h" + +struct Friend; +class QTimer; + +class OfflineMsgEngine : public QObject +{ + Q_OBJECT +public: + OfflineMsgEngine(Friend *); + virtual ~OfflineMsgEngine(); + + void dischargeReceipt(int receipt); + void registerReceipt(int receipt, int messageID, MessageActionPtr msg, const QDateTime ×tamp = QDateTime::currentDateTime()); + +public slots: + void deliverOfflineMsgs(); + void removeAllReciepts(); + static void processAllMsgs(); + +private: + struct MsgPtr { + MessageActionPtr msg; + QDateTime timestamp; + int receipt; + }; + + QMutex mutex; + Friend* f; + QHash receipts; + QMap undeliveredMsgs; + + static QSet engines; + static const int offlineTimeout; + static QMutex globalMutex; +}; + +#endif // OFFLINEMSGENGINE_H diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp index 86c9cce03..9fc989349 100644 --- a/src/widget/form/chatform.cpp +++ b/src/widget/form/chatform.cpp @@ -41,6 +41,7 @@ #include "src/widget/widget.h" #include "src/widget/maskablepixmapwidget.h" #include "src/widget/croppinglabel.h" +#include "src/offlinemsgengine.h" ChatForm::ChatForm(Friend* chatFriend) : f(chatFriend) @@ -57,6 +58,8 @@ ChatForm::ChatForm(Friend* chatFriend) callConfirm = nullptr; + offlineEngine = new OfflineMsgEngine(f); + isTypingLabel = new QLabel(); QFont font = isTypingLabel->font(); font.setItalic(true); @@ -90,7 +93,7 @@ ChatForm::ChatForm(Friend* chatFriend) connect(volButton, SIGNAL(clicked()), this, SLOT(onVolMuteToggle())); connect(chatWidget, &ChatAreaWidget::onFileTranfertInterract, this, &ChatForm::onFileTansBtnClicked); connect(Core::getInstance(), &Core::fileSendFailed, this, &ChatForm::onFileSendFailed); - connect(this, SIGNAL(chatAreaCleared()), this, SLOT(clearReciepts())); + connect(this, SIGNAL(chatAreaCleared()), getOfflineMsgEngine(), SLOT(removeAllReciepts())); connect(nameLabel, &CroppingLabel::textChanged, this, [=](QString text, QString orig) {if (text != orig) emit aliasChanged(text);} ); connect(&typingTimer, &QTimer::timeout, this, [=]{Core::getInstance()->sendTyping(f->getFriendID(), false);}); @@ -143,7 +146,7 @@ void ChatForm::onSendTriggered() else rec = Core::getInstance()->sendMessage(f->getFriendID(), qt_msg); - registerReceipt(rec, id, ma); + getOfflineMsgEngine()->registerReceipt(rec, id, ma); msgEdit->setLastMessage(msg); //set last message only when sending it } @@ -821,7 +824,7 @@ void ChatForm::loadHistory(QDateTime since, bool processUndelivered) rec = Core::getInstance()->sendAction(f->getFriendID(), ca->getRawMessage()); else rec = Core::getInstance()->sendMessage(f->getFriendID(), ca->getRawMessage()); - registerReceipt(rec, it.id, ca); + getOfflineMsgEngine()->registerReceipt(rec, it.id, ca); } } historyMessages.append(ca); @@ -906,30 +909,6 @@ QString ChatForm::secondsToDHMS(quint32 duration) return cD + res.sprintf("%dd%02dh %02dm %02ds", days, hours, minutes, seconds); } -void ChatForm::registerReceipt(int receipt, int messageID, MessageActionPtr msg) -{ - receipts[receipt] = messageID; - undeliveredMsgs[messageID] = msg; -} - -void ChatForm::dischargeReceipt(int receipt) -{ - auto it = receipts.find(receipt); - if (it != receipts.end()) - { - int mID = it.value(); - auto msgIt = undeliveredMsgs.find(mID); - if (msgIt != undeliveredMsgs.end()) - { - HistoryKeeper::getInstance()->markAsSent(mID); - msgIt.value()->markAsSent(); - msgIt.value()->featureUpdate(); - undeliveredMsgs.erase(msgIt); - } - receipts.erase(it); - } -} - void ChatForm::setFriendTyping(bool isTyping) { if (isTyping) @@ -938,32 +917,6 @@ void ChatForm::setFriendTyping(bool isTyping) isTypingLabel->clear(); } -void ChatForm::clearReciepts() -{ - receipts.clear(); - undeliveredMsgs.clear(); -} - -void ChatForm::deliverOfflineMsgs() -{ - if (!Settings::getInstance().getFauxOfflineMessaging()) - return; - - QMap msgs = undeliveredMsgs; - clearReciepts(); - - for (auto iter = msgs.begin(); iter != msgs.end(); iter++) - { - QString messageText = iter.value()->getRawMessage(); - int rec; - if (iter.value()->isAction()) - rec = Core::getInstance()->sendAction(f->getFriendID(), messageText); - else - rec = Core::getInstance()->sendMessage(f->getFriendID(), messageText); - registerReceipt(rec, iter.key(), iter.value()); - } -} - void ChatForm::show(Ui::MainWindow &ui) { GenericChatForm::show(ui); @@ -977,3 +930,8 @@ void ChatForm::hideEvent(QHideEvent*) if (callConfirm) callConfirm->hide(); } + +OfflineMsgEngine *ChatForm::getOfflineMsgEngine() +{ + return offlineEngine; +} diff --git a/src/widget/form/chatform.h b/src/widget/form/chatform.h index 77690318a..eb291c906 100644 --- a/src/widget/form/chatform.h +++ b/src/widget/form/chatform.h @@ -32,6 +32,7 @@ class QPixmap; class CallConfirmWidget; class QHideEvent; class QMoveEvent; +class OfflineMsgEngine; class ChatForm : public GenericChatForm { @@ -44,6 +45,7 @@ public: void dischargeReceipt(int receipt); void setFriendTyping(bool isTyping); + OfflineMsgEngine* getOfflineMsgEngine(); virtual void show(Ui::MainWindow &ui); @@ -60,8 +62,6 @@ signals: void aliasChanged(const QString& alias); public slots: - void deliverOfflineMsgs(); - void clearReciepts(); void startFileSend(ToxFile file); void onFileRecvRequest(ToxFile file); void onAvInvite(int FriendId, int CallId, bool video); @@ -102,7 +102,6 @@ protected: void dragEnterEvent(QDragEnterEvent* ev); void dropEvent(QDropEvent* ev); virtual void hideEvent(QHideEvent* event); - void registerReceipt(int receipt, int messageID, MessageActionPtr msg); private: Friend* f; @@ -115,13 +114,12 @@ private: QTimer *disableCallButtonsTimer; QElapsedTimer timeElapsed; QLabel *isTypingLabel; + OfflineMsgEngine *offlineEngine; QHash ftransWidgets; void startCounter(); void stopCounter(); QString secondsToDHMS(quint32 duration); - QHash receipts; - QMap undeliveredMsgs; CallConfirmWidget *callConfirm; void enableCallButtons(); }; diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index f9c5ef5e5..2e0fc876c 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -37,6 +37,7 @@ #include "src/platform/timer.h" #include "systemtrayicon.h" #include "src/nexus.h" +#include "src/offlinemsgengine.h" #include #include #include @@ -88,6 +89,8 @@ void Widget::init() timer = new QTimer(); timer->start(1000); + offlineMsgTimer = new QTimer(); + offlineMsgTimer->start(15000); //restore window state restoreGeometry(Settings::getInstance().getWindowGeometry()); @@ -222,6 +225,7 @@ void Widget::init() connect(addFriendForm, SIGNAL(friendRequested(QString, QString)), this, SIGNAL(friendRequested(QString, QString))); connect(timer, &QTimer::timeout, this, &Widget::onUserAwayCheck); connect(timer, &QTimer::timeout, this, &Widget::onEventIconTick); + connect(offlineMsgTimer, &QTimer::timeout, &OfflineMsgEngine::processAllMsgs); addFriendForm->show(*ui); @@ -276,6 +280,7 @@ Widget::~Widget() delete addFriendForm; delete filesForm; delete timer; + delete offlineMsgTimer; FriendList::clear(); GroupList::clear(); @@ -684,7 +689,7 @@ void Widget::onFriendStatusChanged(int friendId, Status status) if (isActualChange && status != Status::Offline) { // wait a little - QTimer::singleShot(250, f->getChatForm(), SLOT(deliverOfflineMsgs())); + QTimer::singleShot(250, f->getChatForm()->getOfflineMsgEngine(), SLOT(deliverOfflineMsgs())); } } @@ -748,7 +753,7 @@ void Widget::onReceiptRecieved(int friendId, int receipt) if (!f) return; - f->getChatForm()->dischargeReceipt(receipt); + f->getChatForm()->getOfflineMsgEngine()->dischargeReceipt(receipt); } void Widget::newMessageAlert(GenericChatroomWidget* chat) @@ -1125,7 +1130,7 @@ void Widget::clearAllReceipts() QList frnds = FriendList::getAllFriends(); for (Friend *f : frnds) { - f->getChatForm()->clearReciepts(); + f->getChatForm()->getOfflineMsgEngine()->removeAllReciepts(); } } diff --git a/src/widget/widget.h b/src/widget/widget.h index 8fd76247e..1eef382dd 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h @@ -162,7 +162,7 @@ private: bool notify(QObject *receiver, QEvent *event); bool autoAwayActive = false; Status beforeDisconnect = Status::Offline; - QTimer* timer; + QTimer* timer, *offlineMsgTimer; QTranslator* translator; QRegExp nameMention, sanitizedNameMention; bool eventFlag;