diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-18 12:12:49 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-18 12:12:49 +0200 |
| commit | ec8c2d7492b479846270f702541a26be5643a376 (patch) | |
| tree | f743e215e177a9c5dfb49394a6d9bdcaa1dd498b /src/notmuchworker.cpp | |
| parent | a089820aba3bc51884ad880b9e3cf689eda7361d (diff) | |
| download | qtmaildir-ec8c2d7492b479846270f702541a26be5643a376.tar.gz qtmaildir-ec8c2d7492b479846270f702541a26be5643a376.zip | |
feat(worker): resolve whole threads to their messages, ids and paths
Delete thread MOVES every message now, and a move needs message ids and
file paths that the UI does not hold: a thread the user never expanded has
no nodes in the model for its replies, so those live only in the database.
resolveThreadMessages() answers with both, in one combined query, for the
same reason applyTagsToThreads() resolves threads here rather than in the
UI: a query per thread reopens the same Xapian cursor once per selected
row.
Paths come back RELATIVE to the database root, matching
ThreadSummary::firstMessagePath, because the UI knows accounts only by
their maildir, itself a database-relative prefix. Without them the caller
would know which messages to move and not where any of them belongs.
Tags come back too, because Restore reads a message's `deleted-from:` tag
to decide where to send it and an unexpanded thread's messages have no node
to read tags from.
They are joined by a TAB, not a space. A notmuch tag may absolutely contain
a space: the folder "Inbox/SlackBuilds users" produces
`deleted-from:Inbox/SlackBuilds users`, and splitting that on spaces
truncated the folder to "Inbox/SlackBuilds". Restore then moved the
messages into a folder of that name, CREATING it, so real messages ended up
in a directory mbsync does not sync and read as missing. Under `Create
Both` that folder can propagate to the mail server. A tab cannot appear in
a tag, since notmuch's own dump format is line-based and
whitespace-delimited.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'src/notmuchworker.cpp')
| -rw-r--r-- | src/notmuchworker.cpp | 66 |
1 files changed, 66 insertions, 0 deletions
diff --git a/src/notmuchworker.cpp b/src/notmuchworker.cpp index 6a0694f..62174c9 100644 --- a/src/notmuchworker.cpp +++ b/src/notmuchworker.cpp @@ -776,6 +776,72 @@ void NotmuchWorker::moveMessages(const QStringList &messageIds, emit messagesMovedFrom(origins, destFolder); } +void NotmuchWorker::resolveThreadMessages(const QStringList &threadIds, + const QString &requestTag) +{ + if (threadIds.isEmpty()) + return; + + if (!openReadOnly()) + return; + + // One combined query, for the reason applyTagsToThreads() gives: a query + // per thread reopens the same Xapian cursor once per selected row. + QStringList terms; + terms.reserve(threadIds.size()); + for (const QString &id : threadIds) + terms.append(QStringLiteral("thread:%1").arg(id)); + + const QString query = terms.join(QStringLiteral(" or ")); + NmQuery nmQuery(notmuch_query_create(m_db, query.toUtf8().constData())); + if (!nmQuery) { + emit errorOccurred(QStringLiteral("Cannot resolve selected threads")); + return; + } + + notmuch_messages_t *raw = nullptr; + if (notmuch_query_search_messages(nmQuery.get(), &raw) + != NOTMUCH_STATUS_SUCCESS) { + emit errorOccurred(QStringLiteral("Cannot resolve selected threads")); + return; + } + + // Paths are reported RELATIVE to the database root, matching + // 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(); + + QStringList messageIds; + QStringList paths; + QStringList tags; + NmMessages messages(raw); + for (; notmuch_messages_valid(messages.get()); + notmuch_messages_move_to_next(messages.get())) { + NmMessage message(notmuch_messages_get(messages.get())); + if (!message) + continue; + const char *rawName = notmuch_message_get_filename(message.get()); + if (!rawName) + continue; + messageIds.append( + QString::fromUtf8(notmuch_message_get_message_id(message.get()))); + paths.append( + QDir(dbRoot).relativeFilePath(QString::fromUtf8(rawName))); + // Joined by a TAB, not a space. A notmuch tag may absolutely contain + // a space: a Maildir folder named "Inbox/SlackBuilds users" produces + // `deleted-from:Inbox/SlackBuilds users`, and splitting that on spaces + // truncated the folder to "Inbox/SlackBuilds". Restore then moved the + // messages into a folder of that name, CREATING it, so four real + // messages ended up in a directory mbsync does not sync and the user + // could not find them. A tab cannot appear in a tag, because notmuch's + // own dump/restore format is whitespace-delimited by line. + tags.append(tagsOf(message.get()).join(QLatin1Char('\t'))); + } + + emit threadMessagesResolved(messageIds, paths, tags, requestTag); +} + void NotmuchWorker::requestAllTags(quint64 generation) { if (!openReadOnly()) |
