Refactoring.

This commit is contained in:
Martin Rotter 2016-08-15 10:05:47 +02:00
parent ebc9b2b1c0
commit d8ecbcac04
17 changed files with 321 additions and 342 deletions

View File

@ -23,19 +23,16 @@
#include <QThread>
#include <QDebug>
#include <QMetaType>
#include <QMutex>
#include <QThreadPool>
FeedDownloader::FeedDownloader(QObject *parent)
: QObject(parent), m_results(FeedDownloadResults()), m_msgUpdateMutex(new QMutex()), m_feedsUpdated(0), m_feedsToUpdate(0),
: QObject(parent), m_results(FeedDownloadResults()), m_feedsUpdated(0), m_feedsToUpdate(0),
m_feedsUpdating(0), m_feedsTotalCount(0), m_stopUpdate(false) {
qRegisterMetaType<FeedDownloadResults>("FeedDownloadResults");
}
FeedDownloader::~FeedDownloader() {
m_msgUpdateMutex->unlock();
delete m_msgUpdateMutex;
qDebug("Destroying FeedDownloader instance.");
}
@ -109,9 +106,7 @@ void FeedDownloader::oneFeedUpdateFinished(const QList<Message> &messages) {
<< feed->customId() << " in thread: \'"
<< QThread::currentThreadId() << "\'.";
m_msgUpdateMutex->lock();
int updated_messages = messages.isEmpty() ? 0 : feed->updateMessages(messages);
m_msgUpdateMutex->unlock();
if (updated_messages > 0) {
m_results.appendUpdatedFeed(QPair<QString,int>(feed->title(), updated_messages));

View File

@ -49,8 +49,6 @@ class FeedDownloadResults {
QList<QPair<QString,int> > m_updatedFeeds;
};
class QMutex;
// This class offers means to "update" feeds and "special" categories.
// NOTE: This class is used within separate thread.
class FeedDownloader : public QObject {
@ -94,7 +92,6 @@ class FeedDownloader : public QObject {
void finalizeUpdate();
FeedDownloadResults m_results;
QMutex *m_msgUpdateMutex;
int m_feedsUpdated;
int m_feedsToUpdate;

View File

@ -45,10 +45,7 @@
#include <algorithm>
FeedsModel::FeedsModel(QObject *parent)
: QAbstractItemModel(parent), m_autoUpdateTimer(new QTimer(this)),
m_feedDownloaderThread(nullptr), m_feedDownloader(nullptr),
m_dbCleanerThread(nullptr), m_dbCleaner(nullptr) {
FeedsModel::FeedsModel(QObject *parent) : QAbstractItemModel(parent) {
setObjectName(QSL("FeedsModel"));
// Create root item.
@ -66,9 +63,6 @@ FeedsModel::FeedsModel(QObject *parent)
m_tooltipData << /*: Feed list header "titles" column tooltip.*/ tr("Titles of feeds/categories.") <<
/*: Feed list header "counts" column tooltip.*/ tr("Counts of unread/all mesages.");
connect(m_autoUpdateTimer, SIGNAL(timeout()), this, SLOT(executeNextAutoUpdate()));
updateAutoUpdateStatus();
}
FeedsModel::~FeedsModel() {
@ -83,78 +77,12 @@ FeedsModel::~FeedsModel() {
}
void FeedsModel::quit() {
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()) {
markItemCleared(m_rootItem, true);
}
}
void FeedsModel::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, SIGNAL(feedsUpdateRequested(QList<Feed*>)), m_feedDownloader, SLOT(updateFeeds(QList<Feed*>)));
connect(m_feedDownloaderThread, SIGNAL(finished()), m_feedDownloaderThread, SLOT(deleteLater()));
connect(m_feedDownloader, SIGNAL(finished(FeedDownloadResults)), this, SLOT(onFeedUpdatesFinished(FeedDownloadResults)));
connect(m_feedDownloader, SIGNAL(started()), this, SLOT(onFeedUpdatesStarted()));
connect(m_feedDownloader, SIGNAL(progress(const Feed*,int,int)), this, SLOT(onFeedUpdatesProgress(const Feed*,int,int)));
// Connections are made, start the feed downloader thread.
m_feedDownloaderThread->start();
}
emit feedsUpdateRequested(feeds);
}
/*
void FeedsModel::onFeedUpdatesStarted() {
//: Text display in status bar when feed update is started.
qApp->mainForm()->statusBar()->showProgressFeeds(0, tr("Feed update started"));
@ -180,27 +108,7 @@ void FeedsModel::onFeedUpdatesFinished(const FeedDownloadResults &results) {
emit feedsUpdateFinished();
}
void FeedsModel::updateAllFeeds() {
updateFeeds(m_rootItem->getSubTreeFeeds());
}
DatabaseCleaner *FeedsModel::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;
}
*/
QMimeData *FeedsModel::mimeData(const QModelIndexList &indexes) const {
QMimeData *mime_data = new QMimeData();
@ -298,62 +206,6 @@ Qt::ItemFlags FeedsModel::flags(const QModelIndex &index) const {
return base_flags | additional_flags;
}
void FeedsModel::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 = 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.
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 FeedsModel::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.");
}
}
QVariant FeedsModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (orientation != Qt::Horizontal) {
return QVariant();
@ -612,10 +464,6 @@ bool FeedsModel::hasAnyFeedNewMessages() const {
return false;
}
bool FeedsModel::isFeedUpdateRunning() const {
return m_feedDownloader != nullptr && m_feedDownloader->isUpdateRunning();
}
void FeedsModel::reloadChangedLayout(QModelIndexList list) {
while (!list.isEmpty()) {
QModelIndex indx = list.takeFirst();
@ -727,12 +575,6 @@ void FeedsModel::loadActivatedServiceAccounts() {
}
}
void FeedsModel::stopRunningFeedUpdate() {
if (m_feedDownloader != nullptr) {
m_feedDownloader->stopRunningUpdate();
}
}
QList<Feed*> FeedsModel::feedsForIndex(const QModelIndex &index) const {
return itemForIndex(index)->getSubTreeFeeds();
}

View File

@ -40,9 +40,6 @@ class FeedsModel : public QAbstractItemModel {
explicit FeedsModel(QObject *parent = 0);
virtual ~FeedsModel();
// Access to DB cleaner.
DatabaseCleaner *databaseCleaner();
// Model implementation.
inline QVariant data(const QModelIndex &index, int role) const {
// Return data according to item.
@ -124,18 +121,9 @@ class FeedsModel : public QAbstractItemModel {
return m_rootItem;
}
bool isFeedUpdateRunning() const;
// Resets global auto-update intervals according to settings
// and starts/stop the timer as needed.
void updateAutoUpdateStatus();
// Does necessary job before quitting this component.
void quit();
// Schedules given feeds for update.
void updateFeeds(const QList<Feed*> &feeds);
// Adds given service root account.
bool addServiceAccount(ServiceRoot *root, bool freshly_activated);
@ -143,11 +131,6 @@ class FeedsModel : public QAbstractItemModel {
void loadActivatedServiceAccounts();
public slots:
void stopRunningFeedUpdate();
// Schedules all feeds from all accounts for update.
void updateAllFeeds();
// Checks if new parent node is different from one used by original node.
// If it is, then it reassigns original_node to new parent.
void reassignNodeToNewParent(RootItem *original_node, RootItem *new_parent);
@ -182,23 +165,7 @@ class FeedsModel : public QAbstractItemModel {
private slots:
void onItemDataChanged(const QList<RootItem*> &items);
// Is executed when next auto-update round could be done.
void executeNextAutoUpdate();
// Reacts on feed updates.
void onFeedUpdatesStarted();
void onFeedUpdatesProgress(const Feed *feed, int current, int total);
void onFeedUpdatesFinished(const FeedDownloadResults &results);
signals:
// Update of feeds is finished.
void feedsUpdateFinished();
void feedsUpdateStarted();
// Emitted when model requests update of some feeds.
void feedsUpdateRequested(QList<Feed*> feeds);
// Emitted if counts of messages are changed.
void messageCountsChanged(int unread_messages, int total_messages, bool any_feed_has_unread_messages);
@ -221,18 +188,6 @@ class FeedsModel : public QAbstractItemModel {
QList<QString> m_headerData;
QList<QString> m_tooltipData;
QIcon m_countsIcon;
// Auto-update stuff.
QTimer *m_autoUpdateTimer;
bool m_globalAutoUpdateEnabled;
int m_globalAutoUpdateInitialInterval;
int m_globalAutoUpdateRemainingInterval;
QThread *m_feedDownloaderThread;
FeedDownloader *m_feedDownloader;
QThread *m_dbCleanerThread;
DatabaseCleaner *m_dbCleaner;
};
#endif // FEEDSMODEL_H

View File

@ -120,7 +120,7 @@
#define APP_LOG_PATH "data/log"
#define APP_LOG_FILE "log.txt"
#define APP_QUIT_INSTANCE "app_quit"
#define APP_QUIT_INSTANCE "-q"
#define APP_IS_RUNNING "app_is_running"
#define APP_SKIN_DEFAULT "base/vergilius.xml"
#define APP_THEME_DEFAULT "Faenza"

View File

@ -186,7 +186,7 @@ void FormMain::updateAddItemMenu() {
// NOTE: Clear here deletes items from memory but only those OWNED by the menu.
m_ui->m_menuAddItem->clear();
foreach (ServiceRoot *activated_root, tabWidget()->feedMessageViewer()->feedsView()->sourceModel()->serviceRoots()) {
foreach (ServiceRoot *activated_root, qApp->feedReader()->feedsModel()->serviceRoots()) {
QMenu *root_menu = new QMenu(activated_root->title(), m_ui->m_menuAddItem);
root_menu->setIcon(activated_root->icon());
root_menu->setToolTip(activated_root->description());
@ -233,7 +233,7 @@ void FormMain::updateAddItemMenu() {
void FormMain::updateRecycleBinMenu() {
m_ui->m_menuRecycleBin->clear();
foreach (const ServiceRoot *activated_root, tabWidget()->feedMessageViewer()->feedsView()->sourceModel()->serviceRoots()) {
foreach (const ServiceRoot *activated_root, qApp->feedReader()->feedsModel()->serviceRoots()) {
QMenu *root_menu = new QMenu(activated_root->title(), m_ui->m_menuRecycleBin);
root_menu->setIcon(activated_root->icon());
root_menu->setToolTip(activated_root->description());
@ -273,7 +273,7 @@ void FormMain::updateRecycleBinMenu() {
void FormMain::updateAccountsMenu() {
m_ui->m_menuAccounts->clear();
foreach (ServiceRoot *activated_root, tabWidget()->feedMessageViewer()->feedsView()->sourceModel()->serviceRoots()) {
foreach (ServiceRoot *activated_root, qApp->feedReader()->feedsModel()->serviceRoots()) {
QMenu *root_menu = new QMenu(activated_root->title(), m_ui->m_menuAccounts);
root_menu->setIcon(activated_root->icon());
root_menu->setToolTip(activated_root->description());
@ -558,7 +558,7 @@ void FormMain::showWiki() {
void FormMain::showAddAccountDialog() {
QScopedPointer<FormAddAccount> form_update(new FormAddAccount(qApp->feedReader()->feedServices(),
tabWidget()->feedMessageViewer()->feedsView()->sourceModel(),
qApp->feedReader()->feedsModel(),
this));
form_update->exec();
}

View File

@ -187,11 +187,11 @@ void FormUpdate::startUpdate() {
qDebug("Preparing to launch external installer '%s'.", qPrintable(QDir::toNativeSeparators(m_updateFilePath)));
#if defined(Q_OS_WIN)
HINSTANCE exec_result = ShellExecute(NULL,
NULL,
HINSTANCE exec_result = ShellExecute(nullptr,
nullptr,
reinterpret_cast<const WCHAR*>(QDir::toNativeSeparators(m_updateFilePath).utf16()),
NULL,
NULL,
nullptr,
nullptr,
SW_NORMAL);
if (((int)exec_result) <= 32) {

View File

@ -19,6 +19,7 @@
#include "miscellaneous/application.h"
#include "miscellaneous/iconfactory.h"
#include "miscellaneous/feedreader.h"
#include "gui/dialogs/formmain.h"
#include "gui/tabwidget.h"
#include "gui/feedmessageviewer.h"
@ -77,7 +78,7 @@ void DiscoverFeedsButton::linkTriggered(QAction *action) {
void DiscoverFeedsButton::fillMenu() {
menu()->clear();
foreach (const ServiceRoot *root, qApp->mainForm()->tabWidget()->feedMessageViewer()->feedsView()->sourceModel()->serviceRoots()) {
foreach (const ServiceRoot *root, qApp->feedReader()->feedsModel()->serviceRoots()) {
QMenu *root_menu = menu()->addMenu(root->icon(), root->title());
foreach (const QString &url, m_addresses) {

View File

@ -23,6 +23,7 @@
#include "miscellaneous/iconfactory.h"
#include "miscellaneous/mutex.h"
#include "miscellaneous/databasecleaner.h"
#include "miscellaneous/feedreader.h"
#include "core/messagesproxymodel.h"
#include "core/feeddownloader.h"
#include "core/feedsproxymodel.h"
@ -193,7 +194,7 @@ void FeedMessageViewer::updateMessageButtonsAvailability() {
}
void FeedMessageViewer::updateFeedButtonsAvailability() {
const bool is_update_running = feedsView()->sourceModel()->isFeedUpdateRunning();
const bool is_update_running = qApp->feedReader()->isFeedUpdateRunning();
const bool critical_action_running = qApp->feedUpdateLock()->isLocked();
const RootItem *selected_item = feedsView()->selectedItem();
const bool anything_selected = selected_item != nullptr;
@ -202,6 +203,8 @@ void FeedMessageViewer::updateFeedButtonsAvailability() {
const bool service_selected = anything_selected && selected_item->kind() == RootItemKind::ServiceRoot;
const FormMain *form_main = qApp->mainForm();
// TODO: přesunou do form main.
form_main->m_ui->m_actionStopRunningItemsUpdate->setEnabled(is_update_running);
form_main->m_ui->m_actionBackupDatabaseSettings->setEnabled(!critical_action_running);
form_main->m_ui->m_actionCleanupDatabase->setEnabled(!critical_action_running);
@ -414,7 +417,7 @@ void FeedMessageViewer::initializeViews() {
void FeedMessageViewer::showDbCleanupAssistant() {
if (qApp->feedUpdateLock()->tryLock()) {
QScopedPointer<FormDatabaseCleanup> form_pointer(new FormDatabaseCleanup(this));
form_pointer.data()->setCleaner(m_feedsView->sourceModel()->databaseCleaner());
form_pointer.data()->setCleaner(qApp->feedReader()->databaseCleaner());
form_pointer.data()->exec();
qApp->feedUpdateLock()->unlock();
@ -443,7 +446,7 @@ void FeedMessageViewer::onFeedsUpdateFinished() {
void FeedMessageViewer::onFeedsUpdateStarted() {
// Check only "Stop running update" button.
const bool is_update_running = feedsView()->sourceModel()->isFeedUpdateRunning();
const bool is_update_running = qApp->feedReader()->isFeedUpdateRunning();
qApp->mainForm()->m_ui->m_actionStopRunningItemsUpdate->setEnabled(is_update_running);
}

View File

@ -22,6 +22,7 @@
#include "core/feedsproxymodel.h"
#include "services/abstract/rootitem.h"
#include "miscellaneous/systemfactory.h"
#include "miscellaneous/feedreader.h"
#include "miscellaneous/mutex.h"
#include "gui/systemtrayicon.h"
#include "gui/messagebox.h"
@ -50,8 +51,8 @@ FeedsView::FeedsView(QWidget *parent)
setObjectName(QSL("FeedsView"));
// Allocate models.
m_sourceModel = new FeedsModel(this);
m_proxyModel = new FeedsProxyModel(m_sourceModel, this);
m_sourceModel = qApp->feedReader()->feedsModel();
m_proxyModel = qApp->feedReader()->feedsProxyModel();
// Connections.
connect(m_sourceModel, SIGNAL(requireItemValidationAfterDragDrop(QModelIndex)), this, SLOT(validateItemAfterDragDrop(QModelIndex)));
@ -202,11 +203,11 @@ void FeedsView::expandCollapseCurrentItem() {
}
void FeedsView::updateAllItems() {
m_sourceModel->updateAllFeeds();
qApp->feedReader()->updateAllFeeds();
}
void FeedsView::updateSelectedItems() {
m_sourceModel->updateFeeds(selectedFeeds());
qApp->feedReader()->updateFeeds(selectedFeeds());
}
void FeedsView::clearSelectedFeeds() {

View File

@ -20,6 +20,7 @@
#include "core/messagesproxymodel.h"
#include "core/messagesmodel.h"
#include "miscellaneous/settings.h"
#include "miscellaneous/feedreader.h"
#include "network-web/networkfactory.h"
#include "network-web/webfactory.h"
#include "gui/dialogs/formmain.h"
@ -38,8 +39,8 @@ MessagesView::MessagesView(QWidget *parent)
m_contextMenu(nullptr),
m_columnsAdjusted(false),
m_batchUnreadSwitch(false) {
m_sourceModel = new MessagesModel(this);
m_proxyModel = new MessagesProxyModel(m_sourceModel, this);
m_sourceModel = qApp->feedReader()->messagesModel();
m_proxyModel = qApp->feedReader()->messagesProxyModel();
// Forward count changes to the view.
createConnections();

View File

@ -19,6 +19,7 @@
#include "definitions/definitions.h"
#include "miscellaneous/application.h"
#include "miscellaneous/feedreader.h"
#include "gui/dialogs/formmain.h"
#include "gui/feedmessageviewer.h"
#include "gui/feedsview.h"
@ -124,10 +125,11 @@ void SettingsFeedsMessages::saveSettings() {
settings()->setValue(GROUP(Messages), Messages::PreviewerFontStandard, m_ui->m_lblMessagesFont->font().toString());
qApp->mainForm()->tabWidget()->feedMessageViewer()->loadMessageViewerFonts();
qApp->mainForm()->tabWidget()->feedMessageViewer()->feedsView()->sourceModel()->updateAutoUpdateStatus();
qApp->mainForm()->tabWidget()->feedMessageViewer()->feedsView()->sourceModel()->reloadWholeLayout();
qApp->mainForm()->tabWidget()->feedMessageViewer()->messagesView()->sourceModel()->updateDateFormat();
qApp->mainForm()->tabWidget()->feedMessageViewer()->messagesView()->sourceModel()->reloadWholeLayout();
qApp->feedReader()->updateAutoUpdateStatus();
qApp->feedReader()->feedsModel()->reloadWholeLayout();
qApp->feedReader()->messagesModel()->updateDateFormat();
qApp->feedReader()->messagesModel()->reloadWholeLayout();
onEndSaveSettings();
}

View File

@ -42,19 +42,25 @@
int main(int argc, char *argv[]) {
bool run_minimal_without_gui = false;
for (int i; i < argc; i++) {
for (int i = 0; i < argc; i++) {
const QString str = QString::fromLocal8Bit(argv[i]);
if (str == "-h" || str == "--help") {
if (str == "-h") {
qDebug("Usage: rssguard [OPTIONS]\n\n"
"Option\t\tGNU long option\t\tMeaning\n"
"-h\t\t--help\t\t\tDisplays this help.\n"
"-c\t\t--cron\t\t\tStarts the application without GUI and will regularly update configured feeds. **\n\n"
"** ");
"Option\t\tMeaning\n"
"-h\t\tDisplays this help.\n"
"-q\t\tQuits minimal non-GUI instance of application already running on this machine.\n"
"-c\t\tStarts the application without GUI and will regularly update configured feeds.\n\n"
"Running with '-c' option starts application without GUI and/or tray icon. No "
"message boxes will be shown, no GUI. Only minimal functionality will be enabled, including periodic checking for "
"feed/message updates. Note that you must configure the application via GUI first! So to really get periodic "
"feed updates, you must import some feeds and set their auto-update policy first in the GUI.\n\n"
"You can quick the minimal non-GUI application instance either by just killing it or by running the application "
"executable again with -q option.");
return EXIT_SUCCESS;
}
else if (str == "-c" || str == "--cron") {
else if (str == "-c") {
run_minimal_without_gui = true;
}
}
@ -106,50 +112,56 @@ int main(int argc, char *argv[]) {
qDebug().nospace() << "Creating main application form in thread: \'" << QThread::currentThreadId() << "\'.";
// Instantiate main application window.
FormMain main_window;
// Set correct information for main window.
main_window.setWindowTitle(APP_LONG_NAME);
// Now is a good time to initialize dynamic keyboard shortcuts.
DynamicShortcuts::load(qApp->userActions());
// Display main window.
if (qApp->settings()->value(GROUP(GUI), SETTING(GUI::MainWindowStartsHidden)).toBool() && SystemTrayIcon::isSystemTrayActivated()) {
qDebug("Hiding the main window when the application is starting.");
main_window.switchVisibility(true);
}
else {
qDebug("Showing the main window when the application is starting.");
main_window.show();
}
// Display tray icon if it is enabled and available.
if (SystemTrayIcon::isSystemTrayActivated()) {
qApp->showTrayIcon();
}
// Load activated accounts.
qApp->mainForm()->tabWidget()->feedMessageViewer()->feedsView()->sourceModel()->loadActivatedServiceAccounts();
qApp->mainForm()->tabWidget()->feedMessageViewer()->feedsView()->loadAllExpandStates();
qApp->feedReader()->feedsModel()->loadActivatedServiceAccounts();
// Setup single-instance behavior.
QObject::connect(&application, &Application::messageReceived, &application, &Application::processExecutionMessage);
if (qApp->isFirstRun() || qApp->isFirstRun(APP_VERSION)) {
qApp->showGuiMessage(QSL(APP_NAME), QObject::tr("Welcome to %1.\n\nPlease, check NEW stuff included in this\n"
"version by clicking this popup notification.").arg(APP_LONG_NAME),
QSystemTrayIcon::NoIcon, 0, false, &main_window, SLOT(showAbout()));
if (!run_minimal_without_gui) {
// Instantiate main application window.
FormMain main_window;
// Set correct information for main window.
main_window.setWindowTitle(APP_LONG_NAME);
// Now is a good time to initialize dynamic keyboard shortcuts.
DynamicShortcuts::load(qApp->userActions());
// Display main window.
if (qApp->settings()->value(GROUP(GUI), SETTING(GUI::MainWindowStartsHidden)).toBool() && SystemTrayIcon::isSystemTrayActivated()) {
qDebug("Hiding the main window when the application is starting.");
main_window.switchVisibility(true);
}
else {
qDebug("Showing the main window when the application is starting.");
main_window.show();
}
// Display tray icon if it is enabled and available.
if (SystemTrayIcon::isSystemTrayActivated()) {
qApp->showTrayIcon();
}
if (qApp->isFirstRun() || qApp->isFirstRun(APP_VERSION)) {
qApp->showGuiMessage(QSL(APP_NAME), QObject::tr("Welcome to %1.\n\nPlease, check NEW stuff included in this\n"
"version by clicking this popup notification.").arg(APP_LONG_NAME),
QSystemTrayIcon::NoIcon, 0, false, &main_window, SLOT(showAbout()));
}
else {
qApp->showGuiMessage(QSL(APP_NAME), QObject::tr("Welcome to %1.").arg(APP_NAME), QSystemTrayIcon::NoIcon);
}
if (qApp->settings()->value(GROUP(General), SETTING(General::UpdateOnStartup)).toBool()) {
QTimer::singleShot(STARTUP_UPDATE_DELAY, application.system(), SLOT(checkForUpdatesOnStartup()));
}
qApp->mainForm()->tabWidget()->feedMessageViewer()->feedsView()->loadAllExpandStates();
// Enter global event loop.
return Application::exec();
}
else {
qApp->showGuiMessage(QSL(APP_NAME), QObject::tr("Welcome to %1.").arg(APP_NAME), QSystemTrayIcon::NoIcon);
return Application::exec();
}
if (qApp->settings()->value(GROUP(General), SETTING(General::UpdateOnStartup)).toBool()) {
QTimer::singleShot(STARTUP_UPDATE_DELAY, application.system(), SLOT(checkForUpdatesOnStartup()));
}
// Enter global event loop.
return Application::exec();
}

View File

@ -84,6 +84,38 @@ bool Application::isFirstRun(const QString &version) {
}
}
SystemFactory *Application::system() {
if (m_system == nullptr) {
m_system = new SystemFactory(this);
}
return m_system;
}
SkinFactory *Application::skins() {
if (m_skins == nullptr) {
m_skins = new SkinFactory(this);
}
return m_skins;
}
Localization *Application::localization() {
if (m_localization == nullptr) {
m_localization = new Localization(this);
}
return m_localization;
}
DatabaseFactory *Application::database() {
if (m_database == nullptr) {
m_database = new DatabaseFactory(this);
}
return m_database;
}
void Application::eliminateFirstRun() {
settings()->setValue(GROUP(General), General::FirstRun, false);
}
@ -115,6 +147,14 @@ DownloadManager *Application::downloadManager() {
return m_downloadManager;
}
Settings *Application::settings() {
if (m_settings == nullptr) {
m_settings = Settings::setupSettings(this);
}
return m_settings;
}
Mutex *Application::feedUpdateLock() {
if (m_updateFeedsLock.isNull()) {
// NOTE: Cannot use parent hierarchy because this method can be usually called
@ -125,10 +165,18 @@ Mutex *Application::feedUpdateLock() {
return m_updateFeedsLock.data();
}
FormMain *Application::mainForm() {
return m_mainForm;
}
QWidget *Application::mainFormWidget() {
return m_mainForm;
}
void Application::setMainForm(FormMain *main_form) {
m_mainForm = main_form;
}
void Application::backupDatabaseSettings(bool backup_database, bool backup_settings,
const QString &target_path, const QString &backup_name) {
if (!QFileInfo(target_path).isWritable()) {
@ -201,8 +249,7 @@ void Application::processExecutionMessage(const QString &message) {
SystemTrayIcon *Application::trayIcon() {
if (m_trayIcon == nullptr) {
m_trayIcon = new SystemTrayIcon(APP_ICON_PATH, APP_ICON_PLAIN_PATH, m_mainForm);
connect(m_trayIcon, SIGNAL(shown()),
m_mainForm->tabWidget()->feedMessageViewer()->feedsView()->sourceModel(), SLOT(notifyWithCounts()));
connect(m_trayIcon, &SystemTrayIcon::shown, m_feedReader->feedsModel(), &FeedsModel::notifyWithCounts);
}
return m_trayIcon;

View File

@ -68,61 +68,25 @@ class Application : public QtSingleApplication {
// Check whether GIVEN VERSION of the application starts for the first time.
bool isFirstRun(const QString &version);
inline SystemFactory *system() {
if (m_system == NULL) {
m_system = new SystemFactory(this);
}
return m_system;
}
inline SkinFactory *skins() {
if (m_skins == NULL) {
m_skins = new SkinFactory(this);
}
return m_skins;
}
inline Localization *localization() {
if (m_localization == NULL) {
m_localization = new Localization(this);
}
return m_localization;
}
inline DatabaseFactory *database() {
if (m_database == NULL) {
m_database = new DatabaseFactory(this);
}
return m_database;
}
SystemFactory *system();
SkinFactory *skins();
Localization *localization();
DatabaseFactory *database();
IconFactory *icons();
DownloadManager *downloadManager();
inline Settings *settings() {
if (m_settings == NULL) {
m_settings = Settings::setupSettings(this);
}
return m_settings;
}
Settings *settings();
// Access to application-wide close lock.
Mutex *feedUpdateLock();
inline FormMain *mainForm() {
return m_mainForm;
}
FormMain *mainForm();
QWidget *mainFormWidget();
void setMainForm(FormMain *main_form) {
m_mainForm = main_form;
}
// Access to application tray icon. Always use this in cooperation with
// SystemTrayIcon::isSystemTrayActivated().
SystemTrayIcon *trayIcon();
void setMainForm(FormMain *main_form);
inline QString tempFolderPath() {
return IOFactory::getSystemFolder(QStandardPaths::TempLocation);
@ -142,10 +106,6 @@ class Application : public QtSingleApplication {
const QString &source_database_file_path = QString(),
const QString &source_settings_file_path = QString());
// Access to application tray icon. Always use this in cooperation with
// SystemTrayIcon::isSystemTrayActivated().
SystemTrayIcon *trayIcon();
void showTrayIcon();
void deleteTrayIcon();

146
src/miscellaneous/feedreader.cpp Normal file → Executable file
View File

@ -27,6 +27,8 @@
#include "core/messagesproxymodel.h"
#include "core/feeddownloader.h"
#include "miscellaneous/databasecleaner.h"
#include "miscellaneous/application.h"
#include "miscellaneous/mutex.h"
#include <QThread>
#include <QTimer>
@ -38,9 +40,12 @@ FeedReader::FeedReader(QObject *parent)
m_dbCleanerThread(nullptr), m_dbCleaner(nullptr) {
m_feedDownloader = new FeedDownloader(this);
m_feedsModel = new FeedsModel(this);
m_feedProxyModel = new FeedsProxyModel(m_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();
}
FeedReader::~FeedReader() {
@ -59,6 +64,67 @@ QList<ServiceEntryPoint*> FeedReader::feedServices() {
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);
// 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();
@ -91,13 +157,87 @@ MessagesModel *FeedReader::messagesModel() const {
void FeedReader::start() {
}
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();
}
}
MessagesProxyModel *FeedReader::messagesProxyModel() const {
return m_messagesProxyModel;
}
FeedsProxyModel *FeedReader::feedProxyModel() const {
return m_feedProxyModel;
FeedsProxyModel *FeedReader::feedsProxyModel() const {
return m_feedsProxyModel;
}

29
src/miscellaneous/feedreader.h Normal file → Executable file
View File

@ -20,6 +20,8 @@
#include <QObject>
#include "services/abstract/feed.h"
class FeedDownloader;
class FeedsModel;
@ -29,7 +31,6 @@ class FeedsProxyModel;
class ServiceEntryPoint;
class DatabaseCleaner;
class QTimer;
class Feed;
class FeedReader : public QObject {
Q_OBJECT
@ -48,18 +49,40 @@ class FeedReader : public QObject {
FeedDownloader *feedDownloader() const;
FeedsModel *feedsModel() const;
MessagesModel *messagesModel() const;
FeedsProxyModel *feedProxyModel() const;
FeedsProxyModel *feedsProxyModel() const;
MessagesProxyModel *messagesProxyModel() const;
// Schedules given feeds for update.
void updateFeeds(const QList<Feed*> &feeds);
bool isFeedUpdateRunning() const;
// Resets global auto-update intervals according to settings
// and starts/stop the timer as needed.
void updateAutoUpdateStatus();
public slots:
// Schedules all feeds from all accounts for update.
void updateAllFeeds();
void stopRunningFeedUpdate();
void start();
void stop();
private slots:
// Is executed when next auto-update round could be done.
void executeNextAutoUpdate();
signals:
// Emitted when model requests update of some feeds.
void feedsUpdateRequested(QList<Feed*> feeds);
private:
QList<ServiceEntryPoint*> m_feedServices;
FeedsModel *m_feedsModel;
FeedsProxyModel *m_feedProxyModel;
FeedsProxyModel *m_feedsProxyModel;
MessagesModel *m_messagesModel;
MessagesProxyModel *m_messagesProxyModel;