summaryrefslogtreecommitdiffstats
path: root/src/notmuchworker.h
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-03 08:52:21 +0200
committerDanilo M. <danix@danix.xyz>2026-08-04 12:52:39 +0200
commit23e2b007c76dbd1569b1f97c17100eefed4988af (patch)
treefe121e499b6595d563f5756929cb1d24f43ad31d /src/notmuchworker.h
parentf752848629572a8396ddb6f50748f7accf227312 (diff)
downloadqtmaildir-23e2b007c76dbd1569b1f97c17100eefed4988af.tar.gz
qtmaildir-23e2b007c76dbd1569b1f97c17100eefed4988af.zip
feat: add NotmuchWorker with batched queries and tag mutation
Owns the only notmuch database handle. Queries run read-only and emit threads in batches of 200 with a generation counter so the UI can discard superseded results. Tag mutation closes the read-only handle, opens read-write, applies, and closes, holding the process-wide write lock for milliseconds rather than blocking a concurrent `notmuch new`. Tested against a throwaway database built in a QTemporaryDir, superseding the spec's original "no unit test" position: applyTags is the only code here that writes to a notmuch index. The fixture never touches ~/Mail or ~/.notmuch-config. Two fixes against the drafted implementation, both caught by mutating the code and confirming exactly one test failed: - loadThread conflated "no query given" with "query matched nothing in this thread", so filtering a thread down to zero matches rendered every message expanded. Tracked with an explicit haveMatchSet flag. - applyTags now documents why a stale message id must skip rather than abort: notmuch_database_find_message reports SUCCESS with a null message for an unknown id, and the live ids alongside it still need tagging. Note for fixture authors: notmuch synchronizes maildir flags with tags at index time, so a file named `...:2,S` is indexed without the unread tag no matter what [new] tags requests. Unread fixture messages go in new/. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'src/notmuchworker.h')
-rw-r--r--src/notmuchworker.h69
1 files changed, 69 insertions, 0 deletions
diff --git a/src/notmuchworker.h b/src/notmuchworker.h
new file mode 100644
index 0000000..2551004
--- /dev/null
+++ b/src/notmuchworker.h
@@ -0,0 +1,69 @@
+#pragma once
+
+#include <QObject>
+#include <QStringList>
+#include <QVector>
+
+#include "types.h"
+
+struct _notmuch_database;
+typedef struct _notmuch_database notmuch_database_t;
+
+/// Owns the only notmuch database handle in the process.
+///
+/// libnotmuch is not thread-safe and queries over a large database block, so
+/// this object lives on its own thread and the UI reaches it only through
+/// queued signals. No notmuch pointer ever leaves this class.
+class NotmuchWorker : public QObject
+{
+ Q_OBJECT
+public:
+ /// notmuchConfigPath may be empty, in which case notmuch resolves its own
+ /// config and therefore its own database.path.
+ explicit NotmuchWorker(const QString &notmuchConfigPath, QObject *parent = nullptr);
+ ~NotmuchWorker() override;
+
+ /// Threads emitted per threadsReady() signal.
+ static constexpr int kBatchSize = 200;
+
+public slots:
+ /// Runs a query. generation lets the UI discard results from a superseded
+ /// query without the worker needing to know about cancellation.
+ void runQuery(const QString &query, quint64 generation);
+
+ /// Loads the messages of one thread, oldest first. matchQuery is the
+ /// user's current query; messages matching it render expanded, the rest
+ /// as stubs.
+ void loadThread(const QString &threadId, const QString &matchQuery,
+ quint64 generation);
+
+ /// Applies tag changes. Opens the database read-write, applies, and closes
+ /// immediately: notmuch's write lock is exclusive process-wide, so holding
+ /// it would block the user's cron `notmuch new`.
+ void applyTags(const TagChange &change);
+
+ /// Batch tagging over whole threads. The UI holds thread ids, not message
+ /// ids, for rows it has not opened, so the resolution happens here where
+ /// the database handle lives. This is the path the archive/flag/delete
+ /// actions use on a multi-row selection.
+ void applyTagsToThreads(const QStringList &threadIds,
+ const QStringList &add,
+ const QStringList &remove,
+ const QString &description);
+
+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 errorOccurred(const QString &message);
+
+private:
+ bool openReadOnly();
+ void close();
+
+ QByteArray configPathArg() const;
+
+ QString m_configPath;
+ notmuch_database_t *m_db = nullptr;
+};