rssguard/src/core/feedsmodel.cpp

611 lines
18 KiB
C++
Raw Normal View History

2013-11-12 20:24:19 +01:00
#include "core/feedsmodel.h"
2013-12-21 21:08:52 +01:00
2013-12-12 15:07:17 +01:00
#include "core/defs.h"
2013-12-13 20:48:45 +01:00
#include "core/databasefactory.h"
2013-12-21 21:08:52 +01:00
#include "core/feedsmodelstandardcategory.h"
#include "core/feedsmodelstandardfeed.h"
#include "core/textfactory.h"
2013-12-12 15:07:17 +01:00
#include "gui/iconthemefactory.h"
2013-12-14 11:45:43 +01:00
#include "gui/iconfactory.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>
2013-12-27 10:24:07 +01:00
#include <algorithm>
2013-11-12 20:24:19 +01:00
FeedsModel::FeedsModel(QObject *parent) : QAbstractItemModel(parent) {
2013-12-13 20:48:45 +01:00
setObjectName("FeedsModel");
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);
2013-12-22 11:29:10 +01:00
m_rootItem->setTitle(tr("root"));
2014-01-10 09:18:29 +01:00
m_rootItem->setIcon(IconThemeFactory::instance()->fromTheme("folder-red"));
m_countsIcon = IconThemeFactory::instance()->fromTheme("mail-mark-important");
2013-12-12 15:07:17 +01:00
m_headerData << tr("Title");
2013-12-12 22:28:51 +01:00
m_tooltipData << tr("Titles of feeds/categories.") <<
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
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();
}
2013-12-12 08:27:05 +01:00
FeedsModelRootItem *parent_item;
2013-12-11 18:54:09 +01:00
2014-01-10 08:36:08 +01:00
// TODO: overit zda zde misto internalPointer nepouzit
// metodu itemFornIndex a overit vykonnostni dopady
2013-12-11 18:54:09 +01:00
if (!parent.isValid()) {
parent_item = m_rootItem;
}
else {
2013-12-12 08:27:05 +01:00
parent_item = static_cast<FeedsModelRootItem*>(parent.internalPointer());
2013-12-11 18:54:09 +01:00
}
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-01-10 08:36:08 +01:00
// TODO: overit zda zde misto internalPointer nepouzit
// metodu itemFornIndex a overit vykonnostni dopady
2013-12-12 08:27:05 +01:00
FeedsModelRootItem *child_item = static_cast<FeedsModelRootItem*>(child.internalPointer());
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 {
2013-12-12 08:27:05 +01:00
FeedsModelRootItem *parent_item;
2013-12-11 18:54:09 +01:00
if (parent.column() > 0) {
return 0;
}
2014-01-10 08:36:08 +01:00
// TODO: overit zda zde misto internalPointer nepouzit
// metodu itemFornIndex a overit vykonnostni dopady
2013-12-11 18:54:09 +01:00
if (!parent.isValid()) {
parent_item = m_rootItem;
}
else {
2013-12-12 08:27:05 +01:00
parent_item = static_cast<FeedsModelRootItem*>(parent.internalPointer());
2013-12-11 18:54:09 +01:00
}
return parent_item->childCount();
}
2014-01-18 07:39:43 +01:00
bool FeedsModel::addItem(FeedsModelRootItem *item,
FeedsModelRootItem *parent) {
QModelIndex parent_index = indexForItem(parent);
beginInsertRows(parent_index, parent->childCount(), parent->childCount());
// Add item to hierarchy.
parent->appendChild(item);
// Add item to persistent storage.
item->addItself();
endInsertRows();
return true;
}
bool FeedsModel::removeItem(const QModelIndex &index) {
if (index.isValid()) {
QModelIndex parent_index = index.parent();
FeedsModelRootItem *deleting_item = itemForIndex(index);
FeedsModelRootItem *parent_item = itemForIndex(parent_index);
beginRemoveRows(parent_index, index.row(), index.row());
if (deleting_item->removeItself() &&
parent_item->removeChild(deleting_item)) {
// Free deleted item from the memory
delete deleting_item;
2014-01-07 22:35:11 +01:00
}
endRemoveRows();
return true;
2014-01-07 22:35:11 +01:00
}
return false;
}
2014-01-10 08:36:08 +01:00
QList<Message> FeedsModel::messagesForFeeds(const QList<FeedsModelFeed*> &feeds) {
QList<Message> messages;
2014-01-12 14:13:00 +01:00
QSqlDatabase database = DatabaseFactory::instance()->connection();
QSqlQuery query_read_msg(database);
query_read_msg.setForwardOnly(true);
query_read_msg.prepare("SELECT title, url, author, date_created, contents "
"FROM Messages "
"WHERE deleted = 0 AND feed = :feed;");
foreach (FeedsModelFeed *feed, feeds) {
query_read_msg.bindValue(":feed", feed->id());
if (query_read_msg.exec()) {
while (query_read_msg.next()) {
Message message;
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-01-10 08:36:08 +01:00
// TODO: overit zda zde misto internalPointer nepouzit
// metodu itemFornIndex a overit vykonnostni dopady
2013-12-11 18:54:09 +01:00
if (parent.isValid()) {
2013-12-12 08:27:05 +01:00
return static_cast<FeedsModelRootItem*>(parent.internalPointer())->columnCount();
2013-12-11 18:54:09 +01:00
}
else {
return m_rootItem->columnCount();
}
}
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) {
return static_cast<FeedsModelCategory*>(item);
}
else {
return NULL;
}
}
2013-12-31 14:25:49 +01:00
2013-12-31 13:19:25 +01:00
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();
}
2013-12-31 14:33:42 +01:00
QList<QModelIndex> parents;
2013-12-31 13:19:25 +01:00
2013-12-31 14:33:42 +01:00
// Start with root item.
parents << indexForItem(m_rootItem);
2013-12-31 13:19:25 +01:00
while (!parents.isEmpty()) {
QModelIndex active_index = parents.takeFirst();
int row_count = rowCount(active_index);
if (row_count > 0) {
// This index has children.
// Lets take a look if our target item is among them.
FeedsModelRootItem *active_item = itemForIndex(active_index);
int candidate_index = active_item->childItems().indexOf(item);
if (candidate_index >= 0) {
// We found our item.
return index(candidate_index, 0, active_index);
}
else {
// Item is not found, add all "categories" from active_item.
for (int i = 0; i < row_count; i++) {
FeedsModelRootItem *possible_category = active_item->child(i);
if (possible_category->kind() == FeedsModelRootItem::Category) {
parents << index(i, 0, active_index);
}
}
}
}
}
return QModelIndex();
}
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.
emit dataChanged(index(indx.row(), 0, indx_parent),
index(indx.row(), FDS_MODEL_COUNTS_INDEX, indx_parent));
2013-12-19 08:54:31 +01:00
if (indx_parent.isValid()) {
2013-12-19 08:54:31 +01:00
// Make sure that data of parent are changed too.
2014-01-03 09:00:51 +01:00
list.prepend(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-19 08:54:31 +01:00
// NOTE: Take a look at docs about this.
// I have tested that this is LITTLE slower than code above,
// but it is really SIMPLER, so if code above will be buggy, then
// we can use this.
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-01-12 14:13:00 +01:00
QSqlDatabase database = DatabaseFactory::instance()->connection();
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
2013-12-31 17:33:42 +01:00
if (!query_categories.exec("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()) {
// Process this category.
FeedsModelCategory::Type type = static_cast<FeedsModelCategory::Type>(query_categories.value(CAT_DB_TYPE_INDEX).toInt());
switch (type) {
case FeedsModelCategory::Standard: {
2013-12-14 11:45:43 +01:00
CategoryAssignmentItem pair;
2013-12-13 20:48:45 +01:00
pair.first = query_categories.value(CAT_DB_PARENT_ID_INDEX).toInt();
pair.second = FeedsModelStandardCategory::loadFromRecord(query_categories.record());
categories << pair;
break;
}
case FeedsModelCategory::Feedly:
default:
// NOTE: Not yet implemented.
break;
}
}
// 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
2013-12-31 17:33:42 +01:00
if (!query_feeds.exec("SELECT * FROM Feeds;") ||
query_feeds.lastError().isValid()) {
2013-12-13 20:48:45 +01:00
qFatal("Query for obtaining feeds failed.");
}
while (query_feeds.next()) {
// Process this feed.
FeedsModelFeed::Type type = static_cast<FeedsModelFeed::Type>(query_feeds.value(FDS_DB_TYPE_INDEX).toInt());
switch (type) {
case FeedsModelFeed::StandardAtom10:
2013-12-13 20:48:45 +01:00
case FeedsModelFeed::StandardRdf:
2013-12-18 08:51:35 +01:00
case FeedsModelFeed::StandardRss0X:
case FeedsModelFeed::StandardRss2X: {
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();
2013-12-14 11:45:43 +01:00
pair.second = FeedsModelStandardFeed::loadFromRecord(query_feeds.record());
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);
}
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) {
return static_cast<FeedsModelFeed*>(item);
}
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-01-10 08:36:08 +01:00
feeds.erase(std::unique(feeds.begin(),
feeds.end(), FeedsModelRootItem::isEqual),
feeds.end());
}
return feeds;
}
2013-12-28 11:22:00 +01:00
bool FeedsModel::markFeedsRead(const QList<FeedsModelFeed*> &feeds,
int read) {
2014-01-12 14:13:00 +01:00
QSqlDatabase db_handle = DatabaseFactory::instance()->connection();
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);
2013-12-28 11:22:00 +01:00
if (!query_read_msg.prepare(QString("UPDATE messages SET read = :read "
"WHERE feed IN (%1) AND deleted = 0").arg(textualFeedIds(feeds).join(", ")))) {
qWarning("Query preparation failed for feeds read change.");
db_handle.rollback();
return false;
}
query_read_msg.bindValue(":read", read);
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<FeedsModelFeed *> &feeds,
int deleted) {
2014-01-12 14:13:00 +01:00
QSqlDatabase db_handle = DatabaseFactory::instance()->connection();
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);
2013-12-28 11:22:00 +01:00
if (!query_delete_msg.prepare(QString("UPDATE messages SET deleted = :deleted "
"WHERE feed IN (%1) AND deleted = 0").arg(textualFeedIds(feeds).join(", ")))) {
qWarning("Query preparation failed for feeds clearing.");
db_handle.rollback();
return false;
}
query_delete_msg.bindValue(":deleted", deleted);
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();
FeedsModelCategory *category = static_cast<FeedsModelCategory*>(item);
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<FeedsModelFeed*> feeds;
if (root->kind() == FeedsModelRootItem::Feed) {
// Root itself is a FEED.
feeds.append(static_cast<FeedsModelFeed*>(root));
}
else {
// Root itself is a CATEGORY or ROOT item.
2013-12-31 17:33:42 +01:00
QList<FeedsModelRootItem*> traversable_items;
2013-12-31 17:33:42 +01:00
traversable_items.append(root);
// Iterate all nested categories.
while (!traversable_items.isEmpty()) {
2013-12-31 17:33:42 +01:00
FeedsModelRootItem *active_category = traversable_items.takeFirst();
foreach (FeedsModelRootItem *child, active_category->childItems()) {
if (child->kind() == FeedsModelRootItem::Feed) {
// This child is feed.
feeds.append(static_cast<FeedsModelFeed*>(child));
}
else if (child->kind() == FeedsModelRootItem::Category) {
// This child is category, add its child feeds too.
2013-12-31 17:33:42 +01:00
traversable_items.append(static_cast<FeedsModelCategory*>(child));
}
}
}
}
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 {
qWarning("Feed '%s' is loose, skipping it.",
qPrintable(feed.second->title()));
}
2013-12-13 20:48:45 +01:00
}
2013-12-14 11:45:43 +01:00
}
2013-12-13 20:48:45 +01:00
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
}