service/ImapServiceSearch.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // UID SEARCH commands of ImapService (T-187 text search, Sprint 59 facets, | ||
| 2 | // Sprint 101 negations, T-211 Message-ID lookup) and their tagged result, | ||
| 3 | // split from ImapService.cpp for its line limit (SPRINT-92.md §8). | ||
| 4 | |||
| 5 | #include "ImapService.h" | ||
| 6 | #include "ImapCommandTable.h" | ||
| 7 | #include "ImapResponseParser.h" | ||
| 8 | |||
| 9 | #include <QLocale> | ||
| 10 | #include <utility> | ||
| 11 | #include <QLoggingCategory> | ||
| 12 | |||
| 13 | Q_DECLARE_LOGGING_CATEGORY(lcImap) | ||
| 14 | |||
| 15 | 7 | void ImapService::searchAllUids(qint64 fromUid) { | |
| 16 |
2/2✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 14 taken 5 times.
|
7 | if (m_state != State::Selected) { |
| 17 | 4 | qCWarning(lcImap) << "Cannot search: no folder selected"; // GCOVR_EXCL_BR_LINE — Qt logging-macro cleanup only; folder guard remains measured. | |
| 18 | 2 | return; | |
| 19 | } | ||
| 20 |
2/2✓ Branch 16 → 17 taken 2 times.
✓ Branch 16 → 22 taken 3 times.
|
5 | if (m_commands->hasStatefulCommandInFlight()) { |
| 21 |
1/1✓ Branch 19 → 20 taken 2 times.
|
4 | m_commands->enqueueSerializedCommand([this, fromUid]() { searchAllUids(fromUid); }); |
| 22 | 2 | return; | |
| 23 | } | ||
| 24 | |||
| 25 | 3 | m_pendingSearchUids.clear(); | |
| 26 |
2/2✓ Branch 23 → 24 taken 1 time.
✓ Branch 23 → 37 taken 2 times.
|
3 | if (fromUid > 1) { |
| 27 | // Delta search: only UIDs >= fromUid | ||
| 28 | 1 | m_activeMessageIdSearch = {}; | |
| 29 |
2/2✓ Branch 30 → 31 taken 1 time.
✓ Branch 31 → 32 taken 1 time.
|
3 | m_commands->sendCommand("SEARCH", |
| 30 |
2/2✓ Branch 27 → 28 taken 1 time.
✓ Branch 29 → 30 taken 1 time.
|
3 | QString("UID SEARCH UID %1:*").arg(fromUid)); |
| 31 | } else { | ||
| 32 | 2 | m_activeMessageIdSearch = {}; | |
| 33 |
3/3✓ Branch 40 → 41 taken 2 times.
✓ Branch 41 → 42 taken 2 times.
✓ Branch 42 → 43 taken 2 times.
|
2 | m_commands->sendCommand("SEARCH", "UID SEARCH ALL"); |
| 34 | } | ||
| 35 | } | ||
| 36 | |||
| 37 | // T-187: IMAP text-based SEARCH | ||
| 38 | 39 | void ImapService::searchText(const QString &query, const QString &criteria) { | |
| 39 | // S106-025: the search key is part of the command line and is not quoted — | ||
| 40 | // only the RFC 3501 text keys are accepted, never arbitrary text. | ||
| 41 | 5 | static const QStringList textKeys = {QStringLiteral("SUBJECT"), QStringLiteral("FROM"), | |
| 42 | 5 | QStringLiteral("TO"), QStringLiteral("CC"), | |
| 43 | 5 | QStringLiteral("BCC"), QStringLiteral("BODY"), | |
| 44 |
5/10✓ Branch 2 → 3 taken 5 times.
✓ Branch 2 → 40 taken 34 times.
✓ Branch 4 → 5 taken 5 times.
✗ Branch 4 → 40 not taken.
✓ Branch 30 → 31 taken 35 times.
✓ Branch 30 → 32 taken 5 times.
✗ Branch 96 → 97 not taken.
✗ Branch 96 → 98 not taken.
✗ Branch 126 → 127 not taken.
✗ Branch 126 → 128 not taken.
|
84 | QStringLiteral("TEXT")}; |
| 45 |
2/2✓ Branch 41 → 42 taken 1 time.
✓ Branch 41 → 56 taken 38 times.
|
39 | if (!textKeys.contains(criteria)) { |
| 46 |
7/7✓ Branch 42 → 43 taken 1 time.
✓ Branch 46 → 47 taken 1 time.
✓ Branch 47 → 48 taken 1 time.
✓ Branch 48 → 49 taken 1 time.
✓ Branch 49 → 50 taken 1 time.
✓ Branch 54 → 44 taken 1 time.
✓ Branch 54 → 55 taken 1 time.
|
2 | qCWarning(lcImap) << "searchText: refused search key" << criteria.left(20); |
| 47 | 1 | return; | |
| 48 | } | ||
| 49 |
2/2✓ Branch 56 → 57 taken 3 times.
✓ Branch 56 → 68 taken 35 times.
|
38 | if (m_state != State::Selected) { |
| 50 |
5/5✓ Branch 57 → 58 taken 3 times.
✓ Branch 61 → 62 taken 3 times.
✓ Branch 62 → 63 taken 3 times.
✓ Branch 66 → 59 taken 3 times.
✓ Branch 66 → 67 taken 3 times.
|
6 | qCWarning(lcImap) << "Cannot search: no folder selected"; |
| 51 | 3 | return; | |
| 52 | } | ||
| 53 |
2/2✓ Branch 70 → 71 taken 4 times.
✓ Branch 70 → 79 taken 31 times.
|
35 | if (m_commands->hasStatefulCommandInFlight()) { |
| 54 |
2/4✓ Branch 74 → 75 taken 4 times.
✓ Branch 75 → 76 taken 4 times.
✗ Branch 146 → 147 not taken.
✗ Branch 146 → 148 not taken.
|
4 | m_commands->enqueueSerializedCommand([this, query, criteria]() { |
| 55 | 2 | searchText(query, criteria); | |
| 56 | 2 | }); | |
| 57 | 4 | return; | |
| 58 | } | ||
| 59 | |||
| 60 | 31 | m_pendingSearchUids.clear(); | |
| 61 | // RFC 3501: SEARCH <criteria> <quoted-string> | ||
| 62 | 31 | m_activeMessageIdSearch = {}; | |
| 63 |
2/2✓ Branch 86 → 87 taken 31 times.
✓ Branch 87 → 88 taken 31 times.
|
93 | m_commands->sendCommand("SEARCH", |
| 64 |
3/3✓ Branch 83 → 84 taken 31 times.
✓ Branch 84 → 85 taken 31 times.
✓ Branch 85 → 86 taken 31 times.
|
62 | QString("UID SEARCH %1 %2").arg(criteria, quoteImapString(query))); |
| 65 | 40 | } | |
| 66 | |||
| 67 | // Sprint 59 (S2): composite SEARCH for the visual search facets. | ||
| 68 | 50 | bool ImapService::SearchCriteria::isEmpty() const { | |
| 69 |
7/8✓ Branch 5 → 6 taken 26 times.
✓ Branch 5 → 34 taken 5 times.
✓ Branch 7 → 8 taken 24 times.
✓ Branch 7 → 34 taken 2 times.
✓ Branch 9 → 10 taken 24 times.
✗ Branch 9 → 34 not taken.
✓ Branch 11 → 12 taken 21 times.
✓ Branch 11 → 34 taken 3 times.
|
81 | return text.isEmpty() && from.isEmpty() && to.isEmpty() && listId.isEmpty() && subject.isEmpty() && |
| 70 |
6/6✓ Branch 13 → 14 taken 18 times.
✓ Branch 13 → 34 taken 3 times.
✓ Branch 15 → 16 taken 16 times.
✓ Branch 15 → 34 taken 2 times.
✓ Branch 16 → 17 taken 13 times.
✓ Branch 16 → 34 taken 3 times.
|
21 | !since.isValid() && !before.isValid() && unread == SearchTri::Any && |
| 71 |
6/6✓ Branch 17 → 18 taken 11 times.
✓ Branch 17 → 34 taken 2 times.
✓ Branch 18 → 19 taken 9 times.
✓ Branch 18 → 34 taken 2 times.
✓ Branch 20 → 21 taken 4 times.
✓ Branch 20 → 34 taken 5 times.
|
22 | flagged == SearchTri::Any && answered == SearchTri::Any && |
| 72 |
4/8✓ Branch 22 → 23 taken 4 times.
✗ Branch 22 → 34 not taken.
✓ Branch 24 → 25 taken 4 times.
✗ Branch 24 → 34 not taken.
✓ Branch 26 → 27 taken 4 times.
✗ Branch 26 → 34 not taken.
✓ Branch 28 → 29 taken 4 times.
✗ Branch 28 → 34 not taken.
|
17 | keywords.isEmpty() && notText.isEmpty() && notFrom.isEmpty() && notTo.isEmpty() && |
| 73 |
4/6✓ Branch 3 → 4 taken 31 times.
✓ Branch 3 → 34 taken 19 times.
✓ Branch 30 → 31 taken 4 times.
✗ Branch 30 → 34 not taken.
✓ Branch 32 → 33 taken 4 times.
✗ Branch 32 → 34 not taken.
|
85 | notListId.isEmpty() && notSubject.isEmpty() && notKeywords.isEmpty(); |
| 74 | } | ||
| 75 | |||
| 76 | 30 | QString ImapService::buildSearchCommand(const SearchCriteria &criteria) { | |
| 77 |
3/3✓ Branch 2 → 3 taken 30 times.
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 5 taken 28 times.
|
30 | if (criteria.isEmpty()) |
| 78 | 2 | return QString(); | |
| 79 | |||
| 80 | // RFC 3501 date format is "dd-MMM-yyyy" with English month names; force the C | ||
| 81 | // locale so it never gets localized (e.g. "Feb" not "Févr"). | ||
| 82 | 8 | auto imapDate = [](const QDate &d) { | |
| 83 |
2/2✓ Branch 2 → 3 taken 8 times.
✓ Branch 6 → 7 taken 8 times.
|
16 | return QLocale::c().toString(d, QStringLiteral("dd-MMM-yyyy")); |
| 84 | }; | ||
| 85 | |||
| 86 | 28 | QStringList parts; | |
| 87 |
2/2✓ Branch 6 → 7 taken 11 times.
✓ Branch 6 → 17 taken 17 times.
|
28 | if (!criteria.text.isEmpty()) |
| 88 |
3/3✓ Branch 10 → 11 taken 11 times.
✓ Branch 11 → 12 taken 11 times.
✓ Branch 12 → 13 taken 11 times.
|
11 | parts << QStringLiteral("TEXT") << quoteImapString(criteria.text); |
| 89 |
2/2✓ Branch 18 → 19 taken 5 times.
✓ Branch 18 → 29 taken 23 times.
|
28 | if (!criteria.from.isEmpty()) |
| 90 |
3/3✓ Branch 22 → 23 taken 5 times.
✓ Branch 23 → 24 taken 5 times.
✓ Branch 24 → 25 taken 5 times.
|
5 | parts << QStringLiteral("FROM") << quoteImapString(criteria.from); |
| 91 |
2/2✓ Branch 30 → 31 taken 2 times.
✓ Branch 30 → 50 taken 26 times.
|
28 | if (!criteria.to.isEmpty()) // "to:" means To or Cc, as in the local search |
| 92 |
6/6✓ Branch 34 → 35 taken 2 times.
✓ Branch 35 → 36 taken 2 times.
✓ Branch 36 → 37 taken 2 times.
✓ Branch 40 → 41 taken 2 times.
✓ Branch 41 → 42 taken 2 times.
✓ Branch 42 → 43 taken 2 times.
|
4 | parts << QStringLiteral("OR TO") << quoteImapString(criteria.to) << QStringLiteral("CC") << quoteImapString(criteria.to); |
| 93 |
1/2✗ Branch 51 → 52 not taken.
✓ Branch 51 → 68 taken 28 times.
|
28 | if (!criteria.listId.isEmpty()) |
| 94 | ✗ | parts << QStringLiteral("HEADER") << QStringLiteral("List-Id") << quoteImapString(criteria.listId); | |
| 95 |
2/2✓ Branch 69 → 70 taken 4 times.
✓ Branch 69 → 80 taken 24 times.
|
28 | if (!criteria.subject.isEmpty()) |
| 96 |
3/3✓ Branch 73 → 74 taken 4 times.
✓ Branch 74 → 75 taken 4 times.
✓ Branch 75 → 76 taken 4 times.
|
4 | parts << QStringLiteral("SUBJECT") << quoteImapString(criteria.subject); |
| 97 |
3/3✓ Branch 80 → 81 taken 28 times.
✓ Branch 81 → 82 taken 5 times.
✓ Branch 81 → 92 taken 23 times.
|
28 | if (criteria.since.isValid()) |
| 98 |
3/3✓ Branch 85 → 86 taken 5 times.
✓ Branch 86 → 87 taken 5 times.
✓ Branch 87 → 88 taken 5 times.
|
5 | parts << QStringLiteral("SINCE") << imapDate(criteria.since); |
| 99 |
3/3✓ Branch 92 → 93 taken 28 times.
✓ Branch 93 → 94 taken 3 times.
✓ Branch 93 → 104 taken 25 times.
|
28 | if (criteria.before.isValid()) |
| 100 |
3/3✓ Branch 97 → 98 taken 3 times.
✓ Branch 98 → 99 taken 3 times.
✓ Branch 99 → 100 taken 3 times.
|
3 | parts << QStringLiteral("BEFORE") << imapDate(criteria.before); |
| 101 | 84 | const auto state = [&parts](SearchTri value, const char *yes, const char *no) { | |
| 102 |
6/6✓ Branch 2 → 3 taken 12 times.
✓ Branch 2 → 11 taken 72 times.
✓ Branch 3 → 4 taken 7 times.
✓ Branch 3 → 5 taken 5 times.
✓ Branch 7 → 8 taken 12 times.
✓ Branch 8 → 9 taken 12 times.
|
84 | if (value != SearchTri::Any) parts << QString::fromLatin1(value == SearchTri::Yes ? yes : no); |
| 103 | 84 | }; | |
| 104 |
1/1✓ Branch 104 → 105 taken 28 times.
|
28 | state(criteria.unread, "UNSEEN", "SEEN"); |
| 105 |
1/1✓ Branch 105 → 106 taken 28 times.
|
28 | state(criteria.flagged, "FLAGGED", "UNFLAGGED"); |
| 106 |
1/1✓ Branch 106 → 107 taken 28 times.
|
28 | state(criteria.answered, "ANSWERED", "UNANSWERED"); |
| 107 | // S106-024: KEYWORD/UNKEYWORD take a flag keyword, an atom (RFC 3501 | ||
| 108 | // §6.4.4) — a quoted string makes strict servers answer BAD. A keyword | ||
| 109 | // that is no valid atom cannot match any message flag and is left out. | ||
| 110 | 10 | const auto keywordAtom = [](const QString &keyword) { | |
| 111 |
1/1✓ Branch 2 → 3 taken 10 times.
|
10 | const QString trimmed = keyword.trimmed(); |
| 112 |
2/2✓ Branch 4 → 5 taken 2 times.
✓ Branch 4 → 6 taken 8 times.
|
10 | if (trimmed.isEmpty()) |
| 113 | 2 | return QString(); | |
| 114 |
2/2✓ Branch 27 → 8 taken 52 times.
✓ Branch 27 → 28 taken 7 times.
|
59 | for (const QChar c : trimmed) |
| 115 |
11/17✓ Branch 9 → 10 taken 51 times.
✓ Branch 9 → 17 taken 1 time.
✓ Branch 11 → 12 taken 51 times.
✗ Branch 11 → 17 not taken.
✓ Branch 15 → 16 taken 51 times.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 51 times.
✓ Branch 19 → 20 taken 51 times.
✓ Branch 19 → 21 taken 1 time.
✓ Branch 21 → 22 taken 51 times.
✓ Branch 21 → 23 taken 1 time.
✓ Branch 23 → 24 taken 1 time.
✓ Branch 23 → 26 taken 51 times.
✗ Branch 32 → 33 not taken.
✗ Branch 32 → 34 not taken.
✗ Branch 36 → 37 not taken.
✗ Branch 36 → 38 not taken.
|
103 | if (c.unicode() <= 0x20 || c.unicode() >= 0x7f || QStringLiteral("(){%*\"\\]").contains(c)) |
| 116 | 1 | return QString(); | |
| 117 | 7 | return trimmed; | |
| 118 | 10 | }; | |
| 119 |
2/2✓ Branch 124 → 109 taken 9 times.
✓ Branch 124 → 125 taken 28 times.
|
37 | for (const QString &kw : criteria.keywords) |
| 120 |
5/5✓ Branch 110 → 111 taken 9 times.
✓ Branch 112 → 113 taken 6 times.
✓ Branch 112 → 121 taken 3 times.
✓ Branch 116 → 117 taken 6 times.
✓ Branch 117 → 118 taken 6 times.
|
15 | if (const QString atom = keywordAtom(kw); !atom.isEmpty()) parts << QStringLiteral("KEYWORD") << atom; |
| 121 |
2/2✓ Branch 142 → 127 taken 1 time.
✓ Branch 142 → 143 taken 28 times.
|
29 | for (const QString &kw : criteria.notKeywords) |
| 122 |
4/5✓ Branch 128 → 129 taken 1 time.
✓ Branch 130 → 131 taken 1 time.
✗ Branch 130 → 139 not taken.
✓ Branch 134 → 135 taken 1 time.
✓ Branch 135 → 136 taken 1 time.
|
2 | if (const QString atom = keywordAtom(kw); !atom.isEmpty()) parts << QStringLiteral("UNKEYWORD") << atom; |
| 123 | // Sprint 101 D-14: negations (RFC 3501 NOT …). | ||
| 124 | ✗ | const QList<QPair<const QStringList *, QString>> negated = {{&criteria.notText, "NOT TEXT"}, | |
| 125 | ✗ | {&criteria.notFrom, "NOT FROM"}, {&criteria.notSubject, "NOT SUBJECT"}, | |
| 126 |
2/4✓ Branch 149 → 150 taken 112 times.
✓ Branch 149 → 151 taken 28 times.
✗ Branch 328 → 329 not taken.
✗ Branch 328 → 330 not taken.
|
140 | {&criteria.notListId, "NOT HEADER List-Id"}}; |
| 127 |
2/2✓ Branch 173 → 153 taken 112 times.
✓ Branch 173 → 174 taken 28 times.
|
140 | for (const auto &[values, key] : negated) |
| 128 |
1/8✗ Branch 159 → 160 not taken.
✗ Branch 162 → 163 not taken.
✗ Branch 162 → 168 not taken.
✗ Branch 163 → 164 not taken.
✗ Branch 164 → 165 not taken.
✗ Branch 165 → 166 not taken.
✗ Branch 170 → 158 not taken.
✓ Branch 170 → 171 taken 112 times.
|
112 | for (const QString &value : *values) if (!value.trimmed().isEmpty()) parts << key << quoteImapString(value); |
| 129 |
1/2✗ Branch 196 → 176 not taken.
✓ Branch 196 → 197 taken 28 times.
|
28 | for (const QString &value : criteria.notTo) |
| 130 | ✗ | if (!value.trimmed().isEmpty()) parts << "NOT OR TO" << quoteImapString(value) << "CC" << quoteImapString(value); | |
| 131 |
2/2✓ Branch 198 → 199 taken 1 time.
✓ Branch 198 → 200 taken 27 times.
|
28 | if (parts.isEmpty()) |
| 132 | 1 | return QString(); | |
| 133 | // S106-024: RFC 3501 searches in US-ASCII unless CHARSET names another | ||
| 134 | // character set; terms with umlauts were sent without it, and a strict | ||
| 135 | // server answered NO [BADCHARSET]. | ||
| 136 |
1/1✓ Branch 202 → 203 taken 27 times.
|
27 | const QString joined = parts.join(QLatin1Char(' ')); |
| 137 | 27 | bool ascii = true; | |
| 138 |
2/2✓ Branch 209 → 205 taken 739 times.
✓ Branch 209 → 210 taken 24 times.
|
763 | for (const QChar c : joined) |
| 139 |
2/2✓ Branch 206 → 207 taken 3 times.
✓ Branch 206 → 208 taken 736 times.
|
739 | if (c.unicode() > 0x7f) { ascii = false; break; } |
| 140 |
7/11✓ Branch 210 → 211 taken 24 times.
✓ Branch 210 → 214 taken 3 times.
✓ Branch 217 → 218 taken 27 times.
✓ Branch 220 → 221 taken 3 times.
✓ Branch 220 → 222 taken 24 times.
✓ Branch 222 → 223 taken 24 times.
✓ Branch 222 → 224 taken 3 times.
✗ Branch 365 → 366 not taken.
✗ Branch 365 → 367 not taken.
✗ Branch 369 → 370 not taken.
✗ Branch 369 → 371 not taken.
|
54 | return (ascii ? QStringLiteral("UID SEARCH ") : QStringLiteral("UID SEARCH CHARSET UTF-8 ")) + joined; |
| 141 | 56 | } | |
| 142 | |||
| 143 | 13 | void ImapService::search(const SearchCriteria &criteria) { | |
| 144 |
2/2✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 14 taken 12 times.
|
13 | if (m_state != State::Selected) { |
| 145 |
5/5✓ Branch 3 → 4 taken 1 time.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 12 → 5 taken 1 time.
✓ Branch 12 → 13 taken 1 time.
|
2 | qCWarning(lcImap) << "Cannot search: no folder selected"; |
| 146 | 11 | return; | |
| 147 | } | ||
| 148 |
1/1✓ Branch 14 → 15 taken 12 times.
|
12 | const QString command = buildSearchCommand(criteria); |
| 149 |
2/2✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 28 taken 11 times.
|
12 | if (command.isEmpty()) { |
| 150 | 2 | qCInfo(lcImap) << "search: no server-mappable criteria — skipping"; // GCOVR_EXCL_BR_LINE — Qt logging-macro cleanup only; criteria guard remains measured. | |
| 151 | 1 | return; | |
| 152 | } | ||
| 153 |
3/3✓ Branch 29 → 30 taken 11 times.
✓ Branch 30 → 31 taken 9 times.
✓ Branch 30 → 38 taken 2 times.
|
11 | if (m_commands->hasStatefulCommandInFlight()) { |
| 154 |
2/2✓ Branch 33 → 34 taken 9 times.
✓ Branch 34 → 35 taken 9 times.
|
11 | m_commands->enqueueSerializedCommand([this, criteria]() { search(criteria); }); |
| 155 | 9 | return; | |
| 156 | } | ||
| 157 | |||
| 158 |
1/1✓ Branch 38 → 39 taken 2 times.
|
2 | m_pendingSearchUids.clear(); |
| 159 | 2 | m_activeMessageIdSearch = {}; | |
| 160 |
2/2✓ Branch 42 → 43 taken 2 times.
✓ Branch 43 → 44 taken 2 times.
|
2 | m_commands->sendCommand("SEARCH", command); |
| 161 | 12 | } | |
| 162 | |||
| 163 | // T-211: Search by Message-ID header (for undo-move) | ||
| 164 | 17 | quint64 ImapService::searchByMessageId(const QString &messageId) { | |
| 165 | 17 | return sendMessageIdSearch(messageId, ++m_nextMessageIdSearch); | |
| 166 | } | ||
| 167 | |||
| 168 | 19 | quint64 ImapService::sendMessageIdSearch(const QString &messageId, quint64 sequence) { | |
| 169 |
2/2✓ Branch 2 → 3 taken 9 times.
✓ Branch 2 → 14 taken 10 times.
|
19 | if (m_state != State::Selected) { |
| 170 | 18 | qCWarning(lcImap) << "Cannot search: no folder selected"; // GCOVR_EXCL_BR_LINE — Qt logging-macro cleanup only; folder guard remains measured. | |
| 171 | 9 | return 0; | |
| 172 | } | ||
| 173 |
2/2✓ Branch 16 → 17 taken 4 times.
✓ Branch 16 → 24 taken 6 times.
|
10 | if (m_commands->hasStatefulCommandInFlight()) { |
| 174 |
1/1✓ Branch 20 → 21 taken 4 times.
|
8 | m_commands->enqueueSerializedCommand( |
| 175 |
1/3✓ Branch 19 → 20 taken 4 times.
✗ Branch 54 → 55 not taken.
✗ Branch 54 → 56 not taken.
|
10 | [this, messageId, sequence]() { sendMessageIdSearch(messageId, sequence); }); |
| 176 | 4 | return sequence; | |
| 177 | } | ||
| 178 | |||
| 179 | 6 | m_pendingSearchUids.clear(); | |
| 180 | 6 | m_activeMessageIdSearch = {sequence, m_selectedFolder, m_selectedUidValidity}; | |
| 181 | // RFC 3501: UID SEARCH HEADER Message-ID <message-id> | ||
| 182 |
2/2✓ Branch 35 → 36 taken 6 times.
✓ Branch 36 → 37 taken 6 times.
|
12 | m_commands->sendCommand("SEARCH", |
| 183 | 12 | QStringLiteral("UID SEARCH HEADER Message-ID %1") | |
| 184 |
2/2✓ Branch 33 → 34 taken 6 times.
✓ Branch 34 → 35 taken 6 times.
|
18 | .arg(quoteImapString(messageId))); |
| 185 | 6 | return sequence; | |
| 186 | 6 | } | |
| 187 | |||
| 188 | 48 | void ImapService::handleTaggedSearch(const TaggedResponse &response) { | |
| 189 | 48 | const MessageIdSearch identified = std::exchange(m_activeMessageIdSearch, {}); | |
| 190 |
2/2✓ Branch 5 → 6 taken 46 times.
✓ Branch 5 → 23 taken 2 times.
|
48 | if (response.status == "OK") { |
| 191 |
6/6✓ Branch 6 → 7 taken 46 times.
✓ Branch 10 → 11 taken 46 times.
✓ Branch 11 → 12 taken 46 times.
✓ Branch 13 → 14 taken 46 times.
✓ Branch 18 → 8 taken 46 times.
✓ Branch 18 → 19 taken 46 times.
|
92 | qCInfo(lcImap) << "SEARCH complete:" << m_pendingSearchUids.size() |
| 192 |
1/1✓ Branch 14 → 15 taken 46 times.
|
46 | << "UIDs"; |
| 193 |
2/2✓ Branch 19 → 20 taken 6 times.
✓ Branch 19 → 21 taken 40 times.
|
46 | if (identified.sequence) |
| 194 | 6 | emit messageIdSearchFinished(identified.sequence, identified.folder, | |
| 195 |
1/1✓ Branch 20 → 21 taken 6 times.
|
6 | identified.uidValidity, m_pendingSearchUids, true); |
| 196 |
1/1✓ Branch 21 → 22 taken 46 times.
|
46 | emit searchResultReceived(m_pendingSearchUids); |
| 197 |
1/1✓ Branch 22 → 42 taken 46 times.
|
46 | m_pendingSearchUids.clear(); |
| 198 | } else { | ||
| 199 |
6/6✓ Branch 23 → 24 taken 2 times.
✓ Branch 27 → 28 taken 2 times.
✓ Branch 28 → 29 taken 2 times.
✓ Branch 29 → 30 taken 2 times.
✓ Branch 33 → 25 taken 2 times.
✓ Branch 33 → 34 taken 2 times.
|
4 | qCWarning(lcImap) << "SEARCH failed:" << response.message; |
| 200 | // S106-024: say that the server refused — the empty result below alone | ||
| 201 | // looked exactly like "no matches". | ||
| 202 |
1/1✓ Branch 34 → 35 taken 2 times.
|
2 | emit searchFailed(response.message); |
| 203 | // Still emit a (empty) result so multi-folder search flows advance | ||
| 204 | // instead of waiting forever for a result that will never arrive. | ||
| 205 |
1/1✓ Branch 35 → 36 taken 2 times.
|
2 | m_pendingSearchUids.clear(); |
| 206 |
1/2✗ Branch 36 → 37 not taken.
✓ Branch 36 → 41 taken 2 times.
|
2 | if (identified.sequence) |
| 207 | ✗ | emit messageIdSearchFinished(identified.sequence, identified.folder, | |
| 208 | ✗ | identified.uidValidity, {}, false); | |
| 209 |
1/1✓ Branch 41 → 42 taken 2 times.
|
2 | emit searchResultReceived(m_pendingSearchUids); |
| 210 | } | ||
| 211 | // Drain deferred commands (e.g. body fetch queued during SEARCH) | ||
| 212 |
2/2✓ Branch 45 → 46 taken 2 times.
✓ Branch 45 → 48 taken 46 times.
|
48 | if (!m_commands->deferredCommands().isEmpty()) { |
| 213 |
1/1✓ Branch 47 → 48 taken 2 times.
|
2 | m_commands->runNextDeferredCommand(); |
| 214 | } | ||
| 215 | 48 | } | |
| 216 |