summaryrefslogtreecommitdiffstats
path: root/src/notmuchworker.cpp
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-08-20 09:47:11 +0200
committerDanilo M. <danix@danix.xyz>2026-08-20 09:47:11 +0200
commit98914f2c184539c9be9cac7c3ae8def6820868b8 (patch)
treec87a7fe3ecd4b90e3222ba41e95bb5dc6eecf51b /src/notmuchworker.cpp
parent40059782a5f5658ba02b47c4ab42fcfa4234989d (diff)
downloadqtmaildir-98914f2c184539c9be9cac7c3ae8def6820868b8.tar.gz
qtmaildir-98914f2c184539c9be9cac7c3ae8def6820868b8.zip
fix(worker): read the mail root, not the index directory
notmuch can be configured with `mail_root` and `path` as separate keys, which puts the Xapian index outside the Maildir. Under that layout notmuch_database_get_path() returns the INDEX directory, and the worker treated it as the mail root at four sites. The consequences are not symmetric. Message paths resolved to `../..` escapes that match no account prefix, which is a display defect. But moveMessages() composes its destination from the same root, so Delete would have written into the Xapian tree: outside the Maildir, invisible to mbsync, and gone from every other client. That is the stranded-mail failure of item 103 with a new cause. notmuch_config_get(NOTMUCH_CONFIG_MAIL_ROOT) is correct under both layouts, so no conditional is needed. Verified against the live database: with only `path` set it returns the same string as get_path(), making this a no-op for the current configuration. The fixture gains an opt-in splitIndex(). That is load-bearing rather than convenience: in the ordinary layout the index lives inside the mail root and both accessors return the same string, so a test written against it passes whichever one the code uses. All three new tests fail against the old accessor, confirmed by mutation. Also records the finding as backlog item 124, and corrects item 121's timings, which had been copied from item 74 rather than measured. A cold run seven minutes after boot, with the index verifiably unread, gives 2008 ms to the first rows and 38618 ms to a complete list, against the 642 ms and 5714 ms recorded there. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YDq53rMd3AQp7QmcZzpuBM
Diffstat (limited to 'src/notmuchworker.cpp')
-rw-r--r--src/notmuchworker.cpp30
1 files changed, 26 insertions, 4 deletions
diff --git a/src/notmuchworker.cpp b/src/notmuchworker.cpp
index 66b408c..d0274cd 100644
--- a/src/notmuchworker.cpp
+++ b/src/notmuchworker.cpp
@@ -35,6 +35,28 @@
namespace {
+/// Where the MAIL lives, which is not always where the index lives.
+///
+/// Item 124. notmuch can be configured with `mail_root` and `path` as separate
+/// keys, which is how the Xapian index moves to faster storage while the
+/// Maildir stays put. Under that layout `notmuch_database_get_path()` returns
+/// the INDEX directory, so every path composed from it lands in the wrong tree:
+/// message paths resolve to `../..` escapes that match no account, and a move
+/// writes into the index instead of the Maildir, where mbsync never sees it.
+///
+/// `NOTMUCH_CONFIG_MAIL_ROOT` is correct under BOTH layouts. With only `path`
+/// set it returns that same directory, so this is not a special case for split
+/// configurations but the right question to ask in every one. Measured against
+/// notmuch 0.39: legacy config, `get_path()` and `MAIL_ROOT` agree; split
+/// config, only `MAIL_ROOT` names the Maildir.
+///
+/// The string is owned by notmuch and must not be freed (notmuch.h:2585).
+QString mailRootOf(notmuch_database_t *db)
+{
+ const char *root = notmuch_config_get(db, NOTMUCH_CONFIG_MAIL_ROOT);
+ return root ? QString::fromUtf8(root) : QString();
+}
+
QStringList tagsOf(notmuch_message_t *message)
{
QStringList result;
@@ -295,7 +317,7 @@ void NotmuchWorker::runQuery(const QString &query, quint64 generation,
// database-relative prefix: comparing the two never matched and left every
// row resolving to no account at all.
const QString dbRoot =
- QDir(QString::fromUtf8(notmuch_database_get_path(m_db))).absolutePath();
+ QDir(mailRootOf(m_db)).absolutePath();
QVector<ThreadSummary> batch;
batch.reserve(kBatchSize);
@@ -752,7 +774,7 @@ void NotmuchWorker::moveMessages(const QStringList &messageIds,
return;
}
- const QString root = QString::fromUtf8(notmuch_database_get_path(db));
+ const QString root = mailRootOf(db);
const QString destDir =
root + QLatin1Char('/') + destFolder + QStringLiteral("/cur");
@@ -898,7 +920,7 @@ void NotmuchWorker::resolveQuery(const QString &query,
// ThreadSummary::firstMessagePath: the UI knows accounts only by their
// maildir, itself a database-relative prefix.
const QString dbRoot =
- QDir(QString::fromUtf8(notmuch_database_get_path(m_db))).absolutePath();
+ QDir(mailRootOf(m_db)).absolutePath();
QStringList messageIds;
QStringList paths;
@@ -1059,7 +1081,7 @@ void NotmuchWorker::requestFolders()
if (!openReadOnly())
return;
- const QString root = QString::fromUtf8(notmuch_database_get_path(m_db));
+ const QString root = mailRootOf(m_db);
if (root.isEmpty()) {
emit errorOccurred(
QStringLiteral("notmuch reports no database path."));