rssguard/src/core/feedsmodel.cpp

543 lines
16 KiB
C++
Raw Normal View History

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"
#include "services/abstract/feed.h"
#include "services/abstract/category.h"
#include "services/abstract/serviceroot.h"
#include "services/standard/standardserviceroot.h"
2014-03-27 08:40:23 +01:00
#include "miscellaneous/textfactory.h"
#include "miscellaneous/databasefactory.h"
#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"
2013-12-12 10:10:17 +01:00
2013-12-21 21:08:52 +01:00
#include <QSqlError>
#include <QSqlQuery>
#include <QSqlRecord>
#include <QPair>
#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
FeedsModel::FeedsModel(QObject *parent)
: QAbstractItemModel(parent), m_autoUpdateTimer(new QTimer(this)) {
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.
m_rootItem->setTitle(tr("Root"));
m_rootItem->setIcon(qApp->icons()->fromTheme(QSL("folder-root")));
2014-03-28 09:04:36 +01:00
// Setup icons.
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()));
loadActivatedServiceAccounts();
2015-07-29 07:59:00 +02:00
// Setup the timer.
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
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();
}
}
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);
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.");
}
}
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-07-29 07:59:00 +02:00
bool FeedsModel::removeItem(const QModelIndex &index) {
if (index.isValid()) {
QModelIndex parent_index = index.parent();
2015-07-24 10:05:42 +02:00
RootItem *deleting_item = itemForIndex(index);
RootItem *parent_item = deleting_item->parent();
// Try to persistently remove the item.
2014-01-25 17:21:13 +01:00
if (deleting_item->removeItself()) {
// Item was persistently removed.
// Remove it from the model.
2014-01-25 17:21:13 +01:00
beginRemoveRows(parent_index, index.row(), index.row());
parent_item->removeChild(deleting_item);
2014-01-25 17:21:13 +01:00
endRemoveRows();
delete deleting_item;
return true;
2014-01-25 17:21:13 +01:00
}
}
// Item was not removed successfully.
return false;
}
2015-11-03 13:49:15 +01:00
void FeedsModel::assignNodeToNewParent(RootItem *item, RootItem *new_parent) {
QModelIndex parent_index = indexForItem(new_parent);
int new_index_of_item = new_parent->childCount();
2015-11-03 13:49:15 +01:00
// TODO: sloučit do funkce reassignNodeToNewParent.
2015-11-03 13:49:15 +01:00
beginInsertRows(parent_index, new_index_of_item, new_index_of_item);
new_parent->appendChild(item);
endInsertRows();
2014-01-30 14:37:28 +01:00
}
void FeedsModel::reassignNodeToNewParent(RootItem *original_node, RootItem *new_parent) {
RootItem *original_parent = original_node->parent();
if (original_parent != new_parent) {
2015-11-03 13:49:15 +01:00
// User edited item and set it new parent item,
// se we need to move the item in the model too.
2015-11-03 13:49:15 +01:00
int original_index_of_item = original_parent->childItems().indexOf(original_node);
int new_index_of_item = new_parent->childCount();
// Remove the original item from the model...
2015-11-03 13:49:15 +01:00
beginRemoveRows(indexForItem(original_parent), original_index_of_item, original_index_of_item);
original_parent->removeChild(original_node);
endRemoveRows();
// ... 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);
new_parent->appendChild(original_node);
endInsertRows();
}
2014-01-30 14:37:28 +01:00
}
QList<Feed*> FeedsModel::feedsForScheduledUpdate(bool auto_update_now) {
QList<Feed*> feeds_for_update;
foreach (Feed *feed, allFeeds()) {
2014-03-26 08:33:50 +01:00
switch (feed->autoUpdateType()) {
case Feed::DontAutoUpdate:
// Do not auto-update this feed ever.
continue;
case Feed::DefaultAutoUpdate:
2014-02-04 18:55:37 +01:00
if (auto_update_now) {
feeds_for_update.append(feed);
}
break;
case Feed::SpecificAutoUpdate:
default:
2014-03-26 08:33:50 +01:00
int remaining_interval = feed->autoUpdateRemainingInterval();
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());
}
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);
}
break;
}
}
return feeds_for_update;
}
QList<Message> FeedsModel::messagesForFeeds(const QList<Feed*> &feeds) {
QList<Message> messages;
foreach (const Feed *feed, feeds) {
messages.append(feed->undeletedMessages());
}
return messages;
}
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
}
Category *FeedsModel::categoryForIndex(const QModelIndex &index) const {
2015-07-24 10:05:42 +02:00
RootItem *item = itemForIndex(index);
2013-12-30 19:28:39 +01:00
if (item->kind() == RootItemKind::Category) {
2014-10-28 18:14:17 +01:00
return item->toCategory();
2013-12-30 19:28:39 +01:00
}
else {
return NULL;
}
}
2015-07-29 07:59:00 +02:00
QModelIndex FeedsModel::indexForItem(RootItem *item) const {
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;
while (item->kind() != RootItemKind::Root) {
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();
target_index = index(parent_item->parent()->childItems().indexOf(parent_item), 0, target_index);
}
return target_index;
2013-12-31 13:19:25 +01:00
}
2014-10-27 18:11:30 +01:00
bool FeedsModel::hasAnyFeedNewMessages() {
foreach (const Feed *feed, allFeeds()) {
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) {
while (!list.isEmpty()) {
2014-01-03 09:00:51 +01:00
QModelIndex indx = list.takeFirst();
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-19 13:52:53 +01:00
}
QStringList FeedsModel::textualFeedIds(const QList<Feed*> &feeds) {
2013-12-28 11:22:00 +01:00
QStringList stringy_ids;
stringy_ids.reserve(feeds.size());
foreach (Feed *feed, feeds) {
2013-12-28 11:22:00 +01:00
stringy_ids.append(QString::number(feed->id()));
}
return stringy_ids;
}
2013-12-19 13:52:53 +01:00
void FeedsModel::reloadWholeLayout() {
2013-12-18 08:51:35 +01:00
emit layoutAboutToBeChanged();
emit layoutChanged();
}
void FeedsModel::loadActivatedServiceAccounts() {
// Iterate all globally available feed "service plugins".
foreach (ServiceEntryPoint *entry_point, qApp->feedServices()) {
// Load all stored root nodes from the entry point and add those to the model.
QList<ServiceRoot*> roots = entry_point->initializeSubtree(this);
2013-12-13 20:48:45 +01:00
foreach (ServiceRoot *root, roots) {
if (SystemFactory::isInstanceOf<StandardServiceRoot>(root)) {
}
m_rootItem->appendChild(root);
2013-12-13 20:48:45 +01:00
}
2013-12-14 11:45:43 +01:00
}
}
QList<Feed*> FeedsModel::feedsForIndex(const QModelIndex &index) {
return itemForIndex(index)->getSubTreeFeeds();
2013-12-16 12:53:48 +01:00
}
Feed *FeedsModel::feedForIndex(const QModelIndex &index) {
2015-07-24 10:05:42 +02:00
RootItem *item = itemForIndex(index);
2013-12-30 19:28:39 +01:00
if (item->kind() == RootItemKind::Feed) {
return item->toFeed();
2013-12-30 19:28:39 +01:00
}
else {
return NULL;
}
}
/*
QList<Feed*> FeedsModel::feedsForIndexes(const QModelIndexList &indexes) {
QList<Feed*> feeds;
// Get selected feeds for each index.
foreach (const QModelIndex &index, indexes) {
feeds.append(feedsForIndex(index));
}
// Now we obtained all feeds from corresponding indexes.
if (indexes.size() != feeds.size()) {
// Selection contains duplicate feeds (for
// example situation where feed and its parent category are both
// selected). So, remove duplicates from the list.
2015-07-24 10:05:42 +02:00
qSort(feeds.begin(), feeds.end(), RootItem::lessThan);
feeds.erase(std::unique(feeds.begin(), feeds.end(), RootItem::isEqual), feeds.end());
}
return feeds;
}
*/
bool FeedsModel::markFeedsRead(const QList<Feed*> &feeds, int read) {
2014-09-01 18:17:17 +02:00
QSqlDatabase db_handle = qApp->database()->connection(objectName(), DatabaseFactory::FromSettings);
2013-12-28 11:22:00 +01:00
if (!db_handle.transaction()) {
qWarning("Starting transaction for feeds read change.");
return false;
}
QSqlQuery query_read_msg(db_handle);
2013-12-31 17:33:42 +01:00
query_read_msg.setForwardOnly(true);
2015-06-24 09:10:43 +02:00
if (!query_read_msg.prepare(QString("UPDATE Messages SET is_read = :read "
"WHERE feed IN (%1) AND is_deleted = 0;").arg(textualFeedIds(feeds).join(QSL(", "))))) {
2013-12-28 11:22:00 +01:00
qWarning("Query preparation failed for feeds read change.");
db_handle.rollback();
return false;
}
query_read_msg.bindValue(QSL(":read"), read);
2013-12-28 11:22:00 +01:00
if (!query_read_msg.exec()) {
qDebug("Query execution for feeds read change failed.");
db_handle.rollback();
}
// Commit changes.
if (db_handle.commit()) {
return true;
}
else {
return db_handle.rollback();
}
}
bool FeedsModel::markFeedsDeleted(const QList<Feed*> &feeds, int deleted, bool read_only) {
2014-09-01 18:17:17 +02:00
QSqlDatabase db_handle = qApp->database()->connection(objectName(), DatabaseFactory::FromSettings);
2013-12-28 11:22:00 +01:00
if (!db_handle.transaction()) {
qWarning("Starting transaction for feeds clearing.");
return false;
}
QSqlQuery query_delete_msg(db_handle);
2013-12-31 17:33:42 +01:00
query_delete_msg.setForwardOnly(true);
2014-02-02 14:44:00 +01:00
if (read_only) {
2015-06-24 09:10:43 +02:00
if (!query_delete_msg.prepare(QString("UPDATE Messages SET is_deleted = :deleted "
"WHERE feed IN (%1) AND is_deleted = 0 AND is_read = 1;").arg(textualFeedIds(feeds).join(QSL(", "))))) {
2014-02-02 14:44:00 +01:00
qWarning("Query preparation failed for feeds clearing.");
2013-12-28 11:22:00 +01:00
2014-02-02 14:44:00 +01:00
db_handle.rollback();
return false;
}
}
else {
2015-06-24 09:10:43 +02:00
if (!query_delete_msg.prepare(QString("UPDATE Messages SET is_deleted = :deleted "
"WHERE feed IN (%1) AND is_deleted = 0;").arg(textualFeedIds(feeds).join(QSL(", "))))) {
2014-02-02 14:44:00 +01:00
qWarning("Query preparation failed for feeds clearing.");
db_handle.rollback();
return false;
}
2013-12-28 11:22:00 +01:00
}
query_delete_msg.bindValue(QSL(":deleted"), deleted);
2013-12-28 11:22:00 +01:00
if (!query_delete_msg.exec()) {
qDebug("Query execution for feeds clearing failed.");
db_handle.rollback();
}
// Commit changes.
if (db_handle.commit()) {
return true;
}
else {
return db_handle.rollback();
}
}
QList<Feed*> FeedsModel::allFeeds() {
return m_rootItem->getSubTreeFeeds();
2013-12-14 11:45:43 +01:00
}
2015-11-03 13:49:15 +01:00
QList<Category*> FeedsModel::allCategories() {
return m_rootItem->getSubTreeCategories();
2015-11-03 13:49:15 +01:00
}