service/MailBodyWriteService.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "MailBodyWriteService.h" | ||
| 2 | #include "util/Utf8Digest.h" | ||
| 3 | #include "data/MailCache.h" | ||
| 4 | #include <QCoreApplication> | ||
| 5 | #include <QFutureWatcher> | ||
| 6 | #include <QFileInfo> | ||
| 7 | #include <QPointer> | ||
| 8 | #include <QPromise> | ||
| 9 | #include <QtConcurrent> | ||
| 10 | #include <algorithm> | ||
| 11 | #include <utility> | ||
| 12 | |||
| 13 |
2/2✓ Branch 5 → 6 taken 56 times.
✓ Branch 6 → 7 taken 56 times.
|
56 | MailBodyWriteService::MailBodyWriteService(QObject *parent) : QObject(parent) { |
| 14 |
1/1✓ Branch 7 → 8 taken 56 times.
|
56 | m_pool.setMaxThreadCount(1); |
| 15 |
1/1✓ Branch 8 → 9 taken 56 times.
|
56 | m_pool.setExpiryTimeout(1000); |
| 16 |
1/1✓ Branch 10 → 11 taken 56 times.
|
56 | connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, |
| 17 | 57 | this, [this] { stop(); }); | |
| 18 | 56 | } | |
| 19 | |||
| 20 | 91 | MailBodyWriteService::~MailBodyWriteService() { stop(); } | |
| 21 | |||
| 22 | namespace { | ||
| 23 | QPointer<MailBodyWriteService> sharedMailBodyWriteService; | ||
| 24 | // S106-120: runs first in ~QCoreApplication, while Qt's SQL and plugin state | ||
| 25 | // still exists — aboutToQuit misses a process that ends without the event | ||
| 26 | // loop, and the application-child deletion came only after that state was | ||
| 27 | // gone, with a worker possibly inside QSqlDatabase::open (the crash fixed | ||
| 28 | // for MailSelectionMutationService). | ||
| 29 | } // namespace | ||
| 30 | |||
| 31 | 34 | void MailBodyWriteService::stopSharedBeforeTeardown() { | |
| 32 |
1/2✓ Branch 3 → 4 taken 34 times.
✗ Branch 3 → 6 not taken.
|
34 | if (sharedMailBodyWriteService) sharedMailBodyWriteService->stop(); |
| 33 | 34 | } | |
| 34 | |||
| 35 | 3773 | MailBodyWriteService *MailBodyWriteService::shared() { | |
| 36 |
2/2✓ Branch 3 → 4 taken 34 times.
✓ Branch 3 → 11 taken 3739 times.
|
3773 | if (!sharedMailBodyWriteService) { |
| 37 |
3/6✓ Branch 6 → 7 taken 34 times.
✓ Branch 7 → 8 taken 34 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 34 times.
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 15 not taken.
|
34 | sharedMailBodyWriteService = new MailBodyWriteService(QCoreApplication::instance()); |
| 38 | 34 | qAddPostRoutine(stopSharedBeforeTeardown); | |
| 39 | } | ||
| 40 | 3773 | return sharedMailBodyWriteService; | |
| 41 | } | ||
| 42 | |||
| 43 | 94 | void MailBodyWriteService::stop() { | |
| 44 | 94 | m_stopping = true; | |
| 45 | { | ||
| 46 | 94 | QMutexLocker lock(&m_fencesMutex); | |
| 47 |
6/6✓ Branch 3 → 4 taken 94 times.
✓ Branch 4 → 5 taken 94 times.
✓ Branch 9 → 10 taken 16 times.
✓ Branch 9 → 12 taken 69 times.
✓ Branch 15 → 6 taken 85 times.
✓ Branch 15 → 16 taken 94 times.
|
179 | for (const auto &weak : m_fences) if (const auto fence = weak.lock()) fence->cancel(); |
| 48 | 94 | } | |
| 49 | // Every admitted write is already on this pool; finishing queued work never | ||
| 50 | // depends on a GUI callback starting the next task. | ||
| 51 |
1/1✓ Branch 18 → 19 taken 94 times.
|
94 | m_pool.waitForDone(); |
| 52 | 94 | } | |
| 53 | |||
| 54 | 39 | void MailBodyWriteService::write(QObject *owner, const QString &databasePath, | |
| 55 | const MailId &mailId, quint32 uidValidity, | ||
| 56 | QByteArray raw, MimeMessage parsed, | ||
| 57 | Callback callback, Priority priority) { | ||
| 58 | 39 | qint64 bytes = raw.size(); | |
| 59 | 39 | bytes += 2LL * (parsed.textPlain.size() + parsed.textHtml.size()); | |
| 60 |
4/4✓ Branch 5 → 6 taken 39 times.
✓ Branch 6 → 7 taken 39 times.
✓ Branch 17 → 8 taken 278 times.
✓ Branch 17 → 18 taken 39 times.
|
317 | for (const auto &part : parsed.attachments) |
| 61 | 278 | bytes += part.body.size() + 2LL * (part.filename.size() + part.contentType.size() + part.contentId.size() + | |
| 62 | 278 | part.charset.size() + part.transferEncoding.size()); | |
| 63 | 39 | MailBodyWriteResult rejected; | |
| 64 |
4/4✓ Branch 19 → 20 taken 37 times.
✓ Branch 19 → 26 taken 1 time.
✓ Branch 21 → 22 taken 36 times.
✓ Branch 21 → 26 taken 1 time.
|
75 | if (parsed.limitExceeded || bytes > MaximumRequestBytes || |
| 65 |
8/8✓ Branch 18 → 19 taken 38 times.
✓ Branch 18 → 26 taken 1 time.
✓ Branch 23 → 24 taken 35 times.
✓ Branch 23 → 26 taken 1 time.
✓ Branch 25 → 26 taken 1 time.
✓ Branch 25 → 27 taken 34 times.
✓ Branch 28 → 29 taken 5 times.
✓ Branch 28 → 40 taken 34 times.
|
149 | parsed.attachments.size() > 256 || databasePath.size() > 4096 || |
| 66 | 35 | mailId.accountId.size() > 4096) { | |
| 67 | 5 | rejected.outcome = MailBodyWriteResult::Outcome::Limited; | |
| 68 | 5 | rejected.error = QStringLiteral("Prepared payload exceeds writer limits"); | |
| 69 |
1/1✓ Branch 37 → 38 taken 5 times.
|
5 | callback(std::move(rejected)); |
| 70 | 5 | return; | |
| 71 | } | ||
| 72 | // Speculation may use spare capacity, but cannot consume the final body | ||
| 73 | // request slot or the bytes needed by one ordinary foreground payload. | ||
| 74 | 34 | const int requestLimit = priority == Priority::Speculative | |
| 75 |
1/2✗ Branch 40 → 41 not taken.
✓ Branch 40 → 42 taken 34 times.
|
34 | ? MaximumRequests - 1 : MaximumRequests; |
| 76 | 34 | const qint64 byteLimit = priority == Priority::Speculative | |
| 77 |
1/2✗ Branch 43 → 44 not taken.
✓ Branch 43 → 45 taken 34 times.
|
34 | ? MaximumQueuedBytes - ForegroundReservedBytes : MaximumQueuedBytes; |
| 78 |
4/4✓ Branch 46 → 47 taken 33 times.
✓ Branch 46 → 49 taken 1 time.
✓ Branch 47 → 48 taken 32 times.
✓ Branch 47 → 49 taken 1 time.
|
34 | if (m_stopping || m_pendingCount >= requestLimit || |
| 79 |
2/2✓ Branch 48 → 49 taken 1 time.
✓ Branch 48 → 60 taken 31 times.
|
32 | bytes > byteLimit - m_pendingBytes) { |
| 80 | 3 | rejected.outcome = MailBodyWriteResult::Outcome::Busy; | |
| 81 | 3 | rejected.error = QStringLiteral("Payload writer is busy or stopping"); | |
| 82 |
1/1✓ Branch 57 → 58 taken 3 times.
|
3 | callback(std::move(rejected)); |
| 83 | 3 | return; | |
| 84 | } | ||
| 85 | 31 | ++m_pendingCount; | |
| 86 | 31 | m_pendingBytes += bytes; | |
| 87 |
1/1✓ Branch 60 → 61 taken 31 times.
|
31 | const QPointer<QObject> consumer(owner); |
| 88 |
3/6✓ Branch 61 → 62 taken 31 times.
✓ Branch 62 → 63 taken 31 times.
✗ Branch 63 → 64 not taken.
✓ Branch 63 → 65 taken 31 times.
✗ Branch 109 → 110 not taken.
✗ Branch 109 → 111 not taken.
|
31 | auto *watcher = new QFutureWatcher<MailBodyWriteResult>(this); |
| 89 | 31 | connect(watcher, &QFutureWatcher<MailBodyWriteResult>::finished, this, | |
| 90 |
1/3✓ Branch 68 → 69 taken 31 times.
✗ Branch 114 → 115 not taken.
✗ Branch 114 → 116 not taken.
|
62 | [this, watcher, consumer, bytes, callback = std::move(callback)]() mutable { |
| 91 | 30 | --m_pendingCount; | |
| 92 | 30 | m_pendingBytes -= bytes; | |
| 93 |
2/2✓ Branch 2 → 3 taken 30 times.
✓ Branch 3 → 4 taken 30 times.
|
30 | auto result = watcher->future().takeResult(); |
| 94 |
1/1✓ Branch 5 → 6 taken 30 times.
|
30 | watcher->deleteLater(); |
| 95 |
6/7✓ Branch 6 → 7 taken 30 times.
✗ Branch 6 → 10 not taken.
✓ Branch 8 → 9 taken 22 times.
✓ Branch 8 → 10 taken 8 times.
✓ Branch 11 → 12 taken 22 times.
✓ Branch 11 → 17 taken 8 times.
✓ Branch 14 → 15 taken 22 times.
|
30 | if (!m_stopping && consumer) callback(std::move(result)); |
| 96 | 30 | }); | |
| 97 | 31 | const auto bodyStoreRevision = m_bodyStoreRevision; | |
| 98 | 31 | auto work = [databasePath, mailId, uidValidity, bodyStoreRevision, | |
| 99 | 62 | raw = std::move(raw), parsed = std::move(parsed)]() mutable { | |
| 100 | 31 | MailBodyWriteResult result; | |
| 101 |
1/1✓ Branch 2 → 3 taken 31 times.
|
31 | MailCache writer; |
| 102 |
3/3✓ Branch 3 → 4 taken 31 times.
✓ Branch 4 → 5 taken 4 times.
✓ Branch 4 → 9 taken 27 times.
|
31 | if (!writer.openExistingWriter(databasePath)) { |
| 103 |
1/1✓ Branch 5 → 6 taken 4 times.
|
4 | result.error = writer.lastError(); |
| 104 | 4 | return result; | |
| 105 | } | ||
| 106 | 27 | MailBody body; | |
| 107 | 27 | body.uid = mailId.uid; | |
| 108 | 27 | body.cacheUidValidity = uidValidity; | |
| 109 | 27 | body.cacheAccountId = mailId.accountId; | |
| 110 | 27 | body.textPlain = std::move(parsed.textPlain); | |
| 111 | 27 | body.textHtml = std::move(parsed.textHtml); | |
| 112 |
2/6✗ Branch 15 → 16 not taken.
✓ Branch 15 → 19 taken 27 times.
✗ Branch 17 → 18 not taken.
✗ Branch 17 → 19 not taken.
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 29 taken 27 times.
|
27 | if (!body.textHtml.isEmpty() && body.textHtml.size() <= 1024 * 1024) { |
| 113 | ✗ | const auto digest = MailJD::sha256Utf8HexUpTo(body.textHtml, 1024 * 1024); | |
| 114 | ✗ | if (digest) body.htmlDigest = *digest; | |
| 115 | ✗ | } | |
| 116 | 27 | body.rawSource = std::move(raw); | |
| 117 | 27 | QList<Attachment> attachments; | |
| 118 | 27 | QList<QByteArray> blobs; | |
| 119 |
1/1✓ Branch 32 → 33 taken 27 times.
|
27 | attachments.reserve(parsed.attachments.size()); |
| 120 |
1/1✓ Branch 34 → 35 taken 27 times.
|
27 | blobs.reserve(parsed.attachments.size()); |
| 121 |
4/4✓ Branch 35 → 36 taken 27 times.
✓ Branch 36 → 37 taken 27 times.
✓ Branch 57 → 38 taken 20 times.
✓ Branch 57 → 58 taken 27 times.
|
47 | for (auto &part : parsed.attachments) { |
| 122 | 20 | Attachment attachment; | |
| 123 | 20 | attachment.filename = std::move(part.filename); | |
| 124 | 20 | attachment.contentType = std::move(part.contentType); | |
| 125 | 20 | attachment.contentId = std::move(part.contentId); | |
| 126 | 20 | attachment.presentation = part.presentation; | |
| 127 | 20 | attachment.size = part.body.size(); | |
| 128 |
1/1✓ Branch 47 → 48 taken 20 times.
|
20 | attachments.append(std::move(attachment)); |
| 129 |
1/1✓ Branch 49 → 50 taken 20 times.
|
20 | blobs.append(std::move(part.body)); |
| 130 |
2/2✓ Branch 50 → 51 taken 3 times.
✓ Branch 50 → 52 taken 17 times.
|
23 | result.hasVisibleAttachment |= part.presentation == AttachmentPresentation::Regular || |
| 131 |
2/2✓ Branch 51 → 52 taken 1 time.
✓ Branch 51 → 53 taken 2 times.
|
3 | part.presentation == AttachmentPresentation::ExplicitAttachmentWithContentId; |
| 132 | 20 | result.inlineImageCount += part.presentation == AttachmentPresentation::InlineCandidate; | |
| 133 | 20 | } | |
| 134 |
3/3✓ Branch 58 → 59 taken 27 times.
✓ Branch 59 → 60 taken 5 times.
✓ Branch 59 → 64 taken 22 times.
|
27 | if (!writer.storeMessageContentForIdentity(mailId, uidValidity, body, attachments, blobs)) { |
| 135 |
1/1✓ Branch 60 → 61 taken 5 times.
|
5 | result.error = writer.lastError(); |
| 136 | 5 | return result; | |
| 137 | } | ||
| 138 | 22 | bodyStoreRevision->fetch_add(1, std::memory_order_release); | |
| 139 | // IDs are assigned by the transaction. Read them on the same worker; raw | ||
| 140 | // MIME and BLOBs are released before publishing the display result. | ||
| 141 |
1/1✓ Branch 68 → 69 taken 22 times.
|
22 | body.attachments = writer.attachments().attachments(mailId.folderId, mailId.uid); |
| 142 | // The display callback needs this header before its first content frame. | ||
| 143 | // Resolve it here on the writer lane instead of querying SQLite on the | ||
| 144 | // GUI thread after the completed payload is handed back. | ||
| 145 |
1/1✓ Branch 71 → 72 taken 22 times.
|
22 | result.header = writer.header(mailId.folderId, mailId.uid); |
| 146 |
2/3✓ Branch 74 → 75 taken 22 times.
✗ Branch 75 → 76 not taken.
✓ Branch 75 → 77 taken 22 times.
|
22 | if (writer.uidValidity(mailId.folderId) != uidValidity) |
| 147 | ✗ | result.header.reset(); | |
| 148 |
1/1✓ Branch 77 → 78 taken 22 times.
|
22 | body.rawSource.clear(); |
| 149 | 22 | result.body = std::move(body); | |
| 150 | 22 | result.outcome = MailBodyWriteResult::Outcome::Stored; | |
| 151 | 22 | return result; | |
| 152 | 62 | }; | |
| 153 | // QThreadPool orders queued tasks by priority. Its single worker preserves | ||
| 154 | // the one-writer contract; a foreground body can pass queued header batches | ||
| 155 | // and speculative bodies, while an active SQLite write runs to completion. | ||
| 156 |
1/1✓ Branch 79 → 80 taken 31 times.
|
31 | auto promise = std::make_shared<QPromise<MailBodyWriteResult>>(); |
| 157 |
1/1✓ Branch 81 → 82 taken 31 times.
|
31 | promise->start(); |
| 158 |
2/2✓ Branch 83 → 84 taken 31 times.
✓ Branch 84 → 85 taken 31 times.
|
31 | watcher->setFuture(promise->future()); |
| 159 |
1/3✓ Branch 89 → 90 taken 31 times.
✗ Branch 124 → 125 not taken.
✗ Branch 124 → 126 not taken.
|
31 | m_pool.start([promise, work = std::move(work)]() mutable { |
| 160 |
2/2✓ Branch 3 → 4 taken 31 times.
✓ Branch 4 → 5 taken 31 times.
|
31 | (void)promise->addResult(work()); |
| 161 | 31 | promise->finish(); | |
| 162 | 31 | }, static_cast<int>(priority)); | |
| 163 | 39 | } | |
| 164 | |||
| 165 | 1404 | MailHeaderStateFence::~MailHeaderStateFence() { QObject::disconnect(destroyed); } | |
| 166 | |||
| 167 | 2722 | void MailHeaderStateFence::amend(qint64 uid, const std::function<void(Change &)> &change) { | |
| 168 |
7/8✓ Branch 2 → 3 taken 2722 times.
✗ Branch 2 → 7 not taken.
✓ Branch 4 → 5 taken 315 times.
✓ Branch 4 → 8 taken 2407 times.
✓ Branch 6 → 7 taken 207 times.
✓ Branch 6 → 8 taken 108 times.
✓ Branch 9 → 10 taken 207 times.
✓ Branch 9 → 11 taken 2515 times.
|
3075 | if (uid <= 0 || (!uids.isEmpty() && !uids.contains(uid))) return; |
| 169 | 2515 | QMutexLocker lock(&mutex); | |
| 170 |
5/6✓ Branch 13 → 14 taken 2163 times.
✓ Branch 13 → 16 taken 352 times.
✗ Branch 15 → 16 not taken.
✓ Branch 15 → 17 taken 2163 times.
✓ Branch 18 → 19 taken 352 times.
✓ Branch 18 → 20 taken 2163 times.
|
2515 | if (m_cancelled.load() || !error.isEmpty()) return; |
| 171 |
6/6✓ Branch 21 → 22 taken 2137 times.
✓ Branch 21 → 25 taken 26 times.
✓ Branch 23 → 24 taken 1 time.
✓ Branch 23 → 25 taken 2136 times.
✓ Branch 26 → 27 taken 1 time.
✓ Branch 26 → 34 taken 2162 times.
|
2163 | if (!changes.contains(uid) && changes.size() >= 2048) { |
| 172 | 1 | error = QStringLiteral("Header state history exceeds its identity budget"); | |
| 173 | 1 | return; | |
| 174 | } | ||
| 175 | 4324 | auto size = [](const Change &value) { | |
| 176 | 4324 | qint64 result = 128; | |
| 177 |
4/4✓ Branch 3 → 4 taken 8 times.
✓ Branch 3 → 13 taken 4316 times.
✓ Branch 11 → 7 taken 1 time.
✓ Branch 11 → 12 taken 8 times.
|
4325 | if (value.labels) for (const auto &label : *value.labels) result += 2LL * label.size() + 32; |
| 178 |
4/4✓ Branch 13 → 14 taken 4324 times.
✓ Branch 18 → 19 taken 4353 times.
✓ Branch 20 → 15 taken 29 times.
✓ Branch 20 → 21 taken 4324 times.
|
4353 | for (auto it = value.labelChanges.cbegin(); it != value.labelChanges.cend(); ++it) |
| 179 | 29 | result += 2LL * it.key().size() + 64; | |
| 180 | 4324 | return result; | |
| 181 | }; | ||
| 182 |
1/1✓ Branch 34 → 35 taken 2162 times.
|
2162 | auto &value = changes[uid]; |
| 183 |
1/1✓ Branch 35 → 36 taken 2162 times.
|
2162 | const auto before = size(value); |
| 184 |
1/1✓ Branch 36 → 37 taken 2162 times.
|
2162 | change(value); |
| 185 |
1/1✓ Branch 37 → 38 taken 2162 times.
|
2162 | bytes += size(value) - before; |
| 186 | // Include fixed ownership for every entry, even one containing only flags. | ||
| 187 |
2/2✓ Branch 39 → 40 taken 1 time.
✓ Branch 39 → 48 taken 2161 times.
|
2162 | if (bytes + 128LL * changes.size() > 2LL * 1024 * 1024) { |
| 188 | 1 | changes.clear(); | |
| 189 | 1 | bytes = 0; | |
| 190 | 1 | error = QStringLiteral("Header state history exceeds its byte budget"); | |
| 191 | } | ||
| 192 | 2515 | } | |
| 193 | |||
| 194 | 874 | QString MailHeaderStateFence::apply(QList<MailHeader> &headers) const { | |
| 195 | 874 | QMutexLocker lock(&mutex); | |
| 196 |
2/2✓ Branch 4 → 5 taken 3 times.
✓ Branch 4 → 10 taken 871 times.
|
877 | if (m_cancelled.load()) return QStringLiteral("Header stream is no longer active"); |
| 197 |
2/2✓ Branch 11 → 12 taken 3 times.
✓ Branch 11 → 13 taken 868 times.
|
871 | if (!error.isEmpty()) return error; |
| 198 |
4/4✓ Branch 13 → 14 taken 868 times.
✓ Branch 59 → 60 taken 36144 times.
✓ Branch 61 → 15 taken 35276 times.
✓ Branch 61 → 62 taken 868 times.
|
36144 | for (auto it = headers.begin(); it != headers.end();) { |
| 199 | 35276 | const auto found = changes.constFind(it->uid); | |
| 200 |
2/2✓ Branch 19 → 20 taken 35267 times.
✓ Branch 19 → 22 taken 9 times.
|
35278 | if (found == changes.cend()) { ++it; continue; } |
| 201 | 9 | const auto &change = found.value(); | |
| 202 |
3/3✓ Branch 23 → 24 taken 2 times.
✓ Branch 23 → 27 taken 7 times.
✓ Branch 25 → 26 taken 2 times.
|
9 | if (change.removed) { it = headers.erase(it); continue; } |
| 203 |
2/2✓ Branch 28 → 29 taken 6 times.
✓ Branch 28 → 32 taken 1 time.
|
7 | if (change.flags) it->flags = *change.flags; |
| 204 |
2/2✓ Branch 33 → 34 taken 1 time.
✓ Branch 33 → 37 taken 6 times.
|
7 | if (change.spam) it->isSpam = *change.spam; |
| 205 |
2/2✓ Branch 38 → 39 taken 1 time.
✓ Branch 38 → 42 taken 6 times.
|
7 | if (change.labels) it->labels = *change.labels; |
| 206 |
4/4✓ Branch 42 → 43 taken 7 times.
✓ Branch 53 → 54 taken 10 times.
✓ Branch 55 → 44 taken 3 times.
✓ Branch 55 → 56 taken 7 times.
|
10 | for (auto label = change.labelChanges.cbegin(); label != change.labelChanges.cend(); ++label) { |
| 207 |
1/1✓ Branch 46 → 47 taken 3 times.
|
3 | it->labels.removeAll(label.key()); |
| 208 |
3/3✓ Branch 48 → 49 taken 2 times.
✓ Branch 48 → 52 taken 1 time.
✓ Branch 51 → 52 taken 2 times.
|
3 | if (label.value()) it->labels.append(label.key()); |
| 209 | } | ||
| 210 | 7 | ++it; | |
| 211 | } | ||
| 212 | 868 | return {}; | |
| 213 | 874 | } | |
| 214 | |||
| 215 | 46805 | void MailBodyWriteService::amendHeaderState( | |
| 216 | const QString &databasePath, qint64 folderId, qint64 uid, | ||
| 217 | const std::function<void(MailHeaderStateFence::Change &)> &change) { | ||
| 218 | 46805 | QMutexLocker lock(&m_fencesMutex); | |
| 219 |
4/4✓ Branch 3 → 4 taken 46805 times.
✓ Branch 29 → 30 taken 53000 times.
✓ Branch 31 → 5 taken 6195 times.
✓ Branch 31 → 32 taken 46805 times.
|
53000 | for (auto it = m_fences.begin(); it != m_fences.end();) { |
| 220 | 6195 | const auto fence = it->lock(); | |
| 221 |
3/3✓ Branch 8 → 9 taken 613 times.
✓ Branch 8 → 12 taken 5582 times.
✓ Branch 10 → 11 taken 613 times.
|
6195 | if (!fence) { it = m_fences.erase(it); continue; } |
| 222 |
6/6✓ Branch 14 → 15 taken 2731 times.
✓ Branch 14 → 18 taken 2851 times.
✓ Branch 16 → 17 taken 2721 times.
✓ Branch 16 → 18 taken 10 times.
✓ Branch 19 → 20 taken 2721 times.
✓ Branch 19 → 22 taken 2861 times.
|
5582 | if (fence->writeDomain == databasePath && fence->identity.folderId == folderId) |
| 223 |
1/1✓ Branch 21 → 22 taken 2721 times.
|
2721 | fence->amend(uid, change); |
| 224 | 5582 | ++it; | |
| 225 | 6195 | } | |
| 226 | 46805 | } | |
| 227 | |||
| 228 | 1055 | void MailBodyWriteService::observeCache(MailCache *cache) { | |
| 229 | 1055 | connect(cache, &MailCache::headerStateReplaced, this, | |
| 230 |
1/1✓ Branch 2 → 3 taken 1055 times.
|
1055 | [this, cache](qint64 folderId, const MailHeader &header) { |
| 231 |
1/1✓ Branch 4 → 5 taken 44405 times.
|
44405 | amendHeaderState(cache->writeDomain(), folderId, header.uid, [&](auto &change) { |
| 232 | 4 | change.flags = header.flags; change.spam = header.isSpam; | |
| 233 | 4 | change.labels = header.labels; change.labelChanges.clear(); change.removed = false; | |
| 234 | 4 | }); | |
| 235 | 44405 | }, Qt::DirectConnection); | |
| 236 | 1055 | connect(cache, &MailCache::headerFlagsChanged, this, | |
| 237 |
1/1✓ Branch 4 → 5 taken 1055 times.
|
1055 | [this, cache](qint64 folderId, qint64 uid, quint32 flags) { |
| 238 |
1/1✓ Branch 4 → 5 taken 2316 times.
|
4455 | amendHeaderState(cache->writeDomain(), folderId, uid, [flags](auto &change) { change.flags = flags; }); |
| 239 | 2316 | }, Qt::DirectConnection); | |
| 240 | 1055 | connect(cache, &MailCache::headerLabelChanged, this, | |
| 241 |
1/1✓ Branch 6 → 7 taken 1055 times.
|
1055 | [this, cache](qint64 folderId, qint64 uid, const QString &label, bool present) { |
| 242 |
1/1✓ Branch 4 → 5 taken 59 times.
|
59 | amendHeaderState(cache->writeDomain(), folderId, uid, |
| 243 | 74 | [&](auto &change) { change.labelChanges[label] = present; }); | |
| 244 | 59 | }, Qt::DirectConnection); | |
| 245 | 1055 | connect(cache, &MailCache::headerRemoved, this, | |
| 246 |
1/1✓ Branch 8 → 9 taken 1055 times.
|
1055 | [this, cache](qint64 folderId, qint64 uid) { |
| 247 |
1/1✓ Branch 4 → 5 taken 25 times.
|
25 | amendHeaderState(cache->writeDomain(), folderId, uid, [](auto &change) { change.removed = true; }); |
| 248 | 25 | }, Qt::DirectConnection); | |
| 249 |
1/1✓ Branch 10 → 11 taken 1055 times.
|
1055 | connect(cache, &MailCache::folderHeadersRemoved, this, [this, cache](qint64 folderId) { |
| 250 | 30 | QMutexLocker lock(&m_fencesMutex); | |
| 251 |
4/4✓ Branch 3 → 4 taken 30 times.
✓ Branch 4 → 5 taken 30 times.
✓ Branch 26 → 6 taken 22 times.
✓ Branch 26 → 27 taken 30 times.
|
52 | for (const auto &weak : m_fences) |
| 252 |
8/8✓ Branch 9 → 10 taken 15 times.
✓ Branch 9 → 17 taken 7 times.
✓ Branch 13 → 14 taken 14 times.
✓ Branch 13 → 17 taken 1 time.
✓ Branch 18 → 19 taken 15 times.
✓ Branch 18 → 20 taken 7 times.
✓ Branch 20 → 21 taken 11 times.
✓ Branch 20 → 23 taken 11 times.
|
36 | if (const auto fence = weak.lock(); fence && fence->writeDomain == cache->writeDomain() && |
| 253 |
2/2✓ Branch 15 → 16 taken 11 times.
✓ Branch 15 → 17 taken 3 times.
|
36 | fence->identity.folderId == folderId) fence->cancel(); |
| 254 | 30 | }, Qt::DirectConnection); | |
| 255 | 1055 | } | |
| 256 | |||
| 257 | 1417 | std::shared_ptr<MailHeaderStateFence> MailBodyWriteService::beginHeaderStream( | |
| 258 | QObject *owner, const MailHeaderStreamIdentity &identity, const QList<qint64> &uids) { | ||
| 259 |
10/10✓ Branch 3 → 4 taken 1415 times.
✓ Branch 3 → 19 taken 1 time.
✓ Branch 5 → 6 taken 1414 times.
✓ Branch 5 → 19 taken 1 time.
✓ Branch 7 → 8 taken 1413 times.
✓ Branch 7 → 19 taken 1 time.
✓ Branch 8 → 9 taken 1412 times.
✓ Branch 8 → 19 taken 1 time.
✓ Branch 10 → 11 taken 1411 times.
✓ Branch 10 → 19 taken 1 time.
|
2828 | if (!owner || m_stopping || identity.databasePath.isEmpty() || identity.databasePath.size() > 4096 || identity.folderId <= 0 || |
| 260 |
4/4✓ Branch 12 → 13 taken 1410 times.
✓ Branch 12 → 19 taken 1 time.
✓ Branch 14 → 15 taken 1409 times.
✓ Branch 14 → 19 taken 1 time.
|
4233 | identity.account.isEmpty() || identity.folder.isEmpty() || |
| 261 |
8/8✓ Branch 2 → 3 taken 1416 times.
✓ Branch 2 → 19 taken 1 time.
✓ Branch 16 → 17 taken 1408 times.
✓ Branch 16 → 19 taken 1 time.
✓ Branch 18 → 19 taken 1 time.
✓ Branch 18 → 20 taken 1407 times.
✓ Branch 21 → 22 taken 10 times.
✓ Branch 21 → 23 taken 1407 times.
|
4243 | identity.account.size() > 4096 || identity.folder.size() > 4096 || uids.size() > 2048) |
| 262 | 10 | return {}; | |
| 263 | 1407 | QMutexLocker lock(&m_fencesMutex); | |
| 264 |
4/4✓ Branch 24 → 25 taken 1407 times.
✓ Branch 33 → 34 taken 2709 times.
✓ Branch 35 → 26 taken 1302 times.
✓ Branch 35 → 36 taken 1407 times.
|
2709 | for (auto it = m_fences.begin(); it != m_fences.end();) { |
| 265 |
3/3✓ Branch 28 → 29 taken 742 times.
✓ Branch 28 → 32 taken 560 times.
✓ Branch 30 → 31 taken 742 times.
|
1302 | if (it->expired()) it = m_fences.erase(it); else ++it; |
| 266 | } | ||
| 267 |
2/2✓ Branch 37 → 38 taken 3 times.
✓ Branch 37 → 39 taken 1404 times.
|
1407 | if (m_fences.size() >= 16) return {}; |
| 268 |
1/1✓ Branch 39 → 40 taken 1404 times.
|
1404 | auto fence = std::make_shared<MailHeaderStateFence>(); |
| 269 | 1404 | fence->identity = identity; | |
| 270 |
1/1✓ Branch 42 → 43 taken 1404 times.
|
1404 | const QFileInfo file(identity.databasePath); |
| 271 |
1/1✓ Branch 43 → 44 taken 1404 times.
|
1404 | fence->writeDomain = file.canonicalFilePath(); |
| 272 |
3/3✓ Branch 49 → 50 taken 20 times.
✓ Branch 49 → 55 taken 1384 times.
✓ Branch 50 → 51 taken 20 times.
|
1404 | if (fence->writeDomain.isEmpty()) fence->writeDomain = file.absoluteFilePath(); |
| 273 |
2/2✓ Branch 64 → 57 taken 505 times.
✓ Branch 64 → 65 taken 1403 times.
|
1908 | for (qint64 uid : uids) { |
| 274 |
2/2✓ Branch 58 → 59 taken 1 time.
✓ Branch 58 → 60 taken 504 times.
|
505 | if (uid <= 0) return {}; |
| 275 |
1/1✓ Branch 61 → 62 taken 504 times.
|
504 | fence->uids.insert(uid); |
| 276 | } | ||
| 277 | 1403 | const std::weak_ptr<MailHeaderStateFence> weak = fence; | |
| 278 |
1/1✓ Branch 67 → 69 taken 1403 times.
|
2806 | fence->destroyed = connect(owner, &QObject::destroyed, this, [weak] { |
| 279 |
1/2✓ Branch 4 → 5 taken 47 times.
✗ Branch 4 → 7 not taken.
|
47 | if (const auto fence = weak.lock()) fence->cancel(); |
| 280 | 1403 | }, Qt::DirectConnection); | |
| 281 |
1/1✓ Branch 74 → 75 taken 1403 times.
|
1403 | m_fences.append(fence); |
| 282 | 1403 | return fence; | |
| 283 | 1407 | } | |
| 284 | |||
| 285 | 1 | void MailBodyWriteService::preserveHeaderFlags( | |
| 286 | const std::shared_ptr<MailHeaderStateFence> &fence, qint64 uid, quint32 flags) { | ||
| 287 |
2/3✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 9 not taken.
✓ Branch 6 → 7 taken 1 time.
|
2 | if (fence) fence->amend(uid, [flags](auto &change) { change.flags = flags; }); |
| 288 | 1 | } | |
| 289 | |||
| 290 | 124 | bool MailBodyWriteService::widenHeaderStream( | |
| 291 | const std::shared_ptr<MailHeaderStateFence> &fence, | ||
| 292 | const MailHeaderStreamIdentity &identity, const QList<qint64> &uids) { | ||
| 293 | // The fence mutex list serializes this with amendHeaderState(), which | ||
| 294 | // reads the accepted UIDs from worker threads. | ||
| 295 | 124 | QMutexLocker lock(&m_fencesMutex); | |
| 296 |
6/8✓ Branch 4 → 5 taken 124 times.
✗ Branch 4 → 9 not taken.
✓ Branch 5 → 6 taken 124 times.
✗ Branch 5 → 9 not taken.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 123 times.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 13 taken 123 times.
|
124 | if (!fence || m_stopping || fence->m_cancelled.load()) return false; |
| 297 | 123 | const auto &open = fence->identity; | |
| 298 |
1/2✓ Branch 17 → 18 taken 123 times.
✗ Branch 17 → 22 not taken.
|
246 | if (open.databasePath != identity.databasePath || open.account != identity.account || |
| 299 |
6/8✓ Branch 15 → 16 taken 123 times.
✗ Branch 15 → 22 not taken.
✓ Branch 19 → 20 taken 122 times.
✓ Branch 19 → 22 taken 1 time.
✓ Branch 20 → 21 taken 122 times.
✗ Branch 20 → 22 not taken.
✓ Branch 24 → 25 taken 2 times.
✓ Branch 24 → 26 taken 121 times.
|
368 | open.folder != identity.folder || open.folderId != identity.folderId || |
| 300 |
2/2✓ Branch 21 → 22 taken 1 time.
✓ Branch 21 → 23 taken 121 times.
|
122 | open.uidValidity != identity.uidValidity) |
| 301 | 2 | return false; | |
| 302 |
2/2✓ Branch 28 → 29 taken 13 times.
✓ Branch 28 → 30 taken 108 times.
|
121 | if (fence->uids.isEmpty()) return true; // already accepts every UID |
| 303 |
5/6✓ Branch 31 → 32 taken 107 times.
✓ Branch 31 → 36 taken 1 time.
✗ Branch 35 → 36 not taken.
✓ Branch 35 → 37 taken 107 times.
✓ Branch 38 → 39 taken 1 time.
✓ Branch 38 → 42 taken 107 times.
|
108 | if (uids.isEmpty() || fence->uids.size() + uids.size() > 2048) { |
| 304 | 1 | fence->uids.clear(); | |
| 305 | 1 | return true; | |
| 306 | } | ||
| 307 |
2/2✓ Branch 51 → 44 taken 109 times.
✓ Branch 51 → 52 taken 107 times.
|
216 | for (qint64 uid : uids) { |
| 308 |
1/2✗ Branch 45 → 46 not taken.
✓ Branch 45 → 47 taken 109 times.
|
109 | if (uid <= 0) return false; |
| 309 |
1/1✓ Branch 48 → 49 taken 109 times.
|
109 | fence->uids.insert(uid); |
| 310 | } | ||
| 311 | 107 | return true; | |
| 312 | 124 | } | |
| 313 | |||
| 314 | 67 | static qint64 headerBatchBytes(const QList<MailHeader> &headers) { | |
| 315 | 67 | qint64 bytes = 0; | |
| 316 |
2/2✓ Branch 32 → 4 taken 94 times.
✓ Branch 32 → 33 taken 67 times.
|
161 | for (const auto &header : headers) { |
| 317 | 94 | bytes += 256 + 2LL * (header.subject.size() + header.from.size() + header.to.size() + | |
| 318 | 94 | header.messageId.size() + header.inReplyTo.size()); | |
| 319 |
2/2✓ Branch 23 → 15 taken 282 times.
✓ Branch 23 → 24 taken 94 times.
|
658 | for (const auto &list : {header.labels, header.references, header.inReplyToIds}) |
| 320 |
4/4✓ Branch 21 → 17 taken 5 times.
✓ Branch 21 → 22 taken 282 times.
✓ Branch 25 → 26 taken 282 times.
✓ Branch 25 → 29 taken 94 times.
|
663 | for (const auto &value : list) bytes += 32 + 2LL * value.size(); |
| 321 | } | ||
| 322 | 67 | return bytes; | |
| 323 | } | ||
| 324 | |||
| 325 | 44 | void MailBodyWriteService::writeHeaders( | |
| 326 | QObject *owner, QList<MailHeader> headers, | ||
| 327 | std::shared_ptr<MailHeaderStateFence> fence, HeaderCallback callback) { | ||
| 328 |
10/10✓ Branch 2 → 3 taken 43 times.
✓ Branch 2 → 9 taken 1 time.
✓ Branch 4 → 5 taken 42 times.
✓ Branch 4 → 9 taken 1 time.
✓ Branch 6 → 7 taken 41 times.
✓ Branch 6 → 9 taken 1 time.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 40 times.
✓ Branch 11 → 12 taken 4 times.
✓ Branch 11 → 24 taken 40 times.
|
44 | if (!owner || !fence || headers.isEmpty() || headers.size() > 512) { |
| 329 | 12 | callback({false, QStringLiteral("Header batch exceeds writer identity or byte limits" | |
| 330 | " (%1 headers, limit 512)").arg(headers.size()), {}}); | ||
| 331 | 10 | return; | |
| 332 | } | ||
| 333 |
1/1✓ Branch 24 → 25 taken 40 times.
|
40 | const qint64 bytes = headerBatchBytes(headers); |
| 334 |
1/1✓ Branch 27 → 28 taken 40 times.
|
40 | const auto foreign = std::find_if(headers.cbegin(), headers.cend(), [&](const auto &header) { |
| 335 |
6/6✓ Branch 2 → 3 taken 53 times.
✓ Branch 2 → 9 taken 1 time.
✓ Branch 5 → 6 taken 17 times.
✓ Branch 5 → 10 taken 36 times.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 16 times.
|
54 | return header.uid <= 0 || (!fence->uids.isEmpty() && !fence->uids.contains(header.uid)); |
| 336 | }); | ||
| 337 |
6/6✓ Branch 28 → 29 taken 39 times.
✓ Branch 28 → 32 taken 1 time.
✓ Branch 31 → 32 taken 2 times.
✓ Branch 31 → 33 taken 37 times.
✓ Branch 34 → 35 taken 3 times.
✓ Branch 34 → 67 taken 37 times.
|
40 | if (bytes > 8LL * 1024 * 1024 || foreign != headers.cend()) { |
| 338 | 6 | callback({false, foreign != headers.cend() | |
| 339 |
2/6✓ Branch 64 → 65 taken 2 times.
✓ Branch 64 → 66 taken 1 time.
✗ Branch 145 → 146 not taken.
✗ Branch 145 → 147 not taken.
✗ Branch 149 → 150 not taken.
✗ Branch 149 → 151 not taken.
|
5 | ? QStringLiteral("Header batch exceeds writer identity or byte limits" |
| 340 | " (UID %1 outside the stream's %2 UIDs)") | ||
| 341 |
3/5✓ Branch 43 → 44 taken 2 times.
✓ Branch 62 → 63 taken 2 times.
✓ Branch 62 → 64 taken 1 time.
✗ Branch 140 → 141 not taken.
✗ Branch 140 → 142 not taken.
|
7 | .arg(foreign->uid).arg(fence->uids.size()) |
| 342 |
2/6✓ Branch 58 → 59 taken 1 time.
✓ Branch 58 → 60 taken 2 times.
✗ Branch 131 → 132 not taken.
✗ Branch 131 → 133 not taken.
✗ Branch 135 → 136 not taken.
✗ Branch 135 → 137 not taken.
|
4 | : QStringLiteral("Header batch exceeds writer identity or byte limits" |
| 343 | " (%1 bytes, limit 8 MiB)").arg(bytes), {}}); | ||
| 344 | 3 | return; | |
| 345 | } | ||
| 346 |
6/6✓ Branch 67 → 68 taken 36 times.
✓ Branch 67 → 70 taken 1 time.
✓ Branch 68 → 69 taken 35 times.
✓ Branch 68 → 70 taken 1 time.
✓ Branch 69 → 70 taken 1 time.
✓ Branch 69 → 78 taken 34 times.
|
37 | if (m_stopping || m_pendingCount >= MaximumRequests || bytes > MaximumQueuedBytes - m_pendingBytes) { |
| 347 | 3 | callback({false, QStringLiteral("Cache writer is busy or stopping"), {}}); | |
| 348 | 3 | return; | |
| 349 | } | ||
| 350 | 34 | ++m_pendingCount; | |
| 351 | 34 | m_pendingBytes += bytes; | |
| 352 |
1/1✓ Branch 78 → 79 taken 34 times.
|
34 | const QPointer<QObject> consumer(owner); |
| 353 |
3/6✓ Branch 79 → 80 taken 34 times.
✓ Branch 80 → 81 taken 34 times.
✗ Branch 81 → 82 not taken.
✓ Branch 81 → 83 taken 34 times.
✗ Branch 169 → 170 not taken.
✗ Branch 169 → 171 not taken.
|
34 | auto *watcher = new QFutureWatcher<MailHeaderWriteResult>(this); |
| 354 | 34 | connect(watcher, &QFutureWatcher<MailHeaderWriteResult>::finished, this, | |
| 355 |
1/3✓ Branch 86 → 87 taken 34 times.
✗ Branch 174 → 175 not taken.
✗ Branch 174 → 176 not taken.
|
68 | [this, watcher, consumer, bytes, callback = std::move(callback)]() mutable { |
| 356 | 34 | --m_pendingCount; m_pendingBytes -= bytes; | |
| 357 |
2/2✓ Branch 2 → 3 taken 34 times.
✓ Branch 3 → 4 taken 34 times.
|
34 | auto result = watcher->result(); watcher->deleteLater(); |
| 358 |
6/7✓ Branch 4 → 5 taken 34 times.
✗ Branch 4 → 8 not taken.
✓ Branch 6 → 7 taken 33 times.
✓ Branch 6 → 8 taken 1 time.
✓ Branch 9 → 10 taken 33 times.
✓ Branch 9 → 15 taken 1 time.
✓ Branch 12 → 13 taken 33 times.
|
34 | if (!m_stopping && consumer) callback(std::move(result)); |
| 359 | 34 | }); | |
| 360 |
1/1✓ Branch 93 → 94 taken 34 times.
|
34 | watcher->setFuture(QtConcurrent::run(&m_pool, |
| 361 |
1/3✓ Branch 92 → 93 taken 34 times.
✗ Branch 183 → 184 not taken.
✗ Branch 183 → 185 not taken.
|
68 | [headers = std::move(headers), fence]() mutable { |
| 362 | 34 | const auto &identity = fence->identity; | |
| 363 | 34 | MailHeaderWriteResult result; | |
| 364 | 34 | QList<MailHeader> check; | |
| 365 |
3/3✓ Branch 5 → 6 taken 34 times.
✓ Branch 9 → 10 taken 3 times.
✓ Branch 9 → 11 taken 31 times.
|
34 | if (!(result.error = fence->apply(check)).isEmpty()) return result; |
| 366 | // Reuse the serial worker's connection within one fenced header stream. | ||
| 367 | // A large import otherwise reopens SQLite and rechecks schema for every | ||
| 368 | // bounded batch. A new stream (including a same-path profile reset), | ||
| 369 | // changed database path or failed write drops the previous handle. | ||
| 370 |
2/2✓ Branch 11 → 12 taken 10 times.
✓ Branch 11 → 13 taken 21 times.
|
31 | static thread_local std::unique_ptr<MailCache> writer; |
| 371 |
2/2✓ Branch 13 → 14 taken 10 times.
✓ Branch 13 → 15 taken 21 times.
|
31 | static thread_local QString writerPath; |
| 372 |
2/2✓ Branch 15 → 16 taken 10 times.
✓ Branch 15 → 17 taken 21 times.
|
31 | static thread_local std::weak_ptr<MailHeaderStateFence> writerFence; |
| 373 |
9/10✓ Branch 18 → 19 taken 16 times.
✓ Branch 18 → 25 taken 15 times.
✓ Branch 20 → 21 taken 7 times.
✓ Branch 20 → 24 taken 9 times.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 7 times.
✓ Branch 26 → 27 taken 7 times.
✓ Branch 26 → 28 taken 24 times.
✓ Branch 28 → 29 taken 9 times.
✓ Branch 28 → 32 taken 22 times.
|
31 | if (writer && (writerPath != identity.databasePath || writerFence.lock() != fence)) { |
| 374 | 9 | writer.reset(); | |
| 375 | 9 | writerPath.clear(); | |
| 376 | 9 | writerFence.reset(); | |
| 377 | } | ||
| 378 |
2/2✓ Branch 33 → 34 taken 24 times.
✓ Branch 33 → 48 taken 7 times.
|
31 | if (!writer) { |
| 379 |
1/1✓ Branch 34 → 35 taken 24 times.
|
24 | writer = std::make_unique<MailCache>(); |
| 380 |
3/3✓ Branch 38 → 39 taken 24 times.
✓ Branch 39 → 40 taken 1 time.
✓ Branch 39 → 46 taken 23 times.
|
24 | if (!writer->openExistingWriter(identity.databasePath)) { |
| 381 |
1/1✓ Branch 41 → 42 taken 1 time.
|
1 | result.error = writer->lastError(); |
| 382 | 1 | writer.reset(); | |
| 383 | 1 | return result; | |
| 384 | } | ||
| 385 | 23 | writerPath = identity.databasePath; | |
| 386 | 23 | writerFence = fence; | |
| 387 | } | ||
| 388 |
1/1✓ Branch 49 → 50 taken 30 times.
|
30 | const qint64 previousRevision = writer->threadRevision(identity.folderId); |
| 389 | // Another connection can hold the write lock beyond busy_timeout (5 s); | ||
| 390 | // a single SQLITE_BUSY used to abort the whole folder backfill until the | ||
| 391 | // next open. The fence preparation is idempotent, so the same batch is | ||
| 392 | // retried a bounded number of times. | ||
| 393 | 30 | bool stored = false; | |
| 394 |
2/4✓ Branch 81 → 82 taken 31 times.
✗ Branch 81 → 83 not taken.
✓ Branch 82 → 51 taken 31 times.
✗ Branch 82 → 83 not taken.
|
31 | for (int attempt = 1; attempt <= MaximumBusyAttempts && !stored; ++attempt) { |
| 395 | 62 | stored = writer->storeStreamingHeaders(identity.account, identity.folder, identity.folderId, | |
| 396 |
1/1✓ Branch 53 → 54 taken 31 times.
|
31 | identity.uidValidity, headers, |
| 397 | 31 | [&](auto &batch) { | |
| 398 |
1/1✓ Branch 3 → 4 taken 27 times.
|
27 | const auto error = fence->apply(batch); |
| 399 |
1/2✗ Branch 5 → 6 not taken.
✓ Branch 5 → 8 taken 27 times.
|
27 | if (!error.isEmpty()) return error; |
| 400 |
1/1✓ Branch 8 → 9 taken 27 times.
|
27 | return headerBatchBytes(batch) > 8LL * 1024 * 1024 |
| 401 |
4/4✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 13 taken 26 times.
✓ Branch 15 → 16 taken 1 time.
✓ Branch 15 → 17 taken 26 times.
|
55 | ? QStringLiteral("Updated header batch exceeds writer byte limits") : QString{}; |
| 402 | 27 | }); | |
| 403 |
14/20✓ Branch 55 → 56 taken 7 times.
✓ Branch 55 → 63 taken 24 times.
✓ Branch 57 → 58 taken 7 times.
✓ Branch 61 → 62 taken 7 times.
✓ Branch 62 → 63 taken 6 times.
✓ Branch 62 → 64 taken 1 time.
✓ Branch 65 → 66 taken 7 times.
✓ Branch 65 → 67 taken 24 times.
✓ Branch 67 → 68 taken 7 times.
✓ Branch 67 → 69 taken 24 times.
✓ Branch 69 → 70 taken 7 times.
✓ Branch 69 → 71 taken 24 times.
✓ Branch 71 → 72 taken 30 times.
✓ Branch 71 → 73 taken 1 time.
✗ Branch 128 → 129 not taken.
✗ Branch 128 → 130 not taken.
✗ Branch 132 → 133 not taken.
✗ Branch 132 → 134 not taken.
✗ Branch 136 → 137 not taken.
✗ Branch 136 → 138 not taken.
|
38 | if (stored || !writer->lastError().contains(QStringLiteral("database is locked"))) break; |
| 404 |
4/4✓ Branch 74 → 75 taken 1 time.
✓ Branch 75 → 76 taken 1 time.
✓ Branch 76 → 77 taken 1 time.
✓ Branch 77 → 78 taken 1 time.
|
2 | qWarning() << "Header write found the database locked, attempt" << attempt << "of" |
| 405 |
1/1✓ Branch 78 → 79 taken 1 time.
|
1 | << MaximumBusyAttempts; |
| 406 | } | ||
| 407 |
2/2✓ Branch 83 → 84 taken 6 times.
✓ Branch 83 → 92 taken 24 times.
|
30 | if (!stored) { |
| 408 |
1/1✓ Branch 85 → 86 taken 6 times.
|
6 | result.error = writer->lastError(); |
| 409 | 6 | writer.reset(); | |
| 410 | 6 | writerPath.clear(); | |
| 411 | 6 | writerFence.reset(); | |
| 412 | 6 | return result; | |
| 413 | } | ||
| 414 |
1/1✓ Branch 93 → 94 taken 24 times.
|
24 | const auto committedState = writer->folderPresentationState(identity.folderId); |
| 415 | // A failed revision read must cause a conservative refresh. Both reads | ||
| 416 | // happen on the writer lane, where successful no-op batches keep the | ||
| 417 | // derived projection revision byte-for-byte unchanged. | ||
| 418 |
2/4✓ Branch 94 → 95 taken 24 times.
✗ Branch 94 → 97 not taken.
✓ Branch 95 → 96 taken 24 times.
✗ Branch 95 → 97 not taken.
|
48 | result.projectionChanged = previousRevision < 0 || committedState.threadRevision < 0 || |
| 419 |
2/2✓ Branch 96 → 97 taken 22 times.
✓ Branch 96 → 98 taken 2 times.
|
24 | committedState.threadRevision != previousRevision; |
| 420 |
1/2✓ Branch 99 → 100 taken 24 times.
✗ Branch 99 → 101 not taken.
|
24 | if (committedState.uidValidity == identity.uidValidity) { |
| 421 | 24 | result.totalCount = committedState.headerCount; | |
| 422 | 24 | result.uidValidity = identity.uidValidity; | |
| 423 | } | ||
| 424 |
1/1✓ Branch 103 → 104 taken 24 times.
|
24 | const auto pendingMoves = writer->journal().pendingMoveUids(identity.account, identity.folderId); |
| 425 |
2/2✓ Branch 114 → 107 taken 35 times.
✓ Branch 114 → 115 taken 24 times.
|
59 | for (const auto &header : std::as_const(headers)) |
| 426 |
1/3✗ Branch 109 → 110 not taken.
✓ Branch 109 → 112 taken 35 times.
✗ Branch 110 → 111 not taken.
|
35 | if (pendingMoves.contains(header.uid)) result.pendingMoveUids.insert(header.uid); |
| 427 | 24 | result.headers = std::move(headers); result.stored = true; | |
| 428 | 24 | return result; | |
| 429 | 34 | })); | |
| 430 | 69 | } | |
| 431 |