data/MailSnapshotReader.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "MailSnapshotReader.h" | ||
| 2 | |||
| 3 | #include <QScopeGuard> | ||
| 4 | #include <QSqlDriver> | ||
| 5 | #include <QSqlError> | ||
| 6 | #include <QSqlQuery> | ||
| 7 | #include <QVariant> | ||
| 8 | #include <QtEndian> | ||
| 9 | #include <sqlite3.h> | ||
| 10 | |||
| 11 | #include "util/MessageId.h" | ||
| 12 | #include "util/SpamHeaders.h" | ||
| 13 | #include "util/Utf8Digest.h" | ||
| 14 | #include "data/MailCacheSql.h" | ||
| 15 | |||
| 16 | using MailCacheSql::decodeThreadIds; | ||
| 17 | |||
| 18 | |||
| 19 | // displaySnapshot step: interrupt the read's SQLite statements once | ||
| 20 | // `cancelled` is set. Returns an error text when the connection cannot be | ||
| 21 | // interrupted (the read is then refused). | ||
| 22 | 1098 | QString MailSnapshotReader::installDisplayCancellation(const QSqlDatabase &db, | |
| 23 | const std::atomic_bool *cancelled, | ||
| 24 | sqlite3 **database) { | ||
| 25 |
5/8✓ Branch 2 → 3 taken 1098 times.
✓ Branch 3 → 4 taken 1098 times.
✗ Branch 3 → 6 not taken.
✓ Branch 4 → 5 taken 1098 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 1098 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 14 taken 1098 times.
|
1098 | if (!db.isOpen() || !db.driver()) { |
| 26 | ✗ | return QStringLiteral("Display reader is not open"); | |
| 27 | } | ||
| 28 |
2/2✓ Branch 14 → 15 taken 1098 times.
✓ Branch 15 → 16 taken 1098 times.
|
1098 | auto handle = db.driver()->handle(); |
| 29 |
7/13✓ Branch 16 → 17 taken 1098 times.
✓ Branch 17 → 18 taken 1098 times.
✗ Branch 17 → 22 not taken.
✓ Branch 18 → 19 taken 1098 times.
✓ Branch 19 → 20 taken 1098 times.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 1098 times.
✓ Branch 24 → 25 taken 1098 times.
✗ Branch 24 → 26 not taken.
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 32 taken 1098 times.
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 41 not taken.
|
1098 | if (!handle.isValid() || QByteArray(handle.typeName()) != "sqlite3*") { |
| 30 | ✗ | return QStringLiteral("Display reader requires interruptible SQLite"); | |
| 31 | } | ||
| 32 |
1/1✓ Branch 32 → 33 taken 1098 times.
|
1098 | *database = *static_cast<sqlite3 **>(handle.data()); |
| 33 |
1/1✓ Branch 34 → 35 taken 1098 times.
|
1098 | sqlite3_progress_handler(*database, 1000, [](void *context) { |
| 34 | ✗ | return static_cast<const std::atomic_bool *>(context)->load( | |
| 35 | ✗ | std::memory_order_relaxed) ? 1 : 0; | |
| 36 | }, const_cast<std::atomic_bool *>(cancelled)); | ||
| 37 | 1098 | return {}; | |
| 38 | 1098 | } | |
| 39 | |||
| 40 | // displaySnapshot step: the bounded identity/size row of one mail. | ||
| 41 | 1114 | QString MailSnapshotReader::displayRowQuery() { | |
| 42 | 1114 | return QStringLiteral( | |
| 43 | "SELECT h.id, CASE WHEN length(CAST(f.path AS BLOB)) <= 4096 THEN f.path END, f.uidvalidity, " | ||
| 44 | "COALESCE(length(CAST(h.subject AS BLOB)),0) + " | ||
| 45 | "COALESCE(length(CAST(h.from_addr AS BLOB)),0) + " | ||
| 46 | "COALESCE(length(CAST(h.to_addr AS BLOB)),0) + " | ||
| 47 | "COALESCE(length(CAST(h.message_id AS BLOB)),0) + " | ||
| 48 | "COALESCE(length(CAST(h.in_reply_to AS BLOB)),0) + " | ||
| 49 | "COALESCE(length(CAST(h.ref_ids AS BLOB)),0), " | ||
| 50 | "COALESCE(length(CAST(b.text_plain AS BLOB)),0), " | ||
| 51 | "COALESCE(length(CAST(b.text_html AS BLOB)),0), b.header_id " | ||
| 52 | "FROM folders f JOIN headers h ON h.folder_id=f.id " | ||
| 53 | "LEFT JOIN bodies b ON b.header_id=h.id " | ||
| 54 | "WHERE f.account=:account AND f.id=:fid AND h.uid=:uid"); | ||
| 55 | } | ||
| 56 | |||
| 57 | // displaySnapshot step: header fields of the selected row. | ||
| 58 | 1107 | void MailSnapshotReader::readDisplayHeader(const QSqlQuery &query, qint64 uid, qint64 folderId, | |
| 59 | MailHeader &header) { | ||
| 60 | 1107 | header.uid = uid; | |
| 61 | 1107 | header.folderId = folderId; | |
| 62 |
2/2✓ Branch 2 → 3 taken 1107 times.
✓ Branch 3 → 4 taken 1107 times.
|
1107 | header.subject = query.value(0).toString(); |
| 63 |
2/2✓ Branch 7 → 8 taken 1107 times.
✓ Branch 8 → 9 taken 1107 times.
|
1107 | header.from = query.value(1).toString(); |
| 64 |
2/2✓ Branch 12 → 13 taken 1107 times.
✓ Branch 13 → 14 taken 1107 times.
|
1107 | header.to = query.value(2).toString(); |
| 65 |
4/4✓ Branch 17 → 18 taken 1107 times.
✓ Branch 18 → 19 taken 1107 times.
✓ Branch 20 → 21 taken 1057 times.
✓ Branch 20 → 28 taken 50 times.
|
1107 | if (!query.value(3).isNull()) |
| 66 |
3/3✓ Branch 21 → 22 taken 1057 times.
✓ Branch 22 → 23 taken 1057 times.
✓ Branch 23 → 24 taken 1057 times.
|
1057 | header.date = QDateTime::fromSecsSinceEpoch(query.value(3).toLongLong()); |
| 67 |
2/2✓ Branch 28 → 29 taken 1107 times.
✓ Branch 29 → 30 taken 1107 times.
|
1107 | header.flags = query.value(4).toUInt(); |
| 68 |
2/2✓ Branch 31 → 32 taken 1107 times.
✓ Branch 32 → 33 taken 1107 times.
|
1107 | header.size = query.value(5).toLongLong(); |
| 69 |
2/2✓ Branch 34 → 35 taken 1107 times.
✓ Branch 35 → 36 taken 1107 times.
|
1107 | header.hasAttachments = query.value(6).toBool(); |
| 70 |
3/3✓ Branch 37 → 38 taken 1107 times.
✓ Branch 38 → 39 taken 1107 times.
✓ Branch 39 → 40 taken 1107 times.
|
1107 | header.messageId = MessageId::canonicalFirst(query.value(7).toString()); |
| 71 |
3/3✓ Branch 44 → 45 taken 1107 times.
✓ Branch 45 → 46 taken 1107 times.
✓ Branch 46 → 47 taken 1107 times.
|
1107 | header.inReplyToIds = decodeThreadIds(query.value(8).toString()); |
| 72 |
2/2✓ Branch 52 → 53 taken 35 times.
✓ Branch 52 → 55 taken 1072 times.
|
1107 | if (!header.inReplyToIds.isEmpty()) header.inReplyTo = header.inReplyToIds.first(); |
| 73 |
3/3✓ Branch 55 → 56 taken 1107 times.
✓ Branch 56 → 57 taken 1107 times.
✓ Branch 57 → 58 taken 1107 times.
|
1107 | header.references = decodeThreadIds(query.value(9).toString()); |
| 74 |
2/2✓ Branch 62 → 63 taken 1107 times.
✓ Branch 63 → 64 taken 1107 times.
|
1107 | header.isSpam = query.value(10).toBool(); |
| 75 | // Sprint 105 S105.2.3: the verdict stored at header sync (NULL tool = unknown). | ||
| 76 |
2/2✓ Branch 65 → 66 taken 1107 times.
✓ Branch 66 → 67 taken 1107 times.
|
1107 | header.spamKnown = !query.value(13).isNull(); |
| 77 |
6/6✓ Branch 68 → 69 taken 1107 times.
✓ Branch 69 → 70 taken 1107 times.
✓ Branch 71 → 72 taken 4 times.
✓ Branch 71 → 77 taken 1103 times.
✓ Branch 72 → 73 taken 4 times.
✓ Branch 73 → 74 taken 4 times.
|
1107 | if (!query.value(11).isNull()) header.spamScore = query.value(11).toDouble(); |
| 78 |
6/6✓ Branch 77 → 78 taken 1107 times.
✓ Branch 78 → 79 taken 1107 times.
✓ Branch 80 → 81 taken 4 times.
✓ Branch 80 → 86 taken 1103 times.
✓ Branch 81 → 82 taken 4 times.
✓ Branch 82 → 83 taken 4 times.
|
1107 | if (!query.value(12).isNull()) header.spamThreshold = query.value(12).toDouble(); |
| 79 |
2/2✓ Branch 86 → 87 taken 1107 times.
✓ Branch 87 → 88 taken 1107 times.
|
1107 | header.spamTool = query.value(13).toString(); |
| 80 | 1107 | } | |
| 81 | |||
| 82 | // displaySnapshot step (P90.8): the body columns the reading mode can | ||
| 83 | // display; an empty preferred variant loads both so the fallback works. | ||
| 84 | 896 | QString MailSnapshotReader::displayBodyProjection(MailDisplaySnapshot::PreferredContent preferred, | |
| 85 | qint64 plainBytes, qint64 htmlBytes, | ||
| 86 | MailBody::Projection &bodyProjection) { | ||
| 87 |
4/4✓ Branch 2 → 3 taken 824 times.
✓ Branch 2 → 5 taken 72 times.
✓ Branch 3 → 4 taken 821 times.
✓ Branch 3 → 5 taken 3 times.
|
896 | const bool textProjection = preferred == MailDisplaySnapshot::PreferredContent::Text && |
| 88 | plainBytes > 0; | ||
| 89 |
4/4✓ Branch 6 → 7 taken 34 times.
✓ Branch 6 → 9 taken 862 times.
✓ Branch 7 → 8 taken 24 times.
✓ Branch 7 → 9 taken 10 times.
|
896 | const bool htmlProjection = preferred == MailDisplaySnapshot::PreferredContent::Html && |
| 90 | htmlBytes > 0; | ||
| 91 |
2/2✓ Branch 10 → 11 taken 821 times.
✓ Branch 10 → 12 taken 75 times.
|
971 | bodyProjection = textProjection ? MailBody::Projection::TextOnly |
| 92 |
2/2✓ Branch 12 → 13 taken 24 times.
✓ Branch 12 → 14 taken 51 times.
|
75 | : htmlProjection ? MailBody::Projection::HtmlOnly |
| 93 | : MailBody::Projection::Full; | ||
| 94 | // F100-56: the header block's first 16 KiB for the spam filter's lines. | ||
| 95 | 896 | const QString headerPrefix = QStringLiteral(", substr(CAST(raw_body AS BLOB),1,16384) FROM bodies WHERE header_id=:hid"); | |
| 96 | return textProjection | ||
| 97 |
2/6✓ Branch 45 → 46 taken 821 times.
✓ Branch 45 → 47 taken 75 times.
✗ Branch 66 → 67 not taken.
✗ Branch 66 → 68 not taken.
✗ Branch 70 → 71 not taken.
✗ Branch 70 → 72 not taken.
|
1717 | ? QStringLiteral("text_plain, ''") + headerPrefix |
| 98 | : htmlProjection | ||
| 99 |
6/10✓ Branch 25 → 26 taken 24 times.
✓ Branch 25 → 30 taken 51 times.
✓ Branch 41 → 42 taken 24 times.
✓ Branch 41 → 43 taken 872 times.
✓ Branch 43 → 44 taken 821 times.
✓ Branch 43 → 45 taken 75 times.
✗ Branch 58 → 59 not taken.
✗ Branch 58 → 60 not taken.
✗ Branch 62 → 63 not taken.
✗ Branch 62 → 64 not taken.
|
995 | ? QStringLiteral("'' , text_html") + headerPrefix |
| 100 |
11/15✓ Branch 20 → 21 taken 821 times.
✓ Branch 20 → 25 taken 75 times.
✓ Branch 24 → 34 taken 821 times.
✓ Branch 29 → 34 taken 24 times.
✓ Branch 33 → 34 taken 51 times.
✓ Branch 35 → 36 taken 51 times.
✓ Branch 35 → 37 taken 845 times.
✓ Branch 37 → 38 taken 51 times.
✓ Branch 37 → 39 taken 845 times.
✓ Branch 39 → 40 taken 24 times.
✓ Branch 39 → 41 taken 872 times.
✗ Branch 50 → 51 not taken.
✗ Branch 50 → 52 not taken.
✗ Branch 54 → 55 not taken.
✗ Branch 54 → 56 not taken.
|
3584 | : QStringLiteral("text_plain, text_html") + headerPrefix; |
| 101 | 896 | } | |
| 102 | |||
| 103 | // displaySnapshot step: the body columns of displayBodyProjection(). | ||
| 104 | 896 | void MailSnapshotReader::readDisplayBody(const QSqlQuery &query, MailBody &body) { | |
| 105 |
2/2✓ Branch 2 → 3 taken 896 times.
✓ Branch 3 → 4 taken 896 times.
|
896 | body.textPlain = query.value(0).toString(); |
| 106 |
2/2✓ Branch 7 → 8 taken 896 times.
✓ Branch 8 → 9 taken 896 times.
|
896 | body.textHtml = query.value(1).toString(); |
| 107 |
3/3✓ Branch 12 → 13 taken 896 times.
✓ Branch 13 → 14 taken 896 times.
✓ Branch 14 → 15 taken 896 times.
|
896 | body.spamHeaders = SpamHeaders::extract(query.value(2).toByteArray()); // F100-56 |
| 108 | // The display reader already runs on a resident SQL worker. Carry the | ||
| 109 | // HTML-light digest so the first Quick frame avoids a full HTML scan. | ||
| 110 |
5/6✓ Branch 20 → 21 taken 47 times.
✓ Branch 20 → 24 taken 849 times.
✓ Branch 22 → 23 taken 47 times.
✗ Branch 22 → 24 not taken.
✓ Branch 25 → 26 taken 47 times.
✓ Branch 25 → 34 taken 849 times.
|
896 | if (!body.textHtml.isEmpty() && body.textHtml.size() <= 1024 * 1024) { |
| 111 |
1/1✓ Branch 27 → 28 taken 47 times.
|
47 | const auto digest = MailJD::sha256Utf8HexUpTo(body.textHtml, 1024 * 1024); |
| 112 |
1/2✓ Branch 29 → 30 taken 47 times.
✗ Branch 29 → 32 not taken.
|
47 | if (digest) body.htmlDigest = *digest; |
| 113 | 47 | } | |
| 114 | 896 | } | |
| 115 | |||
| 116 | // displaySnapshot step: one bounded attachment metadata row. | ||
| 117 | 278 | Attachment MailSnapshotReader::readDisplayAttachment(const QSqlQuery &query) { | |
| 118 | 278 | Attachment attachment; | |
| 119 |
2/2✓ Branch 2 → 3 taken 278 times.
✓ Branch 3 → 4 taken 278 times.
|
278 | attachment.id = query.value(0).toLongLong(); |
| 120 |
2/2✓ Branch 5 → 6 taken 278 times.
✓ Branch 6 → 7 taken 278 times.
|
278 | attachment.filename = query.value(1).toString(); |
| 121 |
2/2✓ Branch 10 → 11 taken 278 times.
✓ Branch 11 → 12 taken 278 times.
|
278 | attachment.contentType = query.value(2).toString(); |
| 122 |
2/2✓ Branch 15 → 16 taken 278 times.
✓ Branch 16 → 17 taken 278 times.
|
278 | attachment.size = query.value(3).toLongLong(); |
| 123 |
2/2✓ Branch 18 → 19 taken 278 times.
✓ Branch 19 → 20 taken 278 times.
|
278 | attachment.contentId = query.value(4).toString(); |
| 124 |
2/2✓ Branch 23 → 24 taken 278 times.
✓ Branch 24 → 25 taken 278 times.
|
278 | const int presentation = query.value(5).toInt(); |
| 125 |
2/4✓ Branch 26 → 27 taken 278 times.
✗ Branch 26 → 29 not taken.
✓ Branch 27 → 28 taken 278 times.
✗ Branch 27 → 29 not taken.
|
278 | if (presentation >= 0 && presentation <= static_cast<int>(AttachmentPresentation::RejectedInline)) |
| 126 | 278 | attachment.presentation = static_cast<AttachmentPresentation>(presentation); | |
| 127 | 278 | return attachment; | |
| 128 | ✗ | } | |
| 129 | |||
| 130 | 1115 | MailDisplaySnapshot MailSnapshotReader::displaySnapshot( | |
| 131 | const QString &accountId, qint64 folderId, qint64 uid, | ||
| 132 | MailDisplaySnapshot::PreferredContent preferred, | ||
| 133 | const std::atomic_bool *cancelled) { | ||
| 134 | 1115 | MailDisplaySnapshot result; | |
| 135 | 9223 | const auto superseded = [&] { | |
| 136 |
4/4✓ Branch 2 → 3 taken 8600 times.
✓ Branch 2 → 6 taken 623 times.
✓ Branch 4 → 5 taken 4 times.
✓ Branch 4 → 6 taken 8596 times.
|
9223 | return cancelled && cancelled->load(std::memory_order_relaxed); |
| 137 | 1115 | }; | |
| 138 | 1115 | sqlite3 *database = nullptr; | |
| 139 | 13 | const auto rollbackRead = [&] { | |
| 140 |
2/2✓ Branch 2 → 3 taken 4 times.
✓ Branch 2 → 4 taken 9 times.
|
13 | if (database) sqlite3_progress_handler(database, 0, nullptr, nullptr); |
| 141 | 13 | m_db.rollback(); | |
| 142 | 1128 | }; | |
| 143 | 5 | const auto fail = [&](const QString &error) { | |
| 144 | 5 | rollbackRead(); | |
| 145 | 5 | result = {}; | |
| 146 | 5 | result.error = error; | |
| 147 | 5 | return result; | |
| 148 | 5 | }; | |
| 149 |
1/2✗ Branch 4 → 5 not taken.
✓ Branch 4 → 7 taken 1115 times.
|
1115 | if (superseded()) return result; |
| 150 |
2/2✓ Branch 7 → 8 taken 1098 times.
✓ Branch 7 → 15 taken 17 times.
|
1115 | if (cancelled) { |
| 151 |
1/1✓ Branch 8 → 9 taken 1098 times.
|
1098 | result.error = installDisplayCancellation(m_db, cancelled, &database); |
| 152 |
1/2✗ Branch 12 → 13 not taken.
✓ Branch 12 → 15 taken 1098 times.
|
1098 | if (!result.error.isEmpty()) return result; |
| 153 | } | ||
| 154 | 3345 | const auto clearProgress = qScopeGuard([database] { | |
| 155 |
2/2✓ Branch 2 → 3 taken 1098 times.
✓ Branch 2 → 4 taken 17 times.
|
1115 | if (database) sqlite3_progress_handler(database, 0, nullptr, nullptr); |
| 156 | 1115 | }); | |
| 157 |
2/3✓ Branch 16 → 17 taken 1115 times.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 25 taken 1115 times.
|
1115 | if (!m_db.transaction()) |
| 158 | ✗ | return fail(m_db.lastError().text()); | |
| 159 |
3/3✓ Branch 26 → 27 taken 1 time.
✓ Branch 26 → 35 taken 1114 times.
✓ Branch 30 → 31 taken 1 time.
|
1116 | if (superseded()) return fail(QStringLiteral("Display read superseded")); |
| 160 |
1/1✓ Branch 35 → 36 taken 1114 times.
|
1114 | QSqlQuery query(m_db); |
| 161 |
2/2✓ Branch 36 → 37 taken 1114 times.
✓ Branch 37 → 38 taken 1114 times.
|
1114 | query.prepare(displayRowQuery()); |
| 162 |
1/1✓ Branch 44 → 45 taken 1114 times.
|
2228 | query.bindValue(QStringLiteral(":account"), accountId); |
| 163 |
1/1✓ Branch 53 → 54 taken 1114 times.
|
2228 | query.bindValue(QStringLiteral(":fid"), folderId); |
| 164 |
1/1✓ Branch 62 → 63 taken 1114 times.
|
2228 | query.bindValue(QStringLiteral(":uid"), uid); |
| 165 |
2/3✓ Branch 66 → 67 taken 1114 times.
✗ Branch 67 → 68 not taken.
✓ Branch 67 → 75 taken 1114 times.
|
1114 | if (!query.exec()) |
| 166 | ✗ | return fail(query.lastError().text()); | |
| 167 |
3/3✓ Branch 76 → 77 taken 1 time.
✓ Branch 76 → 85 taken 1113 times.
✓ Branch 80 → 81 taken 1 time.
|
1115 | if (superseded()) return fail(QStringLiteral("Display read superseded")); |
| 168 |
3/3✓ Branch 85 → 86 taken 1113 times.
✓ Branch 86 → 87 taken 2 times.
✓ Branch 86 → 100 taken 1111 times.
|
1113 | if (!query.next()) { |
| 169 |
3/7✓ Branch 87 → 88 taken 2 times.
✓ Branch 88 → 89 taken 2 times.
✗ Branch 90 → 91 not taken.
✓ Branch 90 → 98 taken 2 times.
✗ Branch 91 → 92 not taken.
✗ Branch 92 → 93 not taken.
✗ Branch 93 → 94 not taken.
|
2 | if (query.lastError().isValid()) return fail(query.lastError().text()); |
| 170 | 2 | result.outcome = MailDisplaySnapshot::Outcome::HeaderMissing; | |
| 171 |
1/1✓ Branch 98 → 99 taken 2 times.
|
2 | rollbackRead(); |
| 172 | 2 | return result; | |
| 173 | } | ||
| 174 |
1/3✗ Branch 101 → 102 not taken.
✓ Branch 101 → 110 taken 1111 times.
✗ Branch 105 → 106 not taken.
|
1111 | if (superseded()) return fail(QStringLiteral("Display read superseded")); |
| 175 |
3/4✓ Branch 110 → 111 taken 1111 times.
✓ Branch 111 → 112 taken 1111 times.
✗ Branch 113 → 114 not taken.
✓ Branch 113 → 116 taken 1111 times.
|
1111 | if (query.value(1).isNull()) { |
| 176 | ✗ | result.outcome = MailDisplaySnapshot::Outcome::Limited; | |
| 177 | ✗ | rollbackRead(); | |
| 178 | ✗ | return result; | |
| 179 | } | ||
| 180 |
2/2✓ Branch 116 → 117 taken 1111 times.
✓ Branch 117 → 118 taken 1111 times.
|
1111 | const qint64 headerId = query.value(0).toLongLong(); |
| 181 |
2/2✓ Branch 119 → 120 taken 1111 times.
✓ Branch 120 → 121 taken 1111 times.
|
1111 | result.folderPath = query.value(1).toString(); |
| 182 |
2/2✓ Branch 124 → 125 taken 1111 times.
✓ Branch 125 → 126 taken 1111 times.
|
1111 | result.uidValidity = query.value(2).toUInt(); |
| 183 |
2/2✓ Branch 127 → 128 taken 1111 times.
✓ Branch 128 → 129 taken 1111 times.
|
1111 | const qint64 plainBytes = query.value(4).toLongLong(); |
| 184 |
2/2✓ Branch 130 → 131 taken 1111 times.
✓ Branch 131 → 132 taken 1111 times.
|
1111 | const qint64 htmlBytes = query.value(5).toLongLong(); |
| 185 |
2/2✓ Branch 133 → 134 taken 1111 times.
✓ Branch 134 → 135 taken 1111 times.
|
1111 | const bool bodyPresent = !query.value(6).isNull(); |
| 186 |
7/10✓ Branch 136 → 137 taken 1111 times.
✓ Branch 137 → 138 taken 1111 times.
✓ Branch 138 → 139 taken 1110 times.
✓ Branch 138 → 140 taken 1 time.
✓ Branch 142 → 143 taken 1111 times.
✗ Branch 142 → 144 not taken.
✓ Branch 144 → 145 taken 2 times.
✓ Branch 144 → 147 taken 1109 times.
✗ Branch 508 → 509 not taken.
✗ Branch 508 → 510 not taken.
|
2221 | if (query.value(3).toLongLong() > MailDisplaySnapshot::MaxMetadataBytes || |
| 187 |
2/2✓ Branch 139 → 140 taken 1 time.
✓ Branch 139 → 141 taken 1109 times.
|
1110 | plainBytes + htmlBytes > MailDisplaySnapshot::MaxTextBytes / 2) { |
| 188 | 2 | result.outcome = MailDisplaySnapshot::Outcome::Limited; | |
| 189 |
1/1✓ Branch 145 → 146 taken 2 times.
|
2 | rollbackRead(); |
| 190 | 2 | return result; | |
| 191 | } | ||
| 192 |
1/1✓ Branch 147 → 148 taken 1109 times.
|
1109 | query.finish(); |
| 193 |
1/1✓ Branch 151 → 152 taken 1109 times.
|
1109 | query.prepare(QStringLiteral( |
| 194 | "SELECT subject, from_addr, to_addr, date, flags, size, has_attachments, " | ||
| 195 | "message_id, in_reply_to, ref_ids, is_spam, spam_score, spam_threshold, " | ||
| 196 | "CASE WHEN length(CAST(spam_tool AS BLOB)) <= 64 THEN spam_tool END FROM headers WHERE id=:hid")); | ||
| 197 |
1/1✓ Branch 159 → 160 taken 1109 times.
|
2218 | query.bindValue(QStringLiteral(":hid"), headerId); |
| 198 |
5/11✓ Branch 163 → 164 taken 1109 times.
✓ Branch 164 → 165 taken 1109 times.
✗ Branch 164 → 167 not taken.
✓ Branch 165 → 166 taken 1109 times.
✗ Branch 166 → 167 not taken.
✓ Branch 166 → 168 taken 1109 times.
✗ Branch 169 → 170 not taken.
✓ Branch 169 → 177 taken 1109 times.
✗ Branch 170 → 171 not taken.
✗ Branch 171 → 172 not taken.
✗ Branch 172 → 173 not taken.
|
1109 | if (!query.exec() || !query.next()) return fail(query.lastError().text()); |
| 199 |
3/3✓ Branch 178 → 179 taken 2 times.
✓ Branch 178 → 187 taken 1107 times.
✓ Branch 182 → 183 taken 2 times.
|
1111 | if (superseded()) return fail(QStringLiteral("Display read superseded")); |
| 200 | 1107 | auto &header = result.header; | |
| 201 |
1/1✓ Branch 187 → 188 taken 1107 times.
|
1107 | readDisplayHeader(query, uid, folderId, header); |
| 202 |
1/1✓ Branch 188 → 189 taken 1107 times.
|
1107 | query.finish(); |
| 203 |
1/1✓ Branch 192 → 193 taken 1107 times.
|
1107 | query.prepare(QStringLiteral( |
| 204 | "SELECT CASE WHEN length(CAST(label AS BLOB)) <= 1048576 THEN label END, " | ||
| 205 | "length(CAST(label AS BLOB)) FROM mail_labels " | ||
| 206 | "WHERE header_id=:hid LIMIT 257")); | ||
| 207 |
1/1✓ Branch 200 → 201 taken 1107 times.
|
2214 | query.bindValue(QStringLiteral(":hid"), headerId); |
| 208 |
6/6✓ Branch 204 → 205 taken 1107 times.
✓ Branch 205 → 206 taken 1 time.
✓ Branch 205 → 213 taken 1106 times.
✓ Branch 206 → 207 taken 1 time.
✓ Branch 207 → 208 taken 1 time.
✓ Branch 208 → 209 taken 1 time.
|
1107 | if (!query.exec()) return fail(query.lastError().text()); |
| 209 | 1106 | qint64 metadataBytes = 0; | |
| 210 |
3/3✓ Branch 241 → 242 taken 1381 times.
✓ Branch 242 → 214 taken 277 times.
✓ Branch 242 → 243 taken 1104 times.
|
1381 | while (query.next()) { |
| 211 |
1/3✗ Branch 215 → 216 not taken.
✓ Branch 215 → 224 taken 277 times.
✗ Branch 219 → 220 not taken.
|
277 | if (superseded()) return fail(QStringLiteral("Display read superseded")); |
| 212 |
2/2✓ Branch 224 → 225 taken 277 times.
✓ Branch 225 → 226 taken 277 times.
|
277 | metadataBytes += query.value(1).toLongLong(); |
| 213 |
6/6✓ Branch 228 → 229 taken 276 times.
✓ Branch 228 → 230 taken 1 time.
✓ Branch 229 → 230 taken 1 time.
✓ Branch 229 → 231 taken 275 times.
✓ Branch 232 → 233 taken 2 times.
✓ Branch 232 → 235 taken 275 times.
|
277 | if (header.labels.size() >= MailDisplaySnapshot::MaxLabels || |
| 214 | metadataBytes > MailDisplaySnapshot::MaxMetadataBytes) { | ||
| 215 | 2 | result.outcome = MailDisplaySnapshot::Outcome::Limited; | |
| 216 |
1/1✓ Branch 233 → 234 taken 2 times.
|
2 | rollbackRead(); |
| 217 | 2 | return result; | |
| 218 | } | ||
| 219 |
3/3✓ Branch 235 → 236 taken 275 times.
✓ Branch 236 → 237 taken 275 times.
✓ Branch 237 → 238 taken 275 times.
|
275 | header.labels.append(query.value(0).toString()); |
| 220 | } | ||
| 221 |
3/7✓ Branch 243 → 244 taken 1104 times.
✓ Branch 244 → 245 taken 1104 times.
✗ Branch 246 → 247 not taken.
✓ Branch 246 → 254 taken 1104 times.
✗ Branch 247 → 248 not taken.
✗ Branch 248 → 249 not taken.
✗ Branch 249 → 250 not taken.
|
1104 | if (query.lastError().isValid()) return fail(query.lastError().text()); |
| 222 |
1/1✓ Branch 254 → 255 taken 1104 times.
|
1104 | query.finish(); |
| 223 |
1/3✗ Branch 256 → 257 not taken.
✓ Branch 256 → 265 taken 1104 times.
✗ Branch 260 → 261 not taken.
|
1104 | if (superseded()) return fail(QStringLiteral("Display read superseded")); |
| 224 |
2/2✓ Branch 265 → 266 taken 896 times.
✓ Branch 265 → 385 taken 208 times.
|
1104 | if (bodyPresent) { |
| 225 | // P90.8: load exactly the variant the reading mode can display. The | ||
| 226 | // skipped column reads as an empty value, which is precisely the | ||
| 227 | // emptiness signal the reader's mode fallback relies on. When the | ||
| 228 | // preferred variant is empty, both are loaded so the native/plain | ||
| 229 | // fallback keeps working. | ||
| 230 | const QString projection = | ||
| 231 |
1/1✓ Branch 266 → 267 taken 896 times.
|
896 | displayBodyProjection(preferred, plainBytes, htmlBytes, result.body.projection); |
| 232 |
2/2✓ Branch 270 → 271 taken 896 times.
✓ Branch 271 → 272 taken 896 times.
|
896 | query.prepare(QStringLiteral("SELECT ") + projection); |
| 233 |
1/1✓ Branch 280 → 281 taken 896 times.
|
1792 | query.bindValue(QStringLiteral(":hid"), headerId); |
| 234 |
5/11✓ Branch 284 → 285 taken 896 times.
✓ Branch 285 → 286 taken 896 times.
✗ Branch 285 → 288 not taken.
✓ Branch 286 → 287 taken 896 times.
✗ Branch 287 → 288 not taken.
✓ Branch 287 → 289 taken 896 times.
✗ Branch 290 → 291 not taken.
✓ Branch 290 → 298 taken 896 times.
✗ Branch 291 → 292 not taken.
✗ Branch 292 → 293 not taken.
✗ Branch 293 → 294 not taken.
|
896 | if (!query.exec() || !query.next()) return fail(query.lastError().text()); |
| 235 |
1/3✗ Branch 299 → 300 not taken.
✓ Branch 299 → 308 taken 896 times.
✗ Branch 303 → 304 not taken.
|
896 | if (superseded()) return fail(QStringLiteral("Display read superseded")); |
| 236 | 896 | result.body.uid = uid; | |
| 237 | 896 | result.body.cacheUidValidity = result.uidValidity; | |
| 238 | 896 | result.body.cacheAccountId = accountId; | |
| 239 |
1/1✓ Branch 309 → 310 taken 896 times.
|
896 | readDisplayBody(query, result.body); |
| 240 |
1/1✓ Branch 310 → 311 taken 896 times.
|
896 | query.finish(); |
| 241 |
1/1✓ Branch 314 → 315 taken 896 times.
|
896 | query.prepare(QStringLiteral( |
| 242 | "SELECT id, " | ||
| 243 | "CASE WHEN length(CAST(filename AS BLOB)) <= 1048576 THEN filename END, " | ||
| 244 | "CASE WHEN length(CAST(content_type AS BLOB)) <= 1048576 THEN content_type END, size, " | ||
| 245 | "CASE WHEN length(CAST(content_id AS BLOB)) <= 1048576 THEN content_id END, presentation, " | ||
| 246 | "COALESCE(length(CAST(filename AS BLOB)),0) + " | ||
| 247 | "COALESCE(length(CAST(content_type AS BLOB)),0) + " | ||
| 248 | "COALESCE(length(CAST(content_id AS BLOB)),0) " | ||
| 249 | "FROM attachments WHERE header_id=:hid ORDER BY id LIMIT 257")); | ||
| 250 |
1/1✓ Branch 322 → 323 taken 896 times.
|
1792 | query.bindValue(QStringLiteral(":hid"), headerId); |
| 251 |
2/6✓ Branch 326 → 327 taken 896 times.
✗ Branch 327 → 328 not taken.
✓ Branch 327 → 335 taken 896 times.
✗ Branch 328 → 329 not taken.
✗ Branch 329 → 330 not taken.
✗ Branch 330 → 331 not taken.
|
896 | if (!query.exec()) return fail(query.lastError().text()); |
| 252 |
3/3✓ Branch 365 → 366 taken 1174 times.
✓ Branch 366 → 336 taken 280 times.
✓ Branch 366 → 367 taken 894 times.
|
1174 | while (query.next()) { |
| 253 |
1/3✗ Branch 337 → 338 not taken.
✓ Branch 337 → 346 taken 280 times.
✗ Branch 341 → 342 not taken.
|
280 | if (superseded()) return fail(QStringLiteral("Display read superseded")); |
| 254 |
2/2✓ Branch 346 → 347 taken 280 times.
✓ Branch 347 → 348 taken 280 times.
|
280 | metadataBytes += query.value(6).toLongLong(); |
| 255 |
6/6✓ Branch 350 → 351 taken 279 times.
✓ Branch 350 → 352 taken 1 time.
✓ Branch 351 → 352 taken 1 time.
✓ Branch 351 → 353 taken 278 times.
✓ Branch 354 → 355 taken 2 times.
✓ Branch 354 → 359 taken 278 times.
|
280 | if (result.body.attachments.size() >= MailDisplaySnapshot::MaxAttachments || |
| 256 | metadataBytes > MailDisplaySnapshot::MaxMetadataBytes) { | ||
| 257 | 2 | result.body = {}; | |
| 258 | 2 | result.outcome = MailDisplaySnapshot::Outcome::Limited; | |
| 259 |
1/1✓ Branch 357 → 358 taken 2 times.
|
2 | rollbackRead(); |
| 260 | 2 | return result; | |
| 261 | } | ||
| 262 |
1/1✓ Branch 359 → 360 taken 278 times.
|
278 | Attachment attachment = readDisplayAttachment(query); |
| 263 |
1/1✓ Branch 361 → 362 taken 278 times.
|
278 | result.body.attachments.append(std::move(attachment)); |
| 264 | 278 | } | |
| 265 |
3/7✓ Branch 367 → 368 taken 894 times.
✓ Branch 368 → 369 taken 894 times.
✗ Branch 370 → 371 not taken.
✓ Branch 370 → 378 taken 894 times.
✗ Branch 371 → 372 not taken.
✗ Branch 372 → 373 not taken.
✗ Branch 373 → 374 not taken.
|
894 | if (query.lastError().isValid()) return fail(query.lastError().text()); |
| 266 |
1/1✓ Branch 378 → 379 taken 894 times.
|
894 | query.finish(); |
| 267 | 896 | } | |
| 268 |
1/3✗ Branch 386 → 387 not taken.
✓ Branch 386 → 395 taken 1102 times.
✗ Branch 390 → 391 not taken.
|
1102 | if (superseded()) return fail(QStringLiteral("Display read superseded")); |
| 269 |
2/6✓ Branch 395 → 396 taken 1102 times.
✗ Branch 396 → 397 not taken.
✓ Branch 396 → 404 taken 1102 times.
✗ Branch 397 → 398 not taken.
✗ Branch 398 → 399 not taken.
✗ Branch 399 → 400 not taken.
|
1102 | if (!m_db.commit()) return fail(m_db.lastError().text()); |
| 270 |
2/2✓ Branch 404 → 405 taken 894 times.
✓ Branch 404 → 406 taken 208 times.
|
1102 | result.outcome = bodyPresent ? MailDisplaySnapshot::Outcome::Ready |
| 271 | : MailDisplaySnapshot::Outcome::BodyMissing; | ||
| 272 | 1102 | return result; | |
| 273 | 1115 | } | |
| 274 | |||
| 275 | 198 | MailSelectionResult MailSnapshotReader::selectionSnapshot(const MailSelectionQuery &selection, | |
| 276 | const std::atomic_bool &cancelled) { | ||
| 277 | 198 | MailSelectionResult result; | |
| 278 | // F-41: explicit membership (visible-order range) needs only valid UIDs; | ||
| 279 | // the date/UID endpoints are required for the implicit range form. | ||
| 280 | bool explicitMembership = | ||
| 281 |
3/4✓ Branch 3 → 4 taken 7 times.
✓ Branch 3 → 7 taken 191 times.
✓ Branch 5 → 6 taken 7 times.
✗ Branch 5 → 7 not taken.
|
205 | !selection.explicitUids.isEmpty() && |
| 282 | 7 | selection.explicitUids.size() <= MailSelectionQuery::MaximumExplicitUids; | |
| 283 |
2/2✓ Branch 15 → 10 taken 35 times.
✓ Branch 15 → 16 taken 198 times.
|
233 | for (qint64 uid : selection.explicitUids) |
| 284 |
1/2✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 35 times.
|
35 | if (uid < 0) explicitMembership = false; |
| 285 |
1/2✓ Branch 19 → 20 taken 198 times.
✗ Branch 19 → 29 not taken.
|
396 | if (selection.accountId.isEmpty() || selection.accountId.size() > 4096 || |
| 286 |
5/8✓ Branch 17 → 18 taken 198 times.
✗ Branch 17 → 29 not taken.
✓ Branch 20 → 21 taken 198 times.
✗ Branch 20 → 29 not taken.
✓ Branch 22 → 23 taken 198 times.
✗ Branch 22 → 29 not taken.
✓ Branch 31 → 32 taken 1 time.
✓ Branch 31 → 39 taken 197 times.
|
594 | selection.folderId <= 0 || selection.text.size() > 1024 || |
| 287 |
4/4✓ Branch 23 → 24 taken 105 times.
✓ Branch 23 → 30 taken 93 times.
✓ Branch 24 → 25 taken 98 times.
✓ Branch 24 → 30 taken 7 times.
|
198 | (selection.range && !explicitMembership && |
| 288 |
2/2✓ Branch 25 → 26 taken 97 times.
✓ Branch 25 → 29 taken 1 time.
|
98 | (selection.first.folderId != selection.folderId || |
| 289 |
1/2✓ Branch 26 → 27 taken 97 times.
✗ Branch 26 → 29 not taken.
|
97 | selection.last.folderId != selection.folderId || |
| 290 |
2/4✓ Branch 27 → 28 taken 97 times.
✗ Branch 27 → 29 not taken.
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 30 taken 97 times.
|
97 | selection.first.uid < 0 || selection.last.uid < 0))) { |
| 291 | 1 | result.error = QStringLiteral("Invalid selection scope"); | |
| 292 | 1 | return result; | |
| 293 | } | ||
| 294 |
2/2✓ Branch 40 → 41 taken 1 time.
✓ Branch 40 → 42 taken 196 times.
|
197 | if (cancelled.load()) { result.outcome = MailSelectionResult::Outcome::Cancelled; return result; } |
| 295 |
2/5✓ Branch 42 → 43 taken 196 times.
✗ Branch 43 → 44 not taken.
✓ Branch 43 → 50 taken 196 times.
✗ Branch 44 → 45 not taken.
✗ Branch 45 → 46 not taken.
|
196 | if (!m_db.transaction()) { result.error = m_db.lastError().text(); return result; } |
| 296 | 392 | const auto rollback = qScopeGuard([this] { m_db.rollback(); }); | |
| 297 |
1/1✓ Branch 51 → 52 taken 196 times.
|
196 | QSqlQuery query(m_db); |
| 298 |
1/1✓ Branch 55 → 56 taken 196 times.
|
196 | query.prepare(QStringLiteral("SELECT CASE WHEN length(CAST(path AS BLOB))<=4096 THEN path END, uidvalidity " |
| 299 | "FROM folders WHERE account=:account AND id=:folder")); | ||
| 300 |
2/2✓ Branch 60 → 61 taken 196 times.
✓ Branch 61 → 62 taken 196 times.
|
196 | query.bindValue(":account", selection.accountId); |
| 301 |
2/2✓ Branch 66 → 67 taken 196 times.
✓ Branch 67 → 68 taken 196 times.
|
196 | query.bindValue(":folder", selection.folderId); |
| 302 |
2/5✓ Branch 70 → 71 taken 196 times.
✗ Branch 71 → 72 not taken.
✓ Branch 71 → 78 taken 196 times.
✗ Branch 72 → 73 not taken.
✗ Branch 73 → 74 not taken.
|
196 | if (!query.exec()) { result.error = query.lastError().text(); return result; } |
| 303 |
3/3✓ Branch 78 → 79 taken 196 times.
✓ Branch 79 → 80 taken 1 time.
✓ Branch 79 → 92 taken 195 times.
|
196 | if (!query.next()) { |
| 304 |
3/4✓ Branch 80 → 81 taken 1 time.
✓ Branch 81 → 82 taken 1 time.
✗ Branch 82 → 83 not taken.
✓ Branch 82 → 84 taken 1 time.
|
1 | result.outcome = query.lastError().isValid() ? MailSelectionResult::Outcome::Failed |
| 305 | : MailSelectionResult::Outcome::MissingFolder; | ||
| 306 |
2/2✓ Branch 86 → 87 taken 1 time.
✓ Branch 87 → 88 taken 1 time.
|
1 | result.error = query.lastError().text(); |
| 307 | 1 | return result; | |
| 308 | } | ||
| 309 |
3/4✓ Branch 92 → 93 taken 195 times.
✓ Branch 93 → 94 taken 195 times.
✗ Branch 95 → 96 not taken.
✓ Branch 95 → 97 taken 195 times.
|
195 | if (query.value(0).isNull()) { result.outcome = MailSelectionResult::Outcome::Limited; return result; } |
| 310 |
1/1✓ Branch 97 → 98 taken 195 times.
|
195 | auto snapshot = std::make_shared<MailSelectionSnapshot>(); |
| 311 | 195 | snapshot->accountId = selection.accountId; | |
| 312 | 195 | snapshot->folderId = selection.folderId; | |
| 313 |
2/2✓ Branch 101 → 102 taken 195 times.
✓ Branch 102 → 103 taken 195 times.
|
195 | snapshot->folderPath = query.value(0).toString(); |
| 314 |
2/2✓ Branch 107 → 108 taken 195 times.
✓ Branch 108 → 109 taken 195 times.
|
195 | snapshot->uidValidity = query.value(1).toUInt(); |
| 315 |
1/1✓ Branch 111 → 112 taken 195 times.
|
195 | query.finish(); |
| 316 |
3/3✓ Branch 112 → 113 taken 195 times.
✓ Branch 113 → 114 taken 195 times.
✓ Branch 114 → 115 taken 195 times.
|
195 | snapshot->storage = std::make_shared<QTemporaryDir>(QDir::tempPath() + "/mailjd-selection-XXXXXX"); |
| 317 |
2/4✓ Branch 122 → 123 taken 195 times.
✗ Branch 123 → 124 not taken.
✓ Branch 123 → 130 taken 195 times.
✗ Branch 126 → 127 not taken.
|
195 | if (!snapshot->storage->isValid()) { result.error = snapshot->storage->errorString(); return result; } |
| 318 |
2/2✓ Branch 131 → 132 taken 195 times.
✓ Branch 132 → 133 taken 195 times.
|
195 | QFile output(snapshot->path()); |
| 319 |
5/8✓ Branch 135 → 136 taken 195 times.
✓ Branch 136 → 137 taken 195 times.
✗ Branch 136 → 140 not taken.
✓ Branch 138 → 139 taken 195 times.
✗ Branch 139 → 140 not taken.
✓ Branch 139 → 141 taken 195 times.
✗ Branch 142 → 143 not taken.
✓ Branch 142 → 147 taken 195 times.
|
195 | if (!output.open(QIODevice::WriteOnly) || !output.setPermissions(QFile::ReadOwner | QFile::WriteOwner)) { |
| 320 | ✗ | result.error = output.errorString(); | |
| 321 | ✗ | return result; | |
| 322 | } | ||
| 323 | 6316 | const auto less = [](const MailSelectionKey &a, const MailSelectionKey &b) { | |
| 324 |
2/2✓ Branch 4 → 5 taken 918 times.
✓ Branch 4 → 7 taken 5398 times.
|
6316 | if (a.date.isValid() != b.date.isValid()) return !a.date.isValid(); |
| 325 |
6/6✓ Branch 8 → 9 taken 5251 times.
✓ Branch 8 → 13 taken 147 times.
✓ Branch 11 → 12 taken 5005 times.
✓ Branch 11 → 13 taken 246 times.
✓ Branch 14 → 15 taken 5005 times.
✓ Branch 14 → 18 taken 393 times.
|
5398 | if (a.date.isValid() && a.date.toSecsSinceEpoch() != b.date.toSecsSinceEpoch()) |
| 326 | 5005 | return a.date.toSecsSinceEpoch() < b.date.toSecsSinceEpoch(); | |
| 327 | 393 | return a.uid < b.uid; | |
| 328 | }; | ||
| 329 | 195 | auto low = selection.first, high = selection.last; | |
| 330 |
7/7✓ Branch 149 → 150 taken 104 times.
✓ Branch 149 → 153 taken 91 times.
✓ Branch 150 → 151 taken 104 times.
✓ Branch 151 → 152 taken 44 times.
✓ Branch 151 → 153 taken 60 times.
✓ Branch 154 → 155 taken 44 times.
✓ Branch 154 → 156 taken 151 times.
|
195 | if (selection.range && less(high, low)) std::swap(low, high); |
| 331 | // F-41: visible-order range — membership by explicit UID set instead of | ||
| 332 | // the date/UID span. | ||
| 333 | 195 | QSet<qint64> explicitSet; | |
| 334 |
2/2✓ Branch 157 → 158 taken 7 times.
✓ Branch 157 → 164 taken 188 times.
|
195 | if (explicitMembership) |
| 335 | 7 | explicitSet = QSet<qint64>(selection.explicitUids.cbegin(), | |
| 336 |
1/1✓ Branch 160 → 161 taken 7 times.
|
7 | selection.explicitUids.cend()); |
| 337 | 195 | const bool text = !selection.text.isEmpty(); | |
| 338 | 390 | QString sql = QStringLiteral("SELECT uid,date,") + (text | |
| 339 |
2/4✓ Branch 168 → 169 taken 25 times.
✓ Branch 168 → 172 taken 170 times.
✗ Branch 464 → 465 not taken.
✗ Branch 464 → 466 not taken.
|
610 | ? QStringLiteral("CASE WHEN COALESCE(length(CAST(subject AS BLOB)),0)<=1048576 THEN COALESCE(subject,'') END," |
| 340 | "CASE WHEN COALESCE(length(CAST(from_addr AS BLOB)),0)<=1048576 THEN COALESCE(from_addr,'') END ") | ||
| 341 |
6/8✓ Branch 178 → 179 taken 195 times.
✓ Branch 179 → 180 taken 195 times.
✓ Branch 184 → 185 taken 170 times.
✓ Branch 184 → 186 taken 25 times.
✓ Branch 186 → 187 taken 25 times.
✓ Branch 186 → 188 taken 170 times.
✗ Branch 460 → 461 not taken.
✗ Branch 460 → 462 not taken.
|
610 | : QStringLiteral("NULL,NULL ")) + QStringLiteral("FROM headers WHERE folder_id=:folder "); |
| 342 |
3/3✓ Branch 190 → 191 taken 17 times.
✓ Branch 190 → 198 taken 178 times.
✓ Branch 194 → 195 taken 17 times.
|
212 | if (selection.unreadOnly) sql += QStringLiteral("AND (flags & :seen)=0 "); |
| 343 |
3/3✓ Branch 198 → 199 taken 16 times.
✓ Branch 198 → 206 taken 179 times.
✓ Branch 202 → 203 taken 16 times.
|
211 | if (selection.starredOnly) sql += QStringLiteral("AND (flags & :flagged)!=0 "); |
| 344 |
3/3✓ Branch 206 → 207 taken 16 times.
✓ Branch 206 → 214 taken 179 times.
✓ Branch 210 → 211 taken 16 times.
|
211 | if (selection.attachmentsOnly) sql += QStringLiteral("AND has_attachments!=0 "); |
| 345 |
1/1✓ Branch 217 → 218 taken 195 times.
|
195 | sql += QStringLiteral("ORDER BY uid ASC"); |
| 346 |
1/1✓ Branch 220 → 221 taken 195 times.
|
195 | query.setForwardOnly(true); |
| 347 |
1/1✓ Branch 221 → 222 taken 195 times.
|
195 | query.prepare(sql); |
| 348 |
2/2✓ Branch 224 → 225 taken 195 times.
✓ Branch 225 → 226 taken 195 times.
|
195 | query.bindValue(":folder", selection.folderId); |
| 349 |
4/4✓ Branch 228 → 229 taken 17 times.
✓ Branch 228 → 236 taken 178 times.
✓ Branch 231 → 232 taken 17 times.
✓ Branch 232 → 233 taken 17 times.
|
212 | if (selection.unreadOnly) query.bindValue(":seen", int(MailFlag::Seen)); |
| 350 |
4/4✓ Branch 236 → 237 taken 16 times.
✓ Branch 236 → 244 taken 179 times.
✓ Branch 239 → 240 taken 16 times.
✓ Branch 240 → 241 taken 16 times.
|
211 | if (selection.starredOnly) query.bindValue(":flagged", int(MailFlag::Flagged)); |
| 351 |
2/5✓ Branch 244 → 245 taken 195 times.
✗ Branch 245 → 246 not taken.
✓ Branch 245 → 252 taken 195 times.
✗ Branch 246 → 247 not taken.
✗ Branch 247 → 248 not taken.
|
195 | if (!query.exec()) { result.error = query.lastError().text(); return result; } |
| 352 | 195 | QByteArray buffer; | |
| 353 |
1/1✓ Branch 252 → 253 taken 195 times.
|
195 | buffer.reserve(64 * 1024); |
| 354 |
3/3✓ Branch 348 → 349 taken 126133 times.
✓ Branch 349 → 254 taken 125940 times.
✓ Branch 349 → 350 taken 193 times.
|
126133 | while (query.next()) { |
| 355 |
2/2✓ Branch 255 → 256 taken 1 time.
✓ Branch 255 → 257 taken 125939 times.
|
125941 | if (cancelled.load()) { result.outcome = MailSelectionResult::Outcome::Cancelled; return result; } |
| 356 | 125939 | MailSelectionKey key; | |
| 357 |
2/2✓ Branch 258 → 259 taken 125939 times.
✓ Branch 259 → 260 taken 125939 times.
|
125939 | key.uid = query.value(0).toLongLong(); |
| 358 |
1/2✗ Branch 261 → 262 not taken.
✓ Branch 261 → 269 taken 125939 times.
|
125939 | if (key.uid < 0) { result.error = QStringLiteral("Invalid cached mail identity"); return result; } |
| 359 |
7/7✓ Branch 269 → 270 taken 125939 times.
✓ Branch 270 → 271 taken 125939 times.
✓ Branch 272 → 273 taken 125634 times.
✓ Branch 272 → 280 taken 305 times.
✓ Branch 273 → 274 taken 125634 times.
✓ Branch 274 → 275 taken 125634 times.
✓ Branch 275 → 276 taken 125634 times.
|
125939 | if (!query.value(1).isNull()) key.date = QDateTime::fromSecsSinceEpoch(query.value(1).toLongLong()); |
| 360 |
2/2✓ Branch 280 → 281 taken 2906 times.
✓ Branch 280 → 284 taken 123033 times.
|
125939 | if (explicitMembership) { |
| 361 |
2/2✓ Branch 282 → 283 taken 2871 times.
✓ Branch 282 → 293 taken 35 times.
|
2906 | if (!explicitSet.contains(key.uid)) continue; |
| 362 |
10/10✓ Branch 284 → 285 taken 4574 times.
✓ Branch 284 → 290 taken 118459 times.
✓ Branch 285 → 286 taken 4574 times.
✓ Branch 286 → 287 taken 1638 times.
✓ Branch 286 → 289 taken 2936 times.
✓ Branch 287 → 288 taken 1638 times.
✓ Branch 288 → 289 taken 962 times.
✓ Branch 288 → 290 taken 676 times.
✓ Branch 291 → 292 taken 3898 times.
✓ Branch 291 → 293 taken 119135 times.
|
123033 | } else if (selection.range && (less(key, low) || less(high, key))) { |
| 363 | 3898 | continue; | |
| 364 | } | ||
| 365 |
2/2✓ Branch 293 → 294 taken 151 times.
✓ Branch 293 → 328 taken 119019 times.
|
119170 | if (text) { |
| 366 |
12/18✓ Branch 294 → 295 taken 151 times.
✓ Branch 295 → 296 taken 151 times.
✓ Branch 296 → 297 taken 150 times.
✓ Branch 296 → 300 taken 1 time.
✓ Branch 297 → 298 taken 150 times.
✓ Branch 298 → 299 taken 150 times.
✗ Branch 299 → 300 not taken.
✓ Branch 299 → 301 taken 150 times.
✓ Branch 302 → 303 taken 150 times.
✓ Branch 302 → 304 taken 1 time.
✓ Branch 304 → 305 taken 151 times.
✗ Branch 304 → 306 not taken.
✓ Branch 306 → 307 taken 1 time.
✓ Branch 306 → 308 taken 150 times.
✗ Branch 535 → 536 not taken.
✗ Branch 535 → 537 not taken.
✗ Branch 539 → 540 not taken.
✗ Branch 539 → 541 not taken.
|
151 | if (query.value(2).isNull() || query.value(3).isNull()) { |
| 367 | 1 | result.outcome = MailSelectionResult::Outcome::Limited; | |
| 368 | 1 | return result; // Never return a partial, silently narrowed selection. | |
| 369 | } | ||
| 370 |
8/13✓ Branch 308 → 309 taken 150 times.
✓ Branch 309 → 310 taken 150 times.
✓ Branch 310 → 311 taken 150 times.
✓ Branch 311 → 312 taken 138 times.
✓ Branch 311 → 317 taken 12 times.
✓ Branch 324 → 325 taken 150 times.
✗ Branch 324 → 326 not taken.
✓ Branch 326 → 327 taken 112 times.
✓ Branch 326 → 328 taken 38 times.
✗ Branch 551 → 552 not taken.
✗ Branch 551 → 553 not taken.
✗ Branch 555 → 556 not taken.
✗ Branch 555 → 557 not taken.
|
438 | if (!query.value(2).toString().contains(selection.text, Qt::CaseInsensitive) && |
| 371 |
10/15✓ Branch 312 → 313 taken 138 times.
✓ Branch 313 → 314 taken 138 times.
✓ Branch 314 → 315 taken 138 times.
✓ Branch 315 → 316 taken 112 times.
✓ Branch 315 → 317 taken 26 times.
✓ Branch 318 → 319 taken 138 times.
✓ Branch 318 → 320 taken 12 times.
✓ Branch 320 → 321 taken 138 times.
✓ Branch 320 → 322 taken 12 times.
✓ Branch 322 → 323 taken 150 times.
✗ Branch 322 → 324 not taken.
✗ Branch 543 → 544 not taken.
✗ Branch 543 → 545 not taken.
✗ Branch 547 → 548 not taken.
✗ Branch 547 → 549 not taken.
|
400 | !query.value(3).toString().contains(selection.text, Qt::CaseInsensitive)) continue; |
| 372 | } | ||
| 373 | 119057 | const auto encoded = qToLittleEndian(key.uid); | |
| 374 |
1/1✓ Branch 329 → 330 taken 119057 times.
|
119057 | buffer.append(reinterpret_cast<const char *>(&encoded), sizeof(encoded)); |
| 375 | 119057 | ++snapshot->count; | |
| 376 |
2/2✓ Branch 332 → 333 taken 13 times.
✓ Branch 332 → 341 taken 119044 times.
|
119057 | if (buffer.size() == 64 * 1024) { |
| 377 |
2/4✓ Branch 333 → 334 taken 13 times.
✗ Branch 335 → 336 not taken.
✓ Branch 335 → 340 taken 13 times.
✗ Branch 336 → 337 not taken.
|
13 | if (output.write(buffer) != buffer.size()) { result.error = output.errorString(); return result; } |
| 378 |
1/1✓ Branch 340 → 341 taken 13 times.
|
13 | buffer.clear(); |
| 379 | } | ||
| 380 | 125939 | } | |
| 381 |
3/6✓ Branch 350 → 351 taken 193 times.
✓ Branch 351 → 352 taken 193 times.
✗ Branch 353 → 354 not taken.
✓ Branch 353 → 360 taken 193 times.
✗ Branch 354 → 355 not taken.
✗ Branch 355 → 356 not taken.
|
193 | if (query.lastError().isValid()) { result.error = query.lastError().text(); return result; } |
| 382 |
5/9✓ Branch 360 → 361 taken 193 times.
✓ Branch 362 → 363 taken 193 times.
✗ Branch 362 → 365 not taken.
✓ Branch 363 → 364 taken 193 times.
✗ Branch 364 → 365 not taken.
✓ Branch 364 → 366 taken 193 times.
✗ Branch 367 → 368 not taken.
✓ Branch 367 → 372 taken 193 times.
✗ Branch 368 → 369 not taken.
|
193 | if (output.write(buffer) != buffer.size() || !output.flush()) { result.error = output.errorString(); return result; } |
| 383 |
1/1✓ Branch 372 → 373 taken 193 times.
|
193 | output.close(); |
| 384 |
1/1✓ Branch 373 → 374 taken 193 times.
|
193 | query.finish(); |
| 385 |
1/2✗ Branch 375 → 376 not taken.
✓ Branch 375 → 377 taken 193 times.
|
193 | if (cancelled.load()) { result.outcome = MailSelectionResult::Outcome::Cancelled; return result; } |
| 386 | 193 | result.snapshot = std::move(snapshot); | |
| 387 | 193 | result.outcome = MailSelectionResult::Outcome::Ready; | |
| 388 | 193 | return result; | |
| 389 | 196 | } | |
| 390 | |||
| 391 | 5 | MailSnapshotReader::FlagSnapshotDiff MailSnapshotReader::compareFlagSnapshot( | |
| 392 | const QString &account, const QString &folder, qint64 folderId, | ||
| 393 | quint32 uidValidity, const QList<QPair<qint64, quint32>> &serverFlags, | ||
| 394 | const QHash<qint64, quint32> &visibleFlags, | ||
| 395 | const QSet<qint64> &pendingFlagUids, | ||
| 396 | const std::atomic_bool &cancelled, | ||
| 397 | const QList<qint64> *membership) const { | ||
| 398 | 5 | FlagSnapshotDiff result; | |
| 399 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 5 times.
|
5 | if (cancelled.load(std::memory_order_relaxed)) { |
| 400 | ✗ | result.cancelled = true; | |
| 401 | ✗ | return result; | |
| 402 | } | ||
| 403 | // knownFlags: server flags we have; serverUids: every UID on the server | ||
| 404 | // (the snapshot's own UIDs unless a separate membership list is given). | ||
| 405 | 5 | QHash<qint64, quint32> knownFlags; | |
| 406 |
1/1✓ Branch 6 → 7 taken 5 times.
|
5 | knownFlags.reserve(serverFlags.size()); |
| 407 |
3/3✓ Branch 12 → 13 taken 308 times.
✓ Branch 15 → 9 taken 308 times.
✓ Branch 15 → 16 taken 5 times.
|
313 | for (const auto &[uid, flags] : serverFlags) knownFlags.insert(uid, flags); |
| 408 | 5 | QSet<qint64> serverUids; | |
| 409 |
2/2✓ Branch 17 → 18 taken 2 times.
✓ Branch 17 → 28 taken 3 times.
|
5 | if (membership) { |
| 410 |
1/1✓ Branch 19 → 20 taken 2 times.
|
2 | serverUids.reserve(membership->size()); |
| 411 |
3/3✓ Branch 23 → 24 taken 20 times.
✓ Branch 26 → 22 taken 20 times.
✓ Branch 26 → 27 taken 2 times.
|
22 | for (const qint64 uid : *membership) serverUids.insert(uid); |
| 412 | } else { | ||
| 413 |
1/1✓ Branch 29 → 30 taken 3 times.
|
3 | serverUids.reserve(knownFlags.size()); |
| 414 |
3/3✓ Branch 32 → 33 taken 306 times.
✓ Branch 36 → 31 taken 306 times.
✓ Branch 36 → 37 taken 3 times.
|
309 | for (auto it = knownFlags.cbegin(); it != knownFlags.cend(); ++it) serverUids.insert(it.key()); |
| 415 | } | ||
| 416 | 5 | qsizetype processed = 0; | |
| 417 |
2/2✓ Branch 71 → 39 taken 308 times.
✓ Branch 71 → 72 taken 5 times.
|
313 | for (auto it = knownFlags.cbegin(); it != knownFlags.cend(); ++it) { |
| 418 |
4/6✓ Branch 39 → 40 taken 1 time.
✓ Branch 39 → 43 taken 307 times.
✗ Branch 41 → 42 not taken.
✓ Branch 41 → 43 taken 1 time.
✗ Branch 44 → 45 not taken.
✓ Branch 44 → 46 taken 308 times.
|
308 | if ((++processed & 255) == 0 && cancelled.load(std::memory_order_relaxed)) { |
| 419 | ✗ | result.cancelled = true; | |
| 420 | ✗ | return result; | |
| 421 | } | ||
| 422 | 308 | const auto visible = visibleFlags.constFind(it.key()); | |
| 423 |
3/4✓ Branch 53 → 54 taken 1 time.
✓ Branch 53 → 61 taken 50 times.
✓ Branch 56 → 57 taken 1 time.
✗ Branch 56 → 61 not taken.
|
360 | if (visible != visibleFlags.cend() && visible.value() != it.value() && |
| 424 |
5/6✓ Branch 50 → 51 taken 51 times.
✓ Branch 50 → 61 taken 257 times.
✓ Branch 59 → 60 taken 1 time.
✗ Branch 59 → 61 not taken.
✓ Branch 62 → 63 taken 1 time.
✓ Branch 62 → 68 taken 307 times.
|
360 | serverUids.contains(it.key()) && !pendingFlagUids.contains(it.key())) |
| 425 |
1/1✓ Branch 66 → 67 taken 1 time.
|
1 | result.visibleChanged.append({it.key(), it.value()}); |
| 426 | } | ||
| 427 |
2/2✓ Branch 81 → 73 taken 51 times.
✓ Branch 81 → 82 taken 5 times.
|
56 | for (auto it = visibleFlags.cbegin(); it != visibleFlags.cend(); ++it) |
| 428 |
1/3✗ Branch 75 → 76 not taken.
✓ Branch 75 → 78 taken 51 times.
✗ Branch 77 → 78 not taken.
|
51 | if (!serverUids.contains(it.key())) result.visibleExpunged.append(it.key()); |
| 429 |
6/11✓ Branch 82 → 83 taken 5 times.
✓ Branch 83 → 84 taken 5 times.
✗ Branch 83 → 89 not taken.
✓ Branch 85 → 86 taken 5 times.
✗ Branch 85 → 89 not taken.
✓ Branch 87 → 88 taken 5 times.
✗ Branch 87 → 89 not taken.
✗ Branch 88 → 89 not taken.
✓ Branch 88 → 90 taken 5 times.
✗ Branch 91 → 92 not taken.
✓ Branch 91 → 102 taken 5 times.
|
5 | if (!m_db.isOpen() || account.isEmpty() || folder.isEmpty() || folderId <= 0) { |
| 430 | ✗ | result.error = QStringLiteral("Cache reader or folder identity is unavailable"); | |
| 431 | ✗ | result.missingUids = serverUids.values(); | |
| 432 | ✗ | return result; | |
| 433 | } | ||
| 434 |
1/1✓ Branch 102 → 103 taken 5 times.
|
5 | QSqlQuery identity(m_db); |
| 435 |
1/1✓ Branch 106 → 107 taken 5 times.
|
5 | identity.prepare(QStringLiteral( |
| 436 | "SELECT uidvalidity FROM folders WHERE id=? AND account=? AND path=?")); | ||
| 437 |
1/1✓ Branch 111 → 112 taken 5 times.
|
5 | identity.addBindValue(folderId); |
| 438 |
1/1✓ Branch 115 → 116 taken 5 times.
|
5 | identity.addBindValue(account); |
| 439 |
1/1✓ Branch 119 → 120 taken 5 times.
|
5 | identity.addBindValue(folder); |
| 440 |
2/3✓ Branch 121 → 122 taken 5 times.
✗ Branch 122 → 123 not taken.
✓ Branch 122 → 132 taken 5 times.
|
5 | if (!identity.exec()) { |
| 441 | ✗ | result.error = identity.lastError().text(); | |
| 442 | ✗ | result.missingUids = serverUids.values(); | |
| 443 | ✗ | return result; | |
| 444 | } | ||
| 445 |
7/13✓ Branch 132 → 133 taken 5 times.
✓ Branch 133 → 134 taken 5 times.
✗ Branch 133 → 137 not taken.
✓ Branch 134 → 135 taken 5 times.
✓ Branch 135 → 136 taken 5 times.
✗ Branch 136 → 137 not taken.
✓ Branch 136 → 138 taken 5 times.
✓ Branch 139 → 140 taken 5 times.
✗ Branch 139 → 141 not taken.
✗ Branch 141 → 142 not taken.
✓ Branch 141 → 143 taken 5 times.
✗ Branch 264 → 265 not taken.
✗ Branch 264 → 266 not taken.
|
5 | if (!identity.next() || identity.value(0).toUInt() != uidValidity) { |
| 446 | ✗ | result.stale = true; | |
| 447 | ✗ | return result; | |
| 448 | } | ||
| 449 |
1/1✓ Branch 143 → 144 taken 5 times.
|
5 | identity.finish(); |
| 450 |
1/1✓ Branch 144 → 145 taken 5 times.
|
5 | QSqlQuery cached(m_db); |
| 451 |
1/1✓ Branch 148 → 149 taken 5 times.
|
5 | cached.prepare(QStringLiteral("SELECT uid, flags FROM headers WHERE folder_id=?")); |
| 452 |
1/1✓ Branch 153 → 154 taken 5 times.
|
5 | cached.addBindValue(folderId); |
| 453 |
2/3✓ Branch 155 → 156 taken 5 times.
✗ Branch 156 → 157 not taken.
✓ Branch 156 → 166 taken 5 times.
|
5 | if (!cached.exec()) { |
| 454 | ✗ | result.error = cached.lastError().text(); | |
| 455 | ✗ | result.missingUids = serverUids.values(); | |
| 456 | ✗ | return result; | |
| 457 | } | ||
| 458 | 5 | qsizetype scanned = 0; | |
| 459 | 5 | QList<QPair<qint64, quint32>> offPageChanged; | |
| 460 | 5 | QList<qint64> offPageExpunged; | |
| 461 |
3/3✓ Branch 203 → 204 taken 333 times.
✓ Branch 204 → 167 taken 328 times.
✓ Branch 204 → 205 taken 5 times.
|
333 | while (cached.next()) { |
| 462 |
4/6✓ Branch 167 → 168 taken 1 time.
✓ Branch 167 → 171 taken 327 times.
✗ Branch 169 → 170 not taken.
✓ Branch 169 → 171 taken 1 time.
✗ Branch 172 → 173 not taken.
✓ Branch 172 → 174 taken 328 times.
|
328 | if ((++scanned & 255) == 0 && cancelled.load(std::memory_order_relaxed)) { |
| 463 | ✗ | result.cancelled = true; | |
| 464 | ✗ | return result; | |
| 465 | } | ||
| 466 |
2/2✓ Branch 174 → 175 taken 328 times.
✓ Branch 175 → 176 taken 328 times.
|
328 | const qint64 uid = cached.value(0).toLongLong(); |
| 467 |
1/1✓ Branch 177 → 178 taken 328 times.
|
328 | const bool present = serverUids.remove(uid); |
| 468 | // Visible rows are reconciled against the GUI's own snapshot above. | ||
| 469 |
2/2✓ Branch 179 → 180 taken 277 times.
✓ Branch 179 → 202 taken 51 times.
|
328 | if (!visibleFlags.contains(uid)) { |
| 470 | 277 | const auto known = knownFlags.constFind(uid); | |
| 471 |
2/2✓ Branch 181 → 182 taken 3 times.
✓ Branch 181 → 183 taken 274 times.
|
277 | if (!present) |
| 472 |
1/1✓ Branch 182 → 201 taken 3 times.
|
3 | offPageExpunged.append(uid); |
| 473 |
10/12✓ Branch 185 → 186 taken 257 times.
✓ Branch 185 → 193 taken 17 times.
✓ Branch 187 → 188 taken 257 times.
✓ Branch 188 → 189 taken 257 times.
✓ Branch 189 → 190 taken 5 times.
✓ Branch 189 → 193 taken 252 times.
✓ Branch 194 → 195 taken 257 times.
✓ Branch 194 → 196 taken 17 times.
✓ Branch 196 → 197 taken 5 times.
✓ Branch 196 → 201 taken 269 times.
✗ Branch 286 → 287 not taken.
✗ Branch 286 → 288 not taken.
|
279 | else if (known != knownFlags.cend() && known.value() != cached.value(1).toUInt() && |
| 474 |
1/2✓ Branch 191 → 192 taken 5 times.
✗ Branch 191 → 193 not taken.
|
5 | !pendingFlagUids.contains(uid)) |
| 475 |
1/1✓ Branch 199 → 200 taken 5 times.
|
5 | offPageChanged.append({uid, known.value()}); |
| 476 | } | ||
| 477 | } | ||
| 478 |
3/4✓ Branch 205 → 206 taken 5 times.
✓ Branch 206 → 207 taken 5 times.
✗ Branch 208 → 209 not taken.
✓ Branch 208 → 215 taken 5 times.
|
5 | if (cached.lastError().isValid()) { |
| 479 | ✗ | result.error = cached.lastError().text(); | |
| 480 | // A partial scan can only overstate missing membership. The old | ||
| 481 | // backfill path may refetch cached rows but cannot skip a real arrival. | ||
| 482 | // Off-page expunges need the complete scan and are not reported. | ||
| 483 | } else { | ||
| 484 | 5 | result.offPageChanged = std::move(offPageChanged); | |
| 485 | 5 | result.offPageExpunged = std::move(offPageExpunged); | |
| 486 | } | ||
| 487 |
1/1✓ Branch 219 → 220 taken 5 times.
|
5 | result.missingUids = serverUids.values(); |
| 488 | 5 | return result; | |
| 489 | 5 | } | |
| 490 | |||
| 491 | 32 | MailSnapshotReader::ComposeSourceSnapshot MailSnapshotReader::composeSourceSnapshot( | |
| 492 | const QString &accountId, qint64 folderId, qint64 uid, | ||
| 493 | quint32 uidValidity, | ||
| 494 | const std::atomic_bool &cancelled) { | ||
| 495 | 32 | ComposeSourceSnapshot result; | |
| 496 |
5/8✓ Branch 3 → 4 taken 32 times.
✓ Branch 5 → 6 taken 32 times.
✓ Branch 6 → 7 taken 32 times.
✗ Branch 6 → 12 not taken.
✓ Branch 8 → 9 taken 32 times.
✗ Branch 8 → 12 not taken.
✓ Branch 9 → 10 taken 32 times.
✗ Branch 9 → 12 not taken.
|
64 | if (!m_db.isOpen() || !m_db.driver() || accountId.isEmpty() || |
| 497 |
4/8✓ Branch 4 → 5 taken 32 times.
✗ Branch 4 → 12 not taken.
✓ Branch 10 → 11 taken 32 times.
✗ Branch 10 → 12 not taken.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 32 times.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 23 taken 32 times.
|
64 | folderId <= 0 || uid <= 0 || uidValidity == 0) { |
| 498 | ✗ | result.error = QStringLiteral("Invalid compose source identity"); | |
| 499 | ✗ | return result; | |
| 500 | } | ||
| 501 |
1/2✗ Branch 24 → 25 not taken.
✓ Branch 24 → 27 taken 32 times.
|
32 | if (cancelled.load(std::memory_order_relaxed)) return result; |
| 502 |
2/2✓ Branch 27 → 28 taken 32 times.
✓ Branch 28 → 29 taken 32 times.
|
32 | auto handle = m_db.driver()->handle(); |
| 503 |
7/13✓ Branch 29 → 30 taken 32 times.
✓ Branch 30 → 31 taken 32 times.
✗ Branch 30 → 35 not taken.
✓ Branch 31 → 32 taken 32 times.
✓ Branch 32 → 33 taken 32 times.
✗ Branch 34 → 35 not taken.
✓ Branch 34 → 36 taken 32 times.
✓ Branch 37 → 38 taken 32 times.
✗ Branch 37 → 39 not taken.
✗ Branch 39 → 40 not taken.
✓ Branch 39 → 48 taken 32 times.
✗ Branch 217 → 218 not taken.
✗ Branch 217 → 219 not taken.
|
32 | if (!handle.isValid() || QByteArray(handle.typeName()) != "sqlite3*") { |
| 504 | ✗ | result.error = QStringLiteral("Compose source requires interruptible SQLite"); | |
| 505 | ✗ | return result; | |
| 506 | } | ||
| 507 |
1/1✓ Branch 48 → 49 taken 32 times.
|
32 | auto *database = *static_cast<sqlite3 **>(handle.data()); |
| 508 |
1/1✓ Branch 50 → 51 taken 32 times.
|
32 | sqlite3_progress_handler(database, 1000, [](void *context) { |
| 509 | ✗ | return static_cast<const std::atomic_bool *>(context)->load( | |
| 510 | ✗ | std::memory_order_relaxed) ? 1 : 0; | |
| 511 | }, const_cast<std::atomic_bool *>(&cancelled)); | ||
| 512 | 96 | const auto clearProgress = qScopeGuard([database] { | |
| 513 | 32 | sqlite3_progress_handler(database, 0, nullptr, nullptr); | |
| 514 | 32 | }); | |
| 515 | ✗ | const auto rollback = [&] { | |
| 516 | ✗ | sqlite3_progress_handler(database, 0, nullptr, nullptr); | |
| 517 | ✗ | m_db.rollback(); | |
| 518 | 32 | }; | |
| 519 |
2/3✓ Branch 52 → 53 taken 32 times.
✗ Branch 53 → 54 not taken.
✓ Branch 53 → 61 taken 32 times.
|
32 | if (!m_db.transaction()) { |
| 520 | ✗ | result.error = m_db.lastError().text(); | |
| 521 | ✗ | return result; | |
| 522 | } | ||
| 523 |
1/1✓ Branch 61 → 62 taken 32 times.
|
32 | QSqlQuery query(m_db); |
| 524 |
1/1✓ Branch 65 → 66 taken 32 times.
|
32 | query.prepare(QStringLiteral( |
| 525 | "SELECT f.uidvalidity, " | ||
| 526 | "CASE WHEN length(CAST(h.subject AS BLOB))<=1048576 THEN h.subject END, " | ||
| 527 | "CASE WHEN length(CAST(h.from_addr AS BLOB))<=1048576 THEN h.from_addr END, " | ||
| 528 | "CASE WHEN length(CAST(h.to_addr AS BLOB))<=1048576 THEN h.to_addr END, " | ||
| 529 | "h.date, " | ||
| 530 | "CASE WHEN length(CAST(h.message_id AS BLOB))<=1048576 THEN h.message_id END, " | ||
| 531 | "CASE WHEN length(CAST(h.ref_ids AS BLOB))<=1048576 THEN h.ref_ids END, " | ||
| 532 | "substr(b.text_plain,1,262145), " | ||
| 533 | "substr(CAST(b.raw_body AS BLOB),1,65536), " | ||
| 534 | "COALESCE(length(CAST(h.subject AS BLOB)),0)+" | ||
| 535 | "COALESCE(length(CAST(h.from_addr AS BLOB)),0)+" | ||
| 536 | "COALESCE(length(CAST(h.to_addr AS BLOB)),0)+" | ||
| 537 | "COALESCE(length(CAST(h.message_id AS BLOB)),0)+" | ||
| 538 | "COALESCE(length(CAST(h.ref_ids AS BLOB)),0), b.header_id, " | ||
| 539 | "CASE WHEN length(CAST(b.text_html AS BLOB))<=1048576 THEN b.text_html END " | ||
| 540 | "FROM folders f JOIN headers h ON h.folder_id=f.id " | ||
| 541 | "LEFT JOIN bodies b ON b.header_id=h.id " | ||
| 542 | "WHERE f.account=? AND f.id=? AND h.uid=? AND f.uidvalidity=?")); | ||
| 543 |
1/1✓ Branch 70 → 71 taken 32 times.
|
32 | query.addBindValue(accountId); |
| 544 |
1/1✓ Branch 74 → 75 taken 32 times.
|
32 | query.addBindValue(folderId); |
| 545 |
1/1✓ Branch 78 → 79 taken 32 times.
|
32 | query.addBindValue(uid); |
| 546 |
1/1✓ Branch 82 → 83 taken 32 times.
|
32 | query.addBindValue(uidValidity); |
| 547 |
4/7✓ Branch 84 → 85 taken 32 times.
✓ Branch 85 → 86 taken 32 times.
✗ Branch 85 → 88 not taken.
✗ Branch 87 → 88 not taken.
✓ Branch 87 → 89 taken 32 times.
✗ Branch 90 → 91 not taken.
✓ Branch 90 → 99 taken 32 times.
|
32 | if (!query.exec() || cancelled.load(std::memory_order_relaxed)) { |
| 548 | ✗ | result.error = query.lastError().text(); | |
| 549 | ✗ | rollback(); | |
| 550 | ✗ | return result; | |
| 551 | } | ||
| 552 |
2/3✓ Branch 99 → 100 taken 32 times.
✗ Branch 100 → 101 not taken.
✓ Branch 100 → 115 taken 32 times.
|
32 | if (!query.next()) { |
| 553 | ✗ | result.outcome = query.lastError().isValid() | |
| 554 | ✗ | ? ComposeSourceSnapshot::Outcome::Failed | |
| 555 | : ComposeSourceSnapshot::Outcome::Missing; | ||
| 556 | ✗ | result.error = query.lastError().text(); | |
| 557 | ✗ | rollback(); | |
| 558 | ✗ | return result; | |
| 559 | } | ||
| 560 |
1/2✗ Branch 116 → 117 not taken.
✓ Branch 116 → 120 taken 32 times.
|
32 | if (cancelled.load(std::memory_order_relaxed)) { |
| 561 | ✗ | rollback(); | |
| 562 | ✗ | return result; | |
| 563 | } | ||
| 564 |
3/4✓ Branch 120 → 121 taken 32 times.
✓ Branch 121 → 122 taken 32 times.
✗ Branch 123 → 124 not taken.
✓ Branch 123 → 127 taken 32 times.
|
32 | if (query.value(9).toLongLong() > MailDisplaySnapshot::MaxMetadataBytes) { |
| 565 | ✗ | result.outcome = ComposeSourceSnapshot::Outcome::Limited; | |
| 566 | ✗ | rollback(); | |
| 567 | ✗ | return result; | |
| 568 | } | ||
| 569 |
2/2✓ Branch 127 → 128 taken 32 times.
✓ Branch 128 → 129 taken 32 times.
|
32 | result.uidValidity = query.value(0).toUInt(); |
| 570 | 32 | result.header.folderId = folderId; | |
| 571 | 32 | result.header.uid = uid; | |
| 572 |
2/2✓ Branch 130 → 131 taken 32 times.
✓ Branch 131 → 132 taken 32 times.
|
32 | result.header.subject = query.value(1).toString(); |
| 573 |
2/2✓ Branch 135 → 136 taken 32 times.
✓ Branch 136 → 137 taken 32 times.
|
32 | result.header.from = query.value(2).toString(); |
| 574 |
2/2✓ Branch 140 → 141 taken 32 times.
✓ Branch 141 → 142 taken 32 times.
|
32 | result.header.to = query.value(3).toString(); |
| 575 |
3/4✓ Branch 145 → 146 taken 32 times.
✓ Branch 146 → 147 taken 32 times.
✓ Branch 148 → 149 taken 32 times.
✗ Branch 148 → 156 not taken.
|
32 | if (!query.value(4).isNull()) |
| 576 |
3/3✓ Branch 149 → 150 taken 32 times.
✓ Branch 150 → 151 taken 32 times.
✓ Branch 151 → 152 taken 32 times.
|
32 | result.header.date = QDateTime::fromSecsSinceEpoch(query.value(4).toLongLong()); |
| 577 |
3/3✓ Branch 156 → 157 taken 32 times.
✓ Branch 157 → 158 taken 32 times.
✓ Branch 158 → 159 taken 32 times.
|
32 | result.header.messageId = MessageId::canonicalFirst(query.value(5).toString()); |
| 578 |
3/3✓ Branch 163 → 164 taken 32 times.
✓ Branch 164 → 165 taken 32 times.
✓ Branch 165 → 166 taken 32 times.
|
32 | result.header.references = decodeThreadIds(query.value(6).toString()); |
| 579 |
2/2✓ Branch 170 → 171 taken 32 times.
✓ Branch 171 → 172 taken 32 times.
|
32 | result.quoteText = query.value(7).toString(); |
| 580 |
2/2✓ Branch 175 → 176 taken 32 times.
✓ Branch 176 → 177 taken 32 times.
|
32 | result.envelopePrefix = query.value(8).toByteArray(); |
| 581 |
2/2✓ Branch 180 → 181 taken 32 times.
✓ Branch 181 → 182 taken 32 times.
|
32 | result.bodyPresent = !query.value(10).isNull(); |
| 582 |
2/2✓ Branch 183 → 184 taken 32 times.
✓ Branch 184 → 185 taken 32 times.
|
32 | result.quoteHtml = query.value(11).toString(); |
| 583 | 32 | constexpr qsizetype MaxQuoteCharacters = 262144; | |
| 584 |
1/2✗ Branch 189 → 190 not taken.
✓ Branch 189 → 195 taken 32 times.
|
32 | if (result.quoteText.size() > MaxQuoteCharacters) { |
| 585 | ✗ | result.quoteTruncated = true; | |
| 586 | ✗ | qsizetype keep = MaxQuoteCharacters; | |
| 587 | ✗ | if (result.quoteText.at(keep - 1).isHighSurrogate()) --keep; | |
| 588 | ✗ | result.quoteText.truncate(keep); | |
| 589 | } | ||
| 590 |
1/1✓ Branch 195 → 196 taken 32 times.
|
32 | query.finish(); |
| 591 |
1/2✗ Branch 197 → 198 not taken.
✓ Branch 197 → 201 taken 32 times.
|
32 | if (cancelled.load(std::memory_order_relaxed)) { |
| 592 | ✗ | rollback(); | |
| 593 | ✗ | return {}; | |
| 594 | } | ||
| 595 |
2/3✓ Branch 201 → 202 taken 32 times.
✗ Branch 202 → 203 not taken.
✓ Branch 202 → 210 taken 32 times.
|
32 | if (!m_db.commit()) { |
| 596 | ✗ | result.error = m_db.lastError().text(); | |
| 597 | ✗ | rollback(); | |
| 598 | ✗ | return result; | |
| 599 | } | ||
| 600 | 32 | result.outcome = ComposeSourceSnapshot::Outcome::Ready; | |
| 601 | 32 | return result; | |
| 602 | 32 | } | |
| 603 |