presentation/UiStateStore.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "UiStateStore.h" | ||
| 2 | |||
| 3 | #include <algorithm> | ||
| 4 | #include "DesignTokens.h" | ||
| 5 | #include "WorkspaceState.h" | ||
| 6 | #include <QDir> | ||
| 7 | #include <QFile> | ||
| 8 | #include <QFileInfo> | ||
| 9 | #include <QJsonDocument> | ||
| 10 | #include <QJsonArray> | ||
| 11 | #include <QLockFile> | ||
| 12 | #include <QSaveFile> | ||
| 13 | #include <QSettings> | ||
| 14 | #include <cmath> | ||
| 15 | |||
| 16 |
1/1✓ Branch 5 → 6 taken 1613 times.
|
1613 | UiStateStore::UiStateStore(QString directory) : m_directory(std::move(directory)) {} |
| 17 | |||
| 18 | 3963 | QVariantMap UiStateStore::defaults() { | |
| 19 | ✗ | return {{"ui/schemaVersion", 1}, {"ui/layout", "classic"}, | |
| 20 | ✗ | {"ui/motion", "system"}, {"ui/fontScale", 1.0}, | |
| 21 | {"appearance/theme", "system"}, {"appearance/accentColor", ""}, {"view/defaultMode", "text"}, | ||
| 22 | {"view/threaded", false}, {"view/suggestions", false}, {"ui/composeWindowed", false}, | ||
| 23 | {"ui/closeToTray", true}, | ||
| 24 | // Sprint 93 S93.3.9 (ux.md §6): the calendar's remembered state. | ||
| 25 | ✗ | {"calendar/viewMode", "month"}, {"calendar/workWeek", false}, {"calendar/hourZoom", 48}, | |
| 26 | ✗ | {"calendar/hiddenCalendars", QStringList{}}, {"calendar/lastCalendar", ""}, | |
| 27 | ✗ | {"calendar/agendaPanel", false}, {"calendar/agendaWidth", 320}, {"calendar/agendaTasksHeight", 0}, {"calendar/agendaTasks", "today"}, | |
| 28 | ✗ | {"calendar/agendaTasksShown", true}, {"calendar/agendaTaskFilter", ""}, {"calendar/agendaTaskSort", "due"}, {"calendar/workdayStart", 8}, {"calendar/workdayEnd", 18}, | |
| 29 | ✗ | {"calendar/firstDayOfWeek", 0}, {"calendar/tasks", QVariantMap{}}, {"calendar/lastTaskList", ""}, | |
| 30 | // Sprint 108 D-22: the working days of *Suggest times* (Qt::DayOfWeek, Mon–Fri). | ||
| 31 |
2/4✓ Branch 55 → 56 taken 19815 times.
✓ Branch 55 → 57 taken 3963 times.
✗ Branch 78 → 79 not taken.
✗ Branch 78 → 80 not taken.
|
23778 | {"calendar/workdays", QVariantList{1, 2, 3, 4, 5}}, |
| 32 | // Sprint 100 SB-13: the default reminder of new tasks (0 none, 1 due day 09:00). | ||
| 33 | ✗ | {"calendar/taskDefaultReminder", 0}, | |
| 34 | // Sprint 98 S98.1.6 (D-9, D-20): command-bar history and recent folders. | ||
| 35 | 3963 | {"commandbar/history", QStringList{}}, {"commandbar/recentFolders", QVariantMap{}}, | |
| 36 | // Sprint 101 (D-26, D-30): the search timeline's band and its one-time hint. | ||
| 37 |
2/4✓ Branch 49 → 50 taken 138705 times.
✓ Branch 49 → 51 taken 3963 times.
✗ Branch 62 → 63 not taken.
✗ Branch 62 → 64 not taken.
|
146631 | {"search/timelineCollapsed", false}, {"search/timelineHintSeen", false}, {"search/grouped", false}}; |
| 38 | 11889 | } | |
| 39 | |||
| 40 | 726 | QVariantMap UiStateStore::normalized(const QVariantMap &values) { | |
| 41 |
1/1✓ Branch 2 → 3 taken 726 times.
|
726 | auto result = defaults(); |
| 42 | const QMap<QString, QStringList> choices = { | ||
| 43 | {"ui/layout", {"classic", "wide"}}, | ||
| 44 | {"ui/motion", {"system", "reduced", "full"}}, | ||
| 45 | {"appearance/theme", {"light", "dark", "system"}}, | ||
| 46 | {"view/defaultMode", {"text", "light", "html"}}, | ||
| 47 |
12/24✓ Branch 36 → 37 taken 3630 times.
✓ Branch 36 → 38 taken 726 times.
✓ Branch 40 → 41 taken 3630 times.
✓ Branch 40 → 42 taken 726 times.
✓ Branch 45 → 46 taken 2178 times.
✓ Branch 45 → 47 taken 726 times.
✓ Branch 50 → 51 taken 2178 times.
✓ Branch 50 → 52 taken 726 times.
✓ Branch 55 → 56 taken 2178 times.
✓ Branch 55 → 57 taken 726 times.
✓ Branch 60 → 61 taken 1452 times.
✓ Branch 60 → 62 taken 726 times.
✗ Branch 930 → 931 not taken.
✗ Branch 930 → 932 not taken.
✗ Branch 936 → 937 not taken.
✗ Branch 936 → 938 not taken.
✗ Branch 952 → 953 not taken.
✗ Branch 952 → 954 not taken.
✗ Branch 968 → 969 not taken.
✗ Branch 968 → 970 not taken.
✗ Branch 984 → 985 not taken.
✗ Branch 984 → 986 not taken.
✗ Branch 1000 → 1001 not taken.
✗ Branch 1000 → 1002 not taken.
|
15972 | {"calendar/viewMode", {"day", "week", "month", "year", "agenda"}}}; |
| 48 |
4/4✓ Branch 63 → 64 taken 726 times.
✓ Branch 82 → 83 taken 4356 times.
✓ Branch 84 → 65 taken 3630 times.
✓ Branch 84 → 85 taken 726 times.
|
4356 | for (auto it = choices.cbegin(); it != choices.cend(); ++it) { |
| 49 |
2/2✓ Branch 67 → 68 taken 3630 times.
✓ Branch 68 → 69 taken 3630 times.
|
3630 | const QString value = values.value(it.key()).toString(); |
| 50 |
3/3✓ Branch 73 → 74 taken 3620 times.
✓ Branch 73 → 80 taken 10 times.
✓ Branch 76 → 77 taken 3620 times.
|
3630 | if (it.value().contains(value)) result[it.key()] = value; |
| 51 | 3630 | } | |
| 52 | 726 | bool valid = false; | |
| 53 |
3/3✓ Branch 86 → 87 taken 726 times.
✓ Branch 87 → 88 taken 726 times.
✓ Branch 88 → 89 taken 726 times.
|
726 | const auto accent = values.value("appearance/accentColor").toString(); |
| 54 |
7/7✓ Branch 92 → 93 taken 726 times.
✓ Branch 93 → 94 taken 722 times.
✓ Branch 93 → 105 taken 4 times.
✓ Branch 94 → 95 taken 722 times.
✓ Branch 95 → 96 taken 722 times.
✓ Branch 97 → 98 taken 722 times.
✓ Branch 98 → 99 taken 722 times.
|
726 | if (DesignTokens::validAccentColor(accent)) result["appearance/accentColor"] = accent.trimmed().toLower(); |
| 55 |
2/2✓ Branch 106 → 107 taken 726 times.
✓ Branch 107 → 108 taken 726 times.
|
726 | const QVariant threaded = values.value("view/threaded"); |
| 56 |
7/7✓ Branch 110 → 111 taken 726 times.
✓ Branch 111 → 112 taken 726 times.
✓ Branch 112 → 113 taken 722 times.
✓ Branch 112 → 121 taken 4 times.
✓ Branch 113 → 114 taken 722 times.
✓ Branch 115 → 116 taken 722 times.
✓ Branch 116 → 117 taken 722 times.
|
726 | if (threaded.metaType().id() == QMetaType::Bool) result["view/threaded"] = threaded.toBool(); |
| 57 |
2/2✓ Branch 143 → 123 taken 2178 times.
✓ Branch 143 → 144 taken 726 times.
|
2904 | for (const char *key : {"search/timelineCollapsed", "search/timelineHintSeen", "search/grouped"}) { |
| 58 |
2/2✓ Branch 125 → 126 taken 2178 times.
✓ Branch 126 → 127 taken 2178 times.
|
2178 | const QVariant value = values.value(QString::fromLatin1(key)); |
| 59 |
7/7✓ Branch 129 → 130 taken 2178 times.
✓ Branch 130 → 131 taken 2178 times.
✓ Branch 131 → 132 taken 2172 times.
✓ Branch 131 → 141 taken 6 times.
✓ Branch 132 → 133 taken 2172 times.
✓ Branch 135 → 136 taken 2172 times.
✓ Branch 136 → 137 taken 2172 times.
|
2178 | if (value.metaType().id() == QMetaType::Bool) result[QString::fromLatin1(key)] = value.toBool(); |
| 60 | 2178 | } | |
| 61 |
2/2✓ Branch 145 → 146 taken 726 times.
✓ Branch 146 → 147 taken 726 times.
|
726 | const QVariant suggestions = values.value("view/suggestions"); |
| 62 |
7/7✓ Branch 149 → 150 taken 726 times.
✓ Branch 150 → 151 taken 726 times.
✓ Branch 151 → 152 taken 722 times.
✓ Branch 151 → 160 taken 4 times.
✓ Branch 152 → 153 taken 722 times.
✓ Branch 154 → 155 taken 722 times.
✓ Branch 155 → 156 taken 722 times.
|
726 | if (suggestions.metaType().id() == QMetaType::Bool) result["view/suggestions"] = suggestions.toBool(); |
| 63 |
2/2✓ Branch 161 → 162 taken 726 times.
✓ Branch 162 → 163 taken 726 times.
|
726 | const QVariant composeWindowed = values.value("ui/composeWindowed"); |
| 64 |
4/4✓ Branch 165 → 166 taken 726 times.
✓ Branch 166 → 167 taken 726 times.
✓ Branch 167 → 168 taken 724 times.
✓ Branch 167 → 176 taken 2 times.
|
726 | if (composeWindowed.metaType().id() == QMetaType::Bool) |
| 65 |
3/3✓ Branch 168 → 169 taken 724 times.
✓ Branch 170 → 171 taken 724 times.
✓ Branch 171 → 172 taken 724 times.
|
724 | result["ui/composeWindowed"] = composeWindowed.toBool(); |
| 66 |
2/2✓ Branch 177 → 178 taken 726 times.
✓ Branch 178 → 179 taken 726 times.
|
726 | const QVariant closeToTray = values.value("ui/closeToTray"); |
| 67 |
4/4✓ Branch 181 → 182 taken 726 times.
✓ Branch 182 → 183 taken 726 times.
✓ Branch 183 → 184 taken 724 times.
✓ Branch 183 → 192 taken 2 times.
|
726 | if (closeToTray.metaType().id() == QMetaType::Bool) |
| 68 |
3/3✓ Branch 184 → 185 taken 724 times.
✓ Branch 186 → 187 taken 724 times.
✓ Branch 187 → 188 taken 724 times.
|
724 | result["ui/closeToTray"] = closeToTray.toBool(); |
| 69 | // F93-20: working hours (start < end) and the first day of the week. | ||
| 70 |
3/3✓ Branch 193 → 194 taken 726 times.
✓ Branch 194 → 195 taken 726 times.
✓ Branch 195 → 196 taken 726 times.
|
726 | const int workdayStart = values.value("calendar/workdayStart").toInt(&valid); |
| 71 |
5/5✓ Branch 199 → 200 taken 724 times.
✓ Branch 199 → 208 taken 2 times.
✓ Branch 200 → 201 taken 724 times.
✓ Branch 202 → 203 taken 724 times.
✓ Branch 203 → 204 taken 724 times.
|
726 | if (valid) result["calendar/workdayStart"] = qBound(0, workdayStart, 22); |
| 72 | // The end is bounded even when only the start is stored (Sprint 102 fuzz | ||
| 73 | // target ui_state: start 22 with the default end 18). | ||
| 74 |
3/3✓ Branch 209 → 210 taken 726 times.
✓ Branch 210 → 211 taken 726 times.
✓ Branch 211 → 212 taken 726 times.
|
726 | const int workdayEnd = values.value("calendar/workdayEnd").toInt(&valid); |
| 75 |
5/5✓ Branch 222 → 223 taken 726 times.
✓ Branch 223 → 224 taken 726 times.
✓ Branch 224 → 225 taken 726 times.
✓ Branch 226 → 227 taken 726 times.
✓ Branch 227 → 228 taken 726 times.
|
2904 | result["calendar/workdayEnd"] = qBound(result["calendar/workdayStart"].toInt() + 1, |
| 76 |
8/10✓ Branch 215 → 216 taken 721 times.
✓ Branch 215 → 217 taken 5 times.
✓ Branch 217 → 218 taken 5 times.
✓ Branch 218 → 219 taken 5 times.
✓ Branch 219 → 220 taken 5 times.
✓ Branch 221 → 222 taken 726 times.
✓ Branch 232 → 233 taken 5 times.
✓ Branch 232 → 234 taken 721 times.
✗ Branch 1160 → 1161 not taken.
✗ Branch 1160 → 1162 not taken.
|
3630 | valid ? workdayEnd : result["calendar/workdayEnd"].toInt(), 24); |
| 77 |
3/3✓ Branch 235 → 236 taken 726 times.
✓ Branch 236 → 237 taken 726 times.
✓ Branch 237 → 238 taken 726 times.
|
726 | const int taskReminder = values.value("calendar/taskDefaultReminder").toInt(&valid); |
| 78 |
7/8✓ Branch 241 → 242 taken 724 times.
✓ Branch 241 → 251 taken 2 times.
✓ Branch 242 → 243 taken 8 times.
✓ Branch 242 → 244 taken 716 times.
✓ Branch 243 → 244 taken 8 times.
✗ Branch 243 → 251 not taken.
✓ Branch 245 → 246 taken 724 times.
✓ Branch 246 → 247 taken 724 times.
|
726 | if (valid && (taskReminder == 0 || taskReminder == 1)) result["calendar/taskDefaultReminder"] = taskReminder; |
| 79 | // Sprint 108 D-22: one to seven distinct days 1–7, sorted. | ||
| 80 |
6/6✓ Branch 252 → 253 taken 726 times.
✓ Branch 253 → 254 taken 726 times.
✓ Branch 254 → 255 taken 726 times.
✓ Branch 255 → 256 taken 726 times.
✓ Branch 259 → 260 taken 724 times.
✓ Branch 259 → 310 taken 2 times.
|
726 | if (values.value("calendar/workdays").metaType().id() == QMetaType::QVariantList) { |
| 81 | 724 | QList<int> days; | |
| 82 |
7/7✓ Branch 261 → 262 taken 724 times.
✓ Branch 262 → 263 taken 724 times.
✓ Branch 263 → 264 taken 724 times.
✓ Branch 267 → 268 taken 724 times.
✓ Branch 268 → 269 taken 724 times.
✓ Branch 283 → 270 taken 3662 times.
✓ Branch 283 → 284 taken 724 times.
|
4386 | for (const QVariant &day : values.value("calendar/workdays").toList()) { |
| 83 |
1/1✓ Branch 271 → 272 taken 3662 times.
|
3662 | const int value = day.toInt(&valid); |
| 84 |
6/11✓ Branch 272 → 273 taken 3662 times.
✗ Branch 272 → 278 not taken.
✓ Branch 273 → 274 taken 3662 times.
✗ Branch 273 → 278 not taken.
✓ Branch 274 → 275 taken 3662 times.
✗ Branch 274 → 278 not taken.
✓ Branch 276 → 277 taken 3662 times.
✗ Branch 276 → 278 not taken.
✓ Branch 279 → 280 taken 3662 times.
✗ Branch 279 → 281 not taken.
✓ Branch 280 → 281 taken 3662 times.
|
3662 | if (valid && value >= 1 && value <= 7 && !days.contains(value)) days.append(value); |
| 85 | 724 | } | |
| 86 |
3/3✓ Branch 285 → 286 taken 724 times.
✓ Branch 286 → 287 taken 724 times.
✓ Branch 287 → 288 taken 724 times.
|
724 | std::sort(days.begin(), days.end()); |
| 87 |
1/2✓ Branch 289 → 290 taken 724 times.
✗ Branch 289 → 308 not taken.
|
724 | if (!days.isEmpty()) { |
| 88 | 724 | QVariantList list; | |
| 89 |
5/5✓ Branch 290 → 291 taken 724 times.
✓ Branch 291 → 292 taken 724 times.
✓ Branch 295 → 296 taken 3662 times.
✓ Branch 299 → 293 taken 3662 times.
✓ Branch 299 → 300 taken 724 times.
|
4386 | for (const int day : days) list.append(day); |
| 90 |
2/2✓ Branch 301 → 302 taken 724 times.
✓ Branch 302 → 303 taken 724 times.
|
724 | result["calendar/workdays"] = list; |
| 91 | 724 | } | |
| 92 | 724 | } | |
| 93 |
3/3✓ Branch 311 → 312 taken 726 times.
✓ Branch 312 → 313 taken 726 times.
✓ Branch 313 → 314 taken 726 times.
|
726 | const int firstDay = values.value("calendar/firstDayOfWeek").toInt(&valid); |
| 94 |
7/8✓ Branch 317 → 318 taken 724 times.
✓ Branch 317 → 327 taken 2 times.
✓ Branch 318 → 319 taken 724 times.
✗ Branch 318 → 327 not taken.
✓ Branch 319 → 320 taken 720 times.
✓ Branch 319 → 327 taken 4 times.
✓ Branch 321 → 322 taken 720 times.
✓ Branch 322 → 323 taken 720 times.
|
726 | if (valid && firstDay >= 0 && firstDay <= 7) result["calendar/firstDayOfWeek"] = firstDay; |
| 95 | // F94-59: a smart list, "list:<key>" or "tag:<name>". | ||
| 96 |
3/3✓ Branch 328 → 329 taken 726 times.
✓ Branch 329 → 330 taken 726 times.
✓ Branch 330 → 331 taken 726 times.
|
726 | const QString agendaTasks = values.value("calendar/agendaTasks").toString(); |
| 97 |
7/14✓ Branch 340 → 341 taken 7 times.
✓ Branch 340 → 349 taken 719 times.
✓ Branch 357 → 358 taken 726 times.
✗ Branch 357 → 361 not taken.
✓ Branch 359 → 360 taken 2904 times.
✓ Branch 359 → 361 taken 726 times.
✓ Branch 361 → 362 taken 719 times.
✓ Branch 361 → 369 taken 7 times.
✗ Branch 1253 → 1254 not taken.
✗ Branch 1253 → 1255 not taken.
✗ Branch 1257 → 1258 not taken.
✗ Branch 1257 → 1261 not taken.
✗ Branch 1259 → 1260 not taken.
✗ Branch 1259 → 1261 not taken.
|
4363 | if (QStringList{"today", "week", "planned", "mine"}.contains(agendaTasks) || |
| 98 |
11/20✓ Branch 341 → 342 taken 7 times.
✓ Branch 342 → 343 taken 7 times.
✓ Branch 343 → 344 taken 7 times.
✗ Branch 343 → 347 not taken.
✓ Branch 344 → 345 taken 7 times.
✓ Branch 345 → 346 taken 7 times.
✗ Branch 346 → 347 not taken.
✓ Branch 346 → 350 taken 7 times.
✗ Branch 348 → 349 not taken.
✗ Branch 348 → 350 not taken.
✓ Branch 351 → 352 taken 7 times.
✓ Branch 351 → 353 taken 719 times.
✓ Branch 353 → 354 taken 7 times.
✓ Branch 353 → 355 taken 719 times.
✓ Branch 355 → 356 taken 726 times.
✗ Branch 355 → 357 not taken.
✗ Branch 1245 → 1246 not taken.
✗ Branch 1245 → 1247 not taken.
✗ Branch 1249 → 1250 not taken.
✗ Branch 1249 → 1251 not taken.
|
733 | ((agendaTasks.startsWith("list:") || agendaTasks.startsWith("tag:")) && agendaTasks.size() <= 2100)) |
| 99 |
2/2✓ Branch 363 → 364 taken 719 times.
✓ Branch 364 → 365 taken 719 times.
|
719 | result["calendar/agendaTasks"] = agendaTasks; |
| 100 | // F94-67: optional task section, its filter tokens and order. | ||
| 101 |
2/2✓ Branch 370 → 371 taken 726 times.
✓ Branch 371 → 372 taken 726 times.
|
726 | const QVariant agendaTasksShown = values.value("calendar/agendaTasksShown"); |
| 102 |
7/7✓ Branch 374 → 375 taken 726 times.
✓ Branch 375 → 376 taken 726 times.
✓ Branch 376 → 377 taken 724 times.
✓ Branch 376 → 385 taken 2 times.
✓ Branch 377 → 378 taken 724 times.
✓ Branch 379 → 380 taken 724 times.
✓ Branch 380 → 381 taken 724 times.
|
726 | if (agendaTasksShown.metaType().id() == QMetaType::Bool) result["calendar/agendaTasksShown"] = agendaTasksShown.toBool(); |
| 103 |
3/3✓ Branch 386 → 387 taken 726 times.
✓ Branch 387 → 388 taken 726 times.
✓ Branch 388 → 389 taken 726 times.
|
726 | const QString agendaTaskFilter = values.value("calendar/agendaTaskFilter").toString(); |
| 104 |
3/4✓ Branch 393 → 394 taken 726 times.
✗ Branch 393 → 401 not taken.
✓ Branch 395 → 396 taken 726 times.
✓ Branch 396 → 397 taken 726 times.
|
726 | if (agendaTaskFilter.size() <= 256) result["calendar/agendaTaskFilter"] = agendaTaskFilter; |
| 105 |
3/3✓ Branch 402 → 403 taken 726 times.
✓ Branch 403 → 404 taken 726 times.
✓ Branch 404 → 405 taken 726 times.
|
726 | const QString agendaTaskSort = values.value("calendar/agendaTaskSort").toString(); |
| 106 |
6/8✓ Branch 416 → 417 taken 2904 times.
✓ Branch 416 → 418 taken 726 times.
✓ Branch 418 → 419 taken 719 times.
✓ Branch 418 → 426 taken 7 times.
✓ Branch 420 → 421 taken 719 times.
✓ Branch 421 → 422 taken 719 times.
✗ Branch 1313 → 1314 not taken.
✗ Branch 1313 → 1315 not taken.
|
3630 | if (QStringList{"due", "priority", "title", "created"}.contains(agendaTaskSort)) result["calendar/agendaTaskSort"] = agendaTaskSort; |
| 107 |
3/3✓ Branch 427 → 428 taken 726 times.
✓ Branch 428 → 429 taken 726 times.
✓ Branch 429 → 430 taken 726 times.
|
726 | const int agendaWidth = values.value("calendar/agendaWidth").toInt(&valid); |
| 108 |
5/5✓ Branch 433 → 434 taken 724 times.
✓ Branch 433 → 442 taken 2 times.
✓ Branch 434 → 435 taken 724 times.
✓ Branch 436 → 437 taken 724 times.
✓ Branch 437 → 438 taken 724 times.
|
726 | if (valid) result["calendar/agendaWidth"] = qBound(240, agendaWidth, 640); |
| 109 |
3/3✓ Branch 443 → 444 taken 726 times.
✓ Branch 444 → 445 taken 726 times.
✓ Branch 445 → 446 taken 726 times.
|
726 | const int agendaTasksHeight = values.value("calendar/agendaTasksHeight").toInt(&valid); |
| 110 |
5/7✓ Branch 449 → 450 taken 724 times.
✓ Branch 449 → 461 taken 2 times.
✗ Branch 450 → 451 not taken.
✓ Branch 450 → 453 taken 724 times.
✗ Branch 451 → 452 not taken.
✓ Branch 455 → 456 taken 724 times.
✓ Branch 456 → 457 taken 724 times.
|
726 | if (valid) result["calendar/agendaTasksHeight"] = agendaTasksHeight <= 0 ? 0 : qBound(60, agendaTasksHeight, 2000); |
| 111 |
2/2✓ Branch 462 → 463 taken 726 times.
✓ Branch 463 → 464 taken 726 times.
|
726 | const QVariant agendaPanel = values.value("calendar/agendaPanel"); |
| 112 |
7/7✓ Branch 466 → 467 taken 726 times.
✓ Branch 467 → 468 taken 726 times.
✓ Branch 468 → 469 taken 724 times.
✓ Branch 468 → 477 taken 2 times.
✓ Branch 469 → 470 taken 724 times.
✓ Branch 471 → 472 taken 724 times.
✓ Branch 472 → 473 taken 724 times.
|
726 | if (agendaPanel.metaType().id() == QMetaType::Bool) result["calendar/agendaPanel"] = agendaPanel.toBool(); |
| 113 |
2/2✓ Branch 478 → 479 taken 726 times.
✓ Branch 479 → 480 taken 726 times.
|
726 | const QVariant workWeek = values.value("calendar/workWeek"); |
| 114 |
7/7✓ Branch 482 → 483 taken 726 times.
✓ Branch 483 → 484 taken 726 times.
✓ Branch 484 → 485 taken 722 times.
✓ Branch 484 → 493 taken 4 times.
✓ Branch 485 → 486 taken 722 times.
✓ Branch 487 → 488 taken 722 times.
✓ Branch 488 → 489 taken 722 times.
|
726 | if (workWeek.metaType().id() == QMetaType::Bool) result["calendar/workWeek"] = workWeek.toBool(); |
| 115 |
3/3✓ Branch 494 → 495 taken 726 times.
✓ Branch 495 → 496 taken 726 times.
✓ Branch 496 → 497 taken 726 times.
|
726 | const int zoom = values.value("calendar/hourZoom").toInt(&valid); |
| 116 |
5/5✓ Branch 500 → 501 taken 724 times.
✓ Branch 500 → 509 taken 2 times.
✓ Branch 501 → 502 taken 724 times.
✓ Branch 503 → 504 taken 724 times.
✓ Branch 504 → 505 taken 724 times.
|
726 | if (valid) result["calendar/hourZoom"] = qBound(32, zoom, 96); |
| 117 | // Calendar keys are "accountId\npath" (bounded, no control characters | ||
| 118 | // besides the separator). | ||
| 119 | 1476 | const auto boundedKey = [](const QString &key) { | |
| 120 |
5/6✓ Branch 3 → 4 taken 35 times.
✓ Branch 3 → 6 taken 1441 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 35 times.
✓ Branch 8 → 9 taken 1441 times.
✓ Branch 8 → 10 taken 35 times.
|
1476 | if (key.isEmpty() || key.size() > 2048) return false; |
| 121 |
2/2✓ Branch 23 → 12 taken 440 times.
✓ Branch 23 → 24 taken 30 times.
|
470 | for (const QChar ch : key) |
| 122 |
6/6✓ Branch 13 → 14 taken 19 times.
✓ Branch 13 → 19 taken 421 times.
✓ Branch 17 → 18 taken 5 times.
✓ Branch 17 → 19 taken 14 times.
✓ Branch 20 → 21 taken 5 times.
✓ Branch 20 → 22 taken 435 times.
|
440 | if (ch.category() == QChar::Other_Control && ch != QLatin1Char('\n')) return false; |
| 123 | 30 | return true; | |
| 124 | }; | ||
| 125 | 726 | QStringList hidden; | |
| 126 |
7/7✓ Branch 510 → 511 taken 726 times.
✓ Branch 511 → 512 taken 726 times.
✓ Branch 512 → 513 taken 726 times.
✓ Branch 516 → 517 taken 726 times.
✓ Branch 517 → 518 taken 726 times.
✓ Branch 532 → 519 taken 24 times.
✓ Branch 532 → 533 taken 726 times.
|
750 | for (const auto &value : values.value("calendar/hiddenCalendars").toStringList()) |
| 127 |
8/9✓ Branch 521 → 522 taken 14 times.
✓ Branch 521 → 527 taken 10 times.
✓ Branch 523 → 524 taken 14 times.
✗ Branch 523 → 527 not taken.
✓ Branch 525 → 526 taken 9 times.
✓ Branch 525 → 527 taken 5 times.
✓ Branch 528 → 529 taken 9 times.
✓ Branch 528 → 530 taken 15 times.
✓ Branch 529 → 530 taken 9 times.
|
750 | if (boundedKey(value) && hidden.size() < 256 && !hidden.contains(value)) hidden.append(value); |
| 128 |
2/2✓ Branch 535 → 536 taken 726 times.
✓ Branch 536 → 537 taken 726 times.
|
726 | result["calendar/hiddenCalendars"] = hidden; |
| 129 |
3/3✓ Branch 541 → 542 taken 726 times.
✓ Branch 542 → 543 taken 726 times.
✓ Branch 543 → 544 taken 726 times.
|
726 | const QString lastCalendar = values.value("calendar/lastCalendar").toString(); |
| 130 |
4/4✓ Branch 548 → 549 taken 14 times.
✓ Branch 548 → 556 taken 712 times.
✓ Branch 550 → 551 taken 14 times.
✓ Branch 551 → 552 taken 14 times.
|
726 | if (boundedKey(lastCalendar)) result["calendar/lastCalendar"] = lastCalendar; |
| 131 | // D-9: at most 50 lines of at most 1024 characters; recent folders per | ||
| 132 | // account (at most 20 paths, 64 accounts). CommandHistory re-checks | ||
| 133 | // every line against the catalog on restore. | ||
| 134 |
7/7✓ Branch 3 → 4 taken 2961 times.
✓ Branch 3 → 10 taken 2321 times.
✓ Branch 5 → 6 taken 2960 times.
✓ Branch 5 → 10 taken 1 time.
✓ Branch 7 → 8 taken 2960 times.
✓ Branch 8 → 9 taken 2958 times.
✓ Branch 8 → 10 taken 2 times.
|
5282 | const auto boundedText = [](const QString &text) { return !text.isEmpty() && text.size() <= 1024 && !text.contains('\n'); }; |
| 135 | 726 | QStringList history; | |
| 136 |
7/7✓ Branch 557 → 558 taken 726 times.
✓ Branch 558 → 559 taken 726 times.
✓ Branch 559 → 560 taken 726 times.
✓ Branch 563 → 564 taken 726 times.
✓ Branch 564 → 565 taken 726 times.
✓ Branch 579 → 566 taken 151 times.
✓ Branch 579 → 580 taken 726 times.
|
877 | for (const auto &line : values.value("commandbar/history").toStringList()) |
| 137 |
9/10✓ Branch 567 → 568 taken 151 times.
✓ Branch 568 → 569 taken 148 times.
✓ Branch 568 → 574 taken 3 times.
✓ Branch 570 → 571 taken 127 times.
✓ Branch 570 → 574 taken 21 times.
✓ Branch 572 → 573 taken 127 times.
✗ Branch 572 → 574 not taken.
✓ Branch 575 → 576 taken 127 times.
✓ Branch 575 → 577 taken 24 times.
✓ Branch 576 → 577 taken 127 times.
|
877 | if (boundedText(line) && history.size() < 50 && !history.contains(line)) history.append(line); |
| 138 |
2/2✓ Branch 582 → 583 taken 726 times.
✓ Branch 583 → 584 taken 726 times.
|
726 | result["commandbar/history"] = history; |
| 139 | 726 | QVariantMap recentFolders; | |
| 140 |
3/3✓ Branch 588 → 589 taken 726 times.
✓ Branch 589 → 590 taken 726 times.
✓ Branch 590 → 591 taken 726 times.
|
726 | const QVariantMap storedFolders = values.value("commandbar/recentFolders").toMap(); |
| 141 |
8/9✓ Branch 594 → 595 taken 726 times.
✓ Branch 631 → 632 taken 841 times.
✓ Branch 633 → 634 taken 115 times.
✓ Branch 633 → 637 taken 726 times.
✓ Branch 634 → 635 taken 115 times.
✓ Branch 635 → 636 taken 115 times.
✗ Branch 635 → 637 not taken.
✓ Branch 638 → 596 taken 115 times.
✓ Branch 638 → 639 taken 726 times.
|
841 | for (auto it = storedFolders.cbegin(); it != storedFolders.cend() && recentFolders.size() < 64; ++it) { |
| 142 |
3/3✓ Branch 597 → 598 taken 115 times.
✓ Branch 598 → 599 taken 1 time.
✓ Branch 598 → 600 taken 114 times.
|
115 | if (!boundedText(it.key())) continue; |
| 143 | 114 | QStringList paths; | |
| 144 |
5/5✓ Branch 601 → 602 taken 114 times.
✓ Branch 602 → 603 taken 114 times.
✓ Branch 603 → 604 taken 114 times.
✓ Branch 618 → 605 taken 220 times.
✓ Branch 618 → 619 taken 114 times.
|
334 | for (const auto &path : it.value().toStringList()) |
| 145 |
8/10✓ Branch 606 → 607 taken 220 times.
✓ Branch 607 → 608 taken 220 times.
✗ Branch 607 → 613 not taken.
✓ Branch 609 → 610 taken 210 times.
✓ Branch 609 → 613 taken 10 times.
✓ Branch 611 → 612 taken 210 times.
✗ Branch 611 → 613 not taken.
✓ Branch 614 → 615 taken 210 times.
✓ Branch 614 → 616 taken 10 times.
✓ Branch 615 → 616 taken 210 times.
|
334 | if (boundedText(path) && paths.size() < 20 && !paths.contains(path)) paths.append(path); |
| 146 |
2/3✓ Branch 621 → 622 taken 114 times.
✗ Branch 621 → 627 not taken.
✓ Branch 624 → 625 taken 114 times.
|
114 | if (!paths.isEmpty()) recentFolders.insert(it.key(), paths); |
| 147 | 114 | } | |
| 148 |
2/2✓ Branch 640 → 641 taken 726 times.
✓ Branch 641 → 642 taken 726 times.
|
726 | result["commandbar/recentFolders"] = recentFolders; |
| 149 |
3/3✓ Branch 646 → 647 taken 726 times.
✓ Branch 647 → 648 taken 726 times.
✓ Branch 648 → 649 taken 726 times.
|
726 | const double scale = values.value("ui/fontScale", 1.0).toDouble(&valid); |
| 150 |
9/9✓ Branch 652 → 653 taken 725 times.
✓ Branch 652 → 656 taken 1 time.
✓ Branch 654 → 655 taken 724 times.
✓ Branch 654 → 656 taken 1 time.
✓ Branch 657 → 658 taken 724 times.
✓ Branch 657 → 666 taken 2 times.
✓ Branch 658 → 659 taken 724 times.
✓ Branch 660 → 661 taken 724 times.
✓ Branch 661 → 662 taken 724 times.
|
726 | if (valid && std::isfinite(scale)) result["ui/fontScale"] = qBound(1.0, scale, 2.0); |
| 151 |
2/2✓ Branch 754 → 676 taken 1452 times.
✓ Branch 754 → 755 taken 726 times.
|
5082 | for (const auto &layout : {QStringLiteral("classic"), QStringLiteral("wide")}) { |
| 152 |
1/1✓ Branch 680 → 681 taken 1452 times.
|
2904 | const QString key = QStringLiteral("ui/layouts/%1/paneRatios").arg(layout); |
| 153 |
2/2✓ Branch 684 → 685 taken 1452 times.
✓ Branch 685 → 686 taken 1452 times.
|
1452 | const auto ratios = values.value(key).toList(); |
| 154 |
2/2✓ Branch 689 → 690 taken 726 times.
✓ Branch 689 → 691 taken 726 times.
|
1452 | const int expected = layout == "classic" ? 2 : 3; |
| 155 |
2/2✓ Branch 693 → 694 taken 1445 times.
✓ Branch 693 → 695 taken 7 times.
|
1452 | if (ratios.size() != expected) continue; |
| 156 | 7 | double sum = 0; | |
| 157 | 7 | QVariantList safe; | |
| 158 |
2/2✓ Branch 715 → 697 taken 17 times.
✓ Branch 715 → 716 taken 5 times.
|
22 | for (const auto &ratio : ratios) { |
| 159 |
1/1✓ Branch 698 → 699 taken 17 times.
|
17 | const double value = ratio.toDouble(&valid); |
| 160 |
8/9✓ Branch 699 → 700 taken 16 times.
✓ Branch 699 → 703 taken 1 time.
✓ Branch 701 → 702 taken 16 times.
✗ Branch 701 → 703 not taken.
✓ Branch 702 → 703 taken 1 time.
✓ Branch 702 → 704 taken 15 times.
✓ Branch 705 → 706 taken 2 times.
✓ Branch 705 → 708 taken 15 times.
✓ Branch 706 → 707 taken 2 times.
|
17 | if (!valid || !std::isfinite(value) || value <= 0) { safe.clear(); break; } |
| 161 |
1/1✓ Branch 709 → 710 taken 15 times.
|
15 | safe.append(value); |
| 162 |
2/2✓ Branch 711 → 712 taken 15 times.
✓ Branch 712 → 713 taken 15 times.
|
15 | sum += safe.last().toDouble(); |
| 163 | } | ||
| 164 |
5/6✓ Branch 717 → 718 taken 5 times.
✓ Branch 717 → 720 taken 2 times.
✗ Branch 719 → 720 not taken.
✓ Branch 719 → 721 taken 5 times.
✓ Branch 722 → 723 taken 2 times.
✓ Branch 722 → 724 taken 5 times.
|
7 | if (safe.size() != expected || !std::isfinite(sum)) continue; |
| 165 |
5/5✓ Branch 724 → 725 taken 5 times.
✓ Branch 725 → 726 taken 5 times.
✓ Branch 728 → 729 taken 13 times.
✓ Branch 734 → 727 taken 13 times.
✓ Branch 734 → 735 taken 5 times.
|
18 | for (auto &value : safe) value = value.toDouble() / sum; |
| 166 |
1/1✓ Branch 736 → 737 taken 5 times.
|
5 | result[key] = safe; |
| 167 | 5079 | } | |
| 168 |
3/3✓ Branch 762 → 763 taken 726 times.
✓ Branch 763 → 764 taken 726 times.
✓ Branch 764 → 765 taken 726 times.
|
726 | const auto lastTaskList = values.value("calendar/lastTaskList").toString(); |
| 169 |
4/4✓ Branch 769 → 770 taken 2 times.
✓ Branch 769 → 777 taken 724 times.
✓ Branch 771 → 772 taken 2 times.
✓ Branch 772 → 773 taken 2 times.
|
726 | if (boundedKey(lastTaskList)) result["calendar/lastTaskList"] = lastTaskList; |
| 170 | // S106-335: the task list state is allowlisted like every other key — | ||
| 171 | // known keys only, texts and lists bounded (QuickTaskModel::restoreState | ||
| 172 | // takes them as they are). | ||
| 173 |
3/3✓ Branch 778 → 779 taken 726 times.
✓ Branch 779 → 780 taken 726 times.
✓ Branch 780 → 781 taken 726 times.
|
726 | const auto tasks = values.value("calendar/tasks").toMap(); |
| 174 | 726 | QVariantMap safeTasks; | |
| 175 |
2/2✓ Branch 805 → 786 taken 2904 times.
✓ Branch 805 → 806 taken 726 times.
|
3630 | for (const auto *key : {"view", "grouping", "sort", "filter"}) { |
| 176 |
3/3✓ Branch 788 → 789 taken 2904 times.
✓ Branch 789 → 790 taken 2904 times.
✓ Branch 790 → 791 taken 2904 times.
|
2904 | const QString text = tasks.value(QLatin1String(key)).toString(); |
| 177 |
5/5✓ Branch 794 → 795 taken 2904 times.
✓ Branch 795 → 796 taken 586 times.
✓ Branch 795 → 803 taken 2318 times.
✓ Branch 798 → 799 taken 586 times.
✓ Branch 799 → 800 taken 586 times.
|
2904 | if (boundedText(text)) safeTasks.insert(QLatin1String(key), text); |
| 178 | 2904 | } | |
| 179 |
2/2✓ Branch 829 → 808 taken 2178 times.
✓ Branch 829 → 830 taken 726 times.
|
2904 | for (const auto *key : {"showCompleted", "hideFutureStarts", "tagsExpanded"}) |
| 180 |
9/9✓ Branch 809 → 810 taken 2178 times.
✓ Branch 810 → 811 taken 2178 times.
✓ Branch 812 → 813 taken 585 times.
✓ Branch 812 → 828 taken 1593 times.
✓ Branch 815 → 816 taken 585 times.
✓ Branch 816 → 817 taken 585 times.
✓ Branch 817 → 818 taken 585 times.
✓ Branch 820 → 821 taken 585 times.
✓ Branch 821 → 822 taken 585 times.
|
2178 | if (tasks.contains(QLatin1String(key))) safeTasks.insert(QLatin1String(key), tasks.value(QLatin1String(key)).toBool()); |
| 181 |
8/9✓ Branch 831 → 832 taken 726 times.
✓ Branch 832 → 833 taken 726 times.
✓ Branch 833 → 834 taken 726 times.
✓ Branch 837 → 838 taken 196 times.
✓ Branch 837 → 841 taken 530 times.
✓ Branch 839 → 840 taken 196 times.
✗ Branch 839 → 841 not taken.
✓ Branch 842 → 843 taken 196 times.
✓ Branch 842 → 853 taken 530 times.
|
726 | if (const double width = tasks.value("editorWidth").toDouble(&valid); valid && std::isfinite(width)) |
| 182 |
2/2✓ Branch 843 → 844 taken 196 times.
✓ Branch 848 → 849 taken 196 times.
|
392 | safeTasks.insert(QStringLiteral("editorWidth"), qBound(200.0, width, 2000.0)); |
| 183 |
2/2✓ Branch 897 → 855 taken 1452 times.
✓ Branch 897 → 898 taken 726 times.
|
2178 | for (const auto *key : {"collapsedGroups", "collapsedParents"}) { |
| 184 |
4/4✓ Branch 856 → 857 taken 1452 times.
✓ Branch 857 → 858 taken 1452 times.
✓ Branch 859 → 860 taken 1060 times.
✓ Branch 859 → 861 taken 392 times.
|
1452 | if (!tasks.contains(QLatin1String(key))) continue; |
| 185 | 392 | QStringList entries; | |
| 186 |
7/7✓ Branch 863 → 864 taken 392 times.
✓ Branch 864 → 865 taken 392 times.
✓ Branch 865 → 866 taken 392 times.
✓ Branch 869 → 870 taken 392 times.
✓ Branch 870 → 871 taken 392 times.
✓ Branch 885 → 872 taken 1892 times.
✓ Branch 885 → 886 taken 392 times.
|
2284 | for (const auto &entry : tasks.value(QLatin1String(key)).toStringList()) |
| 187 |
9/10✓ Branch 873 → 874 taken 1892 times.
✓ Branch 874 → 875 taken 1890 times.
✓ Branch 874 → 880 taken 2 times.
✓ Branch 876 → 877 taken 1390 times.
✓ Branch 876 → 880 taken 500 times.
✓ Branch 878 → 879 taken 1390 times.
✗ Branch 878 → 880 not taken.
✓ Branch 881 → 882 taken 1390 times.
✓ Branch 881 → 883 taken 502 times.
✓ Branch 882 → 883 taken 1390 times.
|
2284 | if (boundedText(entry) && entries.size() < 1000 && !entries.contains(entry)) entries.append(entry); |
| 188 |
2/2✓ Branch 889 → 890 taken 392 times.
✓ Branch 890 → 891 taken 392 times.
|
392 | safeTasks.insert(QLatin1String(key), entries); |
| 189 | 392 | } | |
| 190 |
5/5✓ Branch 898 → 899 taken 726 times.
✓ Branch 899 → 900 taken 196 times.
✓ Branch 899 → 907 taken 530 times.
✓ Branch 901 → 902 taken 196 times.
✓ Branch 902 → 903 taken 196 times.
|
726 | if (!safeTasks.isEmpty()) result["calendar/tasks"] = safeTasks; |
| 191 | 726 | return result; | |
| 192 | 2904 | } | |
| 193 | |||
| 194 | 4915 | bool UiStateStore::readBytes(const QString &name, QByteArray *bytes) { | |
| 195 |
2/2✓ Branch 2 → 3 taken 4915 times.
✓ Branch 3 → 4 taken 4915 times.
|
4915 | const QString path = QDir(m_directory).filePath(name); |
| 196 |
1/1✓ Branch 5 → 6 taken 4915 times.
|
4915 | const QFileInfo info(path); |
| 197 |
11/11✓ Branch 6 → 7 taken 4915 times.
✓ Branch 7 → 8 taken 4913 times.
✓ Branch 7 → 12 taken 2 times.
✓ Branch 8 → 9 taken 4913 times.
✓ Branch 9 → 10 taken 1058 times.
✓ Branch 9 → 13 taken 3855 times.
✓ Branch 10 → 11 taken 1058 times.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 13 taken 1057 times.
✓ Branch 14 → 15 taken 3 times.
✓ Branch 14 → 25 taken 4912 times.
|
4915 | if (info.isSymLink() || (info.exists() && !info.isFile())) { |
| 198 |
1/1✓ Branch 19 → 20 taken 3 times.
|
6 | m_error = QStringLiteral("UI state is not a regular file: %1").arg(name); |
| 199 | 3 | return false; | |
| 200 | } | ||
| 201 |
4/4✓ Branch 25 → 26 taken 4912 times.
✓ Branch 26 → 27 taken 3855 times.
✓ Branch 26 → 29 taken 1057 times.
✓ Branch 27 → 28 taken 3855 times.
|
4912 | if (!info.exists()) { bytes->clear(); return true; } |
| 202 |
1/1✓ Branch 29 → 30 taken 1057 times.
|
1057 | QFile file(path); |
| 203 |
7/8✓ Branch 30 → 31 taken 1057 times.
✓ Branch 31 → 32 taken 1053 times.
✓ Branch 31 → 35 taken 4 times.
✓ Branch 33 → 34 taken 1053 times.
✗ Branch 34 → 35 not taken.
✓ Branch 34 → 36 taken 1053 times.
✓ Branch 37 → 38 taken 4 times.
✓ Branch 37 → 48 taken 1053 times.
|
1057 | if (info.size() > MaximumFileBytes || !file.open(QIODevice::ReadOnly)) { |
| 204 |
1/1✓ Branch 42 → 43 taken 4 times.
|
8 | m_error = QStringLiteral("Cannot read bounded UI state: %1").arg(name); |
| 205 | 4 | return false; | |
| 206 | } | ||
| 207 |
1/1✓ Branch 48 → 49 taken 1053 times.
|
1053 | *bytes = file.read(MaximumFileBytes + 1); |
| 208 |
4/7✓ Branch 52 → 53 taken 1053 times.
✗ Branch 52 → 55 not taken.
✓ Branch 53 → 54 taken 1053 times.
✗ Branch 54 → 55 not taken.
✓ Branch 54 → 56 taken 1053 times.
✗ Branch 57 → 58 not taken.
✓ Branch 57 → 68 taken 1053 times.
|
1053 | if (bytes->size() > MaximumFileBytes || file.error() != QFileDevice::NoError) { |
| 209 | ✗ | m_error = QStringLiteral("UI state read failed: %1").arg(name); | |
| 210 | ✗ | return false; | |
| 211 | } | ||
| 212 | 1053 | return true; | |
| 213 | 4915 | } | |
| 214 | |||
| 215 | 1249 | bool UiStateStore::writeJson(const QString &name, const QJsonObject &object) { | |
| 216 |
3/3✓ Branch 2 → 3 taken 1249 times.
✓ Branch 3 → 4 taken 1249 times.
✓ Branch 4 → 5 taken 1249 times.
|
1249 | const QByteArray bytes = QJsonDocument(object).toJson(QJsonDocument::Compact) + '\n'; |
| 217 |
1/2✗ Branch 8 → 9 not taken.
✓ Branch 8 → 16 taken 1249 times.
|
1249 | if (bytes.size() > MaximumFileBytes) { m_error = QStringLiteral("UI state exceeds byte limit"); return false; } |
| 218 | 1249 | QByteArray previous; | |
| 219 |
2/3✓ Branch 16 → 17 taken 1249 times.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 19 taken 1249 times.
|
1249 | if (!readBytes(name, &previous)) return false; |
| 220 |
2/2✓ Branch 20 → 21 taken 189 times.
✓ Branch 20 → 23 taken 1060 times.
|
1249 | if (bytes == previous) { m_error.clear(); return true; } |
| 221 |
3/3✓ Branch 23 → 24 taken 1060 times.
✓ Branch 24 → 25 taken 1060 times.
✓ Branch 25 → 26 taken 1060 times.
|
1060 | QSaveFile file(QDir(m_directory).filePath(name)); |
| 222 |
1/1✓ Branch 28 → 29 taken 1060 times.
|
1060 | file.setDirectWriteFallback(false); |
| 223 |
1/1✓ Branch 30 → 31 taken 1060 times.
|
1060 | if (!file.open(QIODevice::WriteOnly) || |
| 224 |
3/5✓ Branch 33 → 34 taken 1060 times.
✓ Branch 34 → 35 taken 1060 times.
✗ Branch 34 → 40 not taken.
✓ Branch 37 → 38 taken 1060 times.
✗ Branch 37 → 40 not taken.
|
2120 | !file.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner) || |
| 225 |
5/8✓ Branch 31 → 32 taken 1060 times.
✗ Branch 31 → 40 not taken.
✓ Branch 35 → 36 taken 1060 times.
✓ Branch 38 → 39 taken 1060 times.
✗ Branch 39 → 40 not taken.
✓ Branch 39 → 41 taken 1060 times.
✗ Branch 42 → 43 not taken.
✓ Branch 42 → 53 taken 1060 times.
|
3180 | file.write(bytes) != bytes.size() || !file.commit()) { |
| 226 | ✗ | m_error = QStringLiteral("Cannot atomically save UI state: %1").arg(name); | |
| 227 | ✗ | return false; | |
| 228 | } | ||
| 229 | 1060 | m_error.clear(); | |
| 230 | 1060 | return true; | |
| 231 | 1249 | } | |
| 232 | |||
| 233 | 1620 | bool UiStateStore::load(const QString &legacyIniPath) { | |
| 234 | 1620 | m_writable = false; | |
| 235 |
1/1✓ Branch 2 → 3 taken 1620 times.
|
1620 | m_preferences = defaults(); |
| 236 | 1620 | m_error.clear(); | |
| 237 |
11/24✓ Branch 7 → 8 taken 1620 times.
✗ Branch 7 → 15 not taken.
✓ Branch 8 → 9 taken 1620 times.
✓ Branch 9 → 10 taken 1620 times.
✓ Branch 10 → 11 taken 1620 times.
✗ Branch 10 → 15 not taken.
✓ Branch 12 → 13 taken 1620 times.
✓ Branch 13 → 14 taken 1620 times.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 1620 times.
✓ Branch 17 → 18 taken 1620 times.
✗ Branch 17 → 19 not taken.
✓ Branch 19 → 20 taken 1620 times.
✗ Branch 19 → 21 not taken.
✓ Branch 21 → 22 taken 1620 times.
✗ Branch 21 → 23 not taken.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 31 taken 1620 times.
✗ Branch 288 → 289 not taken.
✗ Branch 288 → 290 not taken.
✗ Branch 292 → 293 not taken.
✗ Branch 292 → 294 not taken.
✗ Branch 296 → 297 not taken.
✗ Branch 296 → 298 not taken.
|
1620 | if (m_directory.isEmpty() || QFileInfo(m_directory).isSymLink() || !QDir().mkpath(m_directory)) { |
| 238 | ✗ | m_error = QStringLiteral("Cannot open local UI state directory"); return false; | |
| 239 | } | ||
| 240 |
3/3✓ Branch 31 → 32 taken 1620 times.
✓ Branch 35 → 36 taken 1620 times.
✓ Branch 36 → 37 taken 1620 times.
|
3240 | QLockFile lock(QDir(m_directory).filePath(QStringLiteral("ui-state.lock"))); |
| 241 | // P90.17: bounded wait — a zero-tolerance read failed spuriously while | ||
| 242 | // the coalescing background writer held the lock during its atomic | ||
| 243 | // commit (reproduced under CPU starvation). One second covers any | ||
| 244 | // legitimate write window; stale locks still fail through QLockFile's | ||
| 245 | // own heuristics after the wait. | ||
| 246 |
2/3✓ Branch 41 → 42 taken 1620 times.
✗ Branch 42 → 43 not taken.
✓ Branch 42 → 50 taken 1620 times.
|
1620 | if (!lock.tryLock(1000)) { m_error = QStringLiteral("UI state is in use"); return false; } |
| 247 | 1620 | QByteArray bytes; | |
| 248 |
3/3✓ Branch 53 → 54 taken 1620 times.
✓ Branch 56 → 57 taken 2 times.
✓ Branch 56 → 58 taken 1618 times.
|
1620 | if (!readBytes(QStringLiteral("ui-preferences.json"), &bytes)) return false; |
| 249 |
2/2✓ Branch 59 → 60 taken 84 times.
✓ Branch 59 → 101 taken 1534 times.
|
1618 | if (!bytes.isEmpty()) { |
| 250 |
1/1✓ Branch 60 → 61 taken 84 times.
|
84 | const auto document = QJsonDocument::fromJson(bytes); |
| 251 |
3/3✓ Branch 61 → 62 taken 84 times.
✓ Branch 62 → 63 taken 1 time.
✓ Branch 62 → 70 taken 83 times.
|
85 | if (!document.isObject()) { m_error = QStringLiteral("Invalid UI preferences JSON; original retained"); return false; } |
| 252 |
1/1✓ Branch 70 → 71 taken 83 times.
|
83 | const auto object = document.object(); |
| 253 |
6/6✓ Branch 71 → 72 taken 83 times.
✓ Branch 72 → 73 taken 83 times.
✓ Branch 73 → 74 taken 83 times.
✓ Branch 74 → 75 taken 83 times.
✓ Branch 78 → 79 taken 1 time.
✓ Branch 78 → 86 taken 82 times.
|
83 | if (object.value("ui/schemaVersion") != QJsonValue(1)) { |
| 254 | 1 | m_error = QStringLiteral("Unsupported UI preference version; original retained"); return false; | |
| 255 | } | ||
| 256 |
2/2✓ Branch 86 → 87 taken 82 times.
✓ Branch 87 → 88 taken 82 times.
|
82 | m_preferences = normalized(object.toVariantMap()); |
| 257 |
9/9✓ Branch 93 → 94 taken 82 times.
✓ Branch 93 → 95 taken 1 time.
✓ Branch 97 → 98 taken 82 times.
✓ Branch 97 → 100 taken 2 times.
✓ Branch 101 → 102 taken 1534 times.
✓ Branch 105 → 106 taken 1534 times.
✓ Branch 106 → 107 taken 1534 times.
✓ Branch 111 → 112 taken 1 time.
✓ Branch 111 → 119 taken 1533 times.
|
3153 | } else if (QFileInfo::exists(QDir(m_directory).filePath(QStringLiteral("ui-preferences.json")))) { |
| 258 | 1 | m_error = QStringLiteral("Truncated UI preferences; original retained"); return false; | |
| 259 |
6/7✓ Branch 120 → 121 taken 2 times.
✓ Branch 120 → 124 taken 1531 times.
✓ Branch 121 → 122 taken 2 times.
✓ Branch 122 → 123 taken 2 times.
✗ Branch 122 → 124 not taken.
✓ Branch 125 → 126 taken 2 times.
✓ Branch 125 → 282 taken 1531 times.
|
1533 | } else if (!legacyIniPath.isEmpty() && QFileInfo::exists(legacyIniPath)) { |
| 260 |
1/1✓ Branch 126 → 127 taken 2 times.
|
2 | QFile legacy(legacyIniPath); |
| 261 |
9/16✓ Branch 127 → 128 taken 2 times.
✓ Branch 128 → 129 taken 2 times.
✓ Branch 129 → 130 taken 2 times.
✗ Branch 129 → 135 not taken.
✓ Branch 131 → 132 taken 2 times.
✓ Branch 132 → 133 taken 2 times.
✗ Branch 132 → 135 not taken.
✓ Branch 133 → 134 taken 2 times.
✗ Branch 134 → 135 not taken.
✓ Branch 134 → 136 taken 2 times.
✓ Branch 137 → 138 taken 2 times.
✗ Branch 137 → 139 not taken.
✗ Branch 139 → 140 not taken.
✓ Branch 139 → 147 taken 2 times.
✗ Branch 349 → 350 not taken.
✗ Branch 349 → 351 not taken.
|
2 | if (QFileInfo(legacyIniPath).isSymLink() || !legacy.open(QIODevice::ReadOnly) || legacy.size() > MaximumFileBytes) { |
| 262 | ✗ | m_error = QStringLiteral("Cannot snapshot legacy settings"); return false; | |
| 263 | } | ||
| 264 |
1/1✓ Branch 147 → 148 taken 2 times.
|
2 | const auto original = legacy.read(MaximumFileBytes + 1); |
| 265 |
4/7✓ Branch 149 → 150 taken 2 times.
✗ Branch 149 → 152 not taken.
✓ Branch 150 → 151 taken 2 times.
✗ Branch 151 → 152 not taken.
✓ Branch 151 → 153 taken 2 times.
✗ Branch 154 → 155 not taken.
✓ Branch 154 → 162 taken 2 times.
|
2 | if (original.size() > MaximumFileBytes || legacy.error() != QFileDevice::NoError) { |
| 266 | ✗ | m_error = QStringLiteral("Cannot read legacy settings snapshot"); return false; | |
| 267 | } | ||
| 268 |
2/2✓ Branch 162 → 163 taken 2 times.
✓ Branch 166 → 167 taken 2 times.
|
4 | const QString snapshotPath = QDir(m_directory).filePath(QStringLiteral("pre-quick-settings.ini")); |
| 269 | // Keep the first migration snapshot byte-for-byte, including old widget keys. | ||
| 270 |
1/1✓ Branch 170 → 171 taken 2 times.
|
2 | const QFileInfo snapshotInfo(snapshotPath); |
| 271 |
10/14✓ Branch 171 → 172 taken 2 times.
✓ Branch 172 → 173 taken 2 times.
✗ Branch 172 → 179 not taken.
✓ Branch 173 → 174 taken 2 times.
✓ Branch 174 → 175 taken 1 time.
✓ Branch 174 → 180 taken 1 time.
✓ Branch 175 → 176 taken 1 time.
✓ Branch 176 → 177 taken 1 time.
✗ Branch 176 → 179 not taken.
✓ Branch 177 → 178 taken 1 time.
✗ Branch 178 → 179 not taken.
✓ Branch 178 → 180 taken 1 time.
✗ Branch 181 → 182 not taken.
✓ Branch 181 → 189 taken 2 times.
|
2 | if (snapshotInfo.isSymLink() || (snapshotInfo.exists() && (!snapshotInfo.isFile() || snapshotInfo.size() > MaximumFileBytes))) { |
| 272 | ✗ | m_error = QStringLiteral("Invalid legacy snapshot target"); return false; | |
| 273 | } | ||
| 274 |
3/3✓ Branch 189 → 190 taken 2 times.
✓ Branch 190 → 191 taken 1 time.
✓ Branch 190 → 220 taken 1 time.
|
2 | if (!QFileInfo::exists(snapshotPath)) { |
| 275 |
1/1✓ Branch 191 → 192 taken 1 time.
|
1 | QSaveFile snapshot(snapshotPath); |
| 276 |
1/1✓ Branch 192 → 193 taken 1 time.
|
1 | snapshot.setDirectWriteFallback(false); |
| 277 |
1/1✓ Branch 194 → 195 taken 1 time.
|
1 | if (!snapshot.open(QIODevice::WriteOnly) || |
| 278 |
3/5✓ Branch 197 → 198 taken 1 time.
✓ Branch 198 → 199 taken 1 time.
✗ Branch 198 → 204 not taken.
✓ Branch 201 → 202 taken 1 time.
✗ Branch 201 → 204 not taken.
|
2 | !snapshot.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner) || |
| 279 |
5/8✓ Branch 195 → 196 taken 1 time.
✗ Branch 195 → 204 not taken.
✓ Branch 199 → 200 taken 1 time.
✓ Branch 202 → 203 taken 1 time.
✗ Branch 203 → 204 not taken.
✓ Branch 203 → 205 taken 1 time.
✗ Branch 206 → 207 not taken.
✓ Branch 206 → 214 taken 1 time.
|
3 | snapshot.write(original) != original.size() || !snapshot.commit()) { |
| 280 | ✗ | m_error = QStringLiteral("Cannot save legacy settings snapshot"); return false; | |
| 281 | } | ||
| 282 | 1 | } | |
| 283 | // Read the retained bytes, not a concurrently changed original settings file. | ||
| 284 |
1/1✓ Branch 220 → 221 taken 2 times.
|
2 | QSettings settings(snapshotPath, QSettings::IniFormat); |
| 285 | 2 | QVariantMap imported; | |
| 286 |
2/2✓ Branch 237 → 231 taken 4 times.
✓ Branch 237 → 238 taken 2 times.
|
14 | for (const auto &key : {QStringLiteral("appearance/theme"), QStringLiteral("view/defaultMode")}) |
| 287 |
4/6✓ Branch 232 → 233 taken 4 times.
✓ Branch 233 → 234 taken 4 times.
✓ Branch 239 → 240 taken 4 times.
✓ Branch 239 → 243 taken 2 times.
✗ Branch 371 → 372 not taken.
✗ Branch 371 → 375 not taken.
|
10 | imported[key] = settings.value(key); |
| 288 |
2/3✓ Branch 244 → 245 taken 2 times.
✗ Branch 245 → 246 not taken.
✓ Branch 245 → 253 taken 2 times.
|
2 | if (settings.status() != QSettings::NoError) { m_error = QStringLiteral("Invalid legacy settings snapshot"); return false; } |
| 289 |
1/1✓ Branch 253 → 254 taken 2 times.
|
2 | m_preferences = normalized(imported); |
| 290 | 2 | } | |
| 291 | // No write on load. A repaired default is persisted only by an explicit save. | ||
| 292 | 1615 | m_writable = true; | |
| 293 | 1615 | return true; | |
| 294 | 1620 | } | |
| 295 | |||
| 296 | 649 | bool UiStateStore::savePreferences(const QVariantMap &preferences) { | |
| 297 |
2/2✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 10 taken 648 times.
|
650 | if (!m_writable) { m_error = QStringLiteral("UI preferences are read-only until a successful load"); return false; } |
| 298 |
3/3✓ Branch 10 → 11 taken 648 times.
✓ Branch 14 → 15 taken 648 times.
✓ Branch 15 → 16 taken 648 times.
|
1296 | QLockFile lock(QDir(m_directory).filePath(QStringLiteral("ui-state.lock"))); |
| 299 |
3/3✓ Branch 20 → 21 taken 648 times.
✓ Branch 21 → 22 taken 1 time.
✓ Branch 21 → 29 taken 647 times.
|
649 | if (!lock.tryLock(1000)) { m_error = QStringLiteral("UI state is in use"); return false; } |
| 300 | // Recheck while holding the writer lock: another binary may have upgraded | ||
| 301 | // the file since load(). Never replace a corrupt or unknown-version file. | ||
| 302 | 647 | QByteArray previous; | |
| 303 |
3/3✓ Branch 32 → 33 taken 647 times.
✓ Branch 35 → 36 taken 2 times.
✓ Branch 35 → 37 taken 645 times.
|
647 | if (!readBytes(QStringLiteral("ui-preferences.json"), &previous)) return false; |
| 304 |
5/5✓ Branch 37 → 38 taken 645 times.
✓ Branch 41 → 42 taken 645 times.
✓ Branch 42 → 43 taken 645 times.
✓ Branch 47 → 48 taken 247 times.
✓ Branch 47 → 81 taken 398 times.
|
1290 | if (QFileInfo::exists(QDir(m_directory).filePath(QStringLiteral("ui-preferences.json")))) { |
| 305 |
1/1✓ Branch 48 → 49 taken 247 times.
|
247 | const auto document = QJsonDocument::fromJson(previous); |
| 306 |
20/28✓ Branch 49 → 50 taken 247 times.
✓ Branch 50 → 51 taken 245 times.
✓ Branch 50 → 57 taken 2 times.
✓ Branch 51 → 52 taken 245 times.
✓ Branch 52 → 53 taken 245 times.
✓ Branch 53 → 54 taken 245 times.
✓ Branch 54 → 55 taken 245 times.
✓ Branch 55 → 56 taken 245 times.
✓ Branch 56 → 57 taken 1 time.
✓ Branch 56 → 58 taken 244 times.
✓ Branch 59 → 60 taken 245 times.
✓ Branch 59 → 61 taken 2 times.
✓ Branch 61 → 62 taken 245 times.
✓ Branch 61 → 63 taken 2 times.
✓ Branch 63 → 64 taken 245 times.
✓ Branch 63 → 65 taken 2 times.
✓ Branch 65 → 66 taken 245 times.
✓ Branch 65 → 67 taken 2 times.
✓ Branch 67 → 68 taken 3 times.
✓ Branch 67 → 75 taken 244 times.
✗ Branch 129 → 130 not taken.
✗ Branch 129 → 131 not taken.
✗ Branch 133 → 134 not taken.
✗ Branch 133 → 135 not taken.
✗ Branch 137 → 138 not taken.
✗ Branch 137 → 139 not taken.
✗ Branch 141 → 142 not taken.
✗ Branch 141 → 143 not taken.
|
247 | if (!document.isObject() || document.object().value("ui/schemaVersion") != QJsonValue(1)) { |
| 307 | 3 | m_error = QStringLiteral("Changed or invalid UI preferences; original retained"); return false; | |
| 308 | } | ||
| 309 | 247 | } | |
| 310 |
1/1✓ Branch 81 → 82 taken 642 times.
|
642 | const auto safe = normalized(preferences); |
| 311 |
3/4✓ Branch 82 → 83 taken 642 times.
✓ Branch 86 → 87 taken 642 times.
✗ Branch 90 → 91 not taken.
✓ Branch 90 → 92 taken 642 times.
|
1284 | if (!writeJson(QStringLiteral("ui-preferences.json"), QJsonObject::fromVariantMap(safe))) return false; |
| 312 | 642 | m_preferences = safe; | |
| 313 | 642 | return true; | |
| 314 | 648 | } | |
| 315 | |||
| 316 | 1400 | bool UiStateStore::readSession(QJsonObject *session) { | |
| 317 | 1400 | QByteArray bytes; | |
| 318 |
11/15✓ Branch 2 → 3 taken 1399 times.
✓ Branch 2 → 8 taken 1 time.
✓ Branch 6 → 7 taken 1399 times.
✓ Branch 7 → 8 taken 3 times.
✓ Branch 7 → 9 taken 1396 times.
✓ Branch 10 → 11 taken 1399 times.
✓ Branch 10 → 12 taken 1 time.
✓ Branch 12 → 13 taken 1399 times.
✓ Branch 12 → 14 taken 1 time.
✓ Branch 14 → 15 taken 4 times.
✓ Branch 14 → 16 taken 1396 times.
✗ Branch 68 → 69 not taken.
✗ Branch 68 → 70 not taken.
✗ Branch 72 → 73 not taken.
✗ Branch 72 → 74 not taken.
|
2799 | if (!session || !readBytes(QStringLiteral("workspace-session.json"), &bytes)) return false; |
| 319 |
17/25✓ Branch 17 → 18 taken 1147 times.
✓ Branch 17 → 26 taken 249 times.
✓ Branch 18 → 19 taken 1147 times.
✓ Branch 22 → 23 taken 1147 times.
✓ Branch 23 → 24 taken 1147 times.
✓ Branch 24 → 25 taken 1145 times.
✓ Branch 24 → 26 taken 2 times.
✓ Branch 27 → 28 taken 1147 times.
✓ Branch 27 → 29 taken 249 times.
✓ Branch 29 → 30 taken 1147 times.
✓ Branch 29 → 31 taken 249 times.
✓ Branch 31 → 32 taken 1147 times.
✓ Branch 31 → 33 taken 249 times.
✓ Branch 33 → 34 taken 1147 times.
✓ Branch 33 → 35 taken 249 times.
✓ Branch 35 → 36 taken 1145 times.
✓ Branch 35 → 40 taken 251 times.
✗ Branch 76 → 77 not taken.
✗ Branch 76 → 78 not taken.
✗ Branch 80 → 81 not taken.
✗ Branch 80 → 82 not taken.
✗ Branch 84 → 85 not taken.
✗ Branch 84 → 86 not taken.
✗ Branch 88 → 89 not taken.
✗ Branch 88 → 90 not taken.
|
2543 | if (bytes.isEmpty() && !QFileInfo::exists(QDir(m_directory).filePath(QStringLiteral("workspace-session.json")))) { |
| 320 |
1/1✓ Branch 36 → 37 taken 1145 times.
|
1145 | *session = {}; return true; |
| 321 | } | ||
| 322 |
1/1✓ Branch 40 → 41 taken 251 times.
|
251 | const auto document = QJsonDocument::fromJson(bytes); |
| 323 |
3/3✓ Branch 41 → 42 taken 251 times.
✓ Branch 42 → 43 taken 6 times.
✓ Branch 42 → 50 taken 245 times.
|
257 | if (!document.isObject()) { m_error = QStringLiteral("Invalid workspace session; original retained"); return false; } |
| 324 |
1/1✓ Branch 50 → 51 taken 245 times.
|
245 | WorkspaceState validator; |
| 325 |
4/4✓ Branch 52 → 53 taken 245 times.
✓ Branch 53 → 54 taken 245 times.
✓ Branch 56 → 57 taken 4 times.
✓ Branch 56 → 58 taken 241 times.
|
245 | if (!validator.restoreSession(document.object(), &m_error)) return false; |
| 326 |
1/1✓ Branch 58 → 59 taken 241 times.
|
241 | *session = document.object(); |
| 327 | 241 | m_error.clear(); | |
| 328 | 241 | return true; | |
| 329 | 1400 | } | |
| 330 | |||
| 331 | 616 | bool UiStateStore::saveSession(const QJsonObject &session) { | |
| 332 |
2/2✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 10 taken 615 times.
|
617 | if (!m_writable) { m_error = QStringLiteral("UI session is read-only until a successful load"); return false; } |
| 333 |
3/3✓ Branch 10 → 11 taken 615 times.
✓ Branch 14 → 15 taken 615 times.
✓ Branch 15 → 16 taken 615 times.
|
1230 | QLockFile lock(QDir(m_directory).filePath(QStringLiteral("ui-state.lock"))); |
| 334 |
3/3✓ Branch 20 → 21 taken 615 times.
✓ Branch 21 → 22 taken 1 time.
✓ Branch 21 → 29 taken 614 times.
|
616 | if (!lock.tryLock(1000)) { m_error = QStringLiteral("UI state is in use"); return false; } |
| 335 | // Refuse to overwrite an unknown/corrupt session from a newer binary. | ||
| 336 |
1/1✓ Branch 29 → 30 taken 614 times.
|
614 | QJsonObject previous; |
| 337 |
3/3✓ Branch 30 → 31 taken 614 times.
✓ Branch 31 → 32 taken 6 times.
✓ Branch 31 → 33 taken 608 times.
|
614 | if (!readSession(&previous)) return false; |
| 338 |
1/1✓ Branch 33 → 34 taken 608 times.
|
608 | WorkspaceState validator; |
| 339 |
3/3✓ Branch 35 → 36 taken 608 times.
✓ Branch 37 → 38 taken 1 time.
✓ Branch 37 → 39 taken 607 times.
|
608 | if (!validator.restoreSession(session, &m_error)) return false; |
| 340 |
1/1✓ Branch 42 → 43 taken 607 times.
|
607 | return writeJson(QStringLiteral("workspace-session.json"), session); |
| 341 | 615 | } | |
| 342 |