rssguard/src/miscellaneous/feedreader.cpp
2016-08-16 11:35:02 +02:00

254 lines
8.9 KiB
C++
Executable File

// This file is part of RSS Guard.
//
// Copyright (C) 2011-2016 by Martin Rotter <rotter.martinos@gmail.com>
//
// 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 <http://www.gnu.org/licenses/>.
#include "miscellaneous/feedreader.h"
#include "services/standard/standardserviceentrypoint.h"
#include "services/owncloud/owncloudserviceentrypoint.h"
#include "services/tt-rss/ttrssserviceentrypoint.h"
#include "core/feedsmodel.h"
#include "core/feedsproxymodel.h"
#include "core/messagesmodel.h"
#include "core/messagesproxymodel.h"
#include "core/feeddownloader.h"
#include "miscellaneous/databasecleaner.h"
#include "miscellaneous/application.h"
#include "miscellaneous/mutex.h"
#include <QThread>
#include <QTimer>
FeedReader::FeedReader(QObject *parent)
: QObject(parent), m_feedServices(QList<ServiceEntryPoint*>()), m_autoUpdateTimer(new QTimer(this)),
m_feedDownloaderThread(nullptr), m_feedDownloader(nullptr),
m_dbCleanerThread(nullptr), m_dbCleaner(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();
if (qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::FeedsUpdateOnStartup)).toBool()) {
qDebug("Requesting update for all feeds on application startup.");
QTimer::singleShot(STARTUP_UPDATE_DELAY, this, SLOT(updateAllFeeds()));
}
}
FeedReader::~FeedReader() {
qDebug("Destroying FeedReader instance.");
qDeleteAll(m_feedServices);
}
QList<ServiceEntryPoint*> FeedReader::feedServices() {
if (m_feedServices.isEmpty()) {
// NOTE: All installed services create their entry points here.
m_feedServices.append(new StandardServiceEntryPoint());
m_feedServices.append(new TtRssServiceEntryPoint());
m_feedServices.append(new OwnCloudServiceEntryPoint());
}
return m_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."),
QSystemTrayIcon::Warning, qApp->mainFormWidget(), true);
return;
}
if (m_feedDownloader == nullptr) {
m_feedDownloader = new FeedDownloader();
m_feedDownloaderThread = new QThread();
// Downloader setup.
qRegisterMetaType<QList<Feed*> >("QList<Feed*>");
m_feedDownloader->moveToThread(m_feedDownloaderThread);
connect(this, &FeedReader::feedsUpdateRequested, m_feedDownloader, &FeedDownloader::updateFeeds);
connect(m_feedDownloaderThread, &QThread::finished, m_feedDownloaderThread, &QThread::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);
// Connections are made, start the feed downloader thread.
m_feedDownloaderThread->start();
}
emit feedsUpdateRequested(feeds);
}
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();
// 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.");
}
}
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();
}
DatabaseCleaner *FeedReader::databaseCleaner() {
if (m_dbCleaner == nullptr) {
m_dbCleaner = new DatabaseCleaner();
m_dbCleanerThread = new QThread();
// Downloader setup.
qRegisterMetaType<CleanerOrders>("CleanerOrders");
m_dbCleaner->moveToThread(m_dbCleanerThread);
connect(m_dbCleanerThread, SIGNAL(finished()), m_dbCleanerThread, SLOT(deleteLater()));
// Connections are made, start the feed downloader thread.
m_dbCleanerThread->start();
}
return m_dbCleaner;
}
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->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<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.
// TODO: Co dělat v non-GUI módu.
qApp->showGuiMessage(tr("Starting auto-update of some feeds"),
tr("I will auto-update %n feed(s).", 0, feeds_for_update.size()),
QSystemTrayIcon::Information);
}
}
void FeedReader::stop() {
if (m_autoUpdateTimer->isActive()) {
m_autoUpdateTimer->stop();
}
// Close worker threads.
if (m_feedDownloaderThread != nullptr && m_feedDownloaderThread->isRunning()) {
m_feedDownloader->stopRunningUpdate();
qDebug("Quitting feed downloader thread.");
m_feedDownloaderThread->quit();
if (!m_feedDownloaderThread->wait(CLOSE_LOCK_TIMEOUT)) {
qCritical("Feed downloader thread is running despite it was told to quit. Terminating it.");
m_feedDownloaderThread->terminate();
}
}
if (m_dbCleanerThread != nullptr && m_dbCleanerThread->isRunning()) {
qDebug("Quitting database cleaner thread.");
m_dbCleanerThread->quit();
if (!m_dbCleanerThread->wait(CLOSE_LOCK_TIMEOUT)) {
qCritical("Database cleaner thread is running despite it was told to quit. Terminating it.");
m_dbCleanerThread->terminate();
}
}
// Close workers.
if (m_feedDownloader != nullptr) {
qDebug("Feed downloader exists. Deleting it from memory.");
m_feedDownloader->deleteLater();
}
if (m_dbCleaner != nullptr) {
qDebug("Database cleaner exists. Deleting it from memory.");
m_dbCleaner->deleteLater();
}
if (qApp->settings()->value(GROUP(Messages), SETTING(Messages::ClearReadOnExit)).toBool()) {
m_feedsModel->markItemCleared(m_feedsModel->rootItem(), true);
}
}
MessagesProxyModel *FeedReader::messagesProxyModel() const {
return m_messagesProxyModel;
}
FeedsProxyModel *FeedReader::feedsProxyModel() const {
return m_feedsProxyModel;
}