presentation/WorkspacePersistence.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "WorkspacePersistence.h" | ||
| 2 | #include "QuickTrayController.h" | ||
| 3 | |||
| 4 | 776 | WorkspacePersistence::WorkspacePersistence(QString directory, WorkspaceState &workspace, | |
| 5 | 776 | DesignTokens &tokens, QObject *parent) | |
| 6 |
5/5✓ Branch 5 → 6 taken 776 times.
✓ Branch 7 → 8 taken 776 times.
✓ Branch 13 → 14 taken 776 times.
✓ Branch 15 → 16 taken 776 times.
✓ Branch 16 → 17 taken 776 times.
|
776 | : QObject(parent), m_store(std::move(directory)), m_workspace(workspace), m_tokens(tokens) { |
| 7 |
1/1✓ Branch 17 → 18 taken 776 times.
|
776 | m_timer.setSingleShot(true); |
| 8 |
1/1✓ Branch 18 → 19 taken 776 times.
|
776 | m_timer.setInterval(200); |
| 9 |
1/1✓ Branch 19 → 20 taken 776 times.
|
1349 | connect(&m_timer, &QTimer::timeout, this, [this] { flushAsync(); }); |
| 10 |
1/1✓ Branch 21 → 22 taken 776 times.
|
776 | connect(&workspace, &WorkspaceState::selectionChanged, this, &WorkspacePersistence::schedule); |
| 11 |
1/1✓ Branch 23 → 24 taken 776 times.
|
776 | connect(&workspace, &WorkspaceState::readingModeChanged, this, &WorkspacePersistence::schedule); |
| 12 |
1/1✓ Branch 25 → 26 taken 776 times.
|
776 | connect(&workspace, &WorkspaceState::areaChanged, this, &WorkspacePersistence::schedule); |
| 13 |
1/1✓ Branch 27 → 28 taken 776 times.
|
776 | connect(&workspace, &WorkspaceState::folderExpansionChanged, this, &WorkspacePersistence::schedule); |
| 14 |
1/1✓ Branch 29 → 30 taken 776 times.
|
776 | connect(&workspace, &WorkspaceState::readerStateChanged, this, &WorkspacePersistence::schedule); |
| 15 |
1/1✓ Branch 31 → 32 taken 776 times.
|
776 | connect(&workspace, &WorkspaceState::layoutChanged, this, &WorkspacePersistence::schedule); |
| 16 |
1/1✓ Branch 33 → 34 taken 776 times.
|
776 | connect(&workspace, &QAbstractItemModel::rowsRemoved, this, &WorkspacePersistence::schedule); |
| 17 |
1/1✓ Branch 35 → 36 taken 776 times.
|
776 | connect(&workspace, &QAbstractItemModel::modelReset, this, &WorkspacePersistence::schedule); |
| 18 |
1/1✓ Branch 37 → 38 taken 776 times.
|
776 | connect(&tokens, &DesignTokens::changed, this, &WorkspacePersistence::schedule); |
| 19 | // P90.17: the writer owns the only writing UiStateStore on its own | ||
| 20 | // thread; the GUI thread only snapshots immutable state. | ||
| 21 |
3/6✓ Branch 39 → 40 taken 776 times.
✓ Branch 42 → 43 taken 776 times.
✗ Branch 44 → 45 not taken.
✓ Branch 44 → 46 taken 776 times.
✗ Branch 81 → 82 not taken.
✗ Branch 81 → 83 not taken.
|
776 | auto *writer = new UiStateWriter(m_store.directory()); |
| 22 |
1/1✓ Branch 46 → 47 taken 776 times.
|
776 | writer->moveToThread(&m_writerThread); |
| 23 |
1/1✓ Branch 47 → 48 taken 776 times.
|
776 | connect(&m_writerThread, &QThread::finished, writer, &QObject::deleteLater); |
| 24 |
1/1✓ Branch 49 → 50 taken 776 times.
|
776 | m_writer = writer; |
| 25 |
1/1✓ Branch 50 → 51 taken 776 times.
|
776 | m_writerThread.start(); |
| 26 | // The writing store must pass the same load gate (writability, corrupt | ||
| 27 | // session refusal) as the GUI-side store before the first write. | ||
| 28 |
1/1✓ Branch 51 → 52 taken 776 times.
|
776 | QMetaObject::invokeMethod(writer, &UiStateWriter::initialize, Qt::BlockingQueuedConnection); |
| 29 | 776 | connect(writer, &UiStateWriter::written, this, | |
| 30 |
1/1✓ Branch 52 → 53 taken 776 times.
|
776 | [this](quint64 generation, bool ok, const QString &error) { |
| 31 |
2/2✓ Branch 2 → 3 taken 22 times.
✓ Branch 2 → 4 taken 481 times.
|
503 | if (generation != m_writeGeneration) return; // superseded result |
| 32 | 481 | m_writeInFlight = false; | |
| 33 |
1/2✗ Branch 4 → 5 not taken.
✓ Branch 4 → 12 taken 481 times.
|
481 | if (!ok) { |
| 34 | ✗ | m_error = error; | |
| 35 | // The dispatched snapshot is only an optimization after | ||
| 36 | // a successful commit. A rejected write must stay dirty | ||
| 37 | // so a later flush can retry the same state. | ||
| 38 | ✗ | m_lastDispatched = {}; | |
| 39 | ✗ | if (!m_pendingWrite) m_dirty = true; | |
| 40 | ✗ | emit saveFailed(error); | |
| 41 | } else { | ||
| 42 | 481 | m_error.clear(); | |
| 43 |
3/4✓ Branch 13 → 14 taken 58 times.
✓ Branch 13 → 19 taken 423 times.
✓ Branch 14 → 15 taken 58 times.
✗ Branch 14 → 19 not taken.
|
539 | if (m_dirty && !m_pendingWrite && |
| 44 |
8/10✓ Branch 15 → 16 taken 58 times.
✓ Branch 16 → 17 taken 58 times.
✓ Branch 17 → 18 taken 2 times.
✓ Branch 17 → 19 taken 56 times.
✓ Branch 20 → 21 taken 58 times.
✓ Branch 20 → 22 taken 423 times.
✓ Branch 22 → 23 taken 2 times.
✓ Branch 22 → 24 taken 479 times.
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 30 not taken.
|
539 | buildSnapshot() == m_lastDispatched) |
| 45 | 2 | m_dirty = false; | |
| 46 | 481 | emit saved(); | |
| 47 | } | ||
| 48 | 481 | dispatchPendingWrite(); | |
| 49 | ✗ | }); | |
| 50 | 776 | } | |
| 51 | |||
| 52 | 1541 | WorkspacePersistence::~WorkspacePersistence() { | |
| 53 | 776 | m_writerThread.quit(); | |
| 54 | // Destroying the QThread while its writer runs aborts the process; the | ||
| 55 | // former 2 s limit was exceeded on a loaded CI runner. | ||
| 56 |
1/2✗ Branch 4 → 5 not taken.
✓ Branch 4 → 12 taken 776 times.
|
776 | if (!m_writerThread.wait(5000)) { |
| 57 | ✗ | qWarning() << "Workspace writer still busy after 5 s; waiting for it"; | |
| 58 | ✗ | m_writerThread.wait(); | |
| 59 | } | ||
| 60 | 1541 | } | |
| 61 | |||
| 62 | 777 | bool WorkspacePersistence::load(const QString &legacyIniPath, | |
| 63 | const std::function<bool(const MailId &)> &acceptIdentity) { | ||
| 64 |
1/1✓ Branch 2 → 3 taken 777 times.
|
777 | m_timer.stop(); |
| 65 | 777 | m_loaded = false; | |
| 66 | 777 | m_dirty = false; | |
| 67 | 777 | m_pendingWrite = false; | |
| 68 | 777 | m_error.clear(); | |
| 69 |
1/1✓ Branch 4 → 5 taken 777 times.
|
777 | QJsonObject session; |
| 70 |
7/8✓ Branch 5 → 6 taken 777 times.
✓ Branch 6 → 7 taken 777 times.
✗ Branch 6 → 9 not taken.
✓ Branch 7 → 8 taken 777 times.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 776 times.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 16 taken 776 times.
|
777 | if (!m_store.load(legacyIniPath) || !m_store.readSession(&session)) { |
| 71 | 1 | m_error = m_store.error(); | |
| 72 | 1 | return false; | |
| 73 | } | ||
| 74 |
6/8✓ Branch 16 → 17 taken 776 times.
✓ Branch 17 → 18 taken 13 times.
✓ Branch 17 → 21 taken 763 times.
✓ Branch 18 → 19 taken 13 times.
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 21 taken 13 times.
✗ Branch 22 → 23 not taken.
✓ Branch 22 → 24 taken 776 times.
|
776 | if (!session.isEmpty() && !m_workspace.restoreSession(session, &m_error, acceptIdentity)) return false; |
| 75 | 776 | const auto preferences = m_store.preferences(); | |
| 76 |
4/4✓ Branch 26 → 27 taken 776 times.
✓ Branch 27 → 28 taken 776 times.
✓ Branch 28 → 29 taken 776 times.
✓ Branch 29 → 30 taken 776 times.
|
776 | m_workspace.setLayout(preferences.value("ui/layout").toString()); |
| 77 | // S106-332: the default reading mode is a QSettings value that the | ||
| 78 | // workspace reads itself; the copy in ui-preferences.json (kept from the | ||
| 79 | // migration, never refreshed) is not applied over it. | ||
| 80 |
4/4✓ Branch 35 → 36 taken 776 times.
✓ Branch 36 → 37 taken 776 times.
✓ Branch 37 → 38 taken 776 times.
✓ Branch 38 → 39 taken 776 times.
|
776 | m_tokens.setTheme(preferences.value("appearance/theme").toString()); |
| 81 |
4/4✓ Branch 44 → 45 taken 776 times.
✓ Branch 45 → 46 taken 776 times.
✓ Branch 46 → 47 taken 776 times.
✓ Branch 47 → 48 taken 776 times.
|
776 | m_tokens.setAccentColor(preferences.value("appearance/accentColor").toString()); |
| 82 |
4/4✓ Branch 53 → 54 taken 776 times.
✓ Branch 54 → 55 taken 776 times.
✓ Branch 55 → 56 taken 776 times.
✓ Branch 56 → 57 taken 776 times.
|
776 | m_tokens.setFontScale(preferences.value("ui/fontScale").toDouble()); |
| 83 | // B3: the stored policy governs; "system" evaluates against the live | ||
| 84 | // desktop preference at restore time. | ||
| 85 |
4/4✓ Branch 61 → 62 taken 776 times.
✓ Branch 62 → 63 taken 776 times.
✓ Branch 63 → 64 taken 776 times.
✓ Branch 64 → 65 taken 776 times.
|
776 | m_tokens.applyMotionPolicy(preferences.value("ui/motion").toString()); |
| 86 |
3/3✓ Branch 70 → 71 taken 776 times.
✓ Branch 71 → 72 taken 776 times.
✓ Branch 72 → 73 taken 776 times.
|
776 | m_threaded = preferences.value("view/threaded").toBool(); |
| 87 |
3/3✓ Branch 77 → 78 taken 776 times.
✓ Branch 78 → 79 taken 776 times.
✓ Branch 79 → 80 taken 776 times.
|
776 | m_suggestions = preferences.value("view/suggestions").toBool(); |
| 88 |
3/3✓ Branch 84 → 85 taken 776 times.
✓ Branch 85 → 86 taken 776 times.
✓ Branch 86 → 87 taken 776 times.
|
776 | m_composeWindowed = preferences.value("ui/composeWindowed").toBool(); |
| 89 |
3/3✓ Branch 91 → 92 taken 776 times.
✓ Branch 92 → 93 taken 776 times.
✓ Branch 93 → 94 taken 776 times.
|
776 | m_closeToTray = preferences.value("ui/closeToTray", true).toBool(); |
| 90 | 776 | for (const auto *key : {"viewMode", "workWeek", "hourZoom", "hiddenCalendars", "lastCalendar", "agendaPanel", "agendaWidth", "agendaTasksHeight", | |
| 91 | "agendaTasks", "agendaTasksShown", "agendaTaskFilter", "agendaTaskSort", | ||
| 92 | "workdayStart", "workdayEnd", "workdays", "firstDayOfWeek", "tasks", "lastTaskList", | ||
| 93 |
2/2✓ Branch 119 → 100 taken 14744 times.
✓ Branch 119 → 120 taken 776 times.
|
16296 | "taskDefaultReminder"}) |
| 94 |
2/2✓ Branch 109 → 110 taken 14744 times.
✓ Branch 110 → 111 taken 14744 times.
|
14744 | m_calendarState.insert(QString::fromLatin1(key), |
| 95 |
3/3✓ Branch 102 → 103 taken 14744 times.
✓ Branch 106 → 107 taken 14744 times.
✓ Branch 107 → 108 taken 14744 times.
|
44232 | preferences.value(QStringLiteral("calendar/") + QString::fromLatin1(key))); |
| 96 |
2/2✓ Branch 124 → 125 taken 776 times.
✓ Branch 125 → 126 taken 776 times.
|
1552 | m_commandBarState = {{QStringLiteral("history"), preferences.value("commandbar/history")}, |
| 97 |
4/6✓ Branch 131 → 132 taken 776 times.
✓ Branch 132 → 133 taken 776 times.
✓ Branch 138 → 139 taken 1552 times.
✓ Branch 138 → 140 taken 776 times.
✗ Branch 317 → 318 not taken.
✗ Branch 317 → 319 not taken.
|
3104 | {QStringLiteral("recentFolders"), preferences.value("commandbar/recentFolders")}}; |
| 98 |
3/3✓ Branch 154 → 155 taken 776 times.
✓ Branch 155 → 156 taken 776 times.
✓ Branch 156 → 157 taken 776 times.
|
1552 | m_searchState = {{QStringLiteral("timelineCollapsed"), preferences.value("search/timelineCollapsed").toBool()}, |
| 99 |
3/3✓ Branch 162 → 163 taken 776 times.
✓ Branch 163 → 164 taken 776 times.
✓ Branch 164 → 165 taken 776 times.
|
1552 | {QStringLiteral("timelineHintSeen"), preferences.value("search/timelineHintSeen").toBool()}, |
| 100 |
5/7✓ Branch 170 → 171 taken 776 times.
✓ Branch 171 → 172 taken 776 times.
✓ Branch 172 → 173 taken 776 times.
✓ Branch 178 → 179 taken 2328 times.
✓ Branch 178 → 180 taken 776 times.
✗ Branch 358 → 359 not taken.
✗ Branch 358 → 360 not taken.
|
3880 | {QStringLiteral("grouped"), preferences.value("search/grouped").toBool()}}; |
| 101 | 776 | m_loaded = true; | |
| 102 | 776 | return true; | |
| 103 | 6209 | } | |
| 104 | |||
| 105 | 340 | void WorkspacePersistence::setThreaded(bool threaded) { | |
| 106 |
2/2✓ Branch 2 → 3 taken 291 times.
✓ Branch 2 → 4 taken 49 times.
|
340 | if (m_threaded == threaded) return; |
| 107 | 49 | m_threaded = threaded; | |
| 108 | 49 | schedule(); | |
| 109 | } | ||
| 110 | |||
| 111 | 23 | void WorkspacePersistence::setSuggestions(bool on) { | |
| 112 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 23 times.
|
23 | if (m_suggestions == on) return; |
| 113 | 23 | m_suggestions = on; | |
| 114 | 23 | schedule(); | |
| 115 | } | ||
| 116 | |||
| 117 | 9 | void WorkspacePersistence::setComposeWindowed(bool windowed) { | |
| 118 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 9 times.
|
9 | if (m_composeWindowed == windowed) return; |
| 119 | 9 | m_composeWindowed = windowed; | |
| 120 | 9 | schedule(); | |
| 121 | } | ||
| 122 | |||
| 123 | 765 | void WorkspacePersistence::restoreTray(QuickTrayController *tray) { | |
| 124 | 765 | m_tray = tray; | |
| 125 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 765 times.
|
765 | if (!tray) return; |
| 126 |
4/4✓ Branch 7 → 8 taken 765 times.
✓ Branch 8 → 9 taken 765 times.
✓ Branch 9 → 10 taken 765 times.
✓ Branch 10 → 11 taken 765 times.
|
765 | tray->setCloseToTray(m_store.preferences().value("ui/closeToTray", true).toBool()); |
| 127 | } | ||
| 128 | |||
| 129 | 8 | void WorkspacePersistence::setCloseToTray(bool on) { | |
| 130 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 8 times.
|
8 | if (m_closeToTray == on) return; |
| 131 | 8 | m_closeToTray = on; | |
| 132 |
1/2✓ Branch 5 → 6 taken 8 times.
✗ Branch 5 → 8 not taken.
|
8 | if (m_tray) m_tray->setCloseToTray(on); |
| 133 | 8 | schedule(); | |
| 134 | } | ||
| 135 | |||
| 136 | 307 | void WorkspacePersistence::setCalendarState(const QVariantMap &state) { | |
| 137 |
2/2✓ Branch 3 → 4 taken 15 times.
✓ Branch 3 → 5 taken 292 times.
|
307 | if (m_calendarState == state) return; |
| 138 | 292 | m_calendarState = state; | |
| 139 | 292 | schedule(); | |
| 140 | } | ||
| 141 | |||
| 142 | 262 | void WorkspacePersistence::setCommandBarState(const QVariantMap &state) { | |
| 143 |
2/2✓ Branch 3 → 4 taken 6 times.
✓ Branch 3 → 5 taken 256 times.
|
262 | if (m_commandBarState == state) return; |
| 144 | 256 | m_commandBarState = state; | |
| 145 | 256 | schedule(); | |
| 146 | } | ||
| 147 | |||
| 148 | 13 | void WorkspacePersistence::setSearchState(const QVariantMap &state) { | |
| 149 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 13 times.
|
13 | if (m_searchState == state) return; |
| 150 | 13 | m_searchState = state; | |
| 151 | 13 | schedule(); | |
| 152 | } | ||
| 153 | |||
| 154 | 4498 | void WorkspacePersistence::schedule() { | |
| 155 |
2/2✓ Branch 2 → 3 taken 849 times.
✓ Branch 2 → 4 taken 3649 times.
|
4498 | if (!m_loaded) return; |
| 156 | 3649 | m_dirty = true; | |
| 157 | 3649 | m_timer.start(); | |
| 158 | } | ||
| 159 | |||
| 160 | 676 | WorkspacePersistence::Snapshot WorkspacePersistence::buildSnapshot() const { | |
| 161 |
1/1✓ Branch 2 → 3 taken 676 times.
|
676 | Snapshot snapshot; |
| 162 |
1/1✓ Branch 3 → 4 taken 676 times.
|
676 | snapshot.session = m_workspace.saveSession(); |
| 163 | 676 | auto preferences = m_store.preferences(); | |
| 164 |
2/2✓ Branch 9 → 10 taken 676 times.
✓ Branch 10 → 11 taken 676 times.
|
676 | preferences["ui/layout"] = m_workspace.layout(); |
| 165 |
2/2✓ Branch 17 → 18 taken 676 times.
✓ Branch 18 → 19 taken 676 times.
|
676 | preferences["appearance/theme"] = m_tokens.theme(); |
| 166 |
2/2✓ Branch 25 → 26 taken 676 times.
✓ Branch 26 → 27 taken 676 times.
|
676 | preferences["appearance/accentColor"] = m_tokens.accentColor(); |
| 167 |
2/2✓ Branch 33 → 34 taken 676 times.
✓ Branch 34 → 35 taken 676 times.
|
676 | preferences["ui/fontScale"] = m_tokens.fontScale(); |
| 168 |
2/2✓ Branch 40 → 41 taken 676 times.
✓ Branch 41 → 42 taken 676 times.
|
676 | preferences["ui/motion"] = m_tokens.motionPolicy(); |
| 169 |
2/2✓ Branch 47 → 48 taken 676 times.
✓ Branch 48 → 49 taken 676 times.
|
676 | preferences["view/threaded"] = m_threaded; |
| 170 |
2/2✓ Branch 53 → 54 taken 676 times.
✓ Branch 54 → 55 taken 676 times.
|
676 | preferences["view/suggestions"] = m_suggestions; |
| 171 |
2/2✓ Branch 59 → 60 taken 676 times.
✓ Branch 60 → 61 taken 676 times.
|
676 | preferences["ui/composeWindowed"] = m_composeWindowed; |
| 172 |
2/2✓ Branch 65 → 66 taken 676 times.
✓ Branch 66 → 67 taken 676 times.
|
676 | preferences["ui/closeToTray"] = m_closeToTray; |
| 173 |
4/4✓ Branch 70 → 71 taken 676 times.
✓ Branch 84 → 85 taken 13520 times.
✓ Branch 86 → 72 taken 12844 times.
✓ Branch 86 → 87 taken 676 times.
|
13520 | for (auto it = m_calendarState.cbegin(); it != m_calendarState.cend(); ++it) |
| 174 |
3/3✓ Branch 77 → 78 taken 12844 times.
✓ Branch 78 → 79 taken 12844 times.
✓ Branch 79 → 80 taken 12844 times.
|
25688 | preferences[QStringLiteral("calendar/") + it.key()] = it.value(); |
| 175 |
4/4✓ Branch 91 → 92 taken 676 times.
✓ Branch 92 → 93 taken 676 times.
✓ Branch 94 → 95 taken 676 times.
✓ Branch 95 → 96 taken 676 times.
|
1352 | preferences["commandbar/history"] = m_commandBarState.value(QStringLiteral("history")).toStringList(); |
| 176 |
4/4✓ Branch 108 → 109 taken 676 times.
✓ Branch 109 → 110 taken 676 times.
✓ Branch 111 → 112 taken 676 times.
✓ Branch 112 → 113 taken 676 times.
|
1352 | preferences["commandbar/recentFolders"] = m_commandBarState.value(QStringLiteral("recentFolders")).toMap(); |
| 177 |
4/4✓ Branch 125 → 126 taken 676 times.
✓ Branch 126 → 127 taken 676 times.
✓ Branch 128 → 129 taken 676 times.
✓ Branch 129 → 130 taken 676 times.
|
1352 | preferences["search/timelineCollapsed"] = m_searchState.value(QStringLiteral("timelineCollapsed")).toBool(); |
| 178 |
4/4✓ Branch 141 → 142 taken 676 times.
✓ Branch 142 → 143 taken 676 times.
✓ Branch 144 → 145 taken 676 times.
✓ Branch 145 → 146 taken 676 times.
|
1352 | preferences["search/timelineHintSeen"] = m_searchState.value(QStringLiteral("timelineHintSeen")).toBool(); |
| 179 |
4/4✓ Branch 157 → 158 taken 676 times.
✓ Branch 158 → 159 taken 676 times.
✓ Branch 160 → 161 taken 676 times.
✓ Branch 161 → 162 taken 676 times.
|
1352 | preferences["search/grouped"] = m_searchState.value(QStringLiteral("grouped")).toBool(); |
| 180 | 676 | snapshot.preferences = preferences; | |
| 181 | 676 | return snapshot; | |
| 182 | 676 | } | |
| 183 | |||
| 184 | 573 | void WorkspacePersistence::flushAsync() { | |
| 185 | 573 | m_timer.stop(); | |
| 186 |
3/4✓ Branch 3 → 4 taken 573 times.
✗ Branch 3 → 5 not taken.
✓ Branch 4 → 5 taken 2 times.
✓ Branch 4 → 6 taken 571 times.
|
573 | if (!m_loaded || !m_dirty) return; |
| 187 | // The GUI thread only pays for the immutable snapshot; a newer snapshot | ||
| 188 | // replaces an older pending one (scroll/selection storms coalesce). | ||
| 189 |
1/1✓ Branch 6 → 7 taken 571 times.
|
571 | m_pendingSnapshot = buildSnapshot(); |
| 190 | 571 | m_pendingWrite = true; | |
| 191 | 571 | m_dirty = false; | |
| 192 |
2/2✓ Branch 10 → 11 taken 9 times.
✓ Branch 10 → 12 taken 562 times.
|
571 | if (m_pendingSnapshot == m_lastDispatched) { |
| 193 | // The same snapshot was committed or is currently in flight. Its | ||
| 194 | // failure path restores dirty and invalidates m_lastDispatched. | ||
| 195 | 9 | m_pendingWrite = false; | |
| 196 | 9 | return; | |
| 197 | } | ||
| 198 | 562 | dispatchPendingWrite(); | |
| 199 | } | ||
| 200 | |||
| 201 | 1043 | void WorkspacePersistence::dispatchPendingWrite() { | |
| 202 |
7/8✓ Branch 2 → 3 taken 563 times.
✓ Branch 2 → 6 taken 480 times.
✓ Branch 3 → 4 taken 560 times.
✓ Branch 3 → 6 taken 3 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 560 times.
✓ Branch 8 → 9 taken 483 times.
✓ Branch 8 → 10 taken 560 times.
|
1043 | if (!m_pendingWrite || m_writeInFlight || !m_writer) return; |
| 203 | 560 | m_lastDispatched = m_pendingSnapshot; | |
| 204 | 560 | m_pendingWrite = false; | |
| 205 | 560 | m_writeInFlight = true; | |
| 206 | 560 | ++m_writeGeneration; | |
| 207 |
1/1✓ Branch 15 → 16 taken 560 times.
|
560 | QMetaObject::invokeMethod(m_writer, "write", Qt::QueuedConnection, |
| 208 |
1/1✓ Branch 13 → 14 taken 560 times.
|
560 | Q_ARG(quint64, m_writeGeneration), |
| 209 |
1/1✓ Branch 12 → 13 taken 560 times.
|
560 | Q_ARG(QJsonObject, m_lastDispatched.session), |
| 210 |
1/1✓ Branch 11 → 12 taken 560 times.
|
1120 | Q_ARG(QVariantMap, m_lastDispatched.preferences)); |
| 211 | } | ||
| 212 | |||
| 213 | 50 | bool WorkspacePersistence::flush() { | |
| 214 | 50 | m_timer.stop(); | |
| 215 |
2/2✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 49 times.
|
50 | if (!m_loaded) return false; |
| 216 |
4/6✓ Branch 5 → 6 taken 2 times.
✓ Branch 5 → 9 taken 47 times.
✓ Branch 6 → 7 taken 2 times.
✗ Branch 6 → 9 not taken.
✓ Branch 7 → 8 taken 2 times.
✗ Branch 7 → 9 not taken.
|
49 | if (!m_dirty && !m_pendingWrite && !m_writeInFlight) return true; |
| 217 | // Shutdown contract: durable before returning. Newest state wins; the | ||
| 218 | // blocking call serializes behind any in-flight write on the writer | ||
| 219 | // thread (the writer never calls back into the GUI thread). | ||
| 220 |
1/1✓ Branch 9 → 10 taken 47 times.
|
47 | m_pendingSnapshot = buildSnapshot(); |
| 221 | 47 | m_pendingWrite = false; | |
| 222 | 47 | m_dirty = false; | |
| 223 |
3/6✓ Branch 13 → 14 taken 47 times.
✗ Branch 13 → 17 not taken.
✓ Branch 15 → 16 taken 47 times.
✗ Branch 15 → 17 not taken.
✓ Branch 18 → 19 taken 47 times.
✗ Branch 18 → 38 not taken.
|
47 | if (m_writer && m_writerThread.isRunning()) { |
| 224 | 47 | bool ok = false; | |
| 225 | 47 | ++m_writeGeneration; | |
| 226 |
2/2✓ Branch 22 → 23 taken 47 times.
✓ Branch 24 → 25 taken 47 times.
|
47 | QMetaObject::invokeMethod(m_writer, "write", Qt::BlockingQueuedConnection, |
| 227 | Q_RETURN_ARG(bool, ok), | ||
| 228 |
1/1✓ Branch 21 → 22 taken 47 times.
|
47 | Q_ARG(quint64, m_writeGeneration), |
| 229 |
1/1✓ Branch 20 → 21 taken 47 times.
|
47 | Q_ARG(QJsonObject, m_pendingSnapshot.session), |
| 230 |
1/1✓ Branch 19 → 20 taken 47 times.
|
47 | Q_ARG(QVariantMap, m_pendingSnapshot.preferences)); |
| 231 | 47 | m_lastDispatched = m_pendingSnapshot; | |
| 232 | 47 | m_writeInFlight = false; | |
| 233 |
2/2✓ Branch 26 → 27 taken 1 time.
✓ Branch 26 → 35 taken 46 times.
|
47 | if (!ok) { |
| 234 | 1 | m_lastDispatched = {}; | |
| 235 | 1 | m_dirty = true; | |
| 236 |
1/1✓ Branch 30 → 31 taken 1 time.
|
1 | m_error = tr("Workspace state could not be saved."); |
| 237 |
1/1✓ Branch 33 → 34 taken 1 time.
|
1 | emit saveFailed(m_error); |
| 238 | 1 | return false; | |
| 239 | } | ||
| 240 | 46 | m_error.clear(); | |
| 241 | 46 | return true; | |
| 242 | } | ||
| 243 | // Fallback (writer thread already stopped): synchronous GUI write. | ||
| 244 | ✗ | if (!m_store.saveSession(m_pendingSnapshot.session) || | |
| 245 | ✗ | !m_store.savePreferences(m_pendingSnapshot.preferences)) { | |
| 246 | ✗ | m_lastDispatched = {}; | |
| 247 | ✗ | m_dirty = true; | |
| 248 | ✗ | m_error = m_store.error(); | |
| 249 | ✗ | emit saveFailed(m_error); | |
| 250 | ✗ | return false; | |
| 251 | } | ||
| 252 | ✗ | m_lastDispatched = m_pendingSnapshot; | |
| 253 | ✗ | m_error.clear(); | |
| 254 | ✗ | return true; | |
| 255 | 1 | } | |
| 256 |