aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/notmuchworker.cpp66
-rw-r--r--src/notmuchworker.h27
2 files changed, 93 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())
diff --git a/src/notmuchworker.h b/src/notmuchworker.h
index d1bec59..dfd4303 100644
--- a/src/notmuchworker.h
+++ b/src/notmuchworker.h
@@ -143,6 +143,21 @@ public slots:
const QStringList &remove,
const QString &description);
+ /// Resolves whole threads to the message ids and file paths they contain.
+ ///
+ /// Delete thread MOVES every message, and a move needs message ids, which
+ /// the UI does not hold for a thread it never expanded. The resolution
+ /// happens here for the same reason applyTagsToThreads() does it here:
+ /// the database handle lives on this thread, and one combined query beats
+ /// reopening the cursor per thread.
+ ///
+ /// Paths come back beside the ids because the destination is per ACCOUNT
+ /// and the UI resolves an account from a message's path. Without them the
+ /// caller would know which messages to move and not where any of them
+ /// belongs.
+ void resolveThreadMessages(const QStringList &threadIds,
+ const QString &requestTag);
+
/// 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.
@@ -222,6 +237,18 @@ signals:
/// from here can be passed straight back to move a message home.
void messagesMovedFrom(const QMap<QString, QString> &originByMessageId,
const QString &destFolder);
+ /// The answer to resolveThreadMessages(), as parallel lists: `messageIds`
+ /// and the database-relative `paths` of the same messages, in the same
+ /// order. `requestTag` is echoed back so a caller can tell which request
+ /// this answers.
+ /// `tags` carries each message's tags joined by a space, in the same
+ /// order. Needed because Restore reads a message's `deleted-from:` tag to
+ /// decide where to send it, and an unexpanded thread's messages have no
+ /// node in the model to read tags from.
+ void threadMessagesResolved(const QStringList &messageIds,
+ const QStringList &paths,
+ const QStringList &tags,
+ const QString &requestTag);
void allTagsReady(const QStringList &tags, quint64 generation);
/// One entry per requested query, in the order they were asked for. A query