diff --git a/rssguard.pro b/rssguard.pro index 512df2e2c..411f216fb 100755 --- a/rssguard.pro +++ b/rssguard.pro @@ -326,7 +326,8 @@ HEADERS += src/core/feeddownloader.h \ src/services/standard/atomparser.h \ src/services/standard/feedparser.h \ src/services/standard/rdfparser.h \ - src/services/standard/rssparser.h + src/services/standard/rssparser.h \ + src/miscellaneous/serviceoperator.h SOURCES += src/core/feeddownloader.cpp \ src/core/feedsmodel.cpp \ @@ -444,7 +445,8 @@ SOURCES += src/core/feeddownloader.cpp \ src/services/standard/atomparser.cpp \ src/services/standard/feedparser.cpp \ src/services/standard/rdfparser.cpp \ - src/services/standard/rssparser.cpp + src/services/standard/rssparser.cpp \ + src/miscellaneous/serviceoperator.cpp FORMS += src/gui/toolbareditor.ui \ src/network-web/downloaditem.ui \ diff --git a/src/miscellaneous/application.cpp b/src/miscellaneous/application.cpp index d17b33215..8d1a23bc0 100755 --- a/src/miscellaneous/application.cpp +++ b/src/miscellaneous/application.cpp @@ -373,7 +373,7 @@ void Application::onAboutToQuit() { system()->removeTrolltechJunkRegistryKeys(); #endif - qApp->feedReader()->stop(); + qApp->feedReader()->quit(); database()->saveDatabase(); if (mainForm() != nullptr) { diff --git a/src/miscellaneous/debugging.cpp b/src/miscellaneous/debugging.cpp index 34099ddaf..e433edb1b 100755 --- a/src/miscellaneous/debugging.cpp +++ b/src/miscellaneous/debugging.cpp @@ -23,6 +23,8 @@ #include #include +#include +#include Debugging::Debugging() { @@ -31,13 +33,18 @@ Debugging::Debugging() { void Debugging::performLog(const char *message, QtMsgType type, const char *file, const char *function, int line) { const char *type_string = typeToString(type); + std::time_t t = std::time(nullptr); + char mbstr[32]; + + std::strftime(mbstr, sizeof(mbstr), "%y/%d/%m %H:%M:%S", std::localtime(&t)); + // Write to console. if (file == 0 || function == 0 || line < 0) { - fprintf(stderr, "[%s] %s: %s\n", APP_LOW_NAME, type_string, message); + fprintf(stderr, "[%s] %s: %s (%s)\n", APP_LOW_NAME, type_string, message, mbstr); } else { - fprintf(stderr, "[%s] %s\n Type: %s\n File: %s (line %d)\n Function: %s\n\n", - APP_LOW_NAME, message, type_string, file, line, function); + fprintf(stderr, "[%s] %s (%s)\n Type: %s\n File: %s (line %d)\n Function: %s\n\n", + APP_LOW_NAME, message, mbstr, type_string, file, line, function); } if (type == QtFatalMsg) { diff --git a/src/miscellaneous/feedreader.cpp b/src/miscellaneous/feedreader.cpp index 73a779a6c..aeae24e79 100755 --- a/src/miscellaneous/feedreader.cpp +++ b/src/miscellaneous/feedreader.cpp @@ -20,6 +20,7 @@ #include "services/standard/standardserviceentrypoint.h" #include "services/owncloud/owncloudserviceentrypoint.h" #include "services/tt-rss/ttrssserviceentrypoint.h" +#include "services/abstract/serviceroot.h" #include "core/feedsmodel.h" #include "core/feedsproxymodel.h" @@ -32,10 +33,12 @@ #include #include +#include FeedReader::FeedReader(QObject *parent) - : QObject(parent), m_feedServices(QList()), m_autoUpdateTimer(new QTimer(this)), + : QObject(parent), m_feedServices(QList()), + m_cacheSaveFutureWatcher(QFutureWatcher()), m_autoUpdateTimer(new QTimer(this)), m_feedDownloaderThread(nullptr), m_feedDownloader(nullptr), m_dbCleanerThread(nullptr), m_dbCleaner(nullptr) { m_feedsModel = new FeedsModel(this); @@ -43,8 +46,10 @@ FeedReader::FeedReader(QObject *parent) m_messagesModel = new MessagesModel(this); m_messagesProxyModel = new MessagesProxyModel(m_messagesModel, this); + connect(&m_cacheSaveFutureWatcher, &QFutureWatcher::finished, this, &FeedReader::asyncCacheSaveFinished); connect(m_autoUpdateTimer, &QTimer::timeout, this, &FeedReader::executeNextAutoUpdate); updateAutoUpdateStatus(); + asyncCacheSaveFinished(); if (qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::FeedsUpdateOnStartup)).toBool()) { qDebug("Requesting update for all feeds on application startup."); @@ -209,11 +214,60 @@ void FeedReader::executeNextAutoUpdate() { } } -void FeedReader::stop() { +void FeedReader::checkServicesForAsyncOperations() { + checkServicesForAsyncOperations(false); +} + +void FeedReader::checkServicesForAsyncOperations(bool wait_for_future) { + if (m_cacheSaveFutureWatcher.future().isStarted() || m_cacheSaveFutureWatcher.future().isRunning()) { + qDebug("Previous future is still running or was already started."); + + + // If we want to wait for future synchronously, we want to make sure that + // we save all cached data (app exit). + if (wait_for_future) { + qWarning("Waiting for previously started saving of cached service data."); + m_cacheSaveFutureWatcher.future().waitForFinished(); + } + else { + qWarning("Some cached service data are being saved now, so aborting this saving cycle."); + // Some cache saving is now running. + return; + } + } + + QFuture future = QtConcurrent::run([&] { + foreach (ServiceRoot *service, m_feedsModel->serviceRoots()) { + // Store any cached data. + service->saveAllCachedData(); + } + }); + + if (wait_for_future) { + qDebug("Waiting for saving of cached service data to finish."); + future.waitForFinished(); + } + else { + m_cacheSaveFutureWatcher.setFuture(future); + } +} + +void FeedReader::asyncCacheSaveFinished() { + qDebug("I will start next check for cached service data in 30 seconds."); + + QTimer::singleShot(30000, [&] { + qDebug("Starting next check for cached service data in NOW."); + checkServicesForAsyncOperations(false); + }); +} + +void FeedReader::quit() { if (m_autoUpdateTimer->isActive()) { m_autoUpdateTimer->stop(); } + checkServicesForAsyncOperations(true); + // Close worker threads. if (m_feedDownloaderThread != nullptr && m_feedDownloaderThread->isRunning()) { m_feedDownloader->stopRunningUpdate(); diff --git a/src/miscellaneous/feedreader.h b/src/miscellaneous/feedreader.h index d3715d8c1..6e45bce66 100755 --- a/src/miscellaneous/feedreader.h +++ b/src/miscellaneous/feedreader.h @@ -23,12 +23,15 @@ #include "services/abstract/feed.h" #include "core/feeddownloader.h" +#include + class FeedsModel; class MessagesModel; class MessagesProxyModel; class FeedsProxyModel; class ServiceEntryPoint; +class ServiceOperator; class DatabaseCleaner; class QTimer; @@ -69,11 +72,14 @@ class FeedReader : public QObject { // Schedules all feeds from all accounts for update. void updateAllFeeds(); void stopRunningFeedUpdate(); - void stop(); + void quit(); private slots: // Is executed when next auto-update round could be done. void executeNextAutoUpdate(); + void checkServicesForAsyncOperations(); + void checkServicesForAsyncOperations(bool wait_for_future); + void asyncCacheSaveFinished(); signals: void feedUpdatesStarted(); @@ -88,12 +94,16 @@ class FeedReader : public QObject { MessagesModel *m_messagesModel; MessagesProxyModel *m_messagesProxyModel; + QFutureWatcher m_cacheSaveFutureWatcher; + // Auto-update stuff. QTimer *m_autoUpdateTimer; bool m_globalAutoUpdateEnabled; int m_globalAutoUpdateInitialInterval; int m_globalAutoUpdateRemainingInterval; + ServiceOperator *m_serviceOperator; + QThread *m_feedDownloaderThread; FeedDownloader *m_feedDownloader; diff --git a/src/miscellaneous/serviceoperator.cpp b/src/miscellaneous/serviceoperator.cpp new file mode 100755 index 000000000..cbb07c04f --- /dev/null +++ b/src/miscellaneous/serviceoperator.cpp @@ -0,0 +1,25 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2017 by Martin Rotter +// +// RSS Guard is free 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. +// +// RSS Guard 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 +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with RSS Guard. If not, see . + +#include "miscellaneous/serviceoperator.h" + + +ServiceOperator::ServiceOperator(QObject *parent) : QObject(parent) { +} + +ServiceOperator::~ServiceOperator() { +} diff --git a/src/miscellaneous/serviceoperator.h b/src/miscellaneous/serviceoperator.h new file mode 100755 index 000000000..352ae29df --- /dev/null +++ b/src/miscellaneous/serviceoperator.h @@ -0,0 +1,36 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2017 by Martin Rotter +// +// RSS Guard is free 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. +// +// RSS Guard 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 +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with RSS Guard. If not, see . + +#ifndef SERVICEOPERATOR_H +#define SERVICEOPERATOR_H + +#include + + +class ServiceOperator : public QObject { + Q_OBJECT + + public: + explicit ServiceOperator(QObject *parent = 0); + virtual ~ServiceOperator(); + + signals: + + public slots: +}; + +#endif // SERVICEOPERATOR_H diff --git a/src/services/abstract/serviceroot.cpp b/src/services/abstract/serviceroot.cpp index 6c5c46feb..59494d234 100755 --- a/src/services/abstract/serviceroot.cpp +++ b/src/services/abstract/serviceroot.cpp @@ -192,6 +192,9 @@ QList ServiceRoot::undeletedMessages() const { return DatabaseQueries::getUndeletedMessagesForAccount(database, accountId()); } +void ServiceRoot::saveAllCachedData() { +} + void ServiceRoot::itemChanged(const QList &items) { emit dataChanged(items); } diff --git a/src/services/abstract/serviceroot.h b/src/services/abstract/serviceroot.h index 39f5268e7..e17fe9ae6 100755 --- a/src/services/abstract/serviceroot.h +++ b/src/services/abstract/serviceroot.h @@ -59,6 +59,7 @@ class ServiceRoot : public RootItem { // NOTE: Caller does NOT take ownership of created menu! virtual QList addItemMenu(); + // Returns actions to display as context menu. QList contextMenu(); // Returns list of specific actions to be shown in main window menu @@ -83,6 +84,10 @@ class ServiceRoot : public RootItem { virtual void start(bool freshly_activated) = 0; virtual void stop() = 0; + // Is called in short intervals in worker thread. + // Service can save its cached data (if any) here. + virtual void saveAllCachedData(); + // Account ID corresponds with DB attribute Accounts (id). int accountId() const; void setAccountId(int account_id); diff --git a/src/services/owncloud/owncloudserviceroot.cpp b/src/services/owncloud/owncloudserviceroot.cpp index 928252c7e..c895309e0 100755 --- a/src/services/owncloud/owncloudserviceroot.cpp +++ b/src/services/owncloud/owncloudserviceroot.cpp @@ -31,6 +31,8 @@ #include "services/owncloud/gui/formeditowncloudaccount.h" #include "services/owncloud/gui/formowncloudfeeddetails.h" +#include + OwnCloudServiceRoot::OwnCloudServiceRoot(RootItem *parent) : ServiceRoot(parent), m_recycleBin(new OwnCloudRecycleBin(this)), @@ -195,6 +197,10 @@ void OwnCloudServiceRoot::saveAccountDataToDatabase() { } } +void OwnCloudServiceRoot::saveAllCachedData() { + QThread::msleep(2000); +} + void OwnCloudServiceRoot::addNewFeed(const QString &url) { if (!qApp->feedUpdateLock()->tryLock()) { // Lock was not obtained because diff --git a/src/services/owncloud/owncloudserviceroot.h b/src/services/owncloud/owncloudserviceroot.h index 3c2ddef6b..43ff2a91f 100755 --- a/src/services/owncloud/owncloudserviceroot.h +++ b/src/services/owncloud/owncloudserviceroot.h @@ -51,6 +51,8 @@ class OwnCloudServiceRoot : public ServiceRoot { void updateTitle(); void saveAccountDataToDatabase(); + void saveAllCachedData(); + public slots: void addNewFeed(const QString &url); void addNewCategory();