summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-03 20:49:25 +0200
committerDanilo M. <danix@danix.xyz>2026-08-04 12:54:12 +0200
commit8e65d8f5eba78acc9558e26f9b728521f323b1a6 (patch)
treeb757abae9cfe2a7797332e0190845edfb52ab0a6
parent4175f3e7314410a5e35bb3c06a0faf9065ce2e7a (diff)
downloadqtmaildir-8e65d8f5eba78acc9558e26f9b728521f323b1a6.tar.gz
qtmaildir-8e65d8f5eba78acc9558e26f9b728521f323b1a6.zip
feat(worker): list every tag in the database
Query bar completion cannot offer tag names without a way to enumerate them, and libnotmuch had no call wired up for it. Follows the existing generation-counter pattern; the result crosses the thread boundary as a QStringList. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
-rw-r--r--src/notmuchworker.cpp21
-rw-r--r--src/notmuchworker.h6
-rw-r--r--tests/test_notmuchworker.cpp39
3 files changed, 66 insertions, 0 deletions
diff --git a/src/notmuchworker.cpp b/src/notmuchworker.cpp
index ed97e39..a5096d2 100644
--- a/src/notmuchworker.cpp
+++ b/src/notmuchworker.cpp
@@ -332,3 +332,24 @@ void NotmuchWorker::applyTags(const TagChange &change)
emit tagsApplied(change);
}
+
+void NotmuchWorker::requestAllTags(quint64 generation)
+{
+ if (!openReadOnly())
+ return;
+
+ NmTags tags(notmuch_database_get_all_tags(m_db));
+ if (!tags) {
+ emit errorOccurred(QStringLiteral("Cannot list tags"));
+ return;
+ }
+
+ QStringList result;
+ for (; notmuch_tags_valid(tags.get()); notmuch_tags_move_to_next(tags.get()))
+ result.append(QString::fromUtf8(notmuch_tags_get(tags.get())));
+
+ // Sorted once here so no consumer has to sort again. notmuch returns tags
+ // in Xapian term order, which is byte order, not the user's locale order.
+ result.sort();
+ emit allTagsReady(result, generation);
+}
diff --git a/src/notmuchworker.h b/src/notmuchworker.h
index e0c80c6..88187ec 100644
--- a/src/notmuchworker.h
+++ b/src/notmuchworker.h
@@ -69,11 +69,17 @@ public slots:
const QStringList &remove,
const QString &description);
+ /// Every tag in the database, sorted. Feeds query bar completion, which
+ /// cannot offer tag names it has no way to enumerate. Called at startup,
+ /// after a sync, and after a tag mutation introduces an unknown tag.
+ void requestAllTags(quint64 generation);
+
signals:
void threadsReady(const QVector<ThreadSummary> &threads, quint64 generation);
void queryFinished(int totalThreads, quint64 generation);
void threadLoaded(const QVector<MessageRef> &messages, quint64 generation);
void tagsApplied(const TagChange &change);
+ void allTagsReady(const QStringList &tags, quint64 generation);
void errorOccurred(const QString &message);
private:
diff --git a/tests/test_notmuchworker.cpp b/tests/test_notmuchworker.cpp
index 699f72c..488b630 100644
--- a/tests/test_notmuchworker.cpp
+++ b/tests/test_notmuchworker.cpp
@@ -55,6 +55,9 @@ private slots:
void applyTagsToThreadsSpansMultipleThreads();
void applyTagsToThreadsWithNoThreadsDoesNothing();
+ void requestAllTagsReturnsSortedTags();
+ void requestAllTagsOnUnreadableConfigEmitsError();
+
private:
/// Tags of one message, read back through a fresh worker query.
QStringList tagsOf(const QString &messageId);
@@ -433,5 +436,41 @@ void TestNotmuchWorker::applyTagsToThreadsWithNoThreadsDoesNothing()
QVERIFY(errors.isEmpty());
}
+void TestNotmuchWorker::requestAllTagsReturnsSortedTags()
+{
+ NotmuchWorker worker(m_fixture.configPath());
+ QSignalSpy spy(&worker, &NotmuchWorker::allTagsReady);
+
+ worker.requestAllTags(7);
+
+ QCOMPARE(spy.count(), 1);
+ const QStringList tags = spy.at(0).at(0).toStringList();
+ const quint64 generation = spy.at(0).at(1).value<quint64>();
+
+ QCOMPARE(generation, quint64(7));
+ QVERIFY(tags.contains(QStringLiteral("inbox")));
+ QVERIFY(tags.contains(QStringLiteral("unread")));
+
+ // Completion offers these in order, so the worker sorts once rather than
+ // every consumer sorting again.
+ QStringList sorted = tags;
+ sorted.sort();
+ QCOMPARE(tags, sorted);
+}
+
+void TestNotmuchWorker::requestAllTagsOnUnreadableConfigEmitsError()
+{
+ // Fails closed like every other entry point: never silently fall through to
+ // the user's real database.
+ NotmuchWorker worker(QStringLiteral("/nonexistent/qtmaildir-test/config"));
+ QSignalSpy ready(&worker, &NotmuchWorker::allTagsReady);
+ QSignalSpy errors(&worker, &NotmuchWorker::errorOccurred);
+
+ worker.requestAllTags(1);
+
+ QCOMPARE(errors.size(), 1);
+ QVERIFY(ready.isEmpty());
+}
+
QTEST_MAIN(TestNotmuchWorker)
#include "test_notmuchworker.moc"