rssguard/src/core/feedsmodel.cpp

889 lines
28 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"
2014-03-26 08:33:50 +01:00
#include "core/feedsmodelcategory.h"
#include "core/feedsmodelfeed.h"
2014-09-13 16:11:53 +02:00
#include "core/feedsmodelrecyclebin.h"
2014-09-03 17:38:58 +02:00
#include "core/feedsimportexportmodel.h"
2014-03-27 08:40:23 +01:00
#include "miscellaneous/textfactory.h"
#include "miscellaneous/databasefactory.h"
#include "miscellaneous/iconfactory.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>
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_recycleBin(new FeedsModelRecycleBin()) {
setObjectName(QSL("FeedsModel"));
2013-12-13 20:48:45 +01:00
2014-03-28 09:04:36 +01:00
// Create root item.
2013-12-12 15:07:17 +01:00
m_rootItem = new FeedsModelRootItem();
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
2013-12-13 20:48:45 +01:00
loadFromDatabase();
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
2014-10-28 17:45:37 +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;
}
FeedsModelRootItem *item_for_index = itemForIndex(index);
if (item_for_index->kind() != FeedsModelRootItem::RootItem) {
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.
FeedsModelRootItem *dragged_item = (FeedsModelRootItem*) pointer_to_item;
FeedsModelRootItem *target_item = itemForIndex(parent);
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->kind() == FeedsModelRootItem::Feed) {
qDebug("Drag-drop action for feed '%s' detected, editing the feed.", qPrintable(dragged_item->title()));
2014-10-28 18:14:17 +01:00
FeedsModelFeed *actual_feed = dragged_item->toFeed();
2014-10-28 17:45:37 +01:00
FeedsModelFeed *feed_new = new FeedsModelFeed(*actual_feed);
feed_new->setParent(target_item);
editFeed(actual_feed, feed_new);
emit requireItemValidationAfterDragDrop(indexForItem(actual_feed));
2014-10-28 17:45:37 +01:00
}
else if (dragged_item->kind() == FeedsModelRootItem::Category) {
qDebug("Drag-drop action for category '%s' detected, editing the feed.", qPrintable(dragged_item->title()));
2014-10-28 18:14:17 +01:00
FeedsModelCategory *actual_category = dragged_item->toCategory();
2014-10-28 17:45:37 +01:00
FeedsModelCategory *category_new = new FeedsModelCategory(*actual_category);
category_new->clearChildren();
category_new->setParent(target_item);
editCategory(actual_category, category_new);
emit requireItemValidationAfterDragDrop(indexForItem(actual_category));
2014-10-28 17:45:37 +01:00
}
}
return true;
}
}
Qt::DropActions FeedsModel::supportedDropActions() const {
return Qt::MoveAction;
}
Qt::ItemFlags FeedsModel::flags(const QModelIndex &index) const {
Qt::ItemFlags base_flags = QAbstractItemModel::flags(index);
FeedsModelRootItem *item_for_index = itemForIndex(index);
switch (item_for_index->kind()) {
case FeedsModelRootItem::RecycleBin:
return base_flags;
case FeedsModelRootItem::Category:
return base_flags | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
case FeedsModelRootItem::Feed:
return base_flags | Qt::ItemIsDragEnabled;
case FeedsModelRootItem::RootItem:
default:
return base_flags | Qt::ItemIsDropEnabled;
}
}
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();
}
2014-03-28 09:04:36 +01:00
FeedsModelRootItem *parent_item = itemForIndex(parent);
2013-12-12 08:27:05 +01:00
FeedsModelRootItem *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();
}
2014-03-28 09:04:36 +01:00
FeedsModelRootItem *child_item = itemForIndex(child);
2013-12-12 08:27:05 +01:00
FeedsModelRootItem *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
}
}
bool FeedsModel::removeItem(const QModelIndex &index) {
if (index.isValid()) {
QModelIndex parent_index = index.parent();
FeedsModelRootItem *deleting_item = itemForIndex(index);
FeedsModelRootItem *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;
}
2014-09-01 17:43:22 +02:00
bool FeedsModel::addCategory(FeedsModelCategory *category, FeedsModelRootItem *parent) {
// Get index of parent item (parent standard category).
QModelIndex parent_index = indexForItem(parent);
bool result = category->addItself(parent);
if (result) {
// Category was added to the persistent storage,
// so add it to the model.
beginInsertRows(parent_index, parent->childCount(), parent->childCount());
parent->appendChild(category);
endInsertRows();
}
else {
// We cannot delete (*this) in its method, thus delete it here.
delete category;
2014-01-07 22:35:11 +01:00
}
return result;
}
bool FeedsModel::editCategory(FeedsModelCategory *original_category, FeedsModelCategory *new_category_data) {
FeedsModelRootItem *original_parent = original_category->parent();
FeedsModelRootItem *new_parent = new_category_data->parent();
bool result = original_category->editItself(new_category_data);
if (result && original_parent != new_parent) {
// User edited category and set it new parent item,
// se we need to move the item in the model too.
int original_index_of_category = original_parent->childItems().indexOf(original_category);
int new_index_of_category = new_parent->childCount();
// Remove the original item from the model...
2014-09-01 17:43:22 +02:00
beginRemoveRows(indexForItem(original_parent), original_index_of_category, original_index_of_category);
original_parent->removeChild(original_category);
endRemoveRows();
// ...and insert it under the new parent.
2014-09-01 17:43:22 +02:00
beginInsertRows(indexForItem(new_parent), new_index_of_category, new_index_of_category);
new_parent->appendChild(original_category);
endInsertRows();
}
// Cleanup temporary new category data.
delete new_category_data;
return result;
}
2014-09-01 17:43:22 +02:00
bool FeedsModel::addFeed(FeedsModelFeed *feed, FeedsModelRootItem *parent) {
// Get index of parent item (parent standard category or root item).
QModelIndex parent_index = indexForItem(parent);
bool result = feed->addItself(parent);
if (result) {
// Feed was added to the persistent storage so add it to the model.
beginInsertRows(parent_index, parent->childCount(), parent->childCount());
parent->appendChild(feed);
endInsertRows();
}
else {
delete feed;
}
return result;
2014-01-30 14:37:28 +01:00
}
bool FeedsModel::editFeed(FeedsModelFeed *original_feed, FeedsModelFeed *new_feed_data) {
FeedsModelRootItem *original_parent = original_feed->parent();
FeedsModelRootItem *new_parent = new_feed_data->parent();
bool result = original_feed->editItself(new_feed_data);
if (result && original_parent != new_parent) {
// User edited category and set it new parent item,
// se we need to move the item in the model too.
int original_index_of_feed = original_parent->childItems().indexOf(original_feed);
int new_index_of_feed = new_parent->childCount();
// Remove the original item from the model...
2014-09-17 12:27:33 +02:00
beginRemoveRows(indexForItem(original_parent), original_index_of_feed, original_index_of_feed);
original_parent->removeChild(original_feed);
endRemoveRows();
// ... and insert it under the new parent.
2014-09-17 12:27:33 +02:00
beginInsertRows(indexForItem(new_parent), new_index_of_feed, new_index_of_feed);
new_parent->appendChild(original_feed);
endInsertRows();
}
delete new_feed_data;
return result;
2014-01-30 14:37:28 +01:00
}
2014-02-04 18:55:37 +01:00
QList<FeedsModelFeed*> FeedsModel::feedsForScheduledUpdate(bool auto_update_now) {
QList<FeedsModelFeed*> feeds_for_update;
foreach (FeedsModelFeed *feed, allFeeds()) {
2014-03-26 08:33:50 +01:00
switch (feed->autoUpdateType()) {
case FeedsModelFeed::DontAutoUpdate:
// Do not auto-update this feed ever.
continue;
2014-03-26 08:33:50 +01:00
case FeedsModelFeed::DefaultAutoUpdate:
2014-02-04 18:55:37 +01:00
if (auto_update_now) {
feeds_for_update.append(feed);
}
break;
2014-03-26 08:33:50 +01:00
case FeedsModelFeed::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;
}
2014-01-10 08:36:08 +01:00
QList<Message> FeedsModel::messagesForFeeds(const QList<FeedsModelFeed*> &feeds) {
QList<Message> messages;
2014-08-27 14:57:51 +02:00
QSqlDatabase database = qApp->database()->connection(objectName(),
2014-08-28 08:15:33 +02:00
DatabaseFactory::FromSettings);
QSqlQuery query_read_msg(database);
query_read_msg.setForwardOnly(true);
2015-06-24 09:10:43 +02:00
query_read_msg.prepare("SELECT title, url, author, date_created, contents "
"FROM Messages "
2015-06-24 09:10:43 +02:00
"WHERE is_deleted = 0 AND feed = :feed;");
foreach (FeedsModelFeed *feed, feeds) {
query_read_msg.bindValue(QSL(":feed"), feed->id());
if (query_read_msg.exec()) {
while (query_read_msg.next()) {
Message message;
message.m_feedId = feed->id();
message.m_title = query_read_msg.value(0).toString();
message.m_url = query_read_msg.value(1).toString();
message.m_author = query_read_msg.value(2).toString();
message.m_created = TextFactory::parseDateTime(query_read_msg.value(3).value<qint64>());
message.m_contents = query_read_msg.value(4).toString();
messages.append(message);
}
}
}
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
2013-12-19 13:52:53 +01:00
FeedsModelRootItem *FeedsModel::itemForIndex(const QModelIndex &index) const {
2013-12-16 20:14:53 +01:00
if (index.isValid() && index.model() == this) {
return static_cast<FeedsModelRootItem*>(index.internalPointer());
}
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
}
2013-12-30 19:28:39 +01:00
FeedsModelCategory *FeedsModel::categoryForIndex(const QModelIndex &index) const {
FeedsModelRootItem *item = itemForIndex(index);
if (item->kind() == FeedsModelRootItem::Category) {
2014-10-28 18:14:17 +01:00
return item->toCategory();
2013-12-30 19:28:39 +01:00
}
else {
return NULL;
}
}
FeedsModelRecycleBin *FeedsModel::recycleBinForIndex(const QModelIndex &index) const {
FeedsModelRootItem *item = itemForIndex(index);
if (item->kind() == FeedsModelRootItem::RecycleBin) {
2014-10-28 18:14:17 +01:00
return item->toRecycleBin();
}
else {
return NULL;
}
}
QModelIndex FeedsModel::indexForItem(FeedsModelRootItem *item) const {
2013-12-31 14:25:49 +01:00
if (item == NULL || item->kind() == FeedsModelRootItem::RootItem) {
2013-12-31 13:19:25 +01:00
// Root item lies on invalid index.
return QModelIndex();
}
QStack<FeedsModelRootItem*> chain;
while (item->kind() != FeedsModelRootItem::RootItem) {
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()) {
FeedsModelRootItem *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 FeedsModelFeed *feed, allFeeds()) {
if (feed->status() == FeedsModelFeed::NewMessages) {
return true;
}
}
return false;
}
2014-09-03 17:38:58 +02:00
bool FeedsModel::mergeModel(FeedsImportExportModel *model, QString &output_message) {
if (model == NULL || model->rootItem() == NULL) {
output_message = tr("Invalid tree data.");
qDebug("Root item for merging two models is null.");
2014-09-01 18:17:17 +02:00
return false;
}
QStack<FeedsModelRootItem*> original_parents; original_parents.push(m_rootItem);
2014-09-03 17:38:58 +02:00
QStack<FeedsModelRootItem*> new_parents; new_parents.push(model->rootItem());
bool some_feed_category_error = false;
// We are definitely about to add some new items into the model.
//emit layoutAboutToBeChanged();
// Iterate all new items we would like to merge into current model.
while (!new_parents.isEmpty()) {
FeedsModelRootItem *target_parent = original_parents.pop();
FeedsModelRootItem *source_parent = new_parents.pop();
foreach (FeedsModelRootItem *source_item, source_parent->childItems()) {
2014-09-03 17:38:58 +02:00
if (!model->isItemChecked(source_item)) {
// We can skip this item, because it is not checked and should not be imported.
// NOTE: All descendants are thus skipped too.
continue;
}
if (source_item->kind() == FeedsModelRootItem::Category) {
2014-10-28 18:14:17 +01:00
FeedsModelCategory *source_category = source_item->toCategory();
2014-09-03 07:50:29 +02:00
FeedsModelCategory *new_category = new FeedsModelCategory(*source_category);
// Add category to model.
new_category->clearChildren();
2014-09-03 07:50:29 +02:00
if (addCategory(new_category, target_parent)) {
// Process all children of this category.
original_parents.push(new_category);
new_parents.push(source_category);
}
else {
// Add category failed, but this can mean that the same category (with same title)
// already exists. If such a category exists in current parent, then find it and
// add descendants to it.
FeedsModelRootItem *existing_category = target_parent->child(FeedsModelRootItem::Category, new_category->title());
if (existing_category != NULL) {
original_parents.push(existing_category);
new_parents.push(source_category);
}
else {
some_feed_category_error = true;
}
2014-09-03 07:50:29 +02:00
}
}
else if (source_item->kind() == FeedsModelRootItem::Feed) {
2014-10-28 18:14:17 +01:00
FeedsModelFeed *source_feed = source_item->toFeed();
FeedsModelFeed *new_feed = new FeedsModelFeed(*source_feed);
// Append this feed and end this iteration.
2014-09-03 07:50:29 +02:00
if (!addFeed(new_feed, target_parent)) {
some_feed_category_error = true;
}
}
}
}
// Changes are done now. Finalize the new model.
//emit layoutChanged();
2014-09-03 07:50:29 +02:00
if (some_feed_category_error) {
output_message = tr("Import successfull, but some feeds/categories were not imported due to error.");
}
else {
output_message = tr("Import was completely successfull.");
}
return !some_feed_category_error;
2014-09-01 18:17:17 +02:00
}
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
}
2013-12-28 11:22:00 +01:00
QStringList FeedsModel::textualFeedIds(const QList<FeedsModelFeed*> &feeds) {
QStringList stringy_ids;
stringy_ids.reserve(feeds.size());
foreach (FeedsModelFeed *feed, feeds) {
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();
}
2013-12-13 20:48:45 +01:00
void FeedsModel::loadFromDatabase() {
// Delete all childs of the root node and clear them from the memory.
2013-12-16 10:22:23 +01:00
qDeleteAll(m_rootItem->childItems());
2014-01-14 14:42:20 +01:00
m_rootItem->clearChildren();
2013-12-13 20:48:45 +01:00
2014-09-01 18:17:17 +02:00
QSqlDatabase database = qApp->database()->connection(objectName(), DatabaseFactory::FromSettings);
2013-12-14 11:45:43 +01:00
CategoryAssignment categories;
FeedAssignment feeds;
2013-12-13 20:48:45 +01:00
2013-12-14 11:45:43 +01:00
// Obtain data for categories from the database.
2013-12-31 17:33:42 +01:00
QSqlQuery query_categories(database);
query_categories.setForwardOnly(true);
2013-12-13 20:48:45 +01:00
if (!query_categories.exec(QSL("SELECT * FROM Categories;")) || query_categories.lastError().isValid()) {
2014-01-11 17:53:43 +01:00
qFatal("Query for obtaining categories failed. Error message: '%s'.",
qPrintable(query_categories.lastError().text()));
2013-12-13 20:48:45 +01:00
}
while (query_categories.next()) {
2014-03-29 08:36:34 +01:00
CategoryAssignmentItem pair;
pair.first = query_categories.value(CAT_DB_PARENT_ID_INDEX).toInt();
2015-01-04 14:12:14 +01:00
pair.second = new FeedsModelCategory(query_categories.record());
2013-12-13 20:48:45 +01:00
2014-03-29 08:36:34 +01:00
categories << pair;
2013-12-13 20:48:45 +01:00
}
// All categories are now loaded.
2013-12-31 17:33:42 +01:00
QSqlQuery query_feeds(database);
query_feeds.setForwardOnly(true);
2013-12-13 20:48:45 +01:00
if (!query_feeds.exec(QSL("SELECT * FROM Feeds;")) || query_feeds.lastError().isValid()) {
2015-05-12 09:48:22 +02:00
qFatal("Query for obtaining feeds failed. Error message: '%s'.",
qPrintable(query_feeds.lastError().text()));
2013-12-13 20:48:45 +01:00
}
while (query_feeds.next()) {
// Process this feed.
FeedsModelFeed::Type type = static_cast<FeedsModelFeed::Type>(query_feeds.value(FDS_DB_TYPE_INDEX).toInt());
switch (type) {
2014-03-26 08:33:50 +01:00
case FeedsModelFeed::Atom10:
case FeedsModelFeed::Rdf:
case FeedsModelFeed::Rss0X:
case FeedsModelFeed::Rss2X: {
2013-12-14 11:45:43 +01:00
FeedAssignmentItem pair;
2013-12-13 20:48:45 +01:00
pair.first = query_feeds.value(FDS_DB_CATEGORY_INDEX).toInt();
2015-01-04 14:12:14 +01:00
pair.second = new FeedsModelFeed(query_feeds.record());
2013-12-14 11:45:43 +01:00
pair.second->setType(type);
feeds << pair;
2013-12-13 20:48:45 +01:00
break;
}
default:
break;
}
2013-12-14 11:45:43 +01:00
}
// All data are now obtained, lets create the hierarchy.
assembleCategories(categories);
assembleFeeds(feeds);
2014-09-13 16:11:53 +02:00
// As the last item, add recycle bin, which is needed.
m_rootItem->appendChild(m_recycleBin);
2013-12-14 11:45:43 +01:00
}
2013-12-16 12:53:48 +01:00
QList<FeedsModelFeed*> FeedsModel::feedsForIndex(const QModelIndex &index) {
FeedsModelRootItem *item = itemForIndex(index);
return feedsForItem(item);
2013-12-16 12:53:48 +01:00
}
2013-12-30 19:28:39 +01:00
FeedsModelFeed *FeedsModel::feedForIndex(const QModelIndex &index) {
FeedsModelRootItem *item = itemForIndex(index);
if (item->kind() == FeedsModelRootItem::Feed) {
2014-10-28 18:14:17 +01:00
return item->toFeed();
2013-12-30 19:28:39 +01:00
}
else {
return NULL;
}
}
QList<FeedsModelFeed*> FeedsModel::feedsForIndexes(const QModelIndexList &indexes) {
QList<FeedsModelFeed*> 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.
qSort(feeds.begin(), feeds.end(), FeedsModelRootItem::lessThan);
2014-09-01 18:17:17 +02:00
feeds.erase(std::unique(feeds.begin(), feeds.end(), FeedsModelRootItem::isEqual), feeds.end());
}
return feeds;
}
2014-09-01 18:17:17 +02:00
bool FeedsModel::markFeedsRead(const QList<FeedsModelFeed*> &feeds, int read) {
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();
}
}
2014-09-01 18:17:17 +02:00
bool FeedsModel::markFeedsDeleted(const QList<FeedsModelFeed*> &feeds, int deleted, bool read_only) {
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();
}
}
QHash<int, FeedsModelCategory*> FeedsModel::allCategories() {
return categoriesForItem(m_rootItem);
}
QHash<int, FeedsModelCategory*> FeedsModel::categoriesForItem(FeedsModelRootItem *root) {
2013-12-14 11:45:43 +01:00
QHash<int, FeedsModelCategory*> categories;
QList<FeedsModelRootItem*> parents;
2013-12-14 11:45:43 +01:00
parents.append(root->childItems());
2013-12-14 11:45:43 +01:00
while (!parents.isEmpty()) {
FeedsModelRootItem *item = parents.takeFirst();
if (item->kind() == FeedsModelRootItem::Category) {
// This item is category, add it to the output list and
// scan its children.
int category_id = item->id();
2014-10-28 18:14:17 +01:00
FeedsModelCategory *category = item->toCategory();
if (!categories.contains(category_id)) {
categories.insert(category_id, category);
}
2013-12-14 11:45:43 +01:00
parents.append(category->childItems());
2013-12-14 11:45:43 +01:00
}
}
return categories;
}
QList<FeedsModelFeed*> FeedsModel::allFeeds() {
return feedsForItem(m_rootItem);
}
QList<FeedsModelFeed*> FeedsModel::feedsForItem(FeedsModelRootItem *root) {
QList<FeedsModelRootItem*> children = root->getRecursiveChildren();
QList<FeedsModelFeed*> feeds;
foreach (FeedsModelRootItem *child, children) {
if (child->kind() == FeedsModelRootItem::Feed) {
feeds.append(child->toFeed());
}
}
return feeds;
2013-12-14 11:45:43 +01:00
}
void FeedsModel::assembleFeeds(FeedAssignment feeds) {
QHash<int, FeedsModelCategory*> categories = allCategories();
2013-12-13 20:48:45 +01:00
2013-12-21 21:08:52 +01:00
foreach (const FeedAssignmentItem &feed, feeds) {
2013-12-14 11:45:43 +01:00
if (feed.first == NO_PARENT_CATEGORY) {
// This is top-level feed, add it to the root item.
m_rootItem->appendChild(feed.second);
}
2013-12-14 13:45:24 +01:00
else if (categories.contains(feed.first)) {
// This feed belongs to this category.
2013-12-14 11:45:43 +01:00
categories.value(feed.first)->appendChild(feed.second);
}
2013-12-14 13:45:24 +01:00
else {
2014-09-17 12:27:33 +02:00
qWarning("Feed '%s' is loose, skipping it.", qPrintable(feed.second->title()));
2013-12-14 13:45:24 +01:00
}
2013-12-13 20:48:45 +01:00
}
2013-12-14 11:45:43 +01:00
}
2013-12-13 20:48:45 +01:00
FeedsModelRecycleBin *FeedsModel::recycleBin() const {
return m_recycleBin;
}
2013-12-14 11:45:43 +01:00
void FeedsModel::assembleCategories(CategoryAssignment categories) {
QHash<int, FeedsModelRootItem*> assignments;
assignments.insert(NO_PARENT_CATEGORY, m_rootItem);
2013-12-14 11:45:43 +01:00
// Add top-level categories.
while (!categories.isEmpty()) {
for (int i = 0; i < categories.size(); i++) {
if (assignments.contains(categories.at(i).first)) {
// Parent category of this category is already added.
assignments.value(categories.at(i).first)->appendChild(categories.at(i).second);
// Now, added category can be parent for another categories, add it.
assignments.insert(categories.at(i).second->id(),
categories.at(i).second);
// Remove the category from the list, because it was
// added to the final collection.
categories.removeAt(i);
i--;
}
}
}
2013-12-13 20:48:45 +01:00
}