2014-02-26 07:41:40 +01:00
|
|
|
// This file is part of RSS Guard.
|
|
|
|
//
|
2015-01-04 14:12:14 +01:00
|
|
|
// Copyright (C) 2011-2015 by Martin Rotter <rotter.martinos@gmail.com>
|
2014-02-26 07:41:40 +01:00
|
|
|
//
|
|
|
|
// 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/>.
|
|
|
|
|
2013-11-12 20:24:19 +01:00
|
|
|
#include "core/feedsmodel.h"
|
2013-12-21 21:08:52 +01:00
|
|
|
|
2014-03-27 08:40:23 +01:00
|
|
|
#include "definitions/definitions.h"
|
2015-10-30 10:47:43 +01:00
|
|
|
#include "services/abstract/feed.h"
|
2015-11-03 13:16:11 +01:00
|
|
|
#include "services/abstract/category.h"
|
2015-10-30 13:07:42 +01:00
|
|
|
#include "services/abstract/serviceroot.h"
|
2015-11-24 09:13:43 +01:00
|
|
|
#include "services/abstract/recyclebin.h"
|
2015-11-03 13:16:11 +01:00
|
|
|
#include "services/standard/standardserviceroot.h"
|
2014-03-27 08:40:23 +01:00
|
|
|
#include "miscellaneous/textfactory.h"
|
|
|
|
#include "miscellaneous/databasefactory.h"
|
2015-11-23 19:10:16 +01:00
|
|
|
#include "miscellaneous/databasecleaner.h"
|
2014-03-27 08:40:23 +01:00
|
|
|
#include "miscellaneous/iconfactory.h"
|
2015-07-29 07:59:00 +02:00
|
|
|
#include "miscellaneous/mutex.h"
|
2014-10-28 17:45:37 +01:00
|
|
|
#include "gui/messagebox.h"
|
2015-11-23 14:23:51 +01:00
|
|
|
#include "gui/statusbar.h"
|
|
|
|
#include "gui/dialogs/formmain.h"
|
|
|
|
#include "core/feeddownloader.h"
|
2013-12-12 10:10:17 +01:00
|
|
|
|
2015-11-23 14:23:51 +01:00
|
|
|
#include <QThread>
|
2013-12-21 21:08:52 +01:00
|
|
|
#include <QSqlError>
|
|
|
|
#include <QSqlQuery>
|
|
|
|
#include <QSqlRecord>
|
|
|
|
#include <QPair>
|
2014-09-02 16:47:50 +02:00
|
|
|
#include <QStack>
|
2014-10-28 17:45:37 +01:00
|
|
|
#include <QMimeData>
|
2015-07-29 07:59:00 +02:00
|
|
|
#include <QTimer>
|
2013-12-21 21:08:52 +01:00
|
|
|
|
2013-12-27 10:24:07 +01:00
|
|
|
#include <algorithm>
|
|
|
|
|
2013-11-12 20:24:19 +01:00
|
|
|
|
2014-09-17 10:00:04 +02:00
|
|
|
FeedsModel::FeedsModel(QObject *parent)
|
2015-11-23 19:10:16 +01:00
|
|
|
: QAbstractItemModel(parent), m_autoUpdateTimer(new QTimer(this)),
|
|
|
|
m_feedDownloaderThread(NULL), m_feedDownloader(NULL),
|
|
|
|
m_dbCleanerThread(NULL), m_dbCleaner(NULL) {
|
2015-06-23 18:47:39 +02:00
|
|
|
setObjectName(QSL("FeedsModel"));
|
2013-12-13 20:48:45 +01:00
|
|
|
|
2014-03-28 09:04:36 +01:00
|
|
|
// Create root item.
|
2015-07-24 10:05:42 +02:00
|
|
|
m_rootItem = new RootItem();
|
2013-12-28 16:44:21 +01:00
|
|
|
m_rootItem->setId(NO_PARENT_CATEGORY);
|
2014-02-26 19:18:11 +01:00
|
|
|
|
|
|
|
//: Name of root item of feed list which can be seen in feed add/edit dialog.
|
2014-02-16 08:24:27 +01:00
|
|
|
m_rootItem->setTitle(tr("Root"));
|
2015-06-23 18:47:39 +02:00
|
|
|
m_rootItem->setIcon(qApp->icons()->fromTheme(QSL("folder-root")));
|
2014-03-28 09:04:36 +01:00
|
|
|
|
|
|
|
// Setup icons.
|
2015-06-23 18:47:39 +02:00
|
|
|
m_countsIcon = qApp->icons()->fromTheme(QSL("mail-mark-unread"));
|
2014-02-26 19:18:11 +01:00
|
|
|
|
|
|
|
//: Title text in the feed list header.
|
2013-12-12 15:07:17 +01:00
|
|
|
m_headerData << tr("Title");
|
2014-02-26 19:18:11 +01:00
|
|
|
|
2014-02-27 18:15:16 +01:00
|
|
|
m_tooltipData << /*: Feed list header "titles" column tooltip.*/ tr("Titles of feeds/categories.") <<
|
|
|
|
/*: Feed list header "counts" column tooltip.*/ tr("Counts of unread/all meesages.");
|
2013-12-12 15:07:17 +01:00
|
|
|
|
2015-07-29 07:59:00 +02:00
|
|
|
connect(m_autoUpdateTimer, SIGNAL(timeout()), this, SLOT(executeNextAutoUpdate()));
|
|
|
|
updateAutoUpdateStatus();
|
2013-11-12 20:24:19 +01:00
|
|
|
}
|
2013-12-11 14:07:18 +01:00
|
|
|
|
|
|
|
FeedsModel::~FeedsModel() {
|
|
|
|
qDebug("Destroying FeedsModel instance.");
|
2013-12-31 14:25:49 +01:00
|
|
|
|
2015-12-08 09:34:14 +01:00
|
|
|
foreach (ServiceRoot *account, serviceRoots()) {
|
|
|
|
account->stop();
|
|
|
|
}
|
|
|
|
|
2014-01-10 08:36:08 +01:00
|
|
|
// Delete all model items.
|
2013-12-11 15:00:15 +01:00
|
|
|
delete m_rootItem;
|
2013-12-11 14:07:18 +01:00
|
|
|
}
|
2013-12-11 18:54:09 +01:00
|
|
|
|
2015-07-29 07:59:00 +02:00
|
|
|
void FeedsModel::quit() {
|
|
|
|
if (m_autoUpdateTimer->isActive()) {
|
|
|
|
m_autoUpdateTimer->stop();
|
|
|
|
}
|
2015-11-23 14:23:51 +01:00
|
|
|
|
|
|
|
// Close worker threads.
|
|
|
|
if (m_feedDownloaderThread != NULL && m_feedDownloaderThread->isRunning()) {
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-23 19:10:16 +01:00
|
|
|
if (m_dbCleanerThread != NULL && 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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-23 14:23:51 +01:00
|
|
|
// Close workers.
|
|
|
|
if (m_feedDownloader != NULL) {
|
|
|
|
qDebug("Feed downloader exists. Deleting it from memory.");
|
|
|
|
m_feedDownloader->deleteLater();
|
|
|
|
}
|
|
|
|
|
2015-11-23 19:10:16 +01:00
|
|
|
if (m_dbCleaner != NULL) {
|
|
|
|
qDebug("Database cleaner exists. Deleting it from memory.");
|
|
|
|
m_dbCleaner->deleteLater();
|
|
|
|
}
|
|
|
|
|
2015-11-23 14:23:51 +01:00
|
|
|
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 another critical operation is ongoing."),
|
|
|
|
QSystemTrayIcon::Warning, qApp->mainForm(), true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_feedDownloader == NULL) {
|
|
|
|
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(Feed*,int,int)), this, SLOT(onFeedUpdatesProgress(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"));
|
|
|
|
}
|
|
|
|
|
2016-01-10 11:12:21 +01:00
|
|
|
void FeedsModel::onFeedUpdatesProgress(const Feed *feed, int current, int total) {
|
2015-11-23 14:23:51 +01:00
|
|
|
// Some feed got updated.
|
|
|
|
qApp->mainForm()->statusBar()->showProgressFeeds((current * 100.0) / total,
|
|
|
|
//: Text display in status bar when particular feed is updated.
|
|
|
|
tr("Updated feed '%1'").arg(feed->title()));
|
|
|
|
}
|
|
|
|
|
2016-01-10 11:12:21 +01:00
|
|
|
void FeedsModel::onFeedUpdatesFinished(const FeedDownloadResults &results) {
|
2015-11-23 14:23:51 +01:00
|
|
|
qApp->feedUpdateLock()->unlock();
|
|
|
|
qApp->mainForm()->statusBar()->clearProgressFeeds();
|
|
|
|
|
2016-01-08 10:08:00 +01:00
|
|
|
if (!results.updatedFeeds().isEmpty()) {
|
2015-11-23 14:23:51 +01:00
|
|
|
// Now, inform about results via GUI message/notification.
|
2016-01-08 10:08:00 +01:00
|
|
|
qApp->showGuiMessage(tr("New messages downloaded"), results.overview(10), QSystemTrayIcon::NoIcon,
|
2015-11-23 14:23:51 +01:00
|
|
|
0, false, qApp->icons()->fromTheme(QSL("item-update-all")));
|
|
|
|
}
|
|
|
|
|
|
|
|
emit feedsUpdateFinished();
|
|
|
|
}
|
|
|
|
|
|
|
|
void FeedsModel::updateAllFeeds() {
|
|
|
|
updateFeeds(m_rootItem->getSubTreeFeeds());
|
2015-07-29 07:59:00 +02:00
|
|
|
}
|
|
|
|
|
2015-11-23 19:10:16 +01:00
|
|
|
DatabaseCleaner *FeedsModel::databaseCleaner() {
|
|
|
|
if (m_dbCleaner == NULL) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-11-25 09:37:40 +01:00
|
|
|
QMimeData *FeedsModel::mimeData(const QModelIndexList &indexes) const {
|
|
|
|
QMimeData *mime_data = new QMimeData();
|
|
|
|
QByteArray encoded_data;
|
|
|
|
QDataStream stream(&encoded_data, QIODevice::WriteOnly);
|
|
|
|
|
|
|
|
foreach (const QModelIndex &index, indexes) {
|
|
|
|
if (index.column() != 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
RootItem *item_for_index = itemForIndex(index);
|
|
|
|
|
|
|
|
if (item_for_index->kind() != RootItemKind::Root) {
|
|
|
|
stream << (quintptr) item_for_index;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mime_data->setData(MIME_TYPE_ITEM_POINTER, encoded_data);
|
|
|
|
return mime_data;
|
|
|
|
}
|
|
|
|
|
|
|
|
QStringList FeedsModel::mimeTypes() const {
|
|
|
|
return QStringList() << MIME_TYPE_ITEM_POINTER;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FeedsModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) {
|
|
|
|
Q_UNUSED(row)
|
|
|
|
Q_UNUSED(column)
|
|
|
|
|
|
|
|
if (action == Qt::IgnoreAction) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if (action != Qt::MoveAction) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
QByteArray dragged_items_data = data->data(MIME_TYPE_ITEM_POINTER);
|
|
|
|
|
|
|
|
if (dragged_items_data.isEmpty()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
QDataStream stream(&dragged_items_data, QIODevice::ReadOnly);
|
|
|
|
|
|
|
|
while (!stream.atEnd()) {
|
|
|
|
quintptr pointer_to_item;
|
|
|
|
stream >> pointer_to_item;
|
|
|
|
|
|
|
|
// We have item we want to drag, we also determine the target item.
|
|
|
|
RootItem *dragged_item = (RootItem*) pointer_to_item;
|
|
|
|
RootItem *target_item = itemForIndex(parent);
|
|
|
|
ServiceRoot *dragged_item_root = dragged_item->getParentServiceRoot();
|
|
|
|
ServiceRoot *target_item_root = target_item->getParentServiceRoot();
|
|
|
|
|
|
|
|
if (dragged_item == target_item || dragged_item->parent() == target_item) {
|
|
|
|
qDebug("Dragged item is equal to target item or its parent is equal to target item. Cancelling drag-drop action.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dragged_item_root != target_item_root) {
|
|
|
|
// Transferring of items between different accounts is not possible.
|
2015-11-30 13:03:28 +01:00
|
|
|
qApp->showGuiMessage(tr("Cannot perform drag & drop operation"),
|
2015-11-25 09:37:40 +01:00
|
|
|
tr("You can't transfer dragged item into different account, this is not supported."),
|
|
|
|
QSystemTrayIcon::Warning,
|
|
|
|
qApp->mainForm(),
|
|
|
|
true);
|
|
|
|
|
|
|
|
qDebug("Dragged item cannot be dragged into different account. Cancelling drag-drop action.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-11-25 10:25:36 +01:00
|
|
|
if (dragged_item->performDragDropChange(target_item)) {
|
|
|
|
// Drag & drop is supported by the dragged item and was
|
|
|
|
// completed on data level and in item hierarchy.
|
|
|
|
emit requireItemValidationAfterDragDrop(indexForItem(dragged_item));
|
2015-11-25 09:37:40 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Qt::DropActions FeedsModel::supportedDropActions() const {
|
|
|
|
return Qt::MoveAction;
|
|
|
|
}
|
|
|
|
|
|
|
|
Qt::ItemFlags FeedsModel::flags(const QModelIndex &index) const {
|
|
|
|
Qt::ItemFlags base_flags = QAbstractItemModel::flags(index);
|
2016-01-10 11:12:21 +01:00
|
|
|
const RootItem *item_for_index = itemForIndex(index);
|
2015-11-25 09:37:40 +01:00
|
|
|
Qt::ItemFlags additional_flags = item_for_index->additionalFlags();
|
|
|
|
|
|
|
|
return base_flags | additional_flags;
|
|
|
|
}
|
|
|
|
|
2015-07-29 07:59:00 +02:00
|
|
|
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.
|
2016-01-10 11:12:21 +01:00
|
|
|
const QList<Feed*> feeds_for_update = feedsForScheduledUpdate(m_globalAutoUpdateEnabled && m_globalAutoUpdateRemainingInterval == 0);
|
2015-07-29 07:59:00 +02:00
|
|
|
|
|
|
|
qApp->feedUpdateLock()->unlock();
|
|
|
|
|
|
|
|
if (!feeds_for_update.isEmpty()) {
|
|
|
|
// Request update for given feeds.
|
|
|
|
emit feedsUpdateRequested(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.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-17 12:21:19 +02:00
|
|
|
QVariant FeedsModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
2013-12-12 15:07:17 +01:00
|
|
|
if (orientation != Qt::Horizontal) {
|
|
|
|
return QVariant();
|
|
|
|
}
|
2013-12-11 18:54:09 +01:00
|
|
|
|
2013-12-12 15:07:17 +01:00
|
|
|
switch (role) {
|
|
|
|
case Qt::DisplayRole:
|
2013-12-13 20:48:45 +01:00
|
|
|
if (section == FDS_MODEL_TITLE_INDEX) {
|
|
|
|
return m_headerData.at(FDS_MODEL_TITLE_INDEX);
|
2013-12-12 15:07:17 +01:00
|
|
|
}
|
|
|
|
else {
|
2013-12-11 18:54:09 +01:00
|
|
|
return QVariant();
|
2013-12-12 15:07:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
case Qt::ToolTipRole:
|
2013-12-12 22:28:51 +01:00
|
|
|
return m_tooltipData.at(section);
|
2013-12-12 15:07:17 +01:00
|
|
|
|
|
|
|
case Qt::DecorationRole:
|
2013-12-13 20:48:45 +01:00
|
|
|
if (section == FDS_MODEL_COUNTS_INDEX) {
|
2013-12-12 15:07:17 +01:00
|
|
|
return m_countsIcon;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return QVariant();
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
return QVariant();
|
2013-12-11 18:54:09 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QModelIndex FeedsModel::index(int row, int column, const QModelIndex &parent) const {
|
|
|
|
if (!hasIndex(row, column, parent)) {
|
|
|
|
return QModelIndex();
|
|
|
|
}
|
|
|
|
|
2015-07-24 10:05:42 +02:00
|
|
|
RootItem *parent_item = itemForIndex(parent);
|
|
|
|
RootItem *child_item = parent_item->child(row);
|
2013-12-11 18:54:09 +01:00
|
|
|
|
|
|
|
if (child_item) {
|
|
|
|
return createIndex(row, column, child_item);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return QModelIndex();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QModelIndex FeedsModel::parent(const QModelIndex &child) const {
|
|
|
|
if (!child.isValid()) {
|
|
|
|
return QModelIndex();
|
|
|
|
}
|
|
|
|
|
2015-07-24 10:05:42 +02:00
|
|
|
RootItem *child_item = itemForIndex(child);
|
|
|
|
RootItem *parent_item = child_item->parent();
|
2013-12-11 18:54:09 +01:00
|
|
|
|
|
|
|
if (parent_item == m_rootItem) {
|
|
|
|
return QModelIndex();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return createIndex(parent_item->row(), 0, parent_item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int FeedsModel::rowCount(const QModelIndex &parent) const {
|
|
|
|
if (parent.column() > 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
else {
|
2014-03-28 09:04:36 +01:00
|
|
|
return itemForIndex(parent)->childCount();
|
2013-12-11 18:54:09 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-20 13:29:32 +01:00
|
|
|
void FeedsModel::reloadCountsOfWholeModel() {
|
|
|
|
m_rootItem->updateCounts(true);
|
|
|
|
reloadWholeLayout();
|
|
|
|
notifyWithCounts();
|
|
|
|
}
|
|
|
|
|
2015-11-06 08:25:37 +01:00
|
|
|
void FeedsModel::removeItem(const QModelIndex &index) {
|
2014-01-17 19:10:10 +01:00
|
|
|
if (index.isValid()) {
|
2015-07-24 10:05:42 +02:00
|
|
|
RootItem *deleting_item = itemForIndex(index);
|
2015-11-25 08:36:58 +01:00
|
|
|
QModelIndex parent_index = index.parent();
|
|
|
|
RootItem *parent_item = deleting_item->parent();
|
|
|
|
|
|
|
|
beginRemoveRows(parent_index, index.row(), index.row());
|
|
|
|
parent_item->removeChild(deleting_item);
|
|
|
|
endRemoveRows();
|
|
|
|
|
|
|
|
deleting_item->deleteLater();
|
|
|
|
notifyWithCounts();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void FeedsModel::removeItem(RootItem *deleting_item) {
|
|
|
|
if (deleting_item != NULL) {
|
|
|
|
QModelIndex index = indexForItem(deleting_item);
|
|
|
|
QModelIndex parent_index = index.parent();
|
2015-07-24 10:05:42 +02:00
|
|
|
RootItem *parent_item = deleting_item->parent();
|
2014-01-17 19:10:10 +01:00
|
|
|
|
2015-11-06 08:25:37 +01:00
|
|
|
beginRemoveRows(parent_index, index.row(), index.row());
|
|
|
|
parent_item->removeChild(deleting_item);
|
|
|
|
endRemoveRows();
|
2014-01-26 17:57:10 +01:00
|
|
|
|
2015-11-25 08:36:58 +01:00
|
|
|
deleting_item->deleteLater();
|
|
|
|
notifyWithCounts();
|
2014-01-26 17:57:10 +01:00
|
|
|
}
|
|
|
|
}
|
2014-01-07 19:09:19 +01:00
|
|
|
|
2015-10-30 11:41:02 +01:00
|
|
|
void FeedsModel::reassignNodeToNewParent(RootItem *original_node, RootItem *new_parent) {
|
|
|
|
RootItem *original_parent = original_node->parent();
|
2014-01-30 17:49:51 +01:00
|
|
|
|
2015-10-30 11:41:02 +01:00
|
|
|
if (original_parent != new_parent) {
|
2015-11-05 08:07:08 +01:00
|
|
|
if (original_parent != NULL) {
|
|
|
|
int original_index_of_item = original_parent->childItems().indexOf(original_node);
|
|
|
|
|
|
|
|
if (original_index_of_item >= 0) {
|
|
|
|
// Remove the original item from the model...
|
|
|
|
beginRemoveRows(indexForItem(original_parent), original_index_of_item, original_index_of_item);
|
|
|
|
original_parent->removeChild(original_node);
|
|
|
|
endRemoveRows();
|
|
|
|
}
|
|
|
|
}
|
2014-01-30 17:49:51 +01:00
|
|
|
|
2015-11-05 08:07:08 +01:00
|
|
|
int new_index_of_item = new_parent->childCount();
|
2014-01-30 17:49:51 +01:00
|
|
|
|
|
|
|
// ... and insert it under the new parent.
|
2015-11-03 13:49:15 +01:00
|
|
|
beginInsertRows(indexForItem(new_parent), new_index_of_item, new_index_of_item);
|
2015-10-30 11:41:02 +01:00
|
|
|
new_parent->appendChild(original_node);
|
2014-01-30 17:49:51 +01:00
|
|
|
endInsertRows();
|
|
|
|
}
|
2014-01-30 14:37:28 +01:00
|
|
|
}
|
|
|
|
|
2016-01-10 08:55:22 +01:00
|
|
|
QList<ServiceRoot*> FeedsModel::serviceRoots() const {
|
2015-11-04 09:35:49 +01:00
|
|
|
QList<ServiceRoot*> roots;
|
|
|
|
|
|
|
|
foreach (RootItem *root, m_rootItem->childItems()) {
|
|
|
|
if (root->kind() == RootItemKind::ServiceRoot) {
|
|
|
|
roots.append(root->toServiceRoot());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return roots;
|
|
|
|
}
|
|
|
|
|
2016-01-11 13:00:37 +01:00
|
|
|
bool FeedsModel::containsServiceRootFromEntryPoint(const ServiceEntryPoint *point) const {
|
|
|
|
foreach (const ServiceRoot *root, serviceRoots()) {
|
|
|
|
if (root->code() == point->code()) {
|
2015-11-30 10:50:37 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-01-10 08:55:22 +01:00
|
|
|
StandardServiceRoot *FeedsModel::standardServiceRoot() const {
|
2016-01-11 13:00:37 +01:00
|
|
|
foreach (ServiceRoot *root, serviceRoots()) {
|
2015-11-04 09:35:49 +01:00
|
|
|
StandardServiceRoot *std_service_root;
|
|
|
|
|
|
|
|
if ((std_service_root = dynamic_cast<StandardServiceRoot*>(root)) != NULL) {
|
|
|
|
return std_service_root;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2015-10-30 10:47:43 +01:00
|
|
|
QList<Feed*> FeedsModel::feedsForScheduledUpdate(bool auto_update_now) {
|
|
|
|
QList<Feed*> feeds_for_update;
|
2014-02-04 10:02:07 +01:00
|
|
|
|
2015-10-30 10:47:43 +01:00
|
|
|
foreach (Feed *feed, allFeeds()) {
|
2014-03-26 08:33:50 +01:00
|
|
|
switch (feed->autoUpdateType()) {
|
2015-11-03 13:16:11 +01:00
|
|
|
case Feed::DontAutoUpdate:
|
2014-02-04 10:02:07 +01:00
|
|
|
// Do not auto-update this feed ever.
|
|
|
|
continue;
|
|
|
|
|
2015-11-03 13:16:11 +01:00
|
|
|
case Feed::DefaultAutoUpdate:
|
2014-02-04 18:55:37 +01:00
|
|
|
if (auto_update_now) {
|
2014-02-04 10:02:07 +01:00
|
|
|
feeds_for_update.append(feed);
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
2015-11-03 13:16:11 +01:00
|
|
|
case Feed::SpecificAutoUpdate:
|
2014-02-04 10:02:07 +01:00
|
|
|
default:
|
2014-03-26 08:33:50 +01:00
|
|
|
int remaining_interval = feed->autoUpdateRemainingInterval();
|
2014-02-04 10:02:07 +01:00
|
|
|
|
|
|
|
if (--remaining_interval <= 0) {
|
|
|
|
// Interval of this feed passed, include this feed in the output list
|
|
|
|
// and reset the interval.
|
|
|
|
feeds_for_update.append(feed);
|
2014-03-26 08:33:50 +01:00
|
|
|
feed->setAutoUpdateRemainingInterval(feed->autoUpdateInitialInterval());
|
2014-02-04 10:02:07 +01:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Interval did not pass, set new decremented interval and do NOT
|
|
|
|
// include this feed in the output list.
|
2014-03-26 08:33:50 +01:00
|
|
|
feed->setAutoUpdateRemainingInterval(remaining_interval);
|
2014-02-04 10:02:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return feeds_for_update;
|
|
|
|
}
|
|
|
|
|
2016-01-10 11:12:21 +01:00
|
|
|
QList<Message> FeedsModel::messagesForItem(RootItem *item) const {
|
2015-12-11 10:13:42 +01:00
|
|
|
return item->undeletedMessages();
|
2014-01-04 14:09:38 +01:00
|
|
|
}
|
|
|
|
|
2013-12-11 18:54:09 +01:00
|
|
|
int FeedsModel::columnCount(const QModelIndex &parent) const {
|
2014-03-29 08:36:34 +01:00
|
|
|
Q_UNUSED(parent)
|
|
|
|
|
|
|
|
return FEEDS_VIEW_COLUMN_COUNT;
|
2013-12-11 18:54:09 +01:00
|
|
|
}
|
2013-12-13 20:48:45 +01:00
|
|
|
|
2015-07-24 10:05:42 +02:00
|
|
|
RootItem *FeedsModel::itemForIndex(const QModelIndex &index) const {
|
2013-12-16 20:14:53 +01:00
|
|
|
if (index.isValid() && index.model() == this) {
|
2015-07-24 10:05:42 +02:00
|
|
|
return static_cast<RootItem*>(index.internalPointer());
|
2013-12-16 20:14:53 +01:00
|
|
|
}
|
|
|
|
else {
|
2013-12-31 14:25:49 +01:00
|
|
|
return m_rootItem;
|
2013-12-16 20:14:53 +01:00
|
|
|
}
|
2013-12-14 18:54:35 +01:00
|
|
|
}
|
|
|
|
|
2015-07-29 07:59:00 +02:00
|
|
|
QModelIndex FeedsModel::indexForItem(RootItem *item) const {
|
2015-11-03 10:06:34 +01:00
|
|
|
if (item == NULL || item->kind() == RootItemKind::Root) {
|
2013-12-31 13:19:25 +01:00
|
|
|
// Root item lies on invalid index.
|
|
|
|
return QModelIndex();
|
|
|
|
}
|
|
|
|
|
2015-07-24 10:05:42 +02:00
|
|
|
QStack<RootItem*> chain;
|
2014-09-22 09:57:14 +02:00
|
|
|
|
2015-11-03 10:06:34 +01:00
|
|
|
while (item->kind() != RootItemKind::Root) {
|
2014-09-22 09:57:14 +02:00
|
|
|
chain.push(item);
|
|
|
|
item = item->parent();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now, we have complete chain list: parent --- ..... --- parent --- leaf (item).
|
|
|
|
QModelIndex target_index = indexForItem(m_rootItem);
|
|
|
|
|
|
|
|
// We go through the stack and create our target index.
|
|
|
|
while (!chain.isEmpty()) {
|
2015-07-24 10:05:42 +02:00
|
|
|
RootItem *parent_item = chain.pop();
|
2014-09-22 09:57:14 +02:00
|
|
|
target_index = index(parent_item->parent()->childItems().indexOf(parent_item), 0, target_index);
|
|
|
|
}
|
|
|
|
|
|
|
|
return target_index;
|
2013-12-31 13:19:25 +01:00
|
|
|
}
|
|
|
|
|
2016-01-10 11:12:21 +01:00
|
|
|
bool FeedsModel::hasAnyFeedNewMessages() const {
|
2015-10-30 10:47:43 +01:00
|
|
|
foreach (const Feed *feed, allFeeds()) {
|
2015-11-03 13:16:11 +01:00
|
|
|
if (feed->status() == Feed::NewMessages) {
|
2014-10-27 18:11:30 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-12-19 13:52:53 +01:00
|
|
|
void FeedsModel::reloadChangedLayout(QModelIndexList list) {
|
2013-12-18 14:15:34 +01:00
|
|
|
while (!list.isEmpty()) {
|
2014-01-03 09:00:51 +01:00
|
|
|
QModelIndex indx = list.takeFirst();
|
2014-01-03 07:45:42 +01:00
|
|
|
QModelIndex indx_parent = indx.parent();
|
2013-12-19 08:54:31 +01:00
|
|
|
|
|
|
|
// Underlying data are changed.
|
2014-09-01 18:17:17 +02:00
|
|
|
emit dataChanged(index(indx.row(), 0, indx_parent), index(indx.row(), FDS_MODEL_COUNTS_INDEX, indx_parent));
|
2013-12-18 14:15:34 +01:00
|
|
|
}
|
2013-12-19 13:52:53 +01:00
|
|
|
}
|
2013-12-18 14:15:34 +01:00
|
|
|
|
2015-11-06 09:41:36 +01:00
|
|
|
void FeedsModel::reloadChangedItem(RootItem *item) {
|
|
|
|
QModelIndex index_item = indexForItem(item);
|
|
|
|
reloadChangedLayout(QModelIndexList() << index_item);
|
|
|
|
}
|
|
|
|
|
2015-11-23 13:13:03 +01:00
|
|
|
void FeedsModel::notifyWithCounts() {
|
|
|
|
if (SystemTrayIcon::isSystemTrayActivated()) {
|
|
|
|
qApp->trayIcon()->setNumber(countOfUnreadMessages(), hasAnyFeedNewMessages());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-10 11:12:21 +01:00
|
|
|
void FeedsModel::onItemDataChanged(const QList<RootItem *> &items) {
|
2015-11-23 13:13:03 +01:00
|
|
|
if (items.size() > RELOAD_MODEL_BORDER_NUM) {
|
|
|
|
qDebug("There is request to reload feed model for more than %d items, reloading model fully.", RELOAD_MODEL_BORDER_NUM);
|
2015-11-24 09:13:43 +01:00
|
|
|
reloadWholeLayout();
|
2015-11-23 13:13:03 +01:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
qDebug("There is request to reload feed model, reloading the %d items individually.", items.size());
|
|
|
|
|
|
|
|
foreach (RootItem *item, items) {
|
|
|
|
reloadChangedItem(item);
|
|
|
|
}
|
2015-11-20 13:29:32 +01:00
|
|
|
}
|
|
|
|
|
2015-11-20 09:52:29 +01:00
|
|
|
notifyWithCounts();
|
|
|
|
}
|
|
|
|
|
2013-12-19 13:52:53 +01:00
|
|
|
void FeedsModel::reloadWholeLayout() {
|
2013-12-18 08:51:35 +01:00
|
|
|
emit layoutAboutToBeChanged();
|
|
|
|
emit layoutChanged();
|
|
|
|
}
|
|
|
|
|
2016-01-04 06:30:53 +01:00
|
|
|
bool FeedsModel::addServiceAccount(ServiceRoot *root, bool freshly_activated) {
|
2015-11-30 12:53:04 +01:00
|
|
|
int new_row_index = m_rootItem->childCount();
|
2015-11-20 09:52:29 +01:00
|
|
|
|
2015-11-30 12:53:04 +01:00
|
|
|
beginInsertRows(indexForItem(m_rootItem), new_row_index, new_row_index);
|
|
|
|
m_rootItem->appendChild(root);
|
|
|
|
endInsertRows();
|
2015-11-30 11:13:00 +01:00
|
|
|
|
2015-11-20 09:52:29 +01:00
|
|
|
// Connect.
|
2015-11-30 12:53:04 +01:00
|
|
|
connect(root, SIGNAL(itemRemovalRequested(RootItem*)), this, SLOT(removeItem(RootItem*)));
|
|
|
|
connect(root, SIGNAL(itemReassignmentRequested(RootItem*,RootItem*)), this, SLOT(reassignNodeToNewParent(RootItem*,RootItem*)));
|
2015-11-20 09:52:29 +01:00
|
|
|
connect(root, SIGNAL(readFeedsFilterInvalidationRequested()), this, SIGNAL(readFeedsFilterInvalidationRequested()));
|
2015-11-20 13:29:32 +01:00
|
|
|
connect(root, SIGNAL(dataChanged(QList<RootItem*>)), this, SLOT(onItemDataChanged(QList<RootItem*>)));
|
2015-11-23 13:13:03 +01:00
|
|
|
connect(root, SIGNAL(reloadMessageListRequested(bool)), this, SIGNAL(reloadMessageListRequested(bool)));
|
2015-12-08 09:11:55 +01:00
|
|
|
connect(root, SIGNAL(itemExpandRequested(QList<RootItem*>,bool)), this, SIGNAL(itemExpandRequested(QList<RootItem*>,bool)));
|
2016-01-07 09:10:06 +01:00
|
|
|
connect(root, SIGNAL(itemExpandStateSaveRequested(RootItem*)), this, SIGNAL(itemExpandStateSaveRequested(RootItem*)));
|
2015-11-20 09:52:29 +01:00
|
|
|
|
2016-01-04 06:30:53 +01:00
|
|
|
root->start(freshly_activated);
|
2015-11-20 09:52:29 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-11-24 09:13:43 +01:00
|
|
|
bool FeedsModel::restoreAllBins() {
|
|
|
|
bool result = true;
|
|
|
|
|
|
|
|
foreach (ServiceRoot *root, serviceRoots()) {
|
|
|
|
RecycleBin *bin_of_root = root->recycleBin();
|
|
|
|
|
|
|
|
if (bin_of_root != NULL) {
|
|
|
|
result &= bin_of_root->restore();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FeedsModel::emptyAllBins() {
|
|
|
|
bool result = true;
|
|
|
|
|
|
|
|
foreach (ServiceRoot *root, serviceRoots()) {
|
|
|
|
RecycleBin *bin_of_root = root->recycleBin();
|
|
|
|
|
|
|
|
if (bin_of_root != NULL) {
|
|
|
|
result &= bin_of_root->empty();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2015-10-30 13:07:42 +01:00
|
|
|
void FeedsModel::loadActivatedServiceAccounts() {
|
2015-11-03 13:16:11 +01:00
|
|
|
// Iterate all globally available feed "service plugins".
|
2016-01-05 12:11:03 +01:00
|
|
|
foreach (const ServiceEntryPoint *entry_point, qApp->feedServices()) {
|
2015-10-30 13:07:42 +01:00
|
|
|
// Load all stored root nodes from the entry point and add those to the model.
|
2015-11-30 12:53:04 +01:00
|
|
|
QList<ServiceRoot*> roots = entry_point->initializeSubtree();
|
2013-12-13 20:48:45 +01:00
|
|
|
|
2015-10-30 13:07:42 +01:00
|
|
|
foreach (ServiceRoot *root, roots) {
|
2016-01-04 06:30:53 +01:00
|
|
|
addServiceAccount(root, false);
|
2013-12-13 20:48:45 +01:00
|
|
|
}
|
2013-12-14 11:45:43 +01:00
|
|
|
}
|
2015-12-13 18:10:50 +01:00
|
|
|
|
|
|
|
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()));
|
|
|
|
}
|
2013-12-14 11:45:43 +01:00
|
|
|
}
|
|
|
|
|
2016-01-10 11:12:21 +01:00
|
|
|
QList<Feed*> FeedsModel::feedsForIndex(const QModelIndex &index) const {
|
2015-11-04 09:12:40 +01:00
|
|
|
return itemForIndex(index)->getSubTreeFeeds();
|
2013-12-16 12:53:48 +01:00
|
|
|
}
|
|
|
|
|
2015-11-12 10:53:17 +01:00
|
|
|
bool FeedsModel::markItemRead(RootItem *item, RootItem::ReadStatus read) {
|
2015-11-17 09:02:50 +01:00
|
|
|
return item->markAsReadUnread(read);
|
2013-12-28 11:22:00 +01:00
|
|
|
}
|
|
|
|
|
2015-11-12 10:53:17 +01:00
|
|
|
bool FeedsModel::markItemCleared(RootItem *item, bool clean_read_only) {
|
|
|
|
return item->cleanMessages(clean_read_only);
|
2013-12-28 11:22:00 +01:00
|
|
|
}
|
|
|
|
|
2016-01-10 11:12:21 +01:00
|
|
|
QList<Feed*> FeedsModel::allFeeds() const {
|
2015-11-04 09:12:40 +01:00
|
|
|
return m_rootItem->getSubTreeFeeds();
|
2013-12-14 11:45:43 +01:00
|
|
|
}
|
2015-11-03 13:49:15 +01:00
|
|
|
|
2016-01-10 11:12:21 +01:00
|
|
|
QList<Category*> FeedsModel::allCategories() const {
|
2015-11-04 09:12:40 +01:00
|
|
|
return m_rootItem->getSubTreeCategories();
|
2015-11-03 13:49:15 +01:00
|
|
|
}
|