service/ImapBodyFetch.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "ImapBodyFetch.h" | ||
| 2 | |||
| 3 | #include "ImapCommandTable.h" | ||
| 4 | #include "ImapPushSession.h" | ||
| 5 | |||
| 6 | #include <QLoggingCategory> | ||
| 7 | #include <QSettings> | ||
| 8 | #include <QTimer> | ||
| 9 | |||
| 10 | #include <algorithm> | ||
| 11 | #include <utility> | ||
| 12 | |||
| 13 | Q_DECLARE_LOGGING_CATEGORY(lcImap) | ||
| 14 | |||
| 15 | 1401 | ImapBodyFetch::ImapBodyFetch(QObject *owner, ImapCommandTable &commands, | |
| 16 | ImapPushSession &push, const State &state, | ||
| 17 | 1401 | SelectedMailbox mailbox, Deps deps) | |
| 18 | 1401 | : m_owner(owner), m_commands(commands), m_push(push), m_state(state), | |
| 19 | 1401 | m_mailbox(mailbox), m_deps(std::move(deps)) {} | |
| 20 | |||
| 21 | 4 | void ImapBodyFetch::fetchBody(qint64 uid) { | |
| 22 | 4 | const qint64 acceptedBytes = configuredMaxMessageBytes(); | |
| 23 | 4 | fetchBodyWithLimits(0, uid, acceptedBytes, acceptedBytes + 1); | |
| 24 | 4 | } | |
| 25 | |||
| 26 | 34 | void ImapBodyFetch::fetchBody(qint64 uid, qint64 maxBytes) { | |
| 27 |
2/2✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 5 taken 33 times.
|
34 | if (maxBytes <= 0) { |
| 28 | 1 | fetchBody(uid); | |
| 29 | 1 | return; | |
| 30 | } | ||
| 31 | 33 | const qint64 acceptedBytes = qMin(maxBytes, ImapService::MAX_LITERAL_SIZE); | |
| 32 | 33 | fetchBodyWithLimits(0, uid, acceptedBytes, acceptedBytes); | |
| 33 | } | ||
| 34 | |||
| 35 | 3 | void ImapBodyFetch::requestBody(quint64 requestId, qint64 uid) { | |
| 36 | 3 | const qint64 acceptedBytes = configuredMaxMessageBytes(); | |
| 37 | 3 | fetchBodyWithLimits(requestId, uid, acceptedBytes, acceptedBytes + 1); | |
| 38 | 3 | } | |
| 39 | |||
| 40 | 202 | qint64 ImapBodyFetch::configuredMaxMessageBytes() { | |
| 41 |
1/1✓ Branch 2 → 3 taken 202 times.
|
202 | QSettings settings; |
| 42 | 202 | bool ok = false; | |
| 43 | const int configured = | ||
| 44 |
1/1✓ Branch 8 → 9 taken 202 times.
|
606 | settings.value(QStringLiteral("network/maxMessageSizeMiB"), |
| 45 | ImapService::DefaultMaxMessageSizeMiB) | ||
| 46 |
1/1✓ Branch 9 → 10 taken 202 times.
|
202 | .toInt(&ok); |
| 47 | const int sizeMiB = | ||
| 48 |
2/3✓ Branch 14 → 15 taken 202 times.
✗ Branch 14 → 17 not taken.
✓ Branch 15 → 16 taken 202 times.
|
202 | ok ? qBound(ImapService::MinMaxMessageSizeMiB, configured, ImapService::MaxMaxMessageSizeMiB) |
| 49 | 202 | : ImapService::DefaultMaxMessageSizeMiB; | |
| 50 | 202 | return static_cast<qint64>(sizeMiB) * 1024 * 1024; | |
| 51 | 202 | } | |
| 52 | |||
| 53 | 42 | void ImapBodyFetch::fetchBodyWithLimits(quint64 requestId, qint64 uid, | |
| 54 | qint64 acceptedBytes, | ||
| 55 | qint64 requestedBytes) { | ||
| 56 |
2/2✓ Branch 2 → 3 taken 10 times.
✓ Branch 2 → 23 taken 32 times.
|
42 | if (m_state != State::Selected) { |
| 57 |
5/5✓ Branch 3 → 4 taken 10 times.
✓ Branch 7 → 8 taken 10 times.
✓ Branch 8 → 9 taken 10 times.
✓ Branch 12 → 5 taken 10 times.
✓ Branch 12 → 13 taken 10 times.
|
20 | qCWarning(lcImap) << "Cannot fetch body: no folder selected"; |
| 58 |
2/2✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 22 taken 9 times.
|
10 | if (requestId > 0) { |
| 59 | 2 | m_deps.bodyRequestCompleted( | |
| 60 |
1/7✓ Branch 18 → 19 taken 1 time.
✗ Branch 49 → 50 not taken.
✗ Branch 49 → 51 not taken.
✗ Branch 55 → 56 not taken.
✗ Branch 55 → 57 not taken.
✗ Branch 58 → 59 not taken.
✗ Branch 58 → 60 not taken.
|
2 | BodyResult{requestId, m_mailbox.selectedFolder, uid, BodyOutcome::Failed, {}, |
| 61 | 1 | QStringLiteral("No folder selected"), 0}); | |
| 62 | } | ||
| 63 | 10 | return; | |
| 64 | } | ||
| 65 |
1/2✗ Branch 24 → 25 not taken.
✓ Branch 24 → 27 taken 32 times.
|
32 | if (!m_push.probeBeforeUse("FETCH_BODY")) { |
| 66 | ✗ | completeUndispatched(requestId, m_mailbox.selectedFolder, uid); | |
| 67 | ✗ | return; | |
| 68 | } | ||
| 69 |
2/2✓ Branch 28 → 29 taken 1 time.
✓ Branch 28 → 33 taken 31 times.
|
32 | if (m_commands.hasStatefulCommandInFlight()) { |
| 70 |
1/1✓ Branch 30 → 31 taken 1 time.
|
1 | park(requestId, m_mailbox.selectedFolder, uid, |
| 71 |
1/1✓ Branch 29 → 30 taken 1 time.
|
1 | [this, requestId, uid, acceptedBytes, requestedBytes]() { |
| 72 | 1 | fetchBodyWithLimits(requestId, uid, acceptedBytes, requestedBytes); | |
| 73 | 1 | }); | |
| 74 | 1 | return; | |
| 75 | } | ||
| 76 | |||
| 77 | 31 | m_activeBodyRequestId = requestId; | |
| 78 | 31 | m_activeBodyRequestFolder = m_mailbox.selectedFolder; | |
| 79 | 31 | m_activeBodyFetchLimit = acceptedBytes; | |
| 80 | 31 | m_activeBodyFetchRequestBytes = requestedBytes; | |
| 81 | 31 | m_activeBodyFetchUid = uid; | |
| 82 | 31 | m_bodyFetchRequiresSelect = false; | |
| 83 | 31 | m_bodyFetchRetried = false; | |
| 84 |
3/3✓ Branch 34 → 35 taken 31 times.
✓ Branch 35 → 36 taken 31 times.
✓ Branch 36 → 37 taken 31 times.
|
31 | m_commands.sendCommand("FETCH_BODY", buildFetchBodyCommand(uid, requestedBytes)); |
| 85 | } | ||
| 86 | |||
| 87 | // T-205: Pipeline SELECT + FETCH BODY — sends both commands back-to-back | ||
| 88 | // so the body connection doesn't need to wait for SELECT OK before fetching. | ||
| 89 | 3 | void ImapBodyFetch::selectAndFetchBody(const QString &folderPath, qint64 uid) { | |
| 90 | 3 | requestBodyInFolder(0, folderPath, uid); | |
| 91 | 3 | } | |
| 92 | |||
| 93 | 19 | void ImapBodyFetch::requestBodyInFolder(quint64 requestId, | |
| 94 | const QString &folderPath, qint64 uid) { | ||
| 95 |
4/4✓ Branch 2 → 3 taken 14 times.
✓ Branch 2 → 24 taken 5 times.
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 24 taken 12 times.
|
19 | if (m_state != State::Authenticated && m_state != State::Selected) { |
| 96 |
5/5✓ Branch 4 → 5 taken 2 times.
✓ Branch 8 → 9 taken 2 times.
✓ Branch 9 → 10 taken 2 times.
✓ Branch 13 → 6 taken 2 times.
✓ Branch 13 → 14 taken 2 times.
|
4 | qCWarning(lcImap) << "Cannot selectAndFetchBody: not authenticated"; |
| 97 |
2/2✓ Branch 14 → 15 taken 1 time.
✓ Branch 14 → 23 taken 1 time.
|
2 | if (requestId > 0) { |
| 98 | 2 | m_deps.bodyRequestCompleted( | |
| 99 |
1/7✓ Branch 19 → 20 taken 1 time.
✗ Branch 115 → 116 not taken.
✗ Branch 115 → 117 not taken.
✗ Branch 121 → 122 not taken.
✗ Branch 121 → 123 not taken.
✗ Branch 124 → 125 not taken.
✗ Branch 124 → 126 not taken.
|
2 | BodyResult{requestId, folderPath, uid, BodyOutcome::Failed, {}, |
| 100 | 1 | QStringLiteral("Not authenticated"), 0}); | |
| 101 | } | ||
| 102 | 12 | return; | |
| 103 | } | ||
| 104 |
3/3✓ Branch 24 → 25 taken 17 times.
✓ Branch 25 → 26 taken 1 time.
✓ Branch 25 → 28 taken 16 times.
|
17 | if (!m_push.probeBeforeUse("FETCH_BODY")) { |
| 105 |
1/1✓ Branch 26 → 27 taken 1 time.
|
1 | completeUndispatched(requestId, folderPath, uid); |
| 106 | 1 | return; | |
| 107 | } | ||
| 108 |
3/3✓ Branch 28 → 29 taken 16 times.
✓ Branch 29 → 30 taken 3 times.
✓ Branch 29 → 36 taken 13 times.
|
16 | if (m_commands.hasStatefulCommandInFlight()) { |
| 109 |
2/4✓ Branch 31 → 32 taken 3 times.
✓ Branch 32 → 33 taken 3 times.
✗ Branch 132 → 133 not taken.
✗ Branch 132 → 134 not taken.
|
3 | park(requestId, folderPath, uid, [this, requestId, folderPath, uid]() { |
| 110 | 1 | requestBodyInFolder(requestId, folderPath, uid); | |
| 111 | 1 | }); | |
| 112 | 3 | return; | |
| 113 | } | ||
| 114 | |||
| 115 | 13 | m_activeBodyRequestId = requestId; | |
| 116 | 13 | m_activeBodyRequestFolder = folderPath; | |
| 117 |
1/1✓ Branch 37 → 38 taken 13 times.
|
13 | const qint64 acceptedBytes = configuredMaxMessageBytes(); |
| 118 | 13 | const qint64 requestedBytes = acceptedBytes + 1; | |
| 119 | 13 | m_activeBodyFetchLimit = acceptedBytes; | |
| 120 | 13 | m_activeBodyFetchRequestBytes = requestedBytes; | |
| 121 | 13 | m_activeBodyFetchUid = uid; | |
| 122 | 13 | m_bodyFetchRetried = false; | |
| 123 | |||
| 124 | // P90.9: reuse an already selected mailbox. One initial SELECT per | ||
| 125 | // folder per connection; folder switches on this connection (moves, | ||
| 126 | // other folders) or reconnects invalidate m_mailbox.selectedFolder and force a | ||
| 127 | // fresh SELECT. The mailbox-selected notification still fires so the | ||
| 128 | // transport trace keeps its selection phase for every request. | ||
| 129 |
6/6✓ Branch 38 → 39 taken 8 times.
✓ Branch 38 → 42 taken 5 times.
✓ Branch 40 → 41 taken 6 times.
✓ Branch 40 → 42 taken 2 times.
✓ Branch 43 → 44 taken 6 times.
✓ Branch 43 → 68 taken 7 times.
|
13 | if (m_state == State::Selected && m_mailbox.selectedFolder == folderPath) { |
| 130 | 6 | m_bodyFetchRequiresSelect = false; | |
| 131 |
1/1✓ Branch 44 → 45 taken 6 times.
|
6 | m_deps.bodyMailboxSelected(requestId, true); |
| 132 |
3/3✓ Branch 45 → 46 taken 6 times.
✓ Branch 46 → 47 taken 6 times.
✓ Branch 47 → 48 taken 6 times.
|
6 | m_commands.sendCommand("FETCH_BODY", buildFetchBodyCommand(uid, requestedBytes)); |
| 133 |
6/6✓ Branch 51 → 52 taken 6 times.
✓ Branch 55 → 56 taken 6 times.
✓ Branch 56 → 57 taken 6 times.
✓ Branch 57 → 58 taken 6 times.
✓ Branch 66 → 53 taken 6 times.
✓ Branch 66 → 67 taken 6 times.
|
12 | qCInfo(lcImap) << "P90.9: FETCH_BODY on selected mailbox" << folderPath |
| 134 |
5/5✓ Branch 58 → 59 taken 6 times.
✓ Branch 59 → 60 taken 6 times.
✓ Branch 60 → 61 taken 6 times.
✓ Branch 61 → 62 taken 6 times.
✓ Branch 62 → 63 taken 6 times.
|
6 | << "UID" << uid << "limit" << acceptedBytes << "bytes"; |
| 135 | 6 | return; | |
| 136 | } | ||
| 137 | |||
| 138 | 7 | ImapPendingCommand select; // Bug 34: selected only on SELECT OK | |
| 139 | 7 | select.selectFolder = folderPath; | |
| 140 | 7 | m_mailbox.messageCount = 0; | |
| 141 | 7 | m_mailbox.uidValidity = 0; | |
| 142 | 7 | m_mailbox.highestModseq = 0; | |
| 143 | 7 | m_bodyFetchRequiresSelect = true; | |
| 144 | |||
| 145 | // Send both commands back-to-back (pipelining) | ||
| 146 |
5/5✓ Branch 71 → 72 taken 7 times.
✓ Branch 73 → 74 taken 7 times.
✓ Branch 74 → 75 taken 7 times.
✓ Branch 75 → 76 taken 7 times.
✓ Branch 76 → 77 taken 7 times.
|
14 | m_commands.sendCommand("SELECT", QString("SELECT %1").arg(ImapService::quoteMailboxName(folderPath)), |
| 147 | 7 | std::move(select)); | |
| 148 |
3/3✓ Branch 82 → 83 taken 7 times.
✓ Branch 83 → 84 taken 7 times.
✓ Branch 84 → 85 taken 7 times.
|
7 | m_commands.sendCommand("FETCH_BODY", buildFetchBodyCommand(uid, requestedBytes)); |
| 149 | |||
| 150 |
6/6✓ Branch 88 → 89 taken 7 times.
✓ Branch 92 → 93 taken 7 times.
✓ Branch 93 → 94 taken 7 times.
✓ Branch 94 → 95 taken 7 times.
✓ Branch 103 → 90 taken 7 times.
✓ Branch 103 → 104 taken 7 times.
|
14 | qCInfo(lcImap) << "T-205: Pipelined SELECT + FETCH_BODY for" << folderPath |
| 151 |
5/5✓ Branch 95 → 96 taken 7 times.
✓ Branch 96 → 97 taken 7 times.
✓ Branch 97 → 98 taken 7 times.
✓ Branch 98 → 99 taken 7 times.
✓ Branch 99 → 100 taken 7 times.
|
7 | << "UID" << uid << "limit" << acceptedBytes << "bytes"; |
| 152 | 7 | } | |
| 153 | |||
| 154 | 2 | void ImapBodyFetch::invalidateBodyFetch() { | |
| 155 |
1/1✓ Branch 5 → 6 taken 2 times.
|
4 | m_commands.eraseType(QStringLiteral("FETCH_BODY")); |
| 156 | 2 | m_activeBodyFetchUid = -1; | |
| 157 | 2 | m_activeBodyRequestId = 0; | |
| 158 | 2 | m_activeBodyRequestFolder.clear(); | |
| 159 | 2 | m_activeBodyFetchLimit = 0; | |
| 160 | 2 | m_activeBodyFetchRequestBytes = 0; | |
| 161 | 2 | m_bodyFetchRequiresSelect = false; | |
| 162 | 2 | m_commands.refreshCommandTimeout(); | |
| 163 | 2 | m_commands.refreshCommandDeadline(); | |
| 164 |
1/2✓ Branch 12 → 13 taken 2 times.
✗ Branch 12 → 14 not taken.
|
2 | if (!m_commands.hasTimeoutTrackedCommandInFlight()) |
| 165 | 2 | m_commands.resetCommandAccumulators(); | |
| 166 | 2 | } | |
| 167 | |||
| 168 | 4 | void ImapBodyFetch::park(quint64 requestId, const QString &folderPath, qint64 uid, | |
| 169 | std::function<void()> run) { | ||
| 170 |
2/2✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 8 taken 3 times.
|
4 | if (requestId == 0) { |
| 171 |
1/1✓ Branch 5 → 6 taken 1 time.
|
1 | m_commands.enqueueSerializedCommand(std::move(run)); |
| 172 | 1 | return; | |
| 173 | } | ||
| 174 | 3 | const quint64 ticket = ++m_nextParkTicket; | |
| 175 | 3 | m_parkedRequests.append({ticket, requestId, folderPath, uid}); | |
| 176 |
2/2✓ Branch 13 → 14 taken 3 times.
✓ Branch 14 → 15 taken 3 times.
|
3 | m_commands.enqueueSerializedCommand([this, ticket, run = std::move(run)]() { |
| 177 |
3/3✓ Branch 2 → 3 taken 1 time.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 4 → 5 taken 1 time.
|
1 | const auto it = std::find_if(m_parkedRequests.begin(), m_parkedRequests.end(), |
| 178 | 1 | [ticket](const ParkedRequest &parked) { return parked.ticket == ticket; }); | |
| 179 |
2/3✓ Branch 5 → 6 taken 1 time.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 1 time.
|
1 | if (it == m_parkedRequests.end()) |
| 180 | ✗ | return; // already completed by cancelParkedRequests() | |
| 181 |
1/1✓ Branch 10 → 11 taken 1 time.
|
1 | m_parkedRequests.erase(it); |
| 182 |
1/1✓ Branch 11 → 12 taken 1 time.
|
1 | run(); |
| 183 | }); | ||
| 184 | 3 | } | |
| 185 | |||
| 186 | 2482 | void ImapBodyFetch::cancelParkedRequests() { | |
| 187 | 2482 | const QList<ParkedRequest> parked = std::exchange(m_parkedRequests, {}); | |
| 188 |
2/2✓ Branch 11 → 7 taken 2 times.
✓ Branch 11 → 12 taken 2482 times.
|
2484 | for (const ParkedRequest &request : parked) |
| 189 |
1/1✓ Branch 8 → 9 taken 2 times.
|
2 | completeUndispatched(request.requestId, request.folderPath, request.uid); |
| 190 | 2482 | } | |
| 191 | |||
| 192 | // S92.5.2: the connection failed before the FETCH was written. | ||
| 193 | 3 | void ImapBodyFetch::completeUndispatched(quint64 requestId, const QString &folderPath, | |
| 194 | qint64 uid) { | ||
| 195 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 3 times.
|
3 | if (requestId == 0) |
| 196 | ✗ | return; | |
| 197 | 3 | BodyResult result{requestId, folderPath, uid, BodyOutcome::Cancelled, {}, | |
| 198 | 6 | QStringLiteral("Connection lost before dispatch"), 0}; | |
| 199 | 3 | result.dispatched = false; | |
| 200 |
1/1✓ Branch 9 → 10 taken 3 times.
|
3 | m_deps.bodyRequestCompleted(result); |
| 201 | 3 | } | |
| 202 | |||
| 203 | 2455 | void ImapBodyFetch::completeBodyRequest(BodyOutcome outcome, | |
| 204 | QByteArray bytes, | ||
| 205 | const QString &error, qint64 maxBytes) { | ||
| 206 |
2/2✓ Branch 2 → 3 taken 2438 times.
✓ Branch 2 → 4 taken 17 times.
|
2455 | if (m_activeBodyRequestId == 0) |
| 207 | 2438 | return; | |
| 208 | |||
| 209 | 17 | BodyResult result; | |
| 210 | 17 | result.requestId = m_activeBodyRequestId; | |
| 211 | 17 | result.folderPath = m_activeBodyRequestFolder; | |
| 212 | 17 | result.uid = m_activeBodyFetchUid; | |
| 213 | 17 | result.outcome = outcome; | |
| 214 | 17 | result.bytes = std::move(bytes); | |
| 215 | 17 | result.error = error; | |
| 216 | 17 | result.maxBytes = maxBytes; | |
| 217 | 17 | m_activeBodyRequestId = 0; | |
| 218 | 17 | m_activeBodyRequestFolder.clear(); | |
| 219 | #ifdef MAILJD_E2E_TESTING | ||
| 220 |
4/4✓ Branch 9 → 10 taken 2 times.
✓ Branch 9 → 13 taken 15 times.
✓ Branch 15 → 16 taken 16 times.
✓ Branch 15 → 33 taken 1 time.
|
19 | if (m_e2eBodyCompletionUid <= 0 || |
| 221 |
3/4✓ Branch 10 → 11 taken 2 times.
✗ Branch 10 → 14 not taken.
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 14 taken 1 time.
|
2 | (result.uid == m_e2eBodyCompletionUid && result.folderPath == m_e2eBodyCompletionFolder)) { |
| 222 | 16 | m_e2eBodyCompletionUid = 0; | |
| 223 | 16 | m_e2eBodyCompletionFolder.clear(); | |
| 224 | 16 | const int delayMs = std::exchange(m_e2eNextBodyCompletionDelayMs, 0); | |
| 225 |
2/2✓ Branch 19 → 20 taken 1 time.
✓ Branch 19 → 28 taken 15 times.
|
16 | if (std::exchange(m_e2eFailNextBodyCompletion, false)) { |
| 226 | 1 | result.outcome = BodyOutcome::Failed; | |
| 227 |
1/1✓ Branch 20 → 21 taken 1 time.
|
1 | result.bytes.clear(); |
| 228 | 1 | result.error = QStringLiteral("Body fetch failed"); | |
| 229 | } | ||
| 230 |
1/2✗ Branch 28 → 29 not taken.
✓ Branch 28 → 33 taken 16 times.
|
16 | if (delayMs > 0) { |
| 231 | ✗ | QTimer::singleShot(delayMs, m_owner, | |
| 232 | ✗ | [this, result]() { m_deps.bodyRequestCompleted(result); }); | |
| 233 | ✗ | return; | |
| 234 | } | ||
| 235 | } | ||
| 236 | #endif | ||
| 237 |
1/2✗ Branch 9 → 10 not taken.
✓ Branch 33 → 34 taken 17 times.
|
17 | m_deps.bodyRequestCompleted(result); |
| 238 | 17 | } | |
| 239 | |||
| 240 | #ifdef MAILJD_E2E_TESTING | ||
| 241 | 1 | void ImapBodyFetch::setNextBodyCompletionForE2e(int delayMs, bool fail, | |
| 242 | const QString &folderPath, qint64 uid) { | ||
| 243 | 1 | m_e2eBodyCompletionFolder = folderPath; | |
| 244 | 1 | m_e2eBodyCompletionUid = uid; | |
| 245 |
1/1✓ Branch 3 → 4 taken 1 time.
|
1 | m_e2eNextBodyCompletionDelayMs = qBound(0, delayMs, 5000); |
| 246 | 1 | m_e2eFailNextBodyCompletion = fail; | |
| 247 | 1 | } | |
| 248 | #endif | ||
| 249 | |||
| 250 | 51 | QString ImapBodyFetch::buildFetchBodyCommand(qint64 uid, qint64 maxBytes) { | |
| 251 |
2/2✓ Branch 2 → 3 taken 49 times.
✓ Branch 2 → 14 taken 2 times.
|
51 | if (maxBytes > 0) { |
| 252 | 98 | return QStringLiteral("UID FETCH %1 (UID BODY.PEEK[]<0.%2>)") | |
| 253 |
1/1✓ Branch 7 → 8 taken 49 times.
|
147 | .arg(uid) |
| 254 |
1/1✓ Branch 9 → 10 taken 49 times.
|
49 | .arg(maxBytes); |
| 255 | } | ||
| 256 |
1/1✓ Branch 18 → 19 taken 2 times.
|
4 | return QStringLiteral("UID FETCH %1 (UID BODY.PEEK[])").arg(uid); |
| 257 | } | ||
| 258 | 42 | void ImapBodyFetch::handleTaggedFetchBody(const TaggedResponse &response) { | |
| 259 | // A UID that arrived after this connection's last mailbox sync is | ||
| 260 | // resolved against the old view: Dovecot reports the new EXISTS with | ||
| 261 | // the response but answers OK without data. The view now includes the | ||
| 262 | // message, so an empty OK is retried once before it counts as failed. | ||
| 263 |
4/4✓ Branch 4 → 5 taken 4 times.
✓ Branch 4 → 10 taken 38 times.
✓ Branch 5 → 6 taken 3 times.
✓ Branch 5 → 10 taken 1 time.
|
84 | if (response.status == "OK" && m_activeBodyRequestId > 0 && !m_bodyFetchRetried && |
| 264 |
5/8✓ Branch 3 → 4 taken 42 times.
✗ Branch 3 → 10 not taken.
✓ Branch 6 → 7 taken 3 times.
✗ Branch 6 → 10 not taken.
✓ Branch 8 → 9 taken 3 times.
✗ Branch 8 → 10 not taken.
✓ Branch 11 → 12 taken 3 times.
✓ Branch 11 → 33 taken 39 times.
|
84 | m_activeBodyFetchUid > 0 && m_mailbox.selectedFolder == m_activeBodyRequestFolder) { |
| 265 | 3 | m_bodyFetchRetried = true; | |
| 266 |
6/6✓ Branch 12 → 13 taken 3 times.
✓ Branch 16 → 17 taken 3 times.
✓ Branch 17 → 18 taken 3 times.
✓ Branch 18 → 19 taken 3 times.
✓ Branch 25 → 14 taken 3 times.
✓ Branch 25 → 26 taken 3 times.
|
6 | qCInfo(lcImap) << "Empty body response for UID" << m_activeBodyFetchUid |
| 267 |
3/3✓ Branch 19 → 20 taken 3 times.
✓ Branch 20 → 21 taken 3 times.
✓ Branch 21 → 22 taken 3 times.
|
3 | << "in" << m_mailbox.selectedFolder << "- fetching once more"; |
| 268 |
3/3✓ Branch 26 → 27 taken 3 times.
✓ Branch 27 → 28 taken 3 times.
✓ Branch 28 → 29 taken 3 times.
|
3 | m_commands.sendCommand("FETCH_BODY", buildFetchBodyCommand(m_activeBodyFetchUid, |
| 269 | m_activeBodyFetchRequestBytes)); | ||
| 270 | 3 | return; | |
| 271 | } | ||
| 272 |
1/2✗ Branch 34 → 35 not taken.
✓ Branch 34 → 44 taken 39 times.
|
39 | if (response.status != "OK") { |
| 273 | ✗ | completeBodyRequest(BodyOutcome::Failed, {}, | |
| 274 | ✗ | QStringLiteral("Body fetch failed")); | |
| 275 |
2/2✓ Branch 44 → 45 taken 1 time.
✓ Branch 44 → 54 taken 38 times.
|
39 | } else if (m_activeBodyRequestId > 0) { |
| 276 |
1/1✓ Branch 49 → 50 taken 1 time.
|
1 | completeBodyRequest(BodyOutcome::Failed, {}, |
| 277 | 2 | QStringLiteral("Body response was empty")); | |
| 278 | } | ||
| 279 | 39 | m_activeBodyFetchUid = -1; | |
| 280 | 39 | m_activeBodyFetchLimit = 0; | |
| 281 | 39 | m_activeBodyFetchRequestBytes = 0; | |
| 282 | 39 | m_bodyFetchRequiresSelect = false; | |
| 283 |
1/2✗ Branch 55 → 56 not taken.
✓ Branch 55 → 60 taken 39 times.
|
39 | if (response.status != "OK") { |
| 284 | ✗ | m_deps.errorOccurred("FETCH body failed: " + response.message); | |
| 285 | } | ||
| 286 | // Body is emitted in handleUntagged when the data arrives | ||
| 287 | // Drain deferred commands (e.g. next body fetch in search mode) | ||
| 288 |
2/2✓ Branch 62 → 63 taken 1 time.
✓ Branch 62 → 64 taken 38 times.
|
39 | if (!m_commands.deferredCommands().isEmpty()) { |
| 289 | 1 | m_commands.runNextDeferredCommand(); | |
| 290 | } else { | ||
| 291 | // No deferred commands — restart IDLE if possible | ||
| 292 | // (STORE handler restarts IDLE for markSeen; this covers already-seen mails) | ||
| 293 | 38 | m_push.restartPush(); | |
| 294 | } | ||
| 295 | } | ||
| 296 | |||
| 297 | 2482 | void ImapBodyFetch::resetActiveRequest() { | |
| 298 | 2482 | m_activeBodyFetchUid = -1; | |
| 299 | 2482 | m_activeBodyRequestId = 0; | |
| 300 | 2482 | m_activeBodyRequestFolder.clear(); | |
| 301 | 2482 | m_activeBodyFetchLimit = 0; | |
| 302 | 2482 | m_activeBodyFetchRequestBytes = 0; | |
| 303 | 2482 | m_bodyFetchRequiresSelect = false; | |
| 304 | 2482 | } | |
| 305 |