Huge changes for #325.

This commit is contained in:
Martin Rotter 2020-12-17 14:23:56 +01:00
parent 2ad719c396
commit c232a2eb4c
15 changed files with 144 additions and 65 deletions

View File

@ -3,6 +3,7 @@
#include "core/feeddownloader.h"
#include "3rd-party/boolinq/boolinq.h"
#include "core/feedsmodel.h"
#include "core/messagefilter.h"
#include "definitions/definitions.h"
#include "exceptions/filteringexception.h"
@ -20,7 +21,7 @@
#include <QUrl>
FeedDownloader::FeedDownloader()
: QObject(), m_mutex(new QMutex()), m_feedsUpdated(0), m_feedsOriginalCount(0) {
: QObject(), m_isCacheSynchronizationRunning(false), m_stopCacheSynchronization(false), m_mutex(new QMutex()), m_feedsUpdated(0), m_feedsOriginalCount(0) {
qRegisterMetaType<FeedDownloadResults>("FeedDownloadResults");
}
@ -43,7 +44,14 @@ void FeedDownloader::updateAvailableFeeds() {
qDebugNN << LOGSEC_FEEDDOWNLOADER
<< "Saving cache for feed with DB ID '" << feed->id()
<< "' and title '" << feed->title() << "'.";
cache->saveAllCachedData(false);
cache->saveAllCachedData();
}
if (m_stopCacheSynchronization) {
qWarningNN << LOGSEC_FEEDDOWNLOADER << "Aborting cache synchronization.";
m_stopCacheSynchronization = false;
break;
}
}
@ -52,6 +60,28 @@ void FeedDownloader::updateAvailableFeeds() {
}
}
void FeedDownloader::synchronizeAccountCaches(const QList<CacheForServiceRoot*>& caches) {
m_isCacheSynchronizationRunning = true;
for (CacheForServiceRoot* cache : caches) {
qDebugNN << LOGSEC_FEEDDOWNLOADER
<< "Synchronizing cache back to server on thread" << QUOTE_W_SPACE_DOT(QThread::currentThreadId());
cache->saveAllCachedData();
if (m_stopCacheSynchronization) {
qWarningNN << LOGSEC_FEEDDOWNLOADER << "Aborting cache synchronization.";
m_stopCacheSynchronization = false;
break;
}
}
qDebugNN << LOGSEC_FEEDDOWNLOADER << "All caches synchronized.";
emit cachesSynchronized();
m_isCacheSynchronizationRunning = false;
}
void FeedDownloader::updateFeeds(const QList<Feed*>& feeds) {
QMutexLocker locker(m_mutex);
@ -77,6 +107,7 @@ void FeedDownloader::updateFeeds(const QList<Feed*>& feeds) {
}
void FeedDownloader::stopRunningUpdate() {
m_stopCacheSynchronization = true;
m_feeds.clear();
m_feedsOriginalCount = m_feedsUpdated = 0;
}
@ -291,6 +322,11 @@ void FeedDownloader::finalizeUpdate() {
emit updateFinished(m_results);
}
bool FeedDownloader::isCacheSynchronizationRunning() const
{
return m_isCacheSynchronizationRunning;
}
QString FeedDownloadResults::overview(int how_many_feeds) const {
QStringList result;

View File

@ -8,6 +8,7 @@
#include <QPair>
#include "core/message.h"
#include "services/abstract/cacheforserviceroot.h"
#include "services/abstract/feed.h"
class MessageFilter;
@ -39,12 +40,15 @@ class FeedDownloader : public QObject {
virtual ~FeedDownloader();
bool isUpdateRunning() const;
bool isCacheSynchronizationRunning() const;
public slots:
void synchronizeAccountCaches(const QList<CacheForServiceRoot*>& caches);
void updateFeeds(const QList<Feed*>& feeds);
void stopRunningUpdate();
signals:
void cachesSynchronized();
void updateStarted();
void updateFinished(FeedDownloadResults updated_feeds);
void updateProgress(const Feed* feed, int current, int total);
@ -54,7 +58,9 @@ class FeedDownloader : public QObject {
void updateAvailableFeeds();
void finalizeUpdate();
QList<Feed*> m_feeds;
bool m_isCacheSynchronizationRunning;
bool m_stopCacheSynchronization;
QList<Feed*> m_feeds = {};
QMutex* m_mutex;
FeedDownloadResults m_results;
int m_feedsUpdated;

View File

@ -2,6 +2,7 @@
#include "miscellaneous/feedreader.h"
#include "3rd-party/boolinq/boolinq.h"
#include "core/feeddownloader.h"
#include "core/feedsmodel.h"
#include "core/feedsproxymodel.h"
@ -33,7 +34,6 @@ FeedReader::FeedReader(QObject* parent)
connect(m_autoUpdateTimer, &QTimer::timeout, this, &FeedReader::executeNextAutoUpdate);
updateAutoUpdateStatus();
asyncCacheSaveFinished();
if (qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::FeedsUpdateOnStartup)).toBool()) {
qDebugNN << LOGSEC_CORE
@ -66,11 +66,28 @@ QList<ServiceEntryPoint*> FeedReader::feedServices() {
void FeedReader::updateFeeds(const QList<Feed*>& feeds) {
if (!qApp->feedUpdateLock()->tryLock()) {
qApp->showGuiMessage(tr("Cannot update all items"),
tr("You cannot update all items because another critical operation is ongoing."),
tr("You cannot update items "
"because another critical operation is ongoing."),
QSystemTrayIcon::MessageIcon::Warning, qApp->mainFormWidget(), true);
return;
}
initializeFeedDownloader();
QMetaObject::invokeMethod(m_feedDownloader, "updateFeeds",
Qt::ConnectionType::QueuedConnection,
Q_ARG(QList<Feed*>, feeds));
}
void FeedReader::synchronizeMessageData(const QList<CacheForServiceRoot*>& caches) {
initializeFeedDownloader();
QMetaObject::invokeMethod(m_feedDownloader, "synchronizeAccountCaches",
Qt::ConnectionType::QueuedConnection,
Q_ARG(QList<CacheForServiceRoot*>, caches));
}
void FeedReader::initializeFeedDownloader() {
if (m_feedDownloader == nullptr) {
qDebugNN << LOGSEC_CORE << "Creating FeedDownloader singleton.";
@ -79,6 +96,7 @@ void FeedReader::updateFeeds(const QList<Feed*>& feeds) {
// Downloader setup.
qRegisterMetaType<QList<Feed*>>("QList<Feed*>");
qRegisterMetaType<QList<CacheForServiceRoot*>>("QList<CacheForServiceRoot*>");
m_feedDownloader->moveToThread(m_feedDownloaderThread);
@ -91,10 +109,6 @@ void FeedReader::updateFeeds(const QList<Feed*>& feeds) {
m_feedDownloaderThread->start();
}
QMetaObject::invokeMethod(m_feedDownloader, "updateFeeds",
Qt::ConnectionType::QueuedConnection,
Q_ARG(QList<Feed*>, feeds));
}
void FeedReader::showMessageFiltersManager() {
@ -230,16 +244,39 @@ MessagesModel* FeedReader::messagesModel() const {
}
void FeedReader::executeNextAutoUpdate() {
if (qApp->mainFormWidget()->isActiveWindow() && m_globalAutoUpdateOnlyUnfocused) {
bool disable_update_with_window = qApp->mainFormWidget()->isActiveWindow() && m_globalAutoUpdateOnlyUnfocused;
auto roots = qApp->feedReader()->feedsModel()->serviceRoots();
std::list<CacheForServiceRoot*> full_caches = boolinq::from(roots)
.select([](ServiceRoot* root) -> CacheForServiceRoot* {
auto* cache = root->toCache();
if (cache != nullptr) {
return cache;
}
else {
return nullptr;
}
})
.where([](CacheForServiceRoot* cache) {
return !cache->isEmpty();
}).toStdList();
// Skip this round of auto-updating, but only if user disabled it when main window is active
// and there are no caches to synchronize.
if (disable_update_with_window && full_caches.empty()) {
qDebugNN << LOGSEC_CORE
<< "Delaying scheduled feed auto-update for one minute since window is focused and updates while focused are disabled by the user.";
<< "Delaying scheduled feed auto-update for one minute since window "
<< "is focused and updates while focused are disabled by the "
<< "user and all account caches are empty.";
// Cannot update, quit.
return;
}
if (!qApp->feedUpdateLock()->tryLock()) {
qDebugNN << LOGSEC_CORE << "Delaying scheduled feed auto-updates for one minute due to another running update.";
qDebugNN << LOGSEC_CORE
<< "Delaying scheduled feed auto-updates and message state synchronization for "
<< "one minute due to another running update.";
// Cannot update, quit.
return;
@ -253,51 +290,37 @@ void FeedReader::executeNextAutoUpdate() {
}
qDebugNN << LOGSEC_CORE
<< "Starting auto-update event, pass "
<< m_globalAutoUpdateRemainingInterval << "/" << m_globalAutoUpdateInitialInterval << ".";
<< "Starting auto-update event, remaining "
<< m_globalAutoUpdateRemainingInterval << "minutes out of "
<< m_globalAutoUpdateInitialInterval << "total minutes to next global feed update.";
qApp->feedUpdateLock()->unlock();
// Resynchronize caches.
if (!full_caches.empty()) {
QList<CacheForServiceRoot*> caches = FROM_STD_LIST(QList<CacheForServiceRoot*>, full_caches);
synchronizeMessageData(caches);
}
// Pass needed interval data and lets the model decide which feeds
// should be updated in this pass.
QList<Feed*> feeds_for_update = m_feedsModel->feedsForScheduledUpdate(m_globalAutoUpdateEnabled &&
m_globalAutoUpdateRemainingInterval == 0);
qApp->feedUpdateLock()->unlock();
if (!feeds_for_update.isEmpty()) {
// Request update for given feeds.
updateFeeds(feeds_for_update);
// NOTE: OSD/bubble informing about performing
// of scheduled update can be shown now.
// NOTE: OSD/bubble informing about performing of scheduled update can be shown now.
if (qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::EnableAutoUpdateNotification)).toBool()) {
qApp->showGuiMessage(tr("Starting auto-update of some feeds"),
tr("I will auto-update %n feed(s).", nullptr, feeds_for_update.size()),
QSystemTrayIcon::Information);
QSystemTrayIcon::MessageIcon::Information);
}
}
}
void FeedReader::checkServicesForAsyncOperations() {
for (ServiceRoot* service : m_feedsModel->serviceRoots()) {
auto cache = dynamic_cast<CacheForServiceRoot*>(service);
if (cache != nullptr) {
cache->saveAllCachedData();
}
}
asyncCacheSaveFinished();
}
void FeedReader::asyncCacheSaveFinished() {
qDebugNN << LOGSEC_CORE << "I will start next check for cached service data in 60 seconds.";
QTimer::singleShot(60000, this, [&] {
qDebugNN << LOGSEC_CORE << "Saving cached metadata NOW.";
checkServicesForAsyncOperations();
});
}
QList<MessageFilter*> FeedReader::messageFilters() const {
return m_messageFilters;
}
@ -311,9 +334,10 @@ void FeedReader::quit() {
if (m_feedDownloader != nullptr) {
m_feedDownloader->stopRunningUpdate();
if (m_feedDownloader->isUpdateRunning()) {
if (m_feedDownloader->isUpdateRunning() || m_feedDownloader->isCacheSynchronizationRunning()) {
QEventLoop loop(this);
connect(m_feedDownloader, &FeedDownloader::cachesSynchronized, &loop, &QEventLoop::quit);
connect(m_feedDownloader, &FeedDownloader::updateFinished, &loop, &QEventLoop::quit);
loop.exec();
}

View File

@ -7,6 +7,7 @@
#include "core/feeddownloader.h"
#include "core/messagefilter.h"
#include "services/abstract/cacheforserviceroot.h"
#include "services/abstract/feed.h"
#include <QFutureWatcher>
@ -36,9 +37,12 @@ class RSSGUARD_DLLSPEC FeedReader : public QObject {
FeedsProxyModel* feedsProxyModel() const;
MessagesProxyModel* messagesProxyModel() const;
// Schedules given feeds for update.
// Update feeds in extra thread.
void updateFeeds(const QList<Feed*>& feeds);
// Push back cached message states back to servers in extra thread.
void synchronizeMessageData(const QList<CacheForServiceRoot*>& caches);
void showMessageFiltersManager();
// True if feed update is running right now.
@ -68,14 +72,15 @@ class RSSGUARD_DLLSPEC FeedReader : public QObject {
private slots:
void executeNextAutoUpdate();
void checkServicesForAsyncOperations();
void asyncCacheSaveFinished();
signals:
void feedUpdatesStarted();
void feedUpdatesFinished(FeedDownloadResults updated_feeds);
void feedUpdatesProgress(const Feed* feed, int current, int total);
private:
void initializeFeedDownloader();
private:
QList<ServiceEntryPoint*> m_feedServices;
QList<MessageFilter*> m_messageFilters;

View File

@ -22,7 +22,7 @@ class CacheForServiceRoot {
public:
explicit CacheForServiceRoot();
virtual void saveAllCachedData(bool async = true) = 0;
virtual void saveAllCachedData() = 0;
void addLabelsAssignmentsToCache(const QStringList& ids_of_messages, const QString& lbl_custom_id, bool assign);
void addLabelsAssignmentsToCache(const QList<Message>& ids_of_messages, Label* lbl, bool assign);
@ -31,6 +31,7 @@ class CacheForServiceRoot {
void loadCacheFromFile();
void setUniqueId(int unique_id);
bool isEmpty() const;
protected:
@ -40,7 +41,6 @@ class CacheForServiceRoot {
CacheSnapshot takeMessageCache();
private:
bool isEmpty() const;
void clearCache();
void saveCacheToFile();

View File

@ -672,6 +672,10 @@ bool ServiceRoot::onAfterMessagesRestoredFromBin(RootItem* selected_item, const
return true;
}
CacheForServiceRoot* ServiceRoot::toCache() const {
return dynamic_cast<CacheForServiceRoot*>(const_cast<ServiceRoot*>(this));
}
void ServiceRoot::assembleFeeds(Assignment feeds) {
QHash<int, Category*> categories = getHashedSubTreeCategories();

View File

@ -16,6 +16,7 @@ class LabelsNode;
class Label;
class QAction;
class MessagesModel;
class CacheForServiceRoot;
// Car here represents ID (int, primary key) of the item.
typedef QList<QPair<int, RootItem*>> Assignment;
@ -151,7 +152,8 @@ class ServiceRoot : public RootItem {
// NOTE: Keep in sync with ServiceEntryRoot::code().
virtual QString code() const = 0;
// These are not part of "interface".
// These are not part of "interface".
CacheForServiceRoot* toCache() const;
public:

View File

@ -205,7 +205,7 @@ QString GmailServiceRoot::additionalTooltip() const {
network()->oauth()->tokensExpireIn().toString() : QSL("-"));
}
void GmailServiceRoot::saveAllCachedData(bool async) {
void GmailServiceRoot::saveAllCachedData() {
auto msg_cache = takeMessageCache();
QMapIterator<RootItem::ReadStatus, QStringList> i(msg_cache.m_cachedStatesRead);
@ -216,7 +216,7 @@ void GmailServiceRoot::saveAllCachedData(bool async) {
QStringList ids = i.value();
if (!ids.isEmpty()) {
network()->markMessagesRead(key, ids, async);
network()->markMessagesRead(key, ids, false);
}
}
@ -235,7 +235,7 @@ void GmailServiceRoot::saveAllCachedData(bool async) {
custom_ids.append(msg.m_customId);
}
network()->markMessagesStarred(key, custom_ids, async);
network()->markMessagesStarred(key, custom_ids, false);
}
}
}

View File

@ -33,7 +33,7 @@ class GmailServiceRoot : public ServiceRoot, public CacheForServiceRoot {
virtual void start(bool freshly_activated);
virtual QString code() const;
virtual QString additionalTooltip() const;
virtual void saveAllCachedData(bool async = true);
virtual void saveAllCachedData();
void updateTitle();

View File

@ -14,6 +14,8 @@
#include "services/inoreader/inoreaderfeed.h"
#include "services/inoreader/network/inoreadernetworkfactory.h"
#include <QThread>
InoreaderServiceRoot::InoreaderServiceRoot(InoreaderNetworkFactory* network, RootItem* parent)
: ServiceRoot(parent), m_network(network) {
if (network == nullptr) {
@ -146,7 +148,7 @@ RootItem* InoreaderServiceRoot::obtainNewTreeForSyncIn() const {
}
}
void InoreaderServiceRoot::saveAllCachedData(bool async) {
void InoreaderServiceRoot::saveAllCachedData() {
auto msg_cache = takeMessageCache();
QMapIterator<RootItem::ReadStatus, QStringList> i(msg_cache.m_cachedStatesRead);
@ -157,7 +159,7 @@ void InoreaderServiceRoot::saveAllCachedData(bool async) {
QStringList ids = i.value();
if (!ids.isEmpty()) {
network()->markMessagesRead(key, ids, async);
network()->markMessagesRead(key, ids, false);
}
}
@ -176,7 +178,7 @@ void InoreaderServiceRoot::saveAllCachedData(bool async) {
custom_ids.append(msg.m_customId);
}
network()->markMessagesStarred(key, custom_ids, async);
network()->markMessagesStarred(key, custom_ids, false);
}
}
@ -189,7 +191,7 @@ void InoreaderServiceRoot::saveAllCachedData(bool async) {
QStringList messages = k.value();
if (!messages.isEmpty()) {
network()->editLabels(label_custom_id, true, messages, async);
network()->editLabels(label_custom_id, true, messages, false);
}
}
@ -202,7 +204,7 @@ void InoreaderServiceRoot::saveAllCachedData(bool async) {
QStringList messages = l.value();
if (!messages.isEmpty()) {
network()->editLabels(label_custom_id, false, messages, async);
network()->editLabels(label_custom_id, false, messages, false);
}
}
}

View File

@ -31,7 +31,7 @@ class InoreaderServiceRoot : public ServiceRoot, public CacheForServiceRoot {
virtual void start(bool freshly_activated);
virtual QString code() const;
virtual QString additionalTooltip() const;
virtual void saveAllCachedData(bool async = true);
virtual void saveAllCachedData();
void updateTitle();

View File

@ -80,7 +80,7 @@ OwnCloudNetworkFactory* OwnCloudServiceRoot::network() const {
return m_network;
}
void OwnCloudServiceRoot::saveAllCachedData(bool async) {
void OwnCloudServiceRoot::saveAllCachedData() {
auto msg_cache = takeMessageCache();
QMapIterator<RootItem::ReadStatus, QStringList> i(msg_cache.m_cachedStatesRead);
@ -91,7 +91,7 @@ void OwnCloudServiceRoot::saveAllCachedData(bool async) {
QStringList ids = i.value();
if (!ids.isEmpty()) {
network()->markMessagesRead(key, ids, async);
network()->markMessagesRead(key, ids, false);
}
}
@ -111,7 +111,7 @@ void OwnCloudServiceRoot::saveAllCachedData(bool async) {
guid_hashes.append(msg.m_customHash);
}
network()->markMessagesStarred(key, feed_ids, guid_hashes, async);
network()->markMessagesStarred(key, feed_ids, guid_hashes, false);
}
}
}

View File

@ -27,7 +27,7 @@ class OwnCloudServiceRoot : public ServiceRoot, public CacheForServiceRoot {
virtual bool supportsCategoryAdding() const;
virtual void start(bool freshly_activated);
virtual QString code() const;
virtual void saveAllCachedData(bool async = true);
virtual void saveAllCachedData();
OwnCloudNetworkFactory* network() const;

View File

@ -117,7 +117,7 @@ bool TtRssServiceRoot::canBeDeleted() const {
return true;
}
void TtRssServiceRoot::saveAllCachedData(bool async) {
void TtRssServiceRoot::saveAllCachedData() {
auto msg_cache = takeMessageCache();
QMapIterator<RootItem::ReadStatus, QStringList> i(msg_cache.m_cachedStatesRead);
@ -133,7 +133,7 @@ void TtRssServiceRoot::saveAllCachedData(bool async) {
key == RootItem::ReadStatus::Unread
? UpdateArticle::Mode::SetToTrue
: UpdateArticle::Mode::SetToFalse,
async);
false);
if (network()->lastError() != QNetworkReply::NetworkError::NoError || res.hasError()) {
addMessageStatesToCache(ids, key);
@ -156,7 +156,7 @@ void TtRssServiceRoot::saveAllCachedData(bool async) {
key == RootItem::Importance::Important
? UpdateArticle::Mode::SetToTrue
: UpdateArticle::Mode::SetToFalse,
async);
false);
if (network()->lastError() != QNetworkReply::NetworkError::NoError || res.hasError()) {
addMessageStatesToCache(messages, key);

View File

@ -32,7 +32,7 @@ class TtRssServiceRoot : public ServiceRoot, public CacheForServiceRoot {
virtual bool supportsCategoryAdding() const;
virtual void addNewFeed(RootItem* selected_item, const QString& url = QString());
virtual QString additionalTooltip() const;
virtual void saveAllCachedData(bool async = true);
virtual void saveAllCachedData();
// Access to network.
TtRssNetworkFactory* network() const;