data/SettingsSyncModels.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <QByteArray> | ||
| 4 | #include <QDateTime> | ||
| 5 | #include <QJsonArray> | ||
| 6 | #include <QJsonDocument> | ||
| 7 | #include <QJsonObject> | ||
| 8 | #include <QList> | ||
| 9 | #include <QMap> | ||
| 10 | #include <QString> | ||
| 11 | #include <QStringList> | ||
| 12 | #include <utility> | ||
| 13 | |||
| 14 | // T-311: Settings synchronisation data model. | ||
| 15 | // Represents the complete set of syncable settings as a JSON payload | ||
| 16 | // stored inside a virtual IMAP mail message. | ||
| 17 | // | ||
| 18 | // The mail has: | ||
| 19 | // Subject: X-MailJD-Settings-Sync | ||
| 20 | // Content-Type: application/json; charset=utf-8 | ||
| 21 | // Body: JSON serialisation of this struct | ||
| 22 | |||
| 23 | struct SyncPayload { | ||
| 24 | static constexpr qsizetype MaxRfcMessageBytes = 1024 * 1024; | ||
| 25 | |||
| 26 | int version = 1; | ||
| 27 | QDateTime lastModified; | ||
| 28 | QString clientId; // UUID identifying this installation | ||
| 29 | |||
| 30 | // --- Syncable categories --- | ||
| 31 | |||
| 32 | QMap<QString, QString> folderIcons; // folderPath → MDI icon name | ||
| 33 | QMap<QString, QString> folderColors; // folderPath → #hex color | ||
| 34 | // SettingsCollector v2 key (accountId + path) → #hex color. Plain path keys | ||
| 35 | // remain readable for payloads created before account-aware synchronization. | ||
| 36 | QMap<QString, QString> calendarColors; | ||
| 37 | |||
| 38 | QStringList hiddenFolders; | ||
| 39 | |||
| 40 | struct WhitelistItem { | ||
| 41 | QString type; // "sender" or "domain" | ||
| 42 | QString value; // e.g. "user@example.com" or "example.com" | ||
| 43 | }; | ||
| 44 | QList<WhitelistItem> externalContentWhitelist; | ||
| 45 | |||
| 46 | struct CardDavAccount { | ||
| 47 | QString id; // UUID | ||
| 48 | QString serverUrl; // e.g. "https://cloud.example.com" | ||
| 49 | QString username; | ||
| 50 | // NOTE: password is intentionally NOT synced (security) | ||
| 51 | QStringList selectedBooks; // DAV paths | ||
| 52 | }; | ||
| 53 | QList<CardDavAccount> carddavAccounts; | ||
| 54 | |||
| 55 | // T-334: CalDAV calendar sync config (Sprint 32) | ||
| 56 | // Credentials are shared with CardDAV — referenced by account ID. | ||
| 57 | struct CalDavSyncConfig { | ||
| 58 | QString carddavAccountId; // References CardDavAccount.id | ||
| 59 | QStringList selectedCalendars; // DAV paths of selected calendars | ||
| 60 | QStringList readOnlyCalendars; // Subset marked read-only | ||
| 61 | // Sprint 73: per-config interval is LEGACY/deferred metadata only. The | ||
| 62 | // active CalDAV scheduler is the single global 'caldav/syncIntervalMin' | ||
| 63 | // key (one timer for all DAV accounts). This field is still round- | ||
| 64 | // tripped by settings sync for backward compatibility, but the current | ||
| 65 | // scheduler does NOT read it. Do not wire UI controls to it unless a | ||
| 66 | // per-account scheduler is implemented. | ||
| 67 | int syncIntervalMinutes = 15; | ||
| 68 | }; | ||
| 69 | QList<CalDavSyncConfig> caldavConfigs; | ||
| 70 | |||
| 71 | struct GeneralSettings { | ||
| 72 | QString defaultView = QStringLiteral("text"); | ||
| 73 | QString externalContent = QStringLiteral("block"); | ||
| 74 | QString language = QStringLiteral("auto"); | ||
| 75 | int carddavSyncInterval = 0; // minutes, 0 = off | ||
| 76 | int caldavSyncInterval = 15; // T-334: default minutes, 0 = off | ||
| 77 | }; | ||
| 78 | GeneralSettings general; | ||
| 79 | |||
| 80 | // Sprint 108 S108.5.4 (ComposeSettingsSync): the composer's identities | ||
| 81 | // (key "<account address>|<IMAP host>|<address>") and templates (key: the | ||
| 82 | // lower-case name); values are the synchronized fields as JSON objects. | ||
| 83 | // Parsing keeps at most MaxComposeEntries per map and only object values. | ||
| 84 | static constexpr int MaxComposeEntries = 200; | ||
| 85 | QMap<QString, QJsonObject> identities; | ||
| 86 | QMap<QString, QJsonObject> templates; | ||
| 87 | |||
| 88 | // Which categories are enabled for sync | ||
| 89 | QStringList enabledCategories; | ||
| 90 | |||
| 91 | // --- Serialisation --- | ||
| 92 | |||
| 93 | 404 | QJsonObject toJson() const { | |
| 94 |
1/1✓ Branch 2 → 3 taken 404 times.
|
404 | QJsonObject root; |
| 95 |
3/3✓ Branch 3 → 4 taken 404 times.
✓ Branch 7 → 8 taken 404 times.
✓ Branch 8 → 9 taken 404 times.
|
808 | root[QStringLiteral("version")] = version; |
| 96 |
1/1✓ Branch 18 → 19 taken 404 times.
|
808 | root[QStringLiteral("lastModified")] = |
| 97 |
4/4✓ Branch 12 → 13 taken 404 times.
✓ Branch 13 → 14 taken 404 times.
✓ Branch 14 → 15 taken 404 times.
✓ Branch 19 → 20 taken 404 times.
|
1212 | lastModified.toUTC().toString(Qt::ISODate); |
| 98 |
3/3✓ Branch 25 → 26 taken 404 times.
✓ Branch 29 → 30 taken 404 times.
✓ Branch 30 → 31 taken 404 times.
|
808 | root[QStringLiteral("clientId")] = clientId; |
| 99 | |||
| 100 |
1/1✓ Branch 34 → 35 taken 404 times.
|
404 | QJsonObject settings; |
| 101 | |||
| 102 | // folderIcons | ||
| 103 |
1/1✓ Branch 35 → 36 taken 404 times.
|
404 | QJsonObject icons; |
| 104 |
4/4✓ Branch 36 → 37 taken 404 times.
✓ Branch 45 → 46 taken 30680 times.
✓ Branch 47 → 38 taken 30276 times.
✓ Branch 47 → 48 taken 404 times.
|
30680 | for (auto it = folderIcons.constBegin(); it != folderIcons.constEnd(); ++it) |
| 105 |
3/3✓ Branch 39 → 40 taken 30276 times.
✓ Branch 41 → 42 taken 30276 times.
✓ Branch 42 → 43 taken 30276 times.
|
30276 | icons[it.key()] = it.value(); |
| 106 |
3/3✓ Branch 48 → 49 taken 404 times.
✓ Branch 52 → 53 taken 404 times.
✓ Branch 53 → 54 taken 404 times.
|
808 | settings[QStringLiteral("folderIcons")] = icons; |
| 107 | |||
| 108 | // folderColors | ||
| 109 |
1/1✓ Branch 57 → 58 taken 404 times.
|
404 | QJsonObject colors; |
| 110 |
4/4✓ Branch 58 → 59 taken 404 times.
✓ Branch 67 → 68 taken 454 times.
✓ Branch 69 → 60 taken 50 times.
✓ Branch 69 → 70 taken 404 times.
|
454 | for (auto it = folderColors.constBegin(); it != folderColors.constEnd(); |
| 111 | 50 | ++it) | |
| 112 |
3/3✓ Branch 61 → 62 taken 50 times.
✓ Branch 63 → 64 taken 50 times.
✓ Branch 64 → 65 taken 50 times.
|
50 | colors[it.key()] = it.value(); |
| 113 |
3/3✓ Branch 70 → 71 taken 404 times.
✓ Branch 74 → 75 taken 404 times.
✓ Branch 75 → 76 taken 404 times.
|
808 | settings[QStringLiteral("folderColors")] = colors; |
| 114 | |||
| 115 | // calendarColors | ||
| 116 |
1/1✓ Branch 79 → 80 taken 404 times.
|
404 | QJsonObject calColors; |
| 117 |
4/4✓ Branch 80 → 81 taken 404 times.
✓ Branch 89 → 90 taken 418 times.
✓ Branch 91 → 82 taken 14 times.
✓ Branch 91 → 92 taken 404 times.
|
418 | for (auto it = calendarColors.constBegin(); it != calendarColors.constEnd(); |
| 118 | 14 | ++it) | |
| 119 |
3/3✓ Branch 83 → 84 taken 14 times.
✓ Branch 85 → 86 taken 14 times.
✓ Branch 86 → 87 taken 14 times.
|
14 | calColors[it.key()] = it.value(); |
| 120 |
3/3✓ Branch 92 → 93 taken 404 times.
✓ Branch 96 → 97 taken 404 times.
✓ Branch 97 → 98 taken 404 times.
|
808 | settings[QStringLiteral("calendarColors")] = calColors; |
| 121 | |||
| 122 | // hiddenFolders | ||
| 123 |
1/1✓ Branch 101 → 102 taken 404 times.
|
404 | QJsonArray hidden; |
| 124 |
2/2✓ Branch 110 → 104 taken 52 times.
✓ Branch 110 → 111 taken 404 times.
|
456 | for (const auto &f : hiddenFolders) |
| 125 |
2/2✓ Branch 105 → 106 taken 52 times.
✓ Branch 106 → 107 taken 52 times.
|
52 | hidden.append(f); |
| 126 |
3/3✓ Branch 111 → 112 taken 404 times.
✓ Branch 115 → 116 taken 404 times.
✓ Branch 116 → 117 taken 404 times.
|
808 | settings[QStringLiteral("hiddenFolders")] = hidden; |
| 127 | |||
| 128 | // externalContentWhitelist | ||
| 129 |
1/1✓ Branch 120 → 121 taken 404 times.
|
404 | QJsonArray wl; |
| 130 |
2/2✓ Branch 149 → 123 taken 10 times.
✓ Branch 149 → 150 taken 404 times.
|
414 | for (const auto &item : externalContentWhitelist) { |
| 131 |
1/1✓ Branch 124 → 125 taken 10 times.
|
10 | QJsonObject obj; |
| 132 |
3/3✓ Branch 125 → 126 taken 10 times.
✓ Branch 129 → 130 taken 10 times.
✓ Branch 130 → 131 taken 10 times.
|
20 | obj[QStringLiteral("type")] = item.type; |
| 133 |
3/3✓ Branch 134 → 135 taken 10 times.
✓ Branch 138 → 139 taken 10 times.
✓ Branch 139 → 140 taken 10 times.
|
20 | obj[QStringLiteral("value")] = item.value; |
| 134 |
2/2✓ Branch 143 → 144 taken 10 times.
✓ Branch 144 → 145 taken 10 times.
|
10 | wl.append(obj); |
| 135 | 10 | } | |
| 136 |
3/3✓ Branch 150 → 151 taken 404 times.
✓ Branch 154 → 155 taken 404 times.
✓ Branch 155 → 156 taken 404 times.
|
808 | settings[QStringLiteral("externalContentWhitelist")] = wl; |
| 137 | |||
| 138 | // carddavAccounts (NO passwords!) | ||
| 139 |
1/1✓ Branch 159 → 160 taken 404 times.
|
404 | QJsonArray cdArr; |
| 140 |
2/2✓ Branch 217 → 162 taken 59 times.
✓ Branch 217 → 218 taken 404 times.
|
463 | for (const auto &acc : carddavAccounts) { |
| 141 |
1/1✓ Branch 163 → 164 taken 59 times.
|
59 | QJsonObject obj; |
| 142 |
3/3✓ Branch 164 → 165 taken 59 times.
✓ Branch 168 → 169 taken 59 times.
✓ Branch 169 → 170 taken 59 times.
|
118 | obj[QStringLiteral("id")] = acc.id; |
| 143 |
3/3✓ Branch 173 → 174 taken 59 times.
✓ Branch 177 → 178 taken 59 times.
✓ Branch 178 → 179 taken 59 times.
|
118 | obj[QStringLiteral("serverUrl")] = acc.serverUrl; |
| 144 |
3/3✓ Branch 182 → 183 taken 59 times.
✓ Branch 186 → 187 taken 59 times.
✓ Branch 187 → 188 taken 59 times.
|
118 | obj[QStringLiteral("username")] = acc.username; |
| 145 |
1/1✓ Branch 191 → 192 taken 59 times.
|
59 | QJsonArray books; |
| 146 |
2/2✓ Branch 200 → 194 taken 52 times.
✓ Branch 200 → 201 taken 59 times.
|
111 | for (const auto &b : acc.selectedBooks) |
| 147 |
2/2✓ Branch 195 → 196 taken 52 times.
✓ Branch 196 → 197 taken 52 times.
|
52 | books.append(b); |
| 148 |
3/3✓ Branch 201 → 202 taken 59 times.
✓ Branch 205 → 206 taken 59 times.
✓ Branch 206 → 207 taken 59 times.
|
118 | obj[QStringLiteral("selectedBooks")] = books; |
| 149 |
2/2✓ Branch 210 → 211 taken 59 times.
✓ Branch 211 → 212 taken 59 times.
|
59 | cdArr.append(obj); |
| 150 | 59 | } | |
| 151 |
3/3✓ Branch 218 → 219 taken 404 times.
✓ Branch 222 → 223 taken 404 times.
✓ Branch 223 → 224 taken 404 times.
|
808 | settings[QStringLiteral("carddavAccounts")] = cdArr; |
| 152 | |||
| 153 | // T-334: caldavConfigs (calendar sync tied to CardDAV accounts) | ||
| 154 |
1/1✓ Branch 227 → 228 taken 404 times.
|
404 | QJsonArray caArr; |
| 155 |
2/2✓ Branch 296 → 230 taken 52 times.
✓ Branch 296 → 297 taken 404 times.
|
456 | for (const auto &cfg : caldavConfigs) { |
| 156 |
1/1✓ Branch 231 → 232 taken 52 times.
|
52 | QJsonObject obj; |
| 157 |
3/3✓ Branch 232 → 233 taken 52 times.
✓ Branch 236 → 237 taken 52 times.
✓ Branch 237 → 238 taken 52 times.
|
104 | obj[QStringLiteral("carddavAccountId")] = cfg.carddavAccountId; |
| 158 |
1/1✓ Branch 241 → 242 taken 52 times.
|
52 | QJsonArray cals; |
| 159 |
2/2✓ Branch 250 → 244 taken 52 times.
✓ Branch 250 → 251 taken 52 times.
|
104 | for (const auto &c : cfg.selectedCalendars) |
| 160 |
2/2✓ Branch 245 → 246 taken 52 times.
✓ Branch 246 → 247 taken 52 times.
|
52 | cals.append(c); |
| 161 |
3/3✓ Branch 251 → 252 taken 52 times.
✓ Branch 255 → 256 taken 52 times.
✓ Branch 256 → 257 taken 52 times.
|
104 | obj[QStringLiteral("selectedCalendars")] = cals; |
| 162 |
1/1✓ Branch 260 → 261 taken 52 times.
|
52 | QJsonArray roCals; |
| 163 |
2/2✓ Branch 269 → 263 taken 6 times.
✓ Branch 269 → 270 taken 52 times.
|
58 | for (const auto &r : cfg.readOnlyCalendars) |
| 164 |
2/2✓ Branch 264 → 265 taken 6 times.
✓ Branch 265 → 266 taken 6 times.
|
6 | roCals.append(r); |
| 165 |
3/3✓ Branch 270 → 271 taken 52 times.
✓ Branch 274 → 275 taken 52 times.
✓ Branch 275 → 276 taken 52 times.
|
104 | obj[QStringLiteral("readOnlyCalendars")] = roCals; |
| 166 |
3/3✓ Branch 279 → 280 taken 52 times.
✓ Branch 283 → 284 taken 52 times.
✓ Branch 284 → 285 taken 52 times.
|
104 | obj[QStringLiteral("syncIntervalMinutes")] = cfg.syncIntervalMinutes; |
| 167 |
2/2✓ Branch 288 → 289 taken 52 times.
✓ Branch 289 → 290 taken 52 times.
|
52 | caArr.append(obj); |
| 168 | 52 | } | |
| 169 |
3/3✓ Branch 297 → 298 taken 404 times.
✓ Branch 301 → 302 taken 404 times.
✓ Branch 302 → 303 taken 404 times.
|
808 | settings[QStringLiteral("caldavConfigs")] = caArr; |
| 170 | |||
| 171 | // general | ||
| 172 |
1/1✓ Branch 306 → 307 taken 404 times.
|
404 | QJsonObject gen; |
| 173 |
3/3✓ Branch 307 → 308 taken 404 times.
✓ Branch 311 → 312 taken 404 times.
✓ Branch 312 → 313 taken 404 times.
|
808 | gen[QStringLiteral("defaultView")] = general.defaultView; |
| 174 |
3/3✓ Branch 316 → 317 taken 404 times.
✓ Branch 320 → 321 taken 404 times.
✓ Branch 321 → 322 taken 404 times.
|
808 | gen[QStringLiteral("externalContent")] = general.externalContent; |
| 175 |
3/3✓ Branch 325 → 326 taken 404 times.
✓ Branch 329 → 330 taken 404 times.
✓ Branch 330 → 331 taken 404 times.
|
808 | gen[QStringLiteral("language")] = general.language; |
| 176 |
3/3✓ Branch 334 → 335 taken 404 times.
✓ Branch 338 → 339 taken 404 times.
✓ Branch 339 → 340 taken 404 times.
|
808 | gen[QStringLiteral("carddavSyncInterval")] = general.carddavSyncInterval; |
| 177 |
3/3✓ Branch 343 → 344 taken 404 times.
✓ Branch 347 → 348 taken 404 times.
✓ Branch 348 → 349 taken 404 times.
|
808 | gen[QStringLiteral("caldavSyncInterval")] = general.caldavSyncInterval; |
| 178 |
3/3✓ Branch 352 → 353 taken 404 times.
✓ Branch 356 → 357 taken 404 times.
✓ Branch 357 → 358 taken 404 times.
|
808 | settings[QStringLiteral("general")] = gen; |
| 179 | |||
| 180 |
1/1✓ Branch 361 → 362 taken 404 times.
|
404 | QJsonObject identityObjects; |
| 181 |
4/4✓ Branch 362 → 363 taken 404 times.
✓ Branch 371 → 372 taken 416 times.
✓ Branch 373 → 364 taken 12 times.
✓ Branch 373 → 374 taken 404 times.
|
416 | for (auto it = identities.constBegin(); it != identities.constEnd(); ++it) |
| 182 |
3/3✓ Branch 365 → 366 taken 12 times.
✓ Branch 367 → 368 taken 12 times.
✓ Branch 368 → 369 taken 12 times.
|
12 | identityObjects[it.key()] = it.value(); |
| 183 |
3/3✓ Branch 374 → 375 taken 404 times.
✓ Branch 378 → 379 taken 404 times.
✓ Branch 379 → 380 taken 404 times.
|
808 | settings[QStringLiteral("identities")] = identityObjects; |
| 184 |
1/1✓ Branch 383 → 384 taken 404 times.
|
404 | QJsonObject templateObjects; |
| 185 |
4/4✓ Branch 384 → 385 taken 404 times.
✓ Branch 393 → 394 taken 423 times.
✓ Branch 395 → 386 taken 19 times.
✓ Branch 395 → 396 taken 404 times.
|
423 | for (auto it = templates.constBegin(); it != templates.constEnd(); ++it) |
| 186 |
3/3✓ Branch 387 → 388 taken 19 times.
✓ Branch 389 → 390 taken 19 times.
✓ Branch 390 → 391 taken 19 times.
|
19 | templateObjects[it.key()] = it.value(); |
| 187 |
3/3✓ Branch 396 → 397 taken 404 times.
✓ Branch 400 → 401 taken 404 times.
✓ Branch 401 → 402 taken 404 times.
|
808 | settings[QStringLiteral("templates")] = templateObjects; |
| 188 | |||
| 189 |
3/3✓ Branch 405 → 406 taken 404 times.
✓ Branch 409 → 410 taken 404 times.
✓ Branch 410 → 411 taken 404 times.
|
808 | root[QStringLiteral("settings")] = settings; |
| 190 | |||
| 191 | // enabledCategories | ||
| 192 |
1/1✓ Branch 414 → 415 taken 404 times.
|
404 | QJsonArray cats; |
| 193 |
2/2✓ Branch 423 → 417 taken 843 times.
✓ Branch 423 → 424 taken 404 times.
|
1247 | for (const auto &c : enabledCategories) |
| 194 |
2/2✓ Branch 418 → 419 taken 843 times.
✓ Branch 419 → 420 taken 843 times.
|
843 | cats.append(c); |
| 195 |
3/3✓ Branch 424 → 425 taken 404 times.
✓ Branch 428 → 429 taken 404 times.
✓ Branch 429 → 430 taken 404 times.
|
808 | root[QStringLiteral("enabledCategories")] = cats; |
| 196 | |||
| 197 | 404 | return root; | |
| 198 | 404 | } | |
| 199 | |||
| 200 | 169 | static SyncPayload fromJson(const QJsonObject &root) { | |
| 201 |
1/1✓ Branch 2 → 3 taken 169 times.
|
169 | SyncPayload p; |
| 202 |
2/2✓ Branch 6 → 7 taken 169 times.
✓ Branch 7 → 8 taken 169 times.
|
169 | p.version = root.value(QStringLiteral("version")).toInt(1); |
| 203 |
1/1✓ Branch 16 → 17 taken 169 times.
|
338 | p.lastModified = QDateTime::fromString( |
| 204 |
2/2✓ Branch 14 → 15 taken 169 times.
✓ Branch 15 → 16 taken 169 times.
|
507 | root.value(QStringLiteral("lastModified")).toString(), Qt::ISODate); |
| 205 |
2/2✓ Branch 26 → 27 taken 169 times.
✓ Branch 27 → 28 taken 169 times.
|
169 | p.clientId = root.value(QStringLiteral("clientId")).toString(); |
| 206 | |||
| 207 |
2/2✓ Branch 36 → 37 taken 169 times.
✓ Branch 37 → 38 taken 169 times.
|
169 | auto settings = root.value(QStringLiteral("settings")).toObject(); |
| 208 | |||
| 209 | // folderIcons | ||
| 210 |
2/2✓ Branch 44 → 45 taken 169 times.
✓ Branch 45 → 46 taken 169 times.
|
169 | auto icons = settings.value(QStringLiteral("folderIcons")).toObject(); |
| 211 |
3/3✓ Branch 60 → 61 taken 310 times.
✓ Branch 62 → 50 taken 141 times.
✓ Branch 62 → 63 taken 169 times.
|
310 | for (auto it = icons.constBegin(); it != icons.constEnd(); ++it) |
| 212 |
3/3✓ Branch 52 → 53 taken 141 times.
✓ Branch 53 → 54 taken 141 times.
✓ Branch 54 → 55 taken 141 times.
|
141 | p.folderIcons[it.key()] = it.value().toString(); |
| 213 | |||
| 214 | // folderColors | ||
| 215 |
2/2✓ Branch 66 → 67 taken 169 times.
✓ Branch 67 → 68 taken 169 times.
|
169 | auto colors = settings.value(QStringLiteral("folderColors")).toObject(); |
| 216 |
3/3✓ Branch 82 → 83 taken 204 times.
✓ Branch 84 → 72 taken 35 times.
✓ Branch 84 → 85 taken 169 times.
|
204 | for (auto it = colors.constBegin(); it != colors.constEnd(); ++it) |
| 217 |
3/3✓ Branch 74 → 75 taken 35 times.
✓ Branch 75 → 76 taken 35 times.
✓ Branch 76 → 77 taken 35 times.
|
35 | p.folderColors[it.key()] = it.value().toString(); |
| 218 | |||
| 219 | // calendarColors (backward-compatible: absent = empty) | ||
| 220 | auto calColors = | ||
| 221 |
2/2✓ Branch 88 → 89 taken 169 times.
✓ Branch 89 → 90 taken 169 times.
|
169 | settings.value(QStringLiteral("calendarColors")).toObject(); |
| 222 |
3/3✓ Branch 104 → 105 taken 175 times.
✓ Branch 106 → 94 taken 6 times.
✓ Branch 106 → 107 taken 169 times.
|
175 | for (auto it = calColors.constBegin(); it != calColors.constEnd(); ++it) |
| 223 |
3/3✓ Branch 96 → 97 taken 6 times.
✓ Branch 97 → 98 taken 6 times.
✓ Branch 98 → 99 taken 6 times.
|
6 | p.calendarColors[it.key()] = it.value().toString(); |
| 224 | |||
| 225 | // hiddenFolders | ||
| 226 |
2/2✓ Branch 110 → 111 taken 169 times.
✓ Branch 111 → 112 taken 169 times.
|
169 | auto hidden = settings.value(QStringLiteral("hiddenFolders")).toArray(); |
| 227 |
4/4✓ Branch 115 → 116 taken 169 times.
✓ Branch 116 → 117 taken 169 times.
✓ Branch 126 → 118 taken 36 times.
✓ Branch 126 → 127 taken 169 times.
|
205 | for (const auto &v : hidden) |
| 228 |
2/2✓ Branch 120 → 121 taken 36 times.
✓ Branch 121 → 122 taken 36 times.
|
36 | p.hiddenFolders.append(v.toString()); |
| 229 | |||
| 230 | // externalContentWhitelist | ||
| 231 | auto wl = | ||
| 232 |
2/2✓ Branch 130 → 131 taken 169 times.
✓ Branch 131 → 132 taken 169 times.
|
169 | settings.value(QStringLiteral("externalContentWhitelist")).toArray(); |
| 233 |
4/4✓ Branch 135 → 136 taken 169 times.
✓ Branch 136 → 137 taken 169 times.
✓ Branch 165 → 138 taken 8 times.
✓ Branch 165 → 166 taken 169 times.
|
177 | for (const auto &v : wl) { |
| 234 |
1/1✓ Branch 139 → 140 taken 8 times.
|
8 | auto obj = v.toObject(); |
| 235 | 8 | WhitelistItem item; | |
| 236 |
2/2✓ Branch 143 → 144 taken 8 times.
✓ Branch 144 → 145 taken 8 times.
|
8 | item.type = obj.value(QStringLiteral("type")).toString(); |
| 237 |
2/2✓ Branch 153 → 154 taken 8 times.
✓ Branch 154 → 155 taken 8 times.
|
8 | item.value = obj.value(QStringLiteral("value")).toString(); |
| 238 |
1/1✓ Branch 160 → 161 taken 8 times.
|
8 | p.externalContentWhitelist.append(item); |
| 239 | 8 | } | |
| 240 | |||
| 241 | // carddavAccounts | ||
| 242 | auto cdArr = | ||
| 243 |
2/2✓ Branch 169 → 170 taken 169 times.
✓ Branch 170 → 171 taken 169 times.
|
169 | settings.value(QStringLiteral("carddavAccounts")).toArray(); |
| 244 |
4/4✓ Branch 174 → 175 taken 169 times.
✓ Branch 175 → 176 taken 169 times.
✓ Branch 235 → 177 taken 40 times.
✓ Branch 235 → 236 taken 169 times.
|
209 | for (const auto &v : cdArr) { |
| 245 |
1/1✓ Branch 178 → 179 taken 40 times.
|
40 | auto obj = v.toObject(); |
| 246 | 40 | CardDavAccount acc; | |
| 247 |
2/2✓ Branch 182 → 183 taken 40 times.
✓ Branch 183 → 184 taken 40 times.
|
40 | acc.id = obj.value(QStringLiteral("id")).toString(); |
| 248 |
2/2✓ Branch 192 → 193 taken 40 times.
✓ Branch 193 → 194 taken 40 times.
|
40 | acc.serverUrl = obj.value(QStringLiteral("serverUrl")).toString(); |
| 249 |
2/2✓ Branch 202 → 203 taken 40 times.
✓ Branch 203 → 204 taken 40 times.
|
40 | acc.username = obj.value(QStringLiteral("username")).toString(); |
| 250 |
2/2✓ Branch 212 → 213 taken 40 times.
✓ Branch 213 → 214 taken 40 times.
|
40 | auto books = obj.value(QStringLiteral("selectedBooks")).toArray(); |
| 251 |
4/4✓ Branch 217 → 218 taken 40 times.
✓ Branch 218 → 219 taken 40 times.
✓ Branch 228 → 220 taken 38 times.
✓ Branch 228 → 229 taken 40 times.
|
78 | for (const auto &b : books) |
| 252 |
2/2✓ Branch 222 → 223 taken 38 times.
✓ Branch 223 → 224 taken 38 times.
|
38 | acc.selectedBooks.append(b.toString()); |
| 253 |
1/1✓ Branch 229 → 230 taken 40 times.
|
40 | p.carddavAccounts.append(acc); |
| 254 | 40 | } | |
| 255 | |||
| 256 | // T-334: caldavConfigs (backward-compatible: absent = empty list) | ||
| 257 | auto caArr = | ||
| 258 |
2/2✓ Branch 239 → 240 taken 169 times.
✓ Branch 240 → 241 taken 169 times.
|
169 | settings.value(QStringLiteral("caldavConfigs")).toArray(); |
| 259 |
4/4✓ Branch 244 → 245 taken 169 times.
✓ Branch 245 → 246 taken 169 times.
✓ Branch 314 → 247 taken 39 times.
✓ Branch 314 → 315 taken 169 times.
|
208 | for (const auto &v : caArr) { |
| 260 |
1/1✓ Branch 248 → 249 taken 39 times.
|
39 | auto obj = v.toObject(); |
| 261 | 39 | CalDavSyncConfig cfg; | |
| 262 | cfg.carddavAccountId = | ||
| 263 |
2/2✓ Branch 252 → 253 taken 39 times.
✓ Branch 253 → 254 taken 39 times.
|
39 | obj.value(QStringLiteral("carddavAccountId")).toString(); |
| 264 |
2/2✓ Branch 262 → 263 taken 39 times.
✓ Branch 263 → 264 taken 39 times.
|
39 | auto cals = obj.value(QStringLiteral("selectedCalendars")).toArray(); |
| 265 |
4/4✓ Branch 267 → 268 taken 39 times.
✓ Branch 268 → 269 taken 39 times.
✓ Branch 278 → 270 taken 33 times.
✓ Branch 278 → 279 taken 39 times.
|
72 | for (const auto &c : cals) |
| 266 |
2/2✓ Branch 272 → 273 taken 33 times.
✓ Branch 273 → 274 taken 33 times.
|
33 | cfg.selectedCalendars.append(c.toString()); |
| 267 |
2/2✓ Branch 282 → 283 taken 39 times.
✓ Branch 283 → 284 taken 39 times.
|
39 | auto roCals = obj.value(QStringLiteral("readOnlyCalendars")).toArray(); |
| 268 |
4/4✓ Branch 287 → 288 taken 39 times.
✓ Branch 288 → 289 taken 39 times.
✓ Branch 298 → 290 taken 4 times.
✓ Branch 298 → 299 taken 39 times.
|
43 | for (const auto &r : roCals) |
| 269 |
2/2✓ Branch 292 → 293 taken 4 times.
✓ Branch 293 → 294 taken 4 times.
|
4 | cfg.readOnlyCalendars.append(r.toString()); |
| 270 | 39 | cfg.syncIntervalMinutes = | |
| 271 |
2/2✓ Branch 302 → 303 taken 39 times.
✓ Branch 303 → 304 taken 39 times.
|
39 | obj.value(QStringLiteral("syncIntervalMinutes")).toInt(15); |
| 272 |
1/1✓ Branch 307 → 308 taken 39 times.
|
39 | p.caldavConfigs.append(cfg); |
| 273 | 39 | } | |
| 274 | |||
| 275 | // general | ||
| 276 |
2/2✓ Branch 318 → 319 taken 169 times.
✓ Branch 319 → 320 taken 169 times.
|
169 | auto gen = settings.value(QStringLiteral("general")).toObject(); |
| 277 |
3/3✓ Branch 323 → 324 taken 169 times.
✓ Branch 324 → 325 taken 156 times.
✓ Branch 324 → 387 taken 13 times.
|
169 | if (!gen.isEmpty()) { |
| 278 | p.general.defaultView = | ||
| 279 |
2/2✓ Branch 328 → 329 taken 156 times.
✓ Branch 332 → 333 taken 156 times.
|
312 | gen.value(QStringLiteral("defaultView")).toString(QStringLiteral("text")); |
| 280 | p.general.externalContent = | ||
| 281 |
2/2✓ Branch 343 → 344 taken 156 times.
✓ Branch 347 → 348 taken 156 times.
|
312 | gen.value(QStringLiteral("externalContent")).toString(QStringLiteral("block")); |
| 282 | p.general.language = | ||
| 283 |
2/2✓ Branch 358 → 359 taken 156 times.
✓ Branch 362 → 363 taken 156 times.
|
312 | gen.value(QStringLiteral("language")).toString(QStringLiteral("auto")); |
| 284 | 156 | p.general.carddavSyncInterval = | |
| 285 |
2/2✓ Branch 373 → 374 taken 156 times.
✓ Branch 374 → 375 taken 156 times.
|
156 | gen.value(QStringLiteral("carddavSyncInterval")).toInt(0); |
| 286 | 156 | p.general.caldavSyncInterval = | |
| 287 |
2/2✓ Branch 381 → 382 taken 156 times.
✓ Branch 382 → 383 taken 156 times.
|
156 | gen.value(QStringLiteral("caldavSyncInterval")).toInt(15); |
| 288 | } | ||
| 289 | |||
| 290 | // identities, templates (Sprint 108; absent = none) | ||
| 291 | 1183 | for (const auto &[name, target] : {std::pair{QStringLiteral("identities"), &p.identities}, | |
| 292 |
2/2✓ Branch 435 → 402 taken 338 times.
✓ Branch 435 → 436 taken 169 times.
|
1521 | std::pair{QStringLiteral("templates"), &p.templates}}) { |
| 293 |
2/2✓ Branch 404 → 405 taken 338 times.
✓ Branch 405 → 406 taken 338 times.
|
338 | const auto objects = settings.value(name).toObject(); |
| 294 |
3/3✓ Branch 430 → 431 taken 353 times.
✓ Branch 432 → 408 taken 15 times.
✓ Branch 432 → 433 taken 338 times.
|
353 | for (auto it = objects.constBegin(); it != objects.constEnd(); ++it) { |
| 295 |
2/3✓ Branch 408 → 409 taken 15 times.
✗ Branch 409 → 410 not taken.
✓ Branch 409 → 411 taken 15 times.
|
15 | if (target->size() >= MaxComposeEntries) break; |
| 296 |
5/11✓ Branch 413 → 414 taken 15 times.
✗ Branch 413 → 418 not taken.
✓ Branch 414 → 415 taken 15 times.
✓ Branch 416 → 417 taken 15 times.
✗ Branch 416 → 418 not taken.
✓ Branch 419 → 420 taken 15 times.
✗ Branch 419 → 421 not taken.
✓ Branch 421 → 422 taken 15 times.
✗ Branch 421 → 429 not taken.
✗ Branch 844 → 845 not taken.
✗ Branch 844 → 846 not taken.
|
15 | if (it.value().isObject() && it.key().size() <= 1024) |
| 297 |
3/3✓ Branch 423 → 424 taken 15 times.
✓ Branch 424 → 425 taken 15 times.
✓ Branch 425 → 426 taken 15 times.
|
15 | target->insert(it.key(), it.value().toObject()); |
| 298 | } | ||
| 299 | 845 | } | |
| 300 | |||
| 301 | // enabledCategories | ||
| 302 |
2/2✓ Branch 445 → 446 taken 169 times.
✓ Branch 446 → 447 taken 169 times.
|
169 | auto cats = root.value(QStringLiteral("enabledCategories")).toArray(); |
| 303 |
4/4✓ Branch 450 → 451 taken 169 times.
✓ Branch 451 → 452 taken 169 times.
✓ Branch 461 → 453 taken 413 times.
✓ Branch 461 → 462 taken 169 times.
|
582 | for (const auto &v : cats) |
| 304 |
2/2✓ Branch 455 → 456 taken 413 times.
✓ Branch 456 → 457 taken 413 times.
|
413 | p.enabledCategories.append(v.toString()); |
| 305 | |||
| 306 | 169 | return p; | |
| 307 | 169 | } | |
| 308 | |||
| 309 | // --- RFC-2822 message serialisation --- | ||
| 310 | |||
| 311 | // A header value without control characters. The client id comes from | ||
| 312 | // JSON, which may carry a raw CR/LF inside a string; written into the | ||
| 313 | // header it would end the header block early (Sprint 102 fuzz target | ||
| 314 | // settings_payload). | ||
| 315 | 126 | static QByteArray headerValue(const QString &value) { | |
| 316 | 126 | QString clean; | |
| 317 |
1/1✓ Branch 3 → 4 taken 126 times.
|
126 | clean.reserve(value.size()); |
| 318 |
2/2✓ Branch 15 → 6 taken 3197 times.
✓ Branch 15 → 16 taken 126 times.
|
3323 | for (const QChar character : value) |
| 319 |
5/6✓ Branch 7 → 8 taken 3193 times.
✓ Branch 7 → 11 taken 4 times.
✓ Branch 9 → 10 taken 3193 times.
✗ Branch 9 → 11 not taken.
✓ Branch 12 → 13 taken 3193 times.
✓ Branch 12 → 14 taken 4 times.
|
3197 | if (character.unicode() >= 0x20 && character.unicode() != 0x7f) |
| 320 |
1/1✓ Branch 13 → 14 taken 3193 times.
|
3193 | clean.append(character); |
| 321 |
1/1✓ Branch 16 → 17 taken 126 times.
|
252 | return clean.toUtf8(); |
| 322 | 126 | } | |
| 323 | |||
| 324 | // Build a minimal RFC-2822 message wrapping the JSON payload. | ||
| 325 | // This message is stored via IMAP APPEND in the sync folder. | ||
| 326 | 126 | QByteArray toRfcMessage() const { | |
| 327 | 126 | QByteArray msg; | |
| 328 |
1/1✓ Branch 2 → 3 taken 126 times.
|
126 | msg += "From: mailjd-settings-sync@localhost\r\n"; |
| 329 |
1/1✓ Branch 3 → 4 taken 126 times.
|
126 | msg += "Subject: X-MailJD-Settings-Sync\r\n"; |
| 330 | 126 | msg += "Date: " + | |
| 331 |
6/6✓ Branch 4 → 5 taken 126 times.
✓ Branch 5 → 6 taken 126 times.
✓ Branch 6 → 7 taken 126 times.
✓ Branch 7 → 8 taken 126 times.
✓ Branch 8 → 9 taken 126 times.
✓ Branch 9 → 10 taken 126 times.
|
252 | lastModified.toUTC().toString(Qt::RFC2822Date).toUtf8() + "\r\n"; |
| 332 |
1/1✓ Branch 15 → 16 taken 126 times.
|
126 | msg += "MIME-Version: 1.0\r\n"; |
| 333 |
1/1✓ Branch 16 → 17 taken 126 times.
|
126 | msg += "Content-Type: application/json; charset=utf-8\r\n"; |
| 334 |
1/1✓ Branch 17 → 18 taken 126 times.
|
126 | msg += "Content-Transfer-Encoding: 8bit\r\n"; |
| 335 |
4/4✓ Branch 18 → 19 taken 126 times.
✓ Branch 19 → 20 taken 126 times.
✓ Branch 20 → 21 taken 126 times.
✓ Branch 21 → 22 taken 126 times.
|
126 | msg += "X-MailJD-ClientId: " + headerValue(clientId) + "\r\n"; |
| 336 |
1/1✓ Branch 25 → 26 taken 126 times.
|
126 | msg += "\r\n"; // Header/body separator |
| 337 | |||
| 338 |
2/2✓ Branch 26 → 27 taken 126 times.
✓ Branch 27 → 28 taken 126 times.
|
126 | QJsonDocument doc(toJson()); |
| 339 |
2/2✓ Branch 29 → 30 taken 126 times.
✓ Branch 30 → 31 taken 126 times.
|
126 | msg += doc.toJson(QJsonDocument::Indented); |
| 340 | |||
| 341 | 126 | return msg; | |
| 342 | 126 | } | |
| 343 | |||
| 344 | // Sprint 102 S102.5.4: true when the message was delivered by mail | ||
| 345 | // transport (an MTA added a Received: header). MailJD uploads its payload | ||
| 346 | // with IMAP APPEND, which adds none; a sender cannot remove the MTA's | ||
| 347 | // header. Subaddressing (user+MailJD-Settings@…) can otherwise deliver a | ||
| 348 | // stranger's message into the sync folder. | ||
| 349 | 38 | static bool deliveredByMailTransport(const QByteArray &raw) { | |
| 350 | 38 | qsizetype end = raw.indexOf("\r\n\r\n"); | |
| 351 |
2/2✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 8 taken 37 times.
|
38 | if (end < 0) end = raw.indexOf("\n\n"); |
| 352 |
3/3✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 37 times.
✓ Branch 10 → 11 taken 37 times.
|
38 | const QByteArray header = end < 0 ? raw : raw.left(end); |
| 353 | 38 | qsizetype lineStart = 0; | |
| 354 |
2/2✓ Branch 23 → 12 taken 231 times.
✓ Branch 23 → 24 taken 34 times.
|
265 | while (lineStart < header.size()) { |
| 355 | 231 | qsizetype lineEnd = header.indexOf('\n', lineStart); | |
| 356 |
2/2✓ Branch 13 → 14 taken 35 times.
✓ Branch 13 → 15 taken 196 times.
|
231 | if (lineEnd < 0) lineEnd = header.size(); |
| 357 |
3/3✓ Branch 15 → 16 taken 231 times.
✓ Branch 19 → 20 taken 4 times.
✓ Branch 19 → 21 taken 227 times.
|
231 | if (header.mid(lineStart, 9).compare("Received:", Qt::CaseInsensitive) == 0) |
| 358 | 4 | return true; | |
| 359 | 227 | lineStart = lineEnd + 1; | |
| 360 | } | ||
| 361 | 34 | return false; | |
| 362 | 38 | } | |
| 363 | |||
| 364 | // Parse a SyncPayload from a raw RFC-2822 message (as fetched via IMAP). | ||
| 365 | 59 | static SyncPayload fromRfcMessage(const QByteArray &raw) { | |
| 366 |
2/2✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 18 taken 58 times.
|
59 | if (raw.size() > MaxRfcMessageBytes) |
| 367 | 1 | return {}; | |
| 368 | |||
| 369 | // Find header/body separator (blank line = \r\n\r\n or \n\n) | ||
| 370 | 58 | int bodyStart = raw.indexOf("\r\n\r\n"); | |
| 371 |
2/2✓ Branch 20 → 21 taken 53 times.
✓ Branch 20 → 22 taken 5 times.
|
58 | if (bodyStart >= 0) { |
| 372 | 53 | bodyStart += 4; | |
| 373 | } else { | ||
| 374 | 5 | bodyStart = raw.indexOf("\n\n"); | |
| 375 |
2/2✓ Branch 24 → 25 taken 2 times.
✓ Branch 24 → 26 taken 3 times.
|
5 | if (bodyStart >= 0) |
| 376 | 2 | bodyStart += 2; | |
| 377 | else | ||
| 378 | 3 | bodyStart = 0; // Fallback: treat entire content as body | |
| 379 | } | ||
| 380 | |||
| 381 |
1/1✓ Branch 27 → 28 taken 58 times.
|
58 | QByteArray body = raw.mid(bodyStart); |
| 382 | |||
| 383 |
1/1✓ Branch 28 → 29 taken 58 times.
|
58 | auto doc = QJsonDocument::fromJson(body); |
| 384 |
8/8✓ Branch 29 → 30 taken 58 times.
✓ Branch 30 → 31 taken 56 times.
✓ Branch 30 → 33 taken 2 times.
✓ Branch 31 → 32 taken 56 times.
✓ Branch 32 → 33 taken 1 time.
✓ Branch 32 → 34 taken 55 times.
✓ Branch 35 → 36 taken 3 times.
✓ Branch 35 → 50 taken 55 times.
|
58 | if (doc.isNull() || !doc.isObject()) |
| 385 | 3 | return {}; | |
| 386 | |||
| 387 |
2/2✓ Branch 50 → 51 taken 55 times.
✓ Branch 51 → 52 taken 55 times.
|
55 | SyncPayload p = fromJson(doc.object()); |
| 388 | |||
| 389 | // Also extract clientId from header if missing from JSON | ||
| 390 |
2/2✓ Branch 54 → 55 taken 5 times.
✓ Branch 54 → 76 taken 50 times.
|
55 | if (p.clientId.isEmpty()) { |
| 391 | // Parse X-MailJD-ClientId header | ||
| 392 |
1/1✓ Branch 55 → 56 taken 5 times.
|
5 | QByteArray header = raw.left(bodyStart); |
| 393 | 5 | int idx = header.indexOf("X-MailJD-ClientId:"); | |
| 394 |
2/2✓ Branch 58 → 59 taken 3 times.
✓ Branch 58 → 74 taken 2 times.
|
5 | if (idx >= 0) { |
| 395 | 3 | int lineEnd = header.indexOf('\n', idx); | |
| 396 |
1/2✗ Branch 60 → 61 not taken.
✓ Branch 60 → 63 taken 3 times.
|
3 | if (lineEnd < 0) |
| 397 | ✗ | lineEnd = header.size(); | |
| 398 | QByteArray val = | ||
| 399 |
2/2✓ Branch 63 → 64 taken 3 times.
✓ Branch 64 → 65 taken 3 times.
|
3 | header.mid(idx + 18, lineEnd - idx - 18).trimmed(); |
| 400 | // Remove trailing \r if present | ||
| 401 |
2/3✓ Branch 66 → 67 taken 3 times.
✗ Branch 67 → 68 not taken.
✓ Branch 67 → 69 taken 3 times.
|
3 | if (val.endsWith('\r')) |
| 402 | ✗ | val.chop(1); | |
| 403 |
1/1✓ Branch 69 → 70 taken 3 times.
|
3 | p.clientId = QString::fromUtf8(val); |
| 404 | 3 | } | |
| 405 | 5 | } | |
| 406 | |||
| 407 | 55 | return p; | |
| 408 | 74 | } | |
| 409 | |||
| 410 | // --- Default categories --- | ||
| 411 | |||
| 412 | 7054 | static QStringList allCategories() { | |
| 413 | return { | ||
| 414 | ✗ | QStringLiteral("folderIcons"), | |
| 415 | 7054 | QStringLiteral("folderColors"), | |
| 416 | 7054 | QStringLiteral("calendarColors"), | |
| 417 | 7054 | QStringLiteral("hiddenFolders"), | |
| 418 | 7054 | QStringLiteral("davAccounts"), | |
| 419 | 7054 | QStringLiteral("general"), | |
| 420 | 7054 | QStringLiteral("identities"), | |
| 421 | 7054 | QStringLiteral("templates"), | |
| 422 |
2/4✓ Branch 28 → 29 taken 56432 times.
✓ Branch 28 → 30 taken 7054 times.
✗ Branch 41 → 42 not taken.
✗ Branch 41 → 43 not taken.
|
70540 | }; |
| 423 | 63486 | } | |
| 424 | |||
| 425 | // The Widgets client stored CardDAV and CalDAV accounts as separate | ||
| 426 | // categories; both are one category ("davAccounts") since Sprint 30. | ||
| 427 | 6233 | static QString normalizedCategory(const QString &category) { | |
| 428 |
7/8✓ Branch 6 → 7 taken 6231 times.
✓ Branch 6 → 12 taken 2 times.
✓ Branch 11 → 12 taken 3 times.
✓ Branch 11 → 13 taken 6228 times.
✓ Branch 20 → 21 taken 6233 times.
✗ Branch 20 → 22 not taken.
✓ Branch 22 → 23 taken 5 times.
✓ Branch 22 → 28 taken 6228 times.
|
24930 | if (category == QStringLiteral("carddavAccounts") || |
| 429 |
5/6✓ Branch 14 → 15 taken 6231 times.
✓ Branch 14 → 16 taken 2 times.
✓ Branch 16 → 17 taken 6231 times.
✓ Branch 16 → 18 taken 2 times.
✓ Branch 18 → 19 taken 6233 times.
✗ Branch 18 → 20 not taken.
|
12464 | category == QStringLiteral("caldavAccounts")) |
| 430 | 5 | return QStringLiteral("davAccounts"); | |
| 431 | 6228 | return category; | |
| 432 | } | ||
| 433 | |||
| 434 | // Stored category policy as known categories: legacy names mapped, | ||
| 435 | // duplicates and unknown names (older or newer clients) dropped. | ||
| 436 | 832 | static QStringList normalizedCategories(const QStringList &categories) { | |
| 437 | 832 | QStringList result; | |
| 438 |
2/2✓ Branch 20 → 4 taken 5973 times.
✓ Branch 20 → 21 taken 832 times.
|
6805 | for (const auto &raw : categories) { |
| 439 |
1/1✓ Branch 5 → 6 taken 5973 times.
|
5973 | const auto category = normalizedCategory(raw); |
| 440 |
8/11✓ Branch 6 → 7 taken 5973 times.
✓ Branch 8 → 9 taken 5972 times.
✓ Branch 8 → 12 taken 1 time.
✓ Branch 10 → 11 taken 5971 times.
✓ Branch 10 → 12 taken 1 time.
✓ Branch 13 → 14 taken 5973 times.
✗ Branch 13 → 15 not taken.
✓ Branch 15 → 16 taken 5971 times.
✓ Branch 15 → 17 taken 2 times.
✗ Branch 23 → 24 not taken.
✗ Branch 23 → 25 not taken.
|
5973 | if (allCategories().contains(category) && !result.contains(category)) |
| 441 |
1/1✓ Branch 16 → 17 taken 5971 times.
|
5971 | result.append(category); |
| 442 | 5973 | } | |
| 443 | 832 | return result; | |
| 444 | ✗ | } | |
| 445 | }; | ||
| 446 |