From ef2438e3bcc903513867dc3805759a8423413b9f Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Tue, 5 Apr 2016 10:06:05 +0200 Subject: [PATCH] SQL refactoring. --- src/miscellaneous/databasecleaner.cpp | 53 +--- src/miscellaneous/databasequeries.cpp | 378 ++++++++++++++++++++++++++ src/miscellaneous/databasequeries.h | 12 + src/services/abstract/category.cpp | 42 +-- src/services/abstract/feed.cpp | 222 +-------------- src/services/abstract/recyclebin.cpp | 29 +- 6 files changed, 417 insertions(+), 319 deletions(-) diff --git a/src/miscellaneous/databasecleaner.cpp b/src/miscellaneous/databasecleaner.cpp index dcbcc5205..9af0ed426 100755 --- a/src/miscellaneous/databasecleaner.cpp +++ b/src/miscellaneous/databasecleaner.cpp @@ -18,11 +18,9 @@ #include "miscellaneous/databasecleaner.h" #include "miscellaneous/application.h" -#include "miscellaneous/databasefactory.h" +#include "miscellaneous/databasequeries.h" #include -#include -#include #include @@ -90,55 +88,18 @@ void DatabaseCleaner::purgeDatabaseData(const CleanerOrders &which_data) { emit purgeFinished(result); } -bool DatabaseCleaner::purgeStarredMessages(const QSqlDatabase &database) { - QSqlQuery query = QSqlQuery(database); - - query.setForwardOnly(true); - query.prepare(QSL("DELETE FROM Messages WHERE is_important = :is_important;")); - query.bindValue(QSL(":is_important"), 1); - - return query.exec(); +bool DatabaseCleaner::purgeStarredMessages(const QSqlDatabase &database) { + return DatabaseQueries::purgeImportantMessages(database); } -bool DatabaseCleaner::purgeReadMessages(const QSqlDatabase &database) { - QSqlQuery query = QSqlQuery(database); - - query.setForwardOnly(true); - query.prepare(QSL("DELETE FROM Messages WHERE is_important = :is_important AND is_deleted = :is_deleted AND is_read = :is_read;")); - query.bindValue(QSL(":is_read"), 1); - - // Remove only messages which are NOT in recycle bin. - query.bindValue(QSL(":is_deleted"), 0); - - // Remove only messages which are NOT starred. - query.bindValue(QSL(":is_important"), 0); - - return query.exec(); +bool DatabaseCleaner::purgeReadMessages(const QSqlDatabase &database) { + return DatabaseQueries::purgeReadMessages(database); } bool DatabaseCleaner::purgeOldMessages(const QSqlDatabase &database, int days) { - QSqlQuery query = QSqlQuery(database); - const qint64 since_epoch = QDateTime::currentDateTimeUtc().addDays(-days).toMSecsSinceEpoch(); - - query.setForwardOnly(true); - query.prepare(QSL("DELETE FROM Messages WHERE is_important = :is_important AND date_created < :date_created;")); - query.bindValue(QSL(":date_created"), since_epoch); - - // Remove only messages which are NOT starred. - query.bindValue(QSL(":is_important"), 0); - - return query.exec(); + return DatabaseQueries::purgeOldMessages(database, days); } bool DatabaseCleaner::purgeRecycleBin(const QSqlDatabase &database) { - QSqlQuery query = QSqlQuery(database); - - query.setForwardOnly(true); - query.prepare(QSL("DELETE FROM Messages WHERE is_important = :is_important AND is_deleted = :is_deleted;")); - query.bindValue(QSL(":is_deleted"), 1); - - // Remove only messages which are NOT starred. - query.bindValue(QSL(":is_important"), 0); - - return query.exec(); + return DatabaseQueries::purgeRecycleBin(database); } diff --git a/src/miscellaneous/databasequeries.cpp b/src/miscellaneous/databasequeries.cpp index 9598591dd..0d5ab92e1 100644 --- a/src/miscellaneous/databasequeries.cpp +++ b/src/miscellaneous/databasequeries.cpp @@ -18,6 +18,8 @@ #include "miscellaneous/databasequeries.h" #include +#include +#include bool DatabaseQueries::markMessagesRead(QSqlDatabase db, const QStringList &ids, RootItem::ReadStatus read) { @@ -66,5 +68,381 @@ bool DatabaseQueries::deleteOrRestoreMessagesToFromBin(QSqlDatabase db, const QS QString::number(deleted ? 1 : 0))); } +bool DatabaseQueries::purgeImportantMessages(QSqlDatabase db) { + QSqlQuery query = QSqlQuery(db); + query.setForwardOnly(true); + query.prepare(QSL("DELETE FROM Messages WHERE is_important = 1;")); + + return query.exec(); +} + +bool DatabaseQueries::purgeReadMessages(QSqlDatabase db) { + QSqlQuery query = QSqlQuery(db); + query.setForwardOnly(true); + query.prepare(QSL("DELETE FROM Messages WHERE is_important = :is_important AND is_deleted = :is_deleted AND is_read = :is_read;")); + query.bindValue(QSL(":is_read"), 1); + + // Remove only messages which are NOT in recycle bin. + query.bindValue(QSL(":is_deleted"), 0); + + // Remove only messages which are NOT starred. + query.bindValue(QSL(":is_important"), 0); + + return query.exec(); +} + +bool DatabaseQueries::purgeOldMessages(QSqlDatabase db, int older_than_days) { + QSqlQuery query = QSqlQuery(db); + const qint64 since_epoch = QDateTime::currentDateTimeUtc().addDays(-older_than_days).toMSecsSinceEpoch(); + + query.setForwardOnly(true); + query.prepare(QSL("DELETE FROM Messages WHERE is_important = :is_important AND date_created < :date_created;")); + query.bindValue(QSL(":date_created"), since_epoch); + + // Remove only messages which are NOT starred. + query.bindValue(QSL(":is_important"), 0); + + return query.exec(); +} + +bool DatabaseQueries::purgeRecycleBin(QSqlDatabase db) { + QSqlQuery query = QSqlQuery(db); + + query.setForwardOnly(true); + query.prepare(QSL("DELETE FROM Messages WHERE is_important = :is_important AND is_deleted = :is_deleted;")); + query.bindValue(QSL(":is_deleted"), 1); + + // Remove only messages which are NOT starred. + query.bindValue(QSL(":is_important"), 0); + + return query.exec(); +} + +QMap DatabaseQueries::getMessageCountsForCategory(QSqlDatabase db, int custom_id, int account_id, + bool including_total_counts, bool *ok) { + QMap counts; + QSqlQuery q(db); + q.setForwardOnly(true); + + if (including_total_counts) { + q.prepare("SELECT feed, count(*) FROM Messages " + "WHERE feed IN (SELECT custom_id FROM Feeds WHERE category = :category AND account_id = :account_id) AND is_deleted = 0 AND is_pdeleted = 0 AND account_id = :account_id " + "GROUP BY feed;"); + } + else { + q.prepare("SELECT feed, count(*) FROM Messages " + "WHERE feed IN (SELECT custom_id FROM Feeds WHERE category = :category AND account_id = :account_id) AND is_deleted = 0 AND is_pdeleted = 0 AND is_read = 0 AND account_id = :account_id " + "GROUP BY feed;"); + } + + q.bindValue(QSL(":category"), custom_id); + q.bindValue(QSL(":account_id"), account_id); + + if (q.exec()) { + while (q.next()) { + int feed_id = q.value(0).toInt(); + int new_count = q.value(1).toInt(); + + counts.insert(feed_id, new_count); + } + + if (ok != NULL) { + *ok = true; + } + } + else { + if (ok != NULL) { + *ok = false; + } + } + + return counts; +} + +int DatabaseQueries::getMessageCountsForFeed(QSqlDatabase db, int feed_custom_id, + int account_id, bool including_total_counts, bool *ok) { + QSqlQuery query_all(db); + query_all.setForwardOnly(true); + + if (including_total_counts) { + query_all.prepare("SELECT count(*) FROM Messages " + "WHERE feed = :feed AND is_deleted = 0 AND is_pdeleted = 0 AND account_id = :account_id;"); + } + else { + query_all.prepare("SELECT count(*) FROM Messages " + "WHERE feed = :feed AND is_deleted = 0 AND is_pdeleted = 0 AND is_read = 0 AND account_id = :account_id;"); + } + + query_all.bindValue(QSL(":feed"), feed_custom_id); + query_all.bindValue(QSL(":account_id"), account_id); + + if (query_all.exec() && query_all.next()) { + if (ok != NULL) { + *ok = true; + } + + return query_all.value(0).toInt(); + } + else { + if (ok != NULL) { + *ok = false; + } + + return 0; + } +} + +int DatabaseQueries::getMessageCountsForBin(QSqlDatabase db, int account_id, bool including_total_counts, bool *ok) { + QSqlQuery query_all(db); + query_all.setForwardOnly(true); + + if (including_total_counts) { + query_all.prepare("SELECT count(*) FROM Messages " + "WHERE is_deleted = 1 AND is_pdeleted = 0 AND account_id = :account_id;"); + } + else { + query_all.prepare("SELECT count(*) FROM Messages " + "WHERE is_read = 0 AND is_deleted = 1 AND is_pdeleted = 0 AND account_id = :account_id;"); + } + + query_all.bindValue(QSL(":account_id"), account_id); + + if (query_all.exec() && query_all.next()) { + if (ok != NULL) { + *ok = true; + } + + return query_all.value(0).toInt(); + } + else { + if (ok != NULL) { + *ok = false; + } + + return 0; + } +} + +QList DatabaseQueries::getUndeletedMessages(QSqlDatabase db, int feed_custom_id, int account_id, bool *ok) { + QList messages; + QSqlQuery q(db); + q.setForwardOnly(true); + q.prepare("SELECT * " + "FROM Messages " + "WHERE is_deleted = 0 AND is_pdeleted = 0 AND feed = :feed AND account_id = :account_id;"); + + q.bindValue(QSL(":feed"), feed_custom_id); + q.bindValue(QSL(":account_id"), account_id); + + if (q.exec()) { + while (q.next()) { + bool decoded; + Message message = Message::fromSqlRecord(q.record(), &decoded); + + if (decoded) { + messages.append(message); + } + } + + if (ok != NULL) { + *ok = true; + } + } + else { + if (ok != NULL) { + *ok = false; + } + } + + return messages; +} + +int DatabaseQueries::updateMessages(QSqlDatabase db, + const QList &messages, + int feed_custom_id, + int account_id, + const QString &url, + bool *any_message_changed, + bool *ok) { + if (messages.isEmpty()) { + *any_message_changed = false; + *ok = true; + return 0; + } + + // Does not make any difference, since each feed now has + // its own "custom ID" (standard feeds have their custom ID equal to primary key ID). + int updated_messages = 0; + + // Prepare queries. + QSqlQuery query_select_with_url(db); + QSqlQuery query_select_with_id(db); + QSqlQuery query_update(db); + QSqlQuery query_insert(db); + + // Here we have query which will check for existence of the "same" message in given feed. + // The two message are the "same" if: + // 1) they belong to the same feed AND, + // 2) they have same URL AND, + // 3) they have same AUTHOR. + query_select_with_url.setForwardOnly(true); + query_select_with_url.prepare("SELECT id, date_created, is_read, is_important FROM Messages " + "WHERE feed = :feed AND title = :title AND url = :url AND author = :author AND account_id = :account_id;"); + + // When we have custom ID of the message, we can check directly for existence + // of that particular message. + query_select_with_id.setForwardOnly(true); + query_select_with_id.prepare("SELECT id, date_created, is_read, is_important FROM Messages " + "WHERE custom_id = :custom_id AND account_id = :account_id;"); + + // Used to insert new messages. + query_insert.setForwardOnly(true); + query_insert.prepare("INSERT INTO Messages " + "(feed, title, is_read, is_important, url, author, date_created, contents, enclosures, custom_id, custom_hash, account_id) " + "VALUES (:feed, :title, :is_read, :is_important, :url, :author, :date_created, :contents, :enclosures, :custom_id, :custom_hash, :account_id);"); + + // Used to update existing messages. + query_update.setForwardOnly(true); + query_update.prepare("UPDATE Messages " + "SET title = :title, is_read = :is_read, is_important = :is_important, url = :url, author = :author, date_created = :date_created, contents = :contents, enclosures = :enclosures " + "WHERE id = :id;"); + + if (!db.transaction()) { + db.rollback(); + qDebug("Transaction start for message downloader failed: '%s'.", qPrintable(db.lastError().text())); + return updated_messages; + } + + foreach (Message message, messages) { + // Check if messages contain relative URLs and if they do, then replace them. + if (message.m_url.startsWith(QL1S("/"))) { + QString new_message_url = QUrl(url).toString(QUrl::RemoveUserInfo | + QUrl::RemovePath | + QUrl::RemoveQuery | + QUrl::RemoveFilename | + QUrl::StripTrailingSlash); + + new_message_url += message.m_url; + message.m_url = new_message_url; + } + + int id_existing_message = -1; + qint64 date_existing_message; + bool is_read_existing_message; + bool is_important_existing_message; + + if (message.m_customId.isEmpty()) { + // We need to recognize existing messages according URL & AUTHOR. + // NOTE: This concerns messages from standard account. + query_select_with_url.bindValue(QSL(":feed"), feed_custom_id); + query_select_with_url.bindValue(QSL(":title"), message.m_title); + query_select_with_url.bindValue(QSL(":url"), message.m_url); + query_select_with_url.bindValue(QSL(":author"), message.m_author); + query_select_with_url.bindValue(QSL(":account_id"), account_id); + + if (query_select_with_url.exec() && query_select_with_url.next()) { + id_existing_message = query_select_with_url.value(0).toInt(); + date_existing_message = query_select_with_url.value(1).value(); + is_read_existing_message = query_select_with_url.value(2).toBool(); + is_important_existing_message = query_select_with_url.value(3).toBool(); + } + + query_select_with_url.finish(); + } + else { + // We can recognize existing messages via their custom ID. + // NOTE: This concerns messages from custom accounts, like TT-RSS or ownCloud News. + query_select_with_id.bindValue(QSL(":account_id"), account_id); + query_select_with_id.bindValue(QSL(":custom_id"), message.m_customId); + + if (query_select_with_id.exec() && query_select_with_id.next()) { + id_existing_message = query_select_with_id.value(0).toInt(); + date_existing_message = query_select_with_id.value(1).value(); + is_read_existing_message = query_select_with_id.value(2).toBool(); + is_important_existing_message = query_select_with_id.value(3).toBool(); + } + + query_select_with_id.finish(); + } + + // Now, check if this message is already in the DB. + if (id_existing_message >= 0) { + // Message is already in the DB. + // + // Now, we update it if at least one of next conditions is true: + // 1) Message has custom ID AND (its date OR read status OR starred status are changed). + // 2) Message has its date fetched from feed AND its date is different from date in DB. + if (/* 1 */ (!message.m_customId.isEmpty() && (message.m_created.toMSecsSinceEpoch() != date_existing_message || message.m_isRead != is_read_existing_message || message.m_isImportant != is_important_existing_message)) || + /* 2 */ (message.m_createdFromFeed && message.m_created.toMSecsSinceEpoch() != date_existing_message)) { + // Message exists, it is changed, update it. + query_update.bindValue(QSL(":title"), message.m_title); + query_update.bindValue(QSL(":is_read"), (int) message.m_isRead); + query_update.bindValue(QSL(":is_important"), (int) message.m_isImportant); + query_update.bindValue(QSL(":url"), message.m_url); + query_update.bindValue(QSL(":author"), message.m_author); + query_update.bindValue(QSL(":date_created"), message.m_created.toMSecsSinceEpoch()); + query_update.bindValue(QSL(":contents"), message.m_contents); + query_update.bindValue(QSL(":enclosures"), Enclosures::encodeEnclosuresToString(message.m_enclosures)); + query_update.bindValue(QSL(":id"), id_existing_message); + + *any_message_changed = true; + + if (query_update.exec() && !message.m_isRead) { + updated_messages++; + } + + query_update.finish(); + qDebug("Updating message '%s' in DB.", qPrintable(message.m_title)); + } + } + else { + // Message with this URL is not fetched in this feed yet. + query_insert.bindValue(QSL(":feed"), feed_custom_id); + query_insert.bindValue(QSL(":title"), message.m_title); + query_insert.bindValue(QSL(":is_read"), (int) message.m_isRead); + query_insert.bindValue(QSL(":is_important"), (int) message.m_isImportant); + query_insert.bindValue(QSL(":url"), message.m_url); + query_insert.bindValue(QSL(":author"), message.m_author); + query_insert.bindValue(QSL(":date_created"), message.m_created.toMSecsSinceEpoch()); + query_insert.bindValue(QSL(":contents"), message.m_contents); + query_insert.bindValue(QSL(":enclosures"), Enclosures::encodeEnclosuresToString(message.m_enclosures)); + query_insert.bindValue(QSL(":custom_id"), message.m_customId); + query_insert.bindValue(QSL(":custom_hash"), message.m_customHash); + query_insert.bindValue(QSL(":account_id"), account_id); + + if (query_insert.exec() && query_insert.numRowsAffected() == 1) { + updated_messages++; + } + + query_insert.finish(); + qDebug("Adding new message '%s' to DB.", qPrintable(message.m_title)); + } + } + + // Now, fixup custom IDS for messages which initially did not have them, + // just to keep the data consistent. + if (db.exec("UPDATE Messages " + "SET custom_id = (SELECT id FROM Messages t WHERE t.id = Messages.id) " + "WHERE Messages.custom_id IS NULL OR Messages.custom_id = '';").lastError().isValid()) { + qWarning("Failed to set custom ID for all messages."); + } + + if (!db.commit()) { + db.rollback(); + qDebug("Transaction commit for message downloader failed."); + + if (ok != NULL) { + *ok = false; + } + } + else { + if (ok != NULL) { + *ok = true; + } + } + + return updated_messages; +} + DatabaseQueries::DatabaseQueries() { } diff --git a/src/miscellaneous/databasequeries.h b/src/miscellaneous/databasequeries.h index 2904c04f7..0e20c0895 100644 --- a/src/miscellaneous/databasequeries.h +++ b/src/miscellaneous/databasequeries.h @@ -30,6 +30,18 @@ class DatabaseQueries { static bool switchMessagesImportance(QSqlDatabase db, const QStringList &ids); static bool permanentlyDeleteMessages(QSqlDatabase db, const QStringList &ids); static bool deleteOrRestoreMessagesToFromBin(QSqlDatabase db, const QStringList &ids, bool deleted); + static bool purgeImportantMessages(QSqlDatabase db); + static bool purgeReadMessages(QSqlDatabase db); + static bool purgeOldMessages(QSqlDatabase db, int older_than_days); + static bool purgeRecycleBin(QSqlDatabase db); + static QMap getMessageCountsForCategory(QSqlDatabase db, int custom_id, int account_id, + bool including_total_counts, bool *ok = NULL); + static int getMessageCountsForFeed(QSqlDatabase db, int feed_custom_id, int account_id, + bool including_total_counts, bool *ok = NULL); + static int getMessageCountsForBin(QSqlDatabase db, int account_id, bool including_total_counts, bool *ok = NULL); + static QList getUndeletedMessages(QSqlDatabase db, int feed_custom_id, int account_id, bool *ok = NULL); + static int updateMessages(QSqlDatabase db, const QList &messages, int feed_custom_id, + int account_id, const QString &url, bool *any_message_changed, bool *ok = NULL); private: explicit DatabaseQueries(); diff --git a/src/services/abstract/category.cpp b/src/services/abstract/category.cpp index 173d823ad..30421b415 100755 --- a/src/services/abstract/category.cpp +++ b/src/services/abstract/category.cpp @@ -18,11 +18,10 @@ #include "services/abstract/category.h" #include "miscellaneous/application.h" +#include "miscellaneous/databasequeries.h" #include "services/abstract/serviceroot.h" #include "services/abstract/feed.h" -#include - Category::Category(RootItem *parent) : RootItem(parent) { setKind(RootItemKind::Category); @@ -48,48 +47,23 @@ void Category::updateCounts(bool including_total_count) { } QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); - QSqlQuery query_all(database); - QMap counts; - - query_all.setForwardOnly(true); + bool ok; if (including_total_count) { - query_all.prepare("SELECT feed, count(*) FROM Messages " - "WHERE feed IN (SELECT custom_id FROM Feeds WHERE category = :category AND account_id = :account_id) AND is_deleted = 0 AND is_pdeleted = 0 AND account_id = :account_id " - "GROUP BY feed;"); - query_all.bindValue(QSL(":category"), customId()); - query_all.bindValue(QSL(":account_id"), getParentServiceRoot()->accountId()); - - if (query_all.exec()) { - while (query_all.next()) { - int feed_id = query_all.value(0).toInt(); - int new_count = query_all.value(1).toInt(); - - counts.insert(feed_id, new_count); - } + QMap counts = DatabaseQueries::getMessageCountsForCategory(database, customId(), getParentServiceRoot()->accountId(), + including_total_count, &ok); + if (ok) { foreach (Feed *feed, feeds) { feed->setCountOfAllMessages(counts.value(feed->customId())); } } } - counts.clear(); - query_all.prepare("SELECT feed, count(*) FROM Messages " - "WHERE feed IN (SELECT custom_id FROM Feeds WHERE category = :category AND account_id = :account_id) AND is_deleted = 0 AND is_pdeleted = 0 AND is_read = 0 AND account_id = :account_id " - "GROUP BY feed;"); - query_all.bindValue(QSL(":category"), customId()); - query_all.bindValue(QSL(":account_id"), getParentServiceRoot()->accountId()); - - // Obtain count of unread messages. - if (query_all.exec()) { - while (query_all.next()) { - int feed_id = query_all.value(0).toInt(); - int new_count = query_all.value(1).toInt(); - - counts.insert(feed_id, new_count); - } + QMap counts = DatabaseQueries::getMessageCountsForCategory(database, customId(), getParentServiceRoot()->accountId(), + false, &ok); + if (ok) { foreach (Feed *feed, feeds) { feed->setCountOfUnreadMessages(counts.value(feed->customId())); } diff --git a/src/services/abstract/feed.cpp b/src/services/abstract/feed.cpp index 956632a9f..f8ed435f6 100755 --- a/src/services/abstract/feed.cpp +++ b/src/services/abstract/feed.cpp @@ -19,13 +19,10 @@ #include "definitions/definitions.h" #include "miscellaneous/application.h" -#include "miscellaneous/databasefactory.h" +#include "miscellaneous/databasequeries.h" #include "services/abstract/recyclebin.h" #include "services/abstract/serviceroot.h" -#include -#include - Feed::Feed(RootItem *parent) : RootItem(parent), m_url(QString()), m_status(Normal), m_autoUpdateType(DefaultAutoUpdate), @@ -38,31 +35,8 @@ Feed::~Feed() { } QList Feed::undeletedMessages() const { - QList messages; - int account_id = getParentServiceRoot()->accountId(); QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); - QSqlQuery query_read_msg(database); - - query_read_msg.setForwardOnly(true); - query_read_msg.prepare("SELECT * " - "FROM Messages " - "WHERE is_deleted = 0 AND is_pdeleted = 0 AND feed = :feed AND account_id = :account_id;"); - - query_read_msg.bindValue(QSL(":feed"), customId()); - query_read_msg.bindValue(QSL(":account_id"), account_id); - - if (query_read_msg.exec()) { - while (query_read_msg.next()) { - bool decoded; - Message message = Message::fromSqlRecord(query_read_msg.record(), &decoded); - - if (decoded) { - messages.append(message); - } - } - } - - return messages; + return DatabaseQueries::getUndeletedMessages(database, customId(), getParentServiceRoot()->accountId()); } QVariant Feed::data(int column, int role) const { @@ -138,203 +112,25 @@ void Feed::setAutoUpdateRemainingInterval(int auto_update_remaining_interval) { void Feed::updateCounts(bool including_total_count) { QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); - QSqlQuery query_all(database); - - query_all.setForwardOnly(true); + int account_id = getParentServiceRoot()->accountId(); if (including_total_count) { - query_all.prepare("SELECT count(*) FROM Messages " - "WHERE feed = :feed AND is_deleted = 0 AND is_pdeleted = 0 AND account_id = :account_id;"); - query_all.bindValue(QSL(":feed"), customId()); - query_all.bindValue(QSL(":account_id"), getParentServiceRoot()->accountId()); - - if (query_all.exec() && query_all.next()) { - setCountOfAllMessages(query_all.value(0).toInt()); - } + setCountOfAllMessages(DatabaseQueries::getMessageCountsForFeed(database, customId(), account_id, true)); } - query_all.prepare("SELECT count(*) FROM Messages " - "WHERE feed = :feed AND is_deleted = 0 AND is_pdeleted = 0 AND is_read = 0 AND account_id = :account_id;"); - query_all.bindValue(QSL(":feed"), customId()); - query_all.bindValue(QSL(":account_id"), getParentServiceRoot()->accountId()); - - // Obtain count of unread messages. - if (query_all.exec() && query_all.next()) { - setCountOfUnreadMessages(query_all.value(0).toInt()); - } + setCountOfUnreadMessages(DatabaseQueries::getMessageCountsForFeed(database, customId(), account_id, false)); } int Feed::updateMessages(const QList &messages) { - if (messages.isEmpty()) { - return 0; - } - - // Does not make any difference, since each feed now has - // its own "custom ID" (standard feeds have their custom ID equal to primary key ID). int custom_id = customId(); int account_id = getParentServiceRoot()->accountId(); - int updated_messages = 0; bool anything_updated = false; + bool ok; QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + int updated_messages = DatabaseQueries::updateMessages(database, messages, custom_id, account_id, url(), + &anything_updated, &ok); - // Prepare queries. - QSqlQuery query_select_with_url(database); - QSqlQuery query_select_with_id(database); - QSqlQuery query_update(database); - QSqlQuery query_insert(database); - - // Here we have query which will check for existence of the "same" message in given feed. - // The two message are the "same" if: - // 1) they belong to the same feed AND, - // 2) they have same URL AND, - // 3) they have same AUTHOR. - query_select_with_url.setForwardOnly(true); - query_select_with_url.prepare("SELECT id, date_created, is_read, is_important FROM Messages " - "WHERE feed = :feed AND title = :title AND url = :url AND author = :author AND account_id = :account_id;"); - - // When we have custom ID of the message, we can check directly for existence - // of that particular message. - query_select_with_id.setForwardOnly(true); - query_select_with_id.prepare("SELECT id, date_created, is_read, is_important FROM Messages " - "WHERE custom_id = :custom_id AND account_id = :account_id;"); - - // Used to insert new messages. - query_insert.setForwardOnly(true); - query_insert.prepare("INSERT INTO Messages " - "(feed, title, is_read, is_important, url, author, date_created, contents, enclosures, custom_id, custom_hash, account_id) " - "VALUES (:feed, :title, :is_read, :is_important, :url, :author, :date_created, :contents, :enclosures, :custom_id, :custom_hash, :account_id);"); - - // Used to update existing messages. - query_update.setForwardOnly(true); - query_update.prepare("UPDATE Messages " - "SET title = :title, is_read = :is_read, is_important = :is_important, url = :url, author = :author, date_created = :date_created, contents = :contents, enclosures = :enclosures " - "WHERE id = :id;"); - - if (!database.transaction()) { - database.rollback(); - qDebug("Transaction start for message downloader failed: '%s'.", qPrintable(database.lastError().text())); - return updated_messages; - } - - foreach (Message message, messages) { - // Check if messages contain relative URLs and if they do, then replace them. - if (message.m_url.startsWith(QL1S("/"))) { - QString new_message_url = QUrl(url()).toString(QUrl::RemoveUserInfo | - QUrl::RemovePath | - QUrl::RemoveQuery | - QUrl::RemoveFilename | - QUrl::StripTrailingSlash); - - new_message_url += message.m_url; - message.m_url = new_message_url; - } - - int id_existing_message = -1; - qint64 date_existing_message; - bool is_read_existing_message; - bool is_important_existing_message; - - if (message.m_customId.isEmpty()) { - // We need to recognize existing messages according URL & AUTHOR. - // NOTE: This concerns messages from standard account. - query_select_with_url.bindValue(QSL(":feed"), custom_id); - query_select_with_url.bindValue(QSL(":title"), message.m_title); - query_select_with_url.bindValue(QSL(":url"), message.m_url); - query_select_with_url.bindValue(QSL(":author"), message.m_author); - query_select_with_url.bindValue(QSL(":account_id"), account_id); - - if (query_select_with_url.exec() && query_select_with_url.next()) { - id_existing_message = query_select_with_url.value(0).toInt(); - date_existing_message = query_select_with_url.value(1).value(); - is_read_existing_message = query_select_with_url.value(2).toBool(); - is_important_existing_message = query_select_with_url.value(3).toBool(); - } - - query_select_with_url.finish(); - } - else { - // We can recognize existing messages via their custom ID. - // NOTE: This concerns messages from custom accounts, like TT-RSS or ownCloud News. - query_select_with_id.bindValue(QSL(":account_id"), account_id); - query_select_with_id.bindValue(QSL(":custom_id"), message.m_customId); - - if (query_select_with_id.exec() && query_select_with_id.next()) { - id_existing_message = query_select_with_id.value(0).toInt(); - date_existing_message = query_select_with_id.value(1).value(); - is_read_existing_message = query_select_with_id.value(2).toBool(); - is_important_existing_message = query_select_with_id.value(3).toBool(); - } - - query_select_with_id.finish(); - } - - // Now, check if this message is already in the DB. - if (id_existing_message >= 0) { - // Message is already in the DB. - // - // Now, we update it if at least one of next conditions is true: - // 1) Message has custom ID AND (its date OR read status OR starred status are changed). - // 2) Message has its date fetched from feed AND its date is different from date in DB. - if (/* 1 */ (!message.m_customId.isEmpty() && (message.m_created.toMSecsSinceEpoch() != date_existing_message || message.m_isRead != is_read_existing_message || message.m_isImportant != is_important_existing_message)) || - /* 2 */ (message.m_createdFromFeed && message.m_created.toMSecsSinceEpoch() != date_existing_message)) { - // Message exists, it is changed, update it. - query_update.bindValue(QSL(":title"), message.m_title); - query_update.bindValue(QSL(":is_read"), (int) message.m_isRead); - query_update.bindValue(QSL(":is_important"), (int) message.m_isImportant); - query_update.bindValue(QSL(":url"), message.m_url); - query_update.bindValue(QSL(":author"), message.m_author); - query_update.bindValue(QSL(":date_created"), message.m_created.toMSecsSinceEpoch()); - query_update.bindValue(QSL(":contents"), message.m_contents); - query_update.bindValue(QSL(":enclosures"), Enclosures::encodeEnclosuresToString(message.m_enclosures)); - query_update.bindValue(QSL(":id"), id_existing_message); - - anything_updated = true; - - if (query_update.exec() && !message.m_isRead) { - updated_messages++; - } - - query_update.finish(); - qDebug("Updating message '%s' in DB.", qPrintable(message.m_title)); - } - } - else { - // Message with this URL is not fetched in this feed yet. - query_insert.bindValue(QSL(":feed"), custom_id); - query_insert.bindValue(QSL(":title"), message.m_title); - query_insert.bindValue(QSL(":is_read"), (int) message.m_isRead); - query_insert.bindValue(QSL(":is_important"), (int) message.m_isImportant); - query_insert.bindValue(QSL(":url"), message.m_url); - query_insert.bindValue(QSL(":author"), message.m_author); - query_insert.bindValue(QSL(":date_created"), message.m_created.toMSecsSinceEpoch()); - query_insert.bindValue(QSL(":contents"), message.m_contents); - query_insert.bindValue(QSL(":enclosures"), Enclosures::encodeEnclosuresToString(message.m_enclosures)); - query_insert.bindValue(QSL(":custom_id"), message.m_customId); - query_insert.bindValue(QSL(":custom_hash"), message.m_customHash); - query_insert.bindValue(QSL(":account_id"), account_id); - - if (query_insert.exec() && query_insert.numRowsAffected() == 1) { - updated_messages++; - } - - query_insert.finish(); - qDebug("Adding new message '%s' to DB.", qPrintable(message.m_title)); - } - } - - // Now, fixup custom IDS for messages which initially did not have them, - // just to keep the data consistent. - if (database.exec("UPDATE Messages " - "SET custom_id = (SELECT id FROM Messages t WHERE t.id = Messages.id) " - "WHERE Messages.custom_id IS NULL OR Messages.custom_id = '';").lastError().isValid()) { - qWarning("Failed to set custom ID for all messages."); - } - - if (!database.commit()) { - database.rollback(); - qDebug("Transaction commit for message downloader failed."); - } - else { + if (ok) { if (updated_messages > 0) { setStatus(NewMessages); } diff --git a/src/services/abstract/recyclebin.cpp b/src/services/abstract/recyclebin.cpp index e898fd8a1..7a99bf032 100755 --- a/src/services/abstract/recyclebin.cpp +++ b/src/services/abstract/recyclebin.cpp @@ -20,10 +20,9 @@ #include "miscellaneous/application.h" #include "miscellaneous/iconfactory.h" #include "miscellaneous/textfactory.h" +#include "miscellaneous/databasequeries.h" #include "services/abstract/serviceroot.h" -#include - RecycleBin::RecycleBin(RootItem *parent_item) : RootItem(parent_item), m_contextMenu(QList()) { setKind(RootItemKind::Bin); @@ -47,33 +46,11 @@ int RecycleBin::countOfAllMessages() const { void RecycleBin::updateCounts(bool update_total_count) { QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); - QSqlQuery query_all(database); - const ServiceRoot *parent_root = getParentServiceRoot(); - query_all.setForwardOnly(true); - query_all.prepare("SELECT count(*) FROM Messages " - "WHERE is_read = 0 AND is_deleted = 1 AND is_pdeleted = 0 AND account_id = :account_id;"); - query_all.bindValue(QSL(":account_id"), parent_root->accountId()); - - - if (query_all.exec() && query_all.next()) { - m_unreadCount = query_all.value(0).toInt(); - } - else { - m_unreadCount = 0; - } + m_unreadCount = DatabaseQueries::getMessageCountsForBin(database, getParentServiceRoot()->accountId(), false); if (update_total_count) { - query_all.prepare("SELECT count(*) FROM Messages " - "WHERE is_deleted = 1 AND is_pdeleted = 0 AND account_id = :account_id;"); - query_all.bindValue(QSL(":account_id"), parent_root->accountId()); - - if (query_all.exec() && query_all.next()) { - m_totalCount = query_all.value(0).toInt(); - } - else { - m_totalCount = 0; - } + m_totalCount = DatabaseQueries::getMessageCountsForBin(database, getParentServiceRoot()->accountId(), true); } }