diff --git a/CMakeLists.txt b/CMakeLists.txt index 22a20a72f..a960786a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,8 +20,7 @@ # # Variables: # BUILD_WITH_QT6 - Build either with Qt 6 or Qt 5. -# USE_SYSTEM_SQLITE - Use system-wide SQLite3 library and header file. Defaults to "OFF" in whic -# case bundled "sqlite3.h" and "sqlite3.c" are used. +# USE_SYSTEM_SQLITE - Use system-wide SQLite3 library and header file. Defaults to "ON". # NO_UPDATE_CHECK - Disable automatic checking for new application updates. # IS_FLATPAK_BUILD - Set to "ON" when building RSS Guard with Flatpak. # FORCE_BUNDLE_ICONS - Forcibly bundles icons into executables. @@ -29,8 +28,8 @@ # Otherwise simple text component is used and some features will be disabled. # Default value is "false". If QtWebEngine is installed during compilation, then # value of this variable is tweaked automatically. -# {FEEDLY,GMAIL,INOREADER}_CLIENT_ID - preconfigured OAuth cliend ID. -# {FEEDLY,GMAIL,INOREADER}_CLIENT_SECRET - preconfigured OAuth cliend SECRET. +# {FEEDLY,GMAIL,INOREADER}_CLIENT_ID - preconfigured OAuth client ID. +# {FEEDLY,GMAIL,INOREADER}_CLIENT_SECRET - preconfigured OAuth client SECRET. # # Other information: # - supports Windows, Linux, *BSD, macOS, OS/2, Android, diff --git a/src/librssguard/database/databasequeries.cpp b/src/librssguard/database/databasequeries.cpp index 5665ad11b..4a824f30f 100644 --- a/src/librssguard/database/databasequeries.cpp +++ b/src/librssguard/database/databasequeries.cpp @@ -744,6 +744,33 @@ ArticleCounts DatabaseQueries::getMessageCountsForLabel(const QSqlDatabase& db, } } +ArticleCounts DatabaseQueries::getMessageCountsForProbe(const QSqlDatabase& db, Search* probe, int account_id) { + QSqlQuery q(db); + + q.setForwardOnly(true); + q.prepare(QSL("SELECT COUNT(*), SUM(is_read) FROM Messages " + "WHERE " + " is_deleted = 0 AND " + " is_pdeleted = 0 AND " + " account_id = :account_id AND " + " (title REGEXP :fltr OR contents REGEXP :fltr);")); + + q.bindValue(QSL(":account_id"), account_id); + q.bindValue(QSL(":fltr"), probe->filter()); + + if (q.exec() && q.next()) { + ArticleCounts ac; + + ac.m_total = q.value(0).toInt(); + ac.m_unread = ac.m_total - q.value(1).toInt(); + + return ac; + } + else { + throw ApplicationException(q.lastError().text()); + } +} + QMap DatabaseQueries::getMessageCountsForAllLabels(const QSqlDatabase& db, int account_id, bool* ok) { @@ -2026,6 +2053,54 @@ QStringList DatabaseQueries::customIdsOfMessagesFromLabel(const QSqlDatabase& db return ids; } +void DatabaseQueries::markProbeReadUnread(const QSqlDatabase& db, Search* probe, RootItem::ReadStatus read) { + QSqlQuery q(db); + + q.setForwardOnly(true); + q.prepare(QSL("UPDATE Messages SET is_read = :read " + "WHERE " + " is_deleted = 0 AND " + " is_pdeleted = 0 AND " + " account_id = :account_id AND " + " (title REGEXP :fltr OR contents REGEXP :fltr);")); + q.bindValue(QSL(":read"), read == RootItem::ReadStatus::Read ? 1 : 0); + q.bindValue(QSL(":account_id"), probe->getParentServiceRoot()->accountId()); + q.bindValue(QSL(":fltr"), probe->filter()); + + if (!q.exec()) { + throw ApplicationException(q.lastError().text()); + } +} + +QStringList DatabaseQueries::customIdsOfMessagesFromProbe(const QSqlDatabase& db, + Search* probe, + RootItem::ReadStatus target_read) { + QSqlQuery q(db); + QStringList ids; + + q.setForwardOnly(true); + q.prepare(QSL("SELECT custom_id FROM Messages " + "WHERE " + " is_read = :read AND " + " is_deleted = 0 AND " + " is_pdeleted = 0 AND " + " account_id = :account_id AND " + " (title REGEXP :fltr OR contents REGEXP :fltr);")); + q.bindValue(QSL(":account_id"), probe->getParentServiceRoot()->accountId()); + q.bindValue(QSL(":read"), target_read == RootItem::ReadStatus::Read ? 0 : 1); + q.bindValue(QSL(":fltr"), probe->filter()); + + if (!q.exec()) { + throw ApplicationException(q.lastError().text()); + } + + while (q.next()) { + ids.append(q.value(0).toString()); + } + + return ids; +} + QStringList DatabaseQueries::customIdsOfImportantMessages(const QSqlDatabase& db, RootItem::ReadStatus target_read, int account_id, diff --git a/src/librssguard/database/databasequeries.h b/src/librssguard/database/databasequeries.h index 6d08c4aa0..72725bc9b 100644 --- a/src/librssguard/database/databasequeries.h +++ b/src/librssguard/database/databasequeries.h @@ -54,6 +54,7 @@ class DatabaseQueries { static void updateProbe(const QSqlDatabase& db, Search* probe); // Message operators. + static void markProbeReadUnread(const QSqlDatabase& db, Search* probe, RootItem::ReadStatus read); static bool markLabelledMessagesReadUnread(const QSqlDatabase& db, Label* label, RootItem::ReadStatus read); static bool markImportantMessagesReadUnread(const QSqlDatabase& db, int account_id, RootItem::ReadStatus read); static bool markUnreadMessagesRead(const QSqlDatabase& db, int account_id); @@ -97,6 +98,7 @@ class DatabaseQueries { Label* label, int account_id, bool* ok = nullptr); + static ArticleCounts getMessageCountsForProbe(const QSqlDatabase& db, Search* probe, int account_id); static QMap getMessageCountsForAllLabels(const QSqlDatabase& db, int account_id, bool* ok = nullptr); @@ -127,6 +129,9 @@ class DatabaseQueries { Label* label, RootItem::ReadStatus target_read, bool* ok = nullptr); + static QStringList customIdsOfMessagesFromProbe(const QSqlDatabase& db, + Search* probe, + RootItem::ReadStatus target_read); static QStringList customIdsOfImportantMessages(const QSqlDatabase& db, RootItem::ReadStatus target_read, int account_id, diff --git a/src/librssguard/services/abstract/rootitem.cpp b/src/librssguard/services/abstract/rootitem.cpp index ef06cc215..148c84ceb 100644 --- a/src/librssguard/services/abstract/rootitem.cpp +++ b/src/librssguard/services/abstract/rootitem.cpp @@ -218,7 +218,7 @@ bool RootItem::performDragDropChange(RootItem* target_item) { int RootItem::countOfUnreadMessages() const { return boolinq::from(m_childItems).sum([](RootItem* it) { return (it->kind() == RootItem::Kind::Important || it->kind() == RootItem::Kind::Unread || - it->kind() == RootItem::Kind::Labels) + it->kind() == RootItem::Kind::Labels || it->kind() == RootItem::Kind::Probes) ? 0 : it->countOfUnreadMessages(); }); @@ -227,7 +227,7 @@ int RootItem::countOfUnreadMessages() const { int RootItem::countOfAllMessages() const { return boolinq::from(m_childItems).sum([](RootItem* it) { return (it->kind() == RootItem::Kind::Important || it->kind() == RootItem::Kind::Unread || - it->kind() == RootItem::Kind::Labels) + it->kind() == RootItem::Kind::Labels || it->kind() == RootItem::Kind::Probes) ? 0 : it->countOfAllMessages(); }); diff --git a/src/librssguard/services/abstract/search.cpp b/src/librssguard/services/abstract/search.cpp index 06fa8de7e..9eba8d8ce 100755 --- a/src/librssguard/services/abstract/search.cpp +++ b/src/librssguard/services/abstract/search.cpp @@ -88,15 +88,24 @@ void Search::updateCounts(bool including_total_count) { QSqlDatabase database = qApp->database()->driver()->threadSafeConnection(metaObject()->className()); int account_id = getParentServiceRoot()->accountId(); - /* - auto ac = DatabaseQueries::getMessageCountsForLabel(database, this, account_id); + try { + auto ac = DatabaseQueries::getMessageCountsForProbe(database, this, account_id); - if (including_total_count) { - setCountOfAllMessages(ac.m_total); + if (including_total_count) { + setCountOfAllMessages(ac.m_total); + } + + setCountOfUnreadMessages(ac.m_unread); } + catch (const ApplicationException& ex) { + qCriticalNN << LOGSEC_CORE << "Failed to get counts of probe:" << QUOTE_W_SPACE_DOT(ex.message()); - setCountOfUnreadMessages(ac.m_unread); - */ + if (including_total_count) { + setCountOfAllMessages(-1); + } + + setCountOfUnreadMessages(-1); + } } QList Search::undeletedMessages() const { @@ -160,23 +169,27 @@ bool Search::markAsReadUnread(RootItem::ReadStatus status) { ServiceRoot* service = getParentServiceRoot(); auto* cache = dynamic_cast(service); - /* if (cache != nullptr) { - cache->addMessageStatesToCache(service->customIDSOfMessagesForItem(this, status), status); + try { + cache->addMessageStatesToCache(service->customIDSOfMessagesForItem(this, status), status); + } + catch (const ApplicationException& ex) { + qCriticalNN << LOGSEC_DB << "Cannot add some IDs to state cache:" << QUOTE_W_SPACE_DOT(ex.message()); + return false; + } } QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className()); - if (DatabaseQueries::markLabelledMessagesReadUnread(database, this, status)) { + try { + DatabaseQueries::markProbeReadUnread(database, this, status); service->updateCounts(false); service->itemChanged(service->getSubTree()); service->requestReloadMessageList(status == RootItem::ReadStatus::Read); return true; } - else { + catch (const ApplicationException& ex) { + qCriticalNN << LOGSEC_DB << "Cannot mark probe as read/unread:" << QUOTE_W_SPACE_DOT(ex.message()); return false; } - */ - - return false; } diff --git a/src/librssguard/services/abstract/searchsnode.cpp b/src/librssguard/services/abstract/searchsnode.cpp index 31000e4c6..ce65ffc1e 100755 --- a/src/librssguard/services/abstract/searchsnode.cpp +++ b/src/librssguard/services/abstract/searchsnode.cpp @@ -33,65 +33,6 @@ QList SearchsNode::undeletedMessages() const { // return DatabaseQueries::getUndeletedLabelledMessages(database, getParentServiceRoot()->accountId()); } -int SearchsNode::countOfUnreadMessages() const { - auto chi = childItems(); - - if (chi.isEmpty()) { - return 0; - } - - return boolinq::from(chi) - .max([](RootItem* it) { - return it->countOfUnreadMessages(); - }) - ->countOfUnreadMessages(); -} - -int SearchsNode::countOfAllMessages() const { - auto chi = childItems(); - - if (chi.isEmpty()) { - return 0; - } - - return boolinq::from(chi) - .max([](RootItem* it) { - return it->countOfAllMessages(); - }) - ->countOfAllMessages(); -} - -void SearchsNode::updateCounts(bool including_total_count) { - // TODO: This is still rather slow because this is automatically - // called when message is marked (un)read or starred. - // It would be enough if only labels which are assigned to article - // are recounted, not all. - - QSqlDatabase database = qApp->database()->driver()->threadSafeConnection(metaObject()->className()); - int account_id = getParentServiceRoot()->accountId(); - auto acc = DatabaseQueries::getMessageCountsForAllLabels(database, account_id); - /* - for (Label* lbl : probes()) { - if (!acc.contains(lbl->customId())) { - if (including_total_count) { - lbl->setCountOfAllMessages(0); - } - - lbl->setCountOfUnreadMessages(0); - } - else { - auto ac = acc.value(lbl->customId()); - - if (including_total_count) { - lbl->setCountOfAllMessages(ac.m_total); - } - - lbl->setCountOfUnreadMessages(ac.m_unread); - } - } - */ -} - Search* SearchsNode::probeById(const QString& custom_id) { auto chi = childItems(); @@ -120,6 +61,34 @@ QList SearchsNode::contextMenuFeedsList() { return QList{m_actProbeNew}; } +int SearchsNode::countOfUnreadMessages() const { + auto chi = childItems(); + + if (chi.isEmpty()) { + return 0; + } + + return boolinq::from(chi) + .max([](RootItem* it) { + return it->countOfUnreadMessages(); + }) + ->countOfUnreadMessages(); +} + +int SearchsNode::countOfAllMessages() const { + auto chi = childItems(); + + if (chi.isEmpty()) { + return 0; + } + + return boolinq::from(chi) + .max([](RootItem* it) { + return it->countOfAllMessages(); + }) + ->countOfAllMessages(); +} + void SearchsNode::createProbe() { FormAddEditProbe frm(qApp->mainFormWidget()); Search* new_prb = frm.execForAdd(); @@ -132,6 +101,8 @@ void SearchsNode::createProbe() { getParentServiceRoot()->requestItemReassignment(new_prb, this); getParentServiceRoot()->requestItemExpand({this}, true); + + new_prb->updateCounts(true); } catch (const ApplicationException&) { new_prb->deleteLater(); diff --git a/src/librssguard/services/abstract/searchsnode.h b/src/librssguard/services/abstract/searchsnode.h index 119072a6d..c6fd71b39 100755 --- a/src/librssguard/services/abstract/searchsnode.h +++ b/src/librssguard/services/abstract/searchsnode.h @@ -17,10 +17,9 @@ class SearchsNode : public RootItem { void loadProbes(const QList& probes); virtual QList undeletedMessages() const; - virtual QList contextMenuFeedsList(); virtual int countOfUnreadMessages() const; virtual int countOfAllMessages() const; - virtual void updateCounts(bool including_total_count); + virtual QList contextMenuFeedsList(); Search* probeById(const QString& custom_id); diff --git a/src/librssguard/services/abstract/serviceroot.cpp b/src/librssguard/services/abstract/serviceroot.cpp index e358f821a..04b1829d2 100644 --- a/src/librssguard/services/abstract/serviceroot.cpp +++ b/src/librssguard/services/abstract/serviceroot.cpp @@ -142,7 +142,7 @@ void ServiceRoot::updateCounts(bool including_total_count) { feeds.append(child->toFeed()); } else if (child->kind() != RootItem::Kind::Label && child->kind() != RootItem::Kind::Category && - child->kind() != RootItem::Kind::ServiceRoot) { + child->kind() != RootItem::Kind::ServiceRoot && child->kind() != RootItem::Kind::Probe) { child->updateCounts(including_total_count); } } @@ -589,6 +589,7 @@ QStringList ServiceRoot::customIDSOfMessagesForItem(RootItem* item, ReadStatus t switch (item->kind()) { case RootItem::Kind::Labels: + case RootItem::Kind::Probes: case RootItem::Kind::Category: { auto chi = item->childItems(); @@ -606,6 +607,13 @@ QStringList ServiceRoot::customIDSOfMessagesForItem(RootItem* item, ReadStatus t break; } + case RootItem::Kind::Probe: { + QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className()); + + list = DatabaseQueries::customIdsOfMessagesFromProbe(database, item->toProbe(), target_read); + break; + } + case RootItem::Kind::ServiceRoot: { QSqlDatabase database = qApp->database()->driver()->connection(metaObject()->className()); @@ -740,7 +748,7 @@ bool ServiceRoot::loadMessagesForItem(RootItem* item, MessagesModel* model) { } else if (item->kind() == RootItem::Kind::Probe) { model->setFilter(QSL("Messages.is_deleted = 0 AND Messages.is_pdeleted = 0 AND Messages.account_id = %1 AND " - "Messages.contents REGEXP '%2'") + "(Messages.title REGEXP '%2' OR Messages.contents REGEXP '%2')") .arg(QString::number(accountId()), item->toProbe()->filter())); } else if (item->kind() == RootItem::Kind::Label) { @@ -802,13 +810,14 @@ bool ServiceRoot::onAfterSetMessagesRead(RootItem* selected_item, Q_UNUSED(messages) Q_UNUSED(read) - // TODO: We know that some messages were marked as read or unread, therefore we do not need to recount + // We know that some messages were marked as read or unread, therefore we do not need to recount // all items, but only some: // - recycle bin (if recycle bin IS selected) // - feeds of those messages (if recycle bin is NOT selected) // - important articles (if some messages IS important AND recycle bin is NOT selected) // - unread articles (if some messages IS unread AND recycle bin is NOT selected) // - labels assigned to articles (if recycle bin is NOT selected) + // - probes (if recycle bin is NOT selected) QList to_update; if (selected_item->kind() == RootItem::Kind::Bin) { @@ -871,16 +880,11 @@ bool ServiceRoot::onAfterSetMessagesRead(RootItem* selected_item, l->updateCounts(false); to_update << l; } - /* - for (const QString& lbl_custom_id : lbls.keys()) { - auto* lbl = labelsNode()->labelById(lbl_custom_id); - - if (lbl != nullptr) { - lbl->setCountOfUnreadMessages(lbls.value(lbl_custom_id).m_unread); - to_update << lbl; - } - }*/ } + + // 5. Probes. + m_probesNode->updateCounts(false); + to_update << m_probesNode->childItems(); } itemChanged(to_update); @@ -1135,6 +1139,10 @@ QPair ServiceRoot::updateMessages(QList& messages, Feed* feed if (labelsNode() != nullptr) { labelsNode()->updateCounts(true); } + + if (probesNode() != nullptr) { + probesNode()->updateCounts(true); + } } // NOTE: Do not update model items here. We update only once when all feeds are fetched.