diff --git a/resources/desktop/com.github.rssguard.appdata.xml b/resources/desktop/com.github.rssguard.appdata.xml index d12ea4713..44aaa380f 100644 --- a/resources/desktop/com.github.rssguard.appdata.xml +++ b/resources/desktop/com.github.rssguard.appdata.xml @@ -26,7 +26,7 @@ https://github.com/sponsors/martinrotter - + none diff --git a/resources/sql/db_update_sqlite_1_2.sql b/resources/sql/db_update_sqlite_1_2.sql index bbded1e20..32ededa85 100644 --- a/resources/sql/db_update_sqlite_1_2.sql +++ b/resources/sql/db_update_sqlite_1_2.sql @@ -27,6 +27,13 @@ FROM backup_Feeds; -- ! DROP TABLE backup_Feeds; -- ! +UPDATE Feeds +SET ordr = ( + SELECT COUNT(*) + FROM Feeds ct + WHERE Feeds.account_id = ct.account_id AND Feeds.category = ct.category AND ct.id < Feeds.id +); +-- ! ALTER TABLE Categories RENAME TO backup_Categories; -- ! CREATE TABLE Categories ( @@ -49,6 +56,13 @@ FROM backup_Categories; -- ! DROP TABLE backup_Categories; -- ! +UPDATE Categories +SET ordr = ( + SELECT COUNT(*) + FROM Categories ct + WHERE Categories.account_id = ct.account_id AND Categories.parent_id = ct.parent_id AND ct.id < Categories.id +); +-- ! ALTER TABLE Accounts RENAME TO backup_Accounts; -- ! CREATE TABLE Accounts ( @@ -65,7 +79,7 @@ CREATE TABLE Accounts ( ); -- ! INSERT INTO Accounts (id, ordr, type, proxy_type, proxy_host, proxy_port, proxy_username, proxy_password, custom_data) -SELECT id, id, type, proxy_type, proxy_host, proxy_port, proxy_username, proxy_password, custom_data +SELECT id, id - 1, type, proxy_type, proxy_host, proxy_port, proxy_username, proxy_password, custom_data FROM backup_Accounts; -- ! DROP TABLE backup_Accounts; \ No newline at end of file diff --git a/src/librssguard/core/feedsmodel.cpp b/src/librssguard/core/feedsmodel.cpp index f1b48ea94..ea1f05632 100644 --- a/src/librssguard/core/feedsmodel.cpp +++ b/src/librssguard/core/feedsmodel.cpp @@ -4,6 +4,7 @@ #include "3rd-party/boolinq/boolinq.h" #include "database/databasefactory.h" +#include "database/databasequeries.h" #include "definitions/definitions.h" #include "gui/dialogs/formmain.h" #include "miscellaneous/feedreader.h" @@ -523,6 +524,12 @@ bool FeedsModel::emptyAllBins() { return result; } +void FeedsModel::changeSortOrder(RootItem* item, bool move_top, bool move_bottom, int new_sort_order) { + QSqlDatabase db = qApp->database()->driver()->connection(metaObject()->className()); + + DatabaseQueries::moveItem(item, move_top, move_bottom, new_sort_order, db); +} + void FeedsModel::loadActivatedServiceAccounts() { auto serv = qApp->feedReader()->feedServices(); diff --git a/src/librssguard/core/feedsmodel.h b/src/librssguard/core/feedsmodel.h index 7a29d6839..0b55473c9 100644 --- a/src/librssguard/core/feedsmodel.h +++ b/src/librssguard/core/feedsmodel.h @@ -107,6 +107,8 @@ class RSSGUARD_DLLSPEC FeedsModel : public QAbstractItemModel { bool restoreAllBins(); bool emptyAllBins(); + void changeSortOrder(RootItem* item, bool move_top, bool move_bottom, int new_sort_order); + // Feeds operations. bool markItemRead(RootItem* item, RootItem::ReadStatus read); bool markItemCleared(RootItem* item, bool clean_read_only); diff --git a/src/librssguard/database/databasequeries.cpp b/src/librssguard/database/databasequeries.cpp index d159aaaf2..ebf52f87d 100644 --- a/src/librssguard/database/databasequeries.cpp +++ b/src/librssguard/database/databasequeries.cpp @@ -1939,6 +1939,23 @@ void DatabaseQueries::createOverwriteCategory(const QSqlDatabase& db, Category* if (category->id() <= 0) { // We need to insert category first. + if (category->sortOrder() < 0) { + q.exec(QSL("SELECT MAX(ordr) FROM Categories WHERE account_id = :account_id AND parent_id = :parent_id;")); + q.bindValue(QSL(":account_id"), account_id); + q.bindValue(QSL(":parent_id"), parent_id); + + if (!q.exec()) { + throw ApplicationException(q.lastError().text()); + } + + q.next(); + + int next_order = (q.value(0).isNull() ? 0 : q.value(0).toInt()) + 1; + + category->setSortOrder(next_order); + q.finish(); + } + q.prepare(QSL("INSERT INTO " "Categories (parent_id, ordr, title, date_created, account_id) " "VALUES (0, 0, 'new', 0, %1);").arg(QString::number(account_id))); @@ -1975,6 +1992,23 @@ void DatabaseQueries::createOverwriteFeed(const QSqlDatabase& db, Feed* feed, in if (feed->id() <= 0) { // We need to insert feed first. + if (feed->sortOrder() < 0) { + q.exec(QSL("SELECT MAX(ordr) FROM Feeds WHERE account_id = :account_id AND category = :category;")); + q.bindValue(QSL(":account_id"), account_id); + q.bindValue(QSL(":category"), parent_id); + + if (!q.exec()) { + throw ApplicationException(q.lastError().text()); + } + + q.next(); + + int next_order = (q.value(0).isNull() ? 0 : q.value(0).toInt()) + 1; + + feed->setSortOrder(next_order); + q.finish(); + } + q.prepare(QSL("INSERT INTO " "Feeds (title, ordr, date_created, category, update_type, update_interval, account_id, custom_id) " "VALUES ('new', 0, 0, 0, 0, 1, %1, 'new');").arg(QString::number(account_id))); @@ -2008,6 +2042,9 @@ void DatabaseQueries::createOverwriteFeed(const QSqlDatabase& db, Feed* feed, in q.bindValue(QSL(":account_id"), account_id); q.bindValue(QSL(":custom_id"), feed->customId()); q.bindValue(QSL(":id"), feed->id()); + + // TODO: upravit set na ordr = (SELECT MAX(ordr) FROM Feeds WHERE account_id = :account_id AND category = :category) + 1; + // to ale pokud je sortOrder < 0 q.bindValue(QSL(":ordr"), feed->sortOrder()); q.bindValue(QSL(":is_off"), feed->isSwitchedOff()); q.bindValue(QSL(":open_articles"), feed->openArticlesDirectly()); @@ -2026,8 +2063,22 @@ void DatabaseQueries::createOverwriteAccount(const QSqlDatabase& db, ServiceRoot QSqlQuery q(db); if (account->accountId() <= 0) { - // We need to insert account first. - q.prepare(QSL("INSERT INTO Accounts (ordr, type) VALUES (0, :type);")); + // We need to insert account and generate sort order first. + if (account->sortOrder() < 0) { + if (!q.exec(QSL("SELECT MAX(ordr) FROM Accounts;"))) { + throw ApplicationException(q.lastError().text()); + } + + q.next(); + + int next_order = (q.value(0).isNull() ? 0 : q.value(0).toInt()) + 1; + + account->setSortOrder(next_order); + q.finish(); + } + + q.prepare(QSL("INSERT INTO Accounts (ordr, type) " + "VALUES (0, :type);")); q.bindValue(QSL(":type"), account->code()); if (!q.exec()) { @@ -2095,36 +2146,80 @@ bool DatabaseQueries::deleteCategory(const QSqlDatabase& db, int id) { return q.exec(); } -void DatabaseQueries::fixupOrders(const QSqlDatabase& db) { - // We first determine if there are same orders assigned to some items - // which have same parent category/acc. - QSqlQuery res = db.exec(QSL("SELECT COUNT(*) FROM Accounts GROUP BY ordr HAVING COUNT(*) > 1 " - "UNION ALL " - "SELECT COUNT(*) FROM Categories GROUP BY account_id, parent_id, ordr HAVING COUNT(*) > 1 " - "UNION ALL " - "SELECT COUNT(*) FROM Feeds GROUP BY account_id, category, ordr HAVING COUNT(*) > 1;")); - bool should_fixup = res.lastError().isValid() || res.size() > 0; +void DatabaseQueries::moveItem(RootItem* item, bool move_top, bool move_bottom, int move_index, const QSqlDatabase& db) { + switch (item->kind()) { + case RootItem::Kind::Feed: + moveFeed(item->toFeed(), move_top, move_bottom, move_index, db); + break; - if (should_fixup) { - // Some orders are messed up, fix. - qCriticalNN << LOGSEC_DB << "Order of items is messed up, fixing."; + case RootItem::Kind::Category: + break; - for (const QString& table : { QSL("Accounts"), QSL("Categories"), QSL("Feeds") }) { - QSqlQuery q = db.exec(QSL("UPDATE %1 SET ordr = id;").arg(table)); + case RootItem::Kind::ServiceRoot: - if (q.lastError().isValid()) { - qFatal("Fixup of messed up order failed: '%s'.", qPrintable(q.lastError().text())); - } - } - } - else { - qDebugNN << LOGSEC_DB << "No fixing of item order is needed."; + break; + + default: + return; } } -void DatabaseQueries::moveItemUp(RootItem* item, const QSqlDatabase& db) {} +void DatabaseQueries::moveFeed(Feed* feed, bool move_top, bool move_bottom, int move_index, const QSqlDatabase& db) { + if (feed->sortOrder() == move_index || /* Item is already sorted OK. */ + (!move_top && !move_bottom && move_index < 0 ) || /* Order cannot be smaller than 0 if we do not move to begin/end. */ + (move_top && feed->sortOrder() == 0)) { /* Item is already on top. */ + return; + } -void DatabaseQueries::moveItemDown(RootItem* item, const QSqlDatabase& db) {} + QSqlQuery q(db); + + q.prepare(QSL("SELECT MAX(ordr) FROM Feeds WHERE account_id = :account_id AND category = :category;")); + q.bindValue(QSL(":account_id"), feed->getParentServiceRoot()->accountId()); + q.bindValue(QSL(":category"), feed->parent()->id()); + + int max_sort_order; + + if (q.exec() && q.next()) { + max_sort_order = q.value(0).toInt(); + } + else { + throw ApplicationException(q.lastError().text()); + } + + if (max_sort_order == 0 || /* We only have 1 item, nothing to sort. */ + max_sort_order == feed->sortOrder() || /* Item is already sorted OK. */ + move_index > max_sort_order) { /* Cannot move past biggest sort order. */ + return; + } + + if (move_top) { + move_index = 0; + } + else if (move_bottom) { + move_index = max_sort_order; + } + + int move_low = qMin(move_index, feed->sortOrder()); + int move_high = qMax(move_index, feed->sortOrder()); + + if (feed->sortOrder() > move_index) { + q.prepare(QSL("UPDATE Feeds SET ordr = ordr + 1 " + "WHERE account_id = :account_id AND category = :category AND ordr < :move_high AND ordr >= :move_low;")); + } + else { + q.prepare(QSL("UPDATE Feeds SET ordr = ordr - 1 " + "WHERE account_id = :account_id AND category = :category AND ordr > :move_low AND ordr <= :move_high;")); + } + + q.bindValue(QSL(":account_id"), feed->getParentServiceRoot()->accountId()); + q.bindValue(QSL(":category"), feed->parent()->id()); + q.bindValue(QSL(":move_low"), move_low); + q.bindValue(QSL(":move_high"), move_high); + + if (!q.exec()) { + throw ApplicationException(q.lastError().text()); + } +} MessageFilter* DatabaseQueries::addMessageFilter(const QSqlDatabase& db, const QString& title, const QString& script) { diff --git a/src/librssguard/database/databasequeries.h b/src/librssguard/database/databasequeries.h index a4f9e0841..ca99323a7 100644 --- a/src/librssguard/database/databasequeries.h +++ b/src/librssguard/database/databasequeries.h @@ -135,9 +135,8 @@ class DatabaseQueries { int account_id, bool* ok = nullptr); // Item order methods. - static void fixupOrders(const QSqlDatabase& db); - static void moveItemUp(RootItem* item, const QSqlDatabase& db); - static void moveItemDown(RootItem* item, const QSqlDatabase& db); + static void moveItem(RootItem* item, bool move_top, bool move_bottom, int move_index, const QSqlDatabase& db); + static void moveFeed(Feed* feed, bool move_top, bool move_bottom, int move_index, const QSqlDatabase& db); // Message filters operators. static bool purgeLeftoverMessageFilterAssignments(const QSqlDatabase& db, int account_id); @@ -338,9 +337,6 @@ Assignment DatabaseQueries::getFeeds(const QSqlDatabase& db, template void DatabaseQueries::loadRootFromDatabase(ServiceRoot* root) { QSqlDatabase database = qApp->database()->driver()->connection(root->metaObject()->className()); - - fixupOrders(database); - Assignment categories = DatabaseQueries::getCategories(database, root->accountId()); Assignment feeds = DatabaseQueries::getFeeds(database, qApp->feedReader()->messageFilters(), root->accountId()); auto labels = DatabaseQueries::getLabelsForAccount(database, root->accountId()); diff --git a/src/librssguard/gui/dialogs/formmain.cpp b/src/librssguard/gui/dialogs/formmain.cpp index 3da2bc3ed..0965c4ed1 100644 --- a/src/librssguard/gui/dialogs/formmain.cpp +++ b/src/librssguard/gui/dialogs/formmain.cpp @@ -897,6 +897,8 @@ void FormMain::createConnections() { qApp->feedReader()->showMessageFiltersManager(); tabWidget()->feedMessageViewer()->messagesView()->reloadSelections(); }); + connect(m_ui->m_actionFeedMoveUp, &QAction::triggered, + tabWidget()->feedMessageViewer()->feedsView(), &FeedsView::moveSelectedItemUp); } void FormMain::backupDatabaseSettings() { diff --git a/src/librssguard/gui/feedsview.cpp b/src/librssguard/gui/feedsview.cpp index eb26b2a09..76373b37d 100644 --- a/src/librssguard/gui/feedsview.cpp +++ b/src/librssguard/gui/feedsview.cpp @@ -288,6 +288,10 @@ void FeedsView::deleteSelectedItem() { qApp->feedUpdateLock()->unlock(); } +void FeedsView::moveSelectedItemUp() { + m_sourceModel->changeSortOrder(selectedItem(), false, false, selectedItem()->sortOrder() - 1); +} + void FeedsView::markSelectedItemReadStatus(RootItem::ReadStatus read) { m_sourceModel->markItemRead(selectedItem(), read); } diff --git a/src/librssguard/gui/feedsview.h b/src/librssguard/gui/feedsview.h index c041b8e4d..e715ad94e 100644 --- a/src/librssguard/gui/feedsview.h +++ b/src/librssguard/gui/feedsview.h @@ -66,6 +66,9 @@ class RSSGUARD_DLLSPEC FeedsView : public BaseTreeView { void editSelectedItem(); void deleteSelectedItem(); + // Sort order manipulations. + void moveSelectedItemUp(); + // Selects next/previous item (feed/category) in the list. void selectNextItem(); void selectPreviousItem(); diff --git a/src/librssguard/services/abstract/rootitem.cpp b/src/librssguard/services/abstract/rootitem.cpp index 62e8ed793..198b3f05e 100644 --- a/src/librssguard/services/abstract/rootitem.cpp +++ b/src/librssguard/services/abstract/rootitem.cpp @@ -16,7 +16,7 @@ RootItem::RootItem(RootItem* parent_item) : QObject(nullptr), m_kind(RootItem::Kind::Root), m_id(NO_PARENT_CATEGORY), m_customId(QL1S("")), m_title(QString()), m_description(QString()), m_creationDate(QDateTime::currentDateTimeUtc()), - m_keepOnTop(false), m_sortOrder(0), m_childItems(QList()), m_parentItem(parent_item) {} + m_keepOnTop(false), m_sortOrder(NO_PARENT_CATEGORY), m_childItems(QList()), m_parentItem(parent_item) {} RootItem::RootItem(const RootItem& other) : RootItem(nullptr) { setTitle(other.title());