// For license of this file, see /LICENSE.md. #include "miscellaneous/feedreader.h" #include "core/feeddownloader.h" #include "core/feedsmodel.h" #include "core/feedsproxymodel.h" #include "core/messagesmodel.h" #include "core/messagesproxymodel.h" #include "miscellaneous/application.h" #include "miscellaneous/databasequeries.h" #include "miscellaneous/mutex.h" #include "gui/dialogs/formmessagefiltersmanager.h" #include "services/abstract/cacheforserviceroot.h" #include "services/abstract/serviceroot.h" #include "services/gmail/gmailentrypoint.h" #include "services/inoreader/inoreaderentrypoint.h" #include "services/owncloud/owncloudserviceentrypoint.h" #include "services/standard/standardserviceentrypoint.h" #include "services/tt-rss/ttrssserviceentrypoint.h" #include #include #include FeedReader::FeedReader(QObject* parent) : QObject(parent), m_autoUpdateTimer(new QTimer(this)), m_feedDownloader(nullptr) { m_feedsModel = new FeedsModel(this); m_feedsProxyModel = new FeedsProxyModel(m_feedsModel, this); m_messagesModel = new MessagesModel(this); m_messagesProxyModel = new MessagesProxyModel(m_messagesModel, this); 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."); QTimer::singleShot(qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::FeedsUpdateStartupDelay)).toDouble() * 1000, this, &FeedReader::updateAllFeeds); } } FeedReader::~FeedReader() { qDebug("Destroying FeedReader instance."); qDeleteAll(m_feedServices); qDeleteAll(m_messageFilters); } QList FeedReader::feedServices() { if (m_feedServices.isEmpty()) { // NOTE: All installed services create their entry points here. m_feedServices.append(new GmailEntryPoint()); m_feedServices.append(new InoreaderEntryPoint()); m_feedServices.append(new OwnCloudServiceEntryPoint()); m_feedServices.append(new StandardServiceEntryPoint()); m_feedServices.append(new TtRssServiceEntryPoint()); } return m_feedServices; } void FeedReader::updateFeeds(const QList& feeds) { if (!qApp->feedUpdateLock()->tryLock()) { qApp->showGuiMessage(tr("Cannot update all items"), tr("You cannot update all items because another critical operation is ongoing."), QSystemTrayIcon::MessageIcon::Warning, qApp->mainFormWidget(), true); return; } if (m_feedDownloader == nullptr) { qDebug("Creating FeedDownloader singleton."); m_feedDownloaderThread = new QThread(); m_feedDownloader = new FeedDownloader(); // Downloader setup. qRegisterMetaType>("QList"); m_feedDownloader->moveToThread(m_feedDownloaderThread); connect(m_feedDownloaderThread, &QThread::finished, m_feedDownloaderThread, &QThread::deleteLater); connect(m_feedDownloaderThread, &QThread::finished, m_feedDownloader, &FeedDownloader::deleteLater); connect(m_feedDownloader, &FeedDownloader::updateFinished, this, &FeedReader::feedUpdatesFinished); connect(m_feedDownloader, &FeedDownloader::updateProgress, this, &FeedReader::feedUpdatesProgress); connect(m_feedDownloader, &FeedDownloader::updateStarted, this, &FeedReader::feedUpdatesStarted); connect(m_feedDownloader, &FeedDownloader::updateFinished, qApp->feedUpdateLock(), &Mutex::unlock); m_feedDownloaderThread->start(); } QMetaObject::invokeMethod(m_feedDownloader, "updateFeeds", Qt::ConnectionType::QueuedConnection, Q_ARG(QList, feeds)); } void FeedReader::showMessageFiltersManager() { FormMessageFiltersManager manager(qApp->mainFormWidget()); manager.exec(); } void FeedReader::updateAutoUpdateStatus() { // Restore global intervals. // NOTE: Specific per-feed interval are left intact. m_globalAutoUpdateInitialInterval = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::AutoUpdateInterval)).toInt(); m_globalAutoUpdateRemainingInterval = m_globalAutoUpdateInitialInterval; m_globalAutoUpdateEnabled = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::AutoUpdateEnabled)).toBool(); m_globalAutoUpdateOnlyUnfocused = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::AutoUpdateOnlyUnfocused)).toBool(); // Start global auto-update timer if it is not running yet. // NOTE: The timer must run even if global auto-update // is not enabled because user can still enable auto-update // for individual feeds. if (!m_autoUpdateTimer->isActive()) { m_autoUpdateTimer->setInterval(AUTO_UPDATE_INTERVAL); m_autoUpdateTimer->start(); qDebug("Auto-update timer started with interval %d.", m_autoUpdateTimer->interval()); } else { qDebug("Auto-update timer is already running."); } } bool FeedReader::autoUpdateEnabled() const { return m_globalAutoUpdateEnabled; } int FeedReader::autoUpdateRemainingInterval() const { return m_globalAutoUpdateRemainingInterval; } int FeedReader::autoUpdateInitialInterval() const { return m_globalAutoUpdateInitialInterval; } void FeedReader::loadSaveMessageFilters() { // Load all message filters from database. // All plugin services will hook active filters to // all feeds. QSqlDatabase database = qApp->database()->connection(QSL("FeedReader")); m_messageFilters = DatabaseQueries::getMessageFilters(database); for (auto* filter : m_messageFilters) { filter->setParent(this); } } void FeedReader::updateAllFeeds() { updateFeeds(m_feedsModel->rootItem()->getSubTreeFeeds()); } void FeedReader::stopRunningFeedUpdate() { if (m_feedDownloader != nullptr) { m_feedDownloader->stopRunningUpdate(); } } bool FeedReader::isFeedUpdateRunning() const { return m_feedDownloader != nullptr && m_feedDownloader->isUpdateRunning(); } FeedDownloader* FeedReader::feedDownloader() const { return m_feedDownloader; } FeedsModel* FeedReader::feedsModel() const { return m_feedsModel; } MessagesModel* FeedReader::messagesModel() const { return m_messagesModel; } void FeedReader::executeNextAutoUpdate() { if (qApp->mainFormWidget()->isActiveWindow() && m_globalAutoUpdateOnlyUnfocused) { qDebug("Delaying scheduled feed auto-update for one minute since window is focused and updates" "while focused are disabled by the user."); // Cannot update, quit. return; } if (!qApp->feedUpdateLock()->tryLock()) { qDebug("Delaying scheduled feed auto-updates for one minute due to another running update."); // Cannot update, quit. return; } // If global auto-update is enabled and its interval counter reached zero, // then we need to restore it. if (m_globalAutoUpdateEnabled && --m_globalAutoUpdateRemainingInterval < 0) { // We should start next auto-update interval. m_globalAutoUpdateRemainingInterval = m_globalAutoUpdateInitialInterval; } qDebug("Starting auto-update event, pass %d/%d.", m_globalAutoUpdateRemainingInterval, m_globalAutoUpdateInitialInterval); // Pass needed interval data and lets the model decide which feeds // should be updated in this pass. QList 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. 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); } } } void FeedReader::checkServicesForAsyncOperations() { for (ServiceRoot* service : m_feedsModel->serviceRoots()) { auto cache = dynamic_cast(service); if (cache != nullptr) { cache->saveAllCachedData(); } } asyncCacheSaveFinished(); } void FeedReader::asyncCacheSaveFinished() { qDebug("I will start next check for cached service data in 60 seconds."); QTimer::singleShot(60000, this, [&] { qDebug("Starting next check for cached service data in NOW."); checkServicesForAsyncOperations(); }); } QList FeedReader::messageFilters() const { return m_messageFilters; } void FeedReader::quit() { if (m_autoUpdateTimer->isActive()) { m_autoUpdateTimer->stop(); } // Stop running updates. if (m_feedDownloader != nullptr) { m_feedDownloader->stopRunningUpdate(); if (m_feedDownloader->isUpdateRunning()) { QEventLoop loop(this); connect(m_feedDownloader, &FeedDownloader::updateFinished, &loop, &QEventLoop::quit); loop.exec(); } // Both thread and downloader are auto-deleted when worker thread exits. m_feedDownloaderThread->quit(); } if (qApp->settings()->value(GROUP(Messages), SETTING(Messages::ClearReadOnExit)).toBool()) { m_feedsModel->markItemCleared(m_feedsModel->rootItem(), true); } m_feedsModel->stopServiceAccounts(); } MessagesProxyModel* FeedReader::messagesProxyModel() const { return m_messagesProxyModel; } FeedsProxyModel* FeedReader::feedsProxyModel() const { return m_feedsProxyModel; }