diff options
| -rw-r--r-- | src/notmuchworker.cpp | 22 | ||||
| -rw-r--r-- | src/notmuchworker.h | 9 | ||||
| -rw-r--r-- | tests/test_notmuchworker.cpp | 34 |
3 files changed, 65 insertions, 0 deletions
diff --git a/src/notmuchworker.cpp b/src/notmuchworker.cpp index 2588f4e..d41b54b 100644 --- a/src/notmuchworker.cpp +++ b/src/notmuchworker.cpp @@ -41,6 +41,7 @@ #include "maildirname.h" #include "mimeparser.h" #include "nmraii.h" +#include "searchterm.h" namespace { @@ -1082,6 +1083,27 @@ void NotmuchWorker::applyTagsToThreads(const QStringList &threadIds, applyTags(TagChange{ messageIds, add, remove, description }); } +void NotmuchWorker::applyTagsToQuery(const QString &query, + const QStringList &add, + const QStringList &remove, + const QString &description) +{ + if (!openReadOnly()) + return; + + QStringList messageIds; + if (!collectMessageIds(m_db, SearchTerm::changes(query, add, remove), + &messageIds)) { + emit errorOccurred(QStringLiteral("Cannot resolve the rule's query")); + return; + } + + // Empty is not an error: the rule has nothing left to do. applyTags() + // returns at once on an empty list, and stays silent, as it must + // (CLAUDE.md, item 176). + applyTags(TagChange{ messageIds, add, remove, description }); +} + void NotmuchWorker::applyTags(const TagChange &change) { if (change.messageIds.isEmpty()) diff --git a/src/notmuchworker.h b/src/notmuchworker.h index ad5f616..fa223e6 100644 --- a/src/notmuchworker.h +++ b/src/notmuchworker.h @@ -232,6 +232,15 @@ public slots: const QStringList &remove, const QString &description); + /// Applies `+add -remove` to every message the change would move within + /// `query` (item 212, a rule applied to existing mail). The query is + /// narrowed with SearchTerm::changes() so already-correct messages are + /// never written, then the ids go through applyTags(), which reports the + /// effective set for undo. Emits nothing when nothing would change. + void applyTagsToQuery(const QString &query, const QStringList &add, + 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 diff --git a/tests/test_notmuchworker.cpp b/tests/test_notmuchworker.cpp index 10f8dbc..9b64f8e 100644 --- a/tests/test_notmuchworker.cpp +++ b/tests/test_notmuchworker.cpp @@ -59,6 +59,7 @@ private slots: void aThreadCarriesItsCardMessagesOwnTags(); void applyTagsToThreadsTagsEveryMessage(); + void applyTagsToQueryWritesOnlyWhatWouldChange(); void applyTagsToThreadsSpansMultipleThreads(); void applyTagsToThreadsWithNoThreadsDoesNothing(); @@ -1290,6 +1291,39 @@ void TestNotmuchWorker::applyTagsToThreadsTagsEveryMessage() restore.applyTags(emitted.inverted()); } +void TestNotmuchWorker::applyTagsToQueryWritesOnlyWhatWouldChange() +{ + // Item 212. Two messages in OPPOSITE states (item 87's lesson): a1 + // already carries the tag, a2 does not. Only a2 may be reported, or the + // undo would strip the tag from a message that had it before. + const QString threadId = threadIdOf(QStringLiteral("Release notes")); + QVERIFY(!threadId.isEmpty()); + + NotmuchWorker setup(m_fixture.configPath()); + setup.applyTags(TagChange{ { QStringLiteral("a1@example.org") }, + { QStringLiteral("backfill") }, {}, + QStringLiteral("seed") }); + + NotmuchWorker worker(m_fixture.configPath()); + QSignalSpy applied(&worker, &NotmuchWorker::tagsApplied); + worker.applyTagsToQuery(QStringLiteral("thread:%1").arg(threadId), + { QStringLiteral("backfill") }, {}, + QStringLiteral("Apply rule")); + + QCOMPARE(applied.size(), 1); + const TagChange emitted = applied.first().at(0).value<TagChange>(); + QCOMPARE(emitted.messageIds, QStringList{ QStringLiteral("a2@example.org") }); + QCOMPARE(emitted.description, QStringLiteral("Apply rule")); + QVERIFY(tagsOf(QStringLiteral("a2@example.org")) + .contains(QStringLiteral("backfill"))); + + NotmuchWorker restore(m_fixture.configPath()); + restore.applyTags(TagChange{ { QStringLiteral("a1@example.org"), + QStringLiteral("a2@example.org") }, + {}, { QStringLiteral("backfill") }, + QStringLiteral("restore") }); +} + void TestNotmuchWorker::applyTagsToThreadsSpansMultipleThreads() { const QString threadA = threadIdOf(QStringLiteral("Release notes")); |
