service/SettingsSyncService.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "service/SettingsSyncService.h" | ||
| 2 | |||
| 3 | #include <QLoggingCategory> | ||
| 4 | |||
| 5 | #include <algorithm> | ||
| 6 | #include <functional> | ||
| 7 | #include <QPointer> | ||
| 8 | #include <utility> | ||
| 9 | |||
| 10 | #include "service/ConnectionHealthMonitor.h" | ||
| 11 | #include "service/ImapService.h" | ||
| 12 | |||
| 13 |
4/7✓ Branch 2 → 3 taken 4 times.
✓ Branch 2 → 8 taken 317 times.
✓ Branch 4 → 5 taken 4 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 4 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
|
321 | Q_LOGGING_CATEGORY(lcSync, "mailjd.sync") |
| 14 | |||
| 15 | static constexpr const char *SYNC_SUBJECT = "X-MailJD-Settings-Sync"; | ||
| 16 | |||
| 17 | // ═══════════════════════════════════════════════════════ | ||
| 18 | // Construction / Configuration | ||
| 19 | // ═══════════════════════════════════════════════════════ | ||
| 20 | |||
| 21 |
1/1✓ Branch 3 → 4 taken 32 times.
|
32 | SettingsSyncService::SettingsSyncService(QObject *parent) : QObject(parent) { |
| 22 | // T-720: The reconnect timer/handleDisconnect() plumbing is gone — | ||
| 23 | // ConnectionHealthMonitor owns both the liveness probe + the | ||
| 24 | // exponential-backoff reconnect. The service owns the monitor. | ||
| 25 | 32 | } | |
| 26 | |||
| 27 | 44 | SettingsSyncService::~SettingsSyncService() { shutdown(); } | |
| 28 | |||
| 29 | 23 | void SettingsSyncService::configure(const ImapConfig &config, | |
| 30 | const QString &syncFolder) { | ||
| 31 | 23 | retireTransport(ImapService::DisconnectReason::AccountReload); | |
| 32 | 23 | m_config = config; | |
| 33 | 23 | m_syncFolder = syncFolder; | |
| 34 | |||
| 35 |
2/5✓ Branch 6 → 7 taken 23 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 23 times.
✗ Branch 48 → 49 not taken.
✗ Branch 48 → 50 not taken.
|
23 | m_imap = new ImapService(this); |
| 36 | 23 | m_imap->setAutoIdle(false); // We manage IDLE ourselves | |
| 37 | |||
| 38 | // Wire up signals | ||
| 39 | 23 | connect(m_imap, &ImapService::stateChanged, this, | |
| 40 |
1/1✓ Branch 10 → 11 taken 23 times.
|
23 | &SettingsSyncService::onStateChanged); |
| 41 | 23 | connect(m_imap, &ImapService::folderListReceived, this, | |
| 42 |
1/1✓ Branch 12 → 13 taken 23 times.
|
23 | &SettingsSyncService::onFolderListReceived); |
| 43 | 23 | connect(m_imap, &ImapService::folderSelected, this, | |
| 44 |
1/1✓ Branch 14 → 15 taken 23 times.
|
23 | &SettingsSyncService::onFolderSelected); |
| 45 | 23 | connect(m_imap, &ImapService::folderCreated, this, | |
| 46 |
1/1✓ Branch 16 → 17 taken 23 times.
|
23 | &SettingsSyncService::onFolderCreated); |
| 47 | 23 | connect(m_imap, &ImapService::searchResultReceived, this, | |
| 48 |
1/1✓ Branch 18 → 19 taken 23 times.
|
23 | &SettingsSyncService::onSearchResultReceived); |
| 49 | 23 | connect(m_imap, &ImapService::rawBodyReceived, this, | |
| 50 |
1/1✓ Branch 20 → 21 taken 23 times.
|
23 | &SettingsSyncService::onRawBodyReceived); |
| 51 | 23 | connect(m_imap, &ImapService::messageAppended, this, | |
| 52 |
1/1✓ Branch 22 → 23 taken 23 times.
|
23 | &SettingsSyncService::onMessageAppended); |
| 53 | 23 | connect(m_imap, &ImapService::appendError, this, | |
| 54 |
1/1✓ Branch 24 → 25 taken 23 times.
|
23 | &SettingsSyncService::onAppendError); |
| 55 | 23 | connect(m_imap, &ImapService::folderOperationError, this, | |
| 56 |
1/1✓ Branch 26 → 27 taken 23 times.
|
23 | &SettingsSyncService::onFolderOperationError); |
| 57 | 23 | connect(m_imap, &ImapService::idleNewMessages, this, | |
| 58 |
1/1✓ Branch 28 → 29 taken 23 times.
|
23 | &SettingsSyncService::onIdleNewMessages); |
| 59 | 23 | connect(m_imap, &ImapService::expungeComplete, this, | |
| 60 |
1/1✓ Branch 30 → 31 taken 23 times.
|
23 | &SettingsSyncService::onExpungeComplete); |
| 61 | 23 | connect(m_imap, &ImapService::expungeFailed, this, | |
| 62 |
1/1✓ Branch 32 → 33 taken 23 times.
|
23 | &SettingsSyncService::onExpungeFailed); |
| 63 | // T-720: errorOccurred still resets the phase, but reconnect scheduling | ||
| 64 | // is delegated to the monitor. | ||
| 65 | 23 | connect(m_imap, &ImapService::errorOccurred, this, | |
| 66 |
1/1✓ Branch 34 → 35 taken 23 times.
|
23 | [this](const QString &err) { |
| 67 | ✗ | qCWarning(lcSync) << "IMAP error:" << err; | |
| 68 | ✗ | if (m_phase == Phase::CleaningUp) { | |
| 69 | ✗ | onExpungeFailed(err); | |
| 70 | ✗ | return; | |
| 71 | } | ||
| 72 | ✗ | m_phase = Phase::Idle; | |
| 73 | ✗ | emit syncError(err); | |
| 74 | }); | ||
| 75 | |||
| 76 | // T-720: (Re)create the monitor for the new service and install the | ||
| 77 | // reconnect config. Activation follows m_enabled — see setEnabled(). | ||
| 78 | // systemWatchEnabled=false: secondary monitor — the main connection | ||
| 79 | // (MainWindow) owns the single set of system-wide suspend/network hooks. | ||
| 80 |
2/2✓ Branch 36 → 37 taken 21 times.
✓ Branch 36 → 42 taken 2 times.
|
23 | if (!m_health) |
| 81 |
2/5✓ Branch 38 → 39 taken 21 times.
✗ Branch 39 → 40 not taken.
✓ Branch 39 → 41 taken 21 times.
✗ Branch 77 → 78 not taken.
✗ Branch 77 → 79 not taken.
|
21 | m_health = new ConnectionHealthMonitor(false, this); |
| 82 | 23 | m_health->attach(m_imap); | |
| 83 | 23 | m_health->setReconnectConfig(m_config); | |
| 84 | 23 | m_health->setActive(m_enabled); | |
| 85 |
1/2✗ Branch 45 → 46 not taken.
✓ Branch 45 → 47 taken 23 times.
|
23 | if (m_enabled) |
| 86 | ✗ | m_imap->connectToServer(m_config); | |
| 87 | 23 | } | |
| 88 | |||
| 89 | 18 | void SettingsSyncService::setEnabled(bool enabled) { | |
| 90 |
2/2✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 17 times.
|
18 | if (m_enabled == enabled) |
| 91 | 1 | return; | |
| 92 |
6/8✓ Branch 4 → 5 taken 14 times.
✓ Branch 4 → 9 taken 3 times.
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 9 taken 13 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 1 time.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 17 times.
|
17 | if (enabled && !m_imap && !m_config.host.isEmpty()) |
| 93 | ✗ | configure(m_config, m_syncFolder); | |
| 94 | 17 | m_enabled = enabled; | |
| 95 | |||
| 96 |
4/4✓ Branch 12 → 13 taken 14 times.
✓ Branch 12 → 26 taken 3 times.
✓ Branch 13 → 14 taken 13 times.
✓ Branch 13 → 26 taken 1 time.
|
17 | if (enabled && m_imap) { |
| 97 |
5/5✓ Branch 14 → 15 taken 13 times.
✓ Branch 18 → 19 taken 13 times.
✓ Branch 19 → 20 taken 13 times.
✓ Branch 23 → 16 taken 13 times.
✓ Branch 23 → 24 taken 13 times.
|
26 | qCInfo(lcSync) << "Settings sync enabled, connecting..."; |
| 98 | 13 | m_imap->connectToServer(m_config); | |
| 99 |
2/2✓ Branch 26 → 27 taken 3 times.
✓ Branch 26 → 38 taken 1 time.
|
17 | } else if (!enabled) { |
| 100 |
5/5✓ Branch 27 → 28 taken 3 times.
✓ Branch 31 → 32 taken 3 times.
✓ Branch 32 → 33 taken 3 times.
✓ Branch 36 → 29 taken 3 times.
✓ Branch 36 → 37 taken 3 times.
|
6 | qCInfo(lcSync) << "Settings sync disabled"; |
| 101 | 3 | retireTransport(ImapService::DisconnectReason::UserRequested); | |
| 102 | } | ||
| 103 | // T-720: Reflect the new enabled state on the monitor so reconnect is | ||
| 104 | // only active while the user has sync turned on. | ||
| 105 |
2/2✓ Branch 38 → 39 taken 15 times.
✓ Branch 38 → 40 taken 2 times.
|
17 | if (m_health) |
| 106 | 15 | m_health->setActive(m_enabled); | |
| 107 | } | ||
| 108 | |||
| 109 | 37 | void SettingsSyncService::shutdown() { | |
| 110 | 37 | m_enabled = false; | |
| 111 | 37 | retireTransport(ImapService::DisconnectReason::ApplicationShutdown); | |
| 112 | 37 | } | |
| 113 | |||
| 114 | 63 | void SettingsSyncService::retireTransport(ImapService::DisconnectReason reason) { | |
| 115 | 63 | ++m_generation; | |
| 116 | 63 | m_phase = Phase::Idle; | |
| 117 | 63 | m_pendingUpload.clear(); | |
| 118 | 63 | m_currentSettingsUid = -1; | |
| 119 | 63 | m_newAppendedUid = -1; | |
| 120 | 63 | m_validatedSettingsUid = -1; | |
| 121 | 63 | m_validatedUidValidity = 0; | |
| 122 | 63 | m_previousSettingsUid = -1; | |
| 123 | 63 | m_uploadUidValidity = 0; | |
| 124 | 63 | m_messageCount = 0; | |
| 125 |
2/2✓ Branch 3 → 4 taken 28 times.
✓ Branch 3 → 6 taken 35 times.
|
63 | if (m_health) { |
| 126 | 28 | m_health->setActive(false); | |
| 127 | 28 | m_health->detach(); | |
| 128 | } | ||
| 129 |
2/2✓ Branch 7 → 8 taken 25 times.
✓ Branch 7 → 12 taken 38 times.
|
63 | if (auto *retired = std::exchange(m_imap, nullptr)) { |
| 130 | // deleteLater keeps this socket alive until the event loop drains. Cut | ||
| 131 | // every service callback as well as the monitor before disconnecting it. | ||
| 132 | 25 | QObject::disconnect(retired, nullptr, this, nullptr); | |
| 133 | 25 | retired->clearDeferredCommands(); | |
| 134 | 25 | retired->disconnect(reason); | |
| 135 | 25 | retired->deleteLater(); | |
| 136 | } | ||
| 137 | 63 | } | |
| 138 | |||
| 139 | 51 | void SettingsSyncService::afterIdle(std::function<void()> command) { | |
| 140 |
2/4✓ Branch 2 → 3 taken 51 times.
✗ Branch 2 → 4 not taken.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 51 times.
|
51 | if (!m_imap || !m_enabled) |
| 141 | ✗ | return; | |
| 142 |
1/1✓ Branch 5 → 6 taken 51 times.
|
51 | QPointer<SettingsSyncService> guard(this); |
| 143 | 51 | const auto generation = m_generation; | |
| 144 |
2/4✓ Branch 9 → 10 taken 51 times.
✓ Branch 10 → 11 taken 51 times.
✗ Branch 20 → 21 not taken.
✗ Branch 20 → 22 not taken.
|
51 | m_imap->executeAfterIdle([guard, generation, command = std::move(command)] { |
| 145 |
4/8✓ Branch 3 → 4 taken 50 times.
✗ Branch 3 → 11 not taken.
✓ Branch 5 → 6 taken 50 times.
✗ Branch 5 → 11 not taken.
✓ Branch 7 → 8 taken 50 times.
✗ Branch 7 → 11 not taken.
✓ Branch 12 → 13 taken 50 times.
✗ Branch 12 → 14 not taken.
|
100 | if (guard && guard->m_generation == generation && guard->m_enabled && |
| 146 |
1/2✓ Branch 9 → 10 taken 50 times.
✗ Branch 9 → 11 not taken.
|
50 | guard->m_imap) |
| 147 | 50 | command(); | |
| 148 | 50 | }); | |
| 149 | 51 | } | |
| 150 | |||
| 151 | // ═══════════════════════════════════════════════════════ | ||
| 152 | // IMAP State Machine | ||
| 153 | // ═══════════════════════════════════════════════════════ | ||
| 154 | |||
| 155 | 89 | void SettingsSyncService::onStateChanged(ImapService::State state) { | |
| 156 |
2/2✓ Branch 2 → 3 taken 13 times.
✓ Branch 2 → 15 taken 76 times.
|
89 | if (state == ImapService::State::Authenticated) { |
| 157 |
5/5✓ Branch 3 → 4 taken 13 times.
✓ Branch 7 → 8 taken 13 times.
✓ Branch 8 → 9 taken 13 times.
✓ Branch 12 → 5 taken 13 times.
✓ Branch 12 → 13 taken 13 times.
|
26 | qCInfo(lcSync) << "Sync IMAP authenticated, checking folder..."; |
| 158 | 13 | emit connected(); | |
| 159 | 13 | checkOrCreateFolder(); | |
| 160 |
4/4✓ Branch 15 → 16 taken 75 times.
✓ Branch 15 → 17 taken 1 time.
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 19 taken 74 times.
|
76 | } else if (state == ImapService::State::Disconnected || |
| 161 | state == ImapService::State::Error) { | ||
| 162 | // T-720: Keep the phase reset so a stale WatchingIdle phase cannot | ||
| 163 | // receive push notifications on the next (reconnected) session. | ||
| 164 | // Reconnect scheduling itself moved to ConnectionHealthMonitor. | ||
| 165 | 2 | m_phase = Phase::Idle; | |
| 166 | 2 | m_validatedSettingsUid = -1; | |
| 167 | 2 | m_validatedUidValidity = 0; | |
| 168 |
1/2✗ Branch 17 → 18 not taken.
✓ Branch 17 → 19 taken 2 times.
|
2 | if (m_enabled) emit connectionLost(); |
| 169 | } | ||
| 170 | 89 | } | |
| 171 | |||
| 172 | // ═══════════════════════════════════════════════════════ | ||
| 173 | // Phase: Check / Create sync folder | ||
| 174 | // ═══════════════════════════════════════════════════════ | ||
| 175 | |||
| 176 | 13 | void SettingsSyncService::checkOrCreateFolder() { | |
| 177 | 13 | m_phase = Phase::CheckingFolder; | |
| 178 |
6/6✓ Branch 2 → 3 taken 13 times.
✓ Branch 6 → 7 taken 13 times.
✓ Branch 7 → 8 taken 13 times.
✓ Branch 8 → 9 taken 13 times.
✓ Branch 12 → 4 taken 13 times.
✓ Branch 12 → 13 taken 13 times.
|
26 | qCInfo(lcSync) << "Checking if sync folder exists:" << m_syncFolder; |
| 179 | 13 | m_imap->listFolders(); // Will trigger onFolderListReceived | |
| 180 | 13 | } | |
| 181 | |||
| 182 | 15 | void SettingsSyncService::onFolderListReceived( | |
| 183 | const QList<FolderInfo> &folders) { | ||
| 184 |
2/2✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 14 times.
|
15 | if (m_phase != Phase::CheckingFolder) |
| 185 | 1 | return; | |
| 186 | |||
| 187 | 14 | bool found = false; | |
| 188 |
2/2✓ Branch 12 → 6 taken 15 times.
✓ Branch 12 → 13 taken 1 time.
|
16 | for (const auto &f : folders) { |
| 189 |
2/2✓ Branch 8 → 9 taken 13 times.
✓ Branch 8 → 10 taken 2 times.
|
15 | if (f.path == m_syncFolder) { |
| 190 | 13 | found = true; | |
| 191 | 13 | break; | |
| 192 | } | ||
| 193 | } | ||
| 194 | |||
| 195 |
2/2✓ Branch 13 → 14 taken 13 times.
✓ Branch 13 → 25 taken 1 time.
|
14 | if (found) { |
| 196 |
5/5✓ Branch 14 → 15 taken 13 times.
✓ Branch 18 → 19 taken 13 times.
✓ Branch 19 → 20 taken 13 times.
✓ Branch 23 → 16 taken 13 times.
✓ Branch 23 → 24 taken 13 times.
|
26 | qCInfo(lcSync) << "Sync folder exists, selecting..."; |
| 197 | 13 | m_phase = Phase::SelectingFolder; | |
| 198 | 13 | m_imap->selectFolder(m_syncFolder); | |
| 199 | } else { | ||
| 200 |
6/6✓ Branch 25 → 26 taken 1 time.
✓ Branch 29 → 30 taken 1 time.
✓ Branch 30 → 31 taken 1 time.
✓ Branch 31 → 32 taken 1 time.
✓ Branch 35 → 27 taken 1 time.
✓ Branch 35 → 36 taken 1 time.
|
2 | qCInfo(lcSync) << "Sync folder not found, creating:" << m_syncFolder; |
| 201 | 1 | m_phase = Phase::CreatingFolder; | |
| 202 | 1 | m_imap->createFolder(m_syncFolder); | |
| 203 | } | ||
| 204 | } | ||
| 205 | |||
| 206 | 2 | void SettingsSyncService::onFolderCreated(const QString &folderPath) { | |
| 207 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 2 times.
|
2 | if (m_phase != Phase::CreatingFolder) |
| 208 | ✗ | return; | |
| 209 |
2/2✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 17 taken 1 time.
|
2 | if (folderPath == m_syncFolder) { |
| 210 |
5/5✓ Branch 6 → 7 taken 1 time.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 15 → 8 taken 1 time.
✓ Branch 15 → 16 taken 1 time.
|
2 | qCInfo(lcSync) << "Sync folder created, selecting..."; |
| 211 | 1 | m_phase = Phase::SelectingFolder; | |
| 212 | 1 | m_imap->selectFolder(m_syncFolder); | |
| 213 | } | ||
| 214 | } | ||
| 215 | |||
| 216 | 19 | void SettingsSyncService::onFolderSelected(const QString &path, | |
| 217 | int messageCount, | ||
| 218 | quint32 uidValidity, | ||
| 219 | quint64 /*highestModseq*/) { | ||
| 220 |
2/2✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 18 times.
|
19 | if (path != m_syncFolder) |
| 221 | 1 | return; | |
| 222 | |||
| 223 | 18 | m_messageCount = messageCount; | |
| 224 |
6/6✓ Branch 5 → 6 taken 18 times.
✓ Branch 9 → 10 taken 18 times.
✓ Branch 10 → 11 taken 18 times.
✓ Branch 11 → 12 taken 18 times.
✓ Branch 15 → 7 taken 18 times.
✓ Branch 15 → 16 taken 18 times.
|
36 | qCInfo(lcSync) << "Sync folder selected, messages:" << messageCount; |
| 225 | |||
| 226 | 18 | emit syncFolderReady(); | |
| 227 | |||
| 228 |
2/2✓ Branch 17 → 18 taken 14 times.
✓ Branch 17 → 19 taken 4 times.
|
18 | if (m_phase == Phase::SelectingFolder) { |
| 229 | // Initial connect: fetch latest settings, then start watching | ||
| 230 | 14 | doFetch(); | |
| 231 |
2/2✓ Branch 19 → 20 taken 3 times.
✓ Branch 19 → 49 taken 1 time.
|
4 | } else if (m_phase == Phase::CleaningUp) { |
| 232 | // APPEND may race another client or a deleted/recreated mailbox. Only | ||
| 233 | // prune the previously validated payload in the same mailbox epoch. | ||
| 234 |
4/4✓ Branch 20 → 21 taken 2 times.
✓ Branch 20 → 23 taken 1 time.
✓ Branch 21 → 22 taken 1 time.
✓ Branch 21 → 23 taken 1 time.
|
3 | const bool sameEpoch = uidValidity != 0 && uidValidity == m_uploadUidValidity; |
| 235 |
2/2✓ Branch 24 → 25 taken 1 time.
✓ Branch 24 → 26 taken 2 times.
|
3 | m_validatedSettingsUid = sameEpoch ? m_newAppendedUid : -1; |
| 236 |
2/2✓ Branch 27 → 28 taken 1 time.
✓ Branch 27 → 29 taken 2 times.
|
3 | m_validatedUidValidity = sameEpoch ? uidValidity : 0; |
| 237 |
1/2✓ Branch 31 → 32 taken 1 time.
✗ Branch 31 → 36 not taken.
|
1 | if (sameEpoch && m_previousSettingsUid > 0 && |
| 238 |
6/8✓ Branch 30 → 31 taken 1 time.
✓ Branch 30 → 36 taken 2 times.
✓ Branch 32 → 33 taken 1 time.
✗ Branch 32 → 36 not taken.
✓ Branch 34 → 35 taken 1 time.
✗ Branch 34 → 36 not taken.
✓ Branch 37 → 38 taken 1 time.
✓ Branch 37 → 48 taken 2 times.
|
4 | m_previousSettingsUid < m_newAppendedUid && m_imap->hasUidExpungeCapability()) { |
| 239 |
1/1✓ Branch 41 → 42 taken 1 time.
|
2 | m_imap->storeFlag(m_previousSettingsUid, QStringLiteral("\\Deleted"), true); |
| 240 |
2/2✓ Branch 44 → 45 taken 1 time.
✓ Branch 45 → 46 taken 1 time.
|
1 | m_imap->expungeUids({m_previousSettingsUid}, m_syncFolder, uidValidity); |
| 241 | } else { | ||
| 242 | 2 | finishUpload(); | |
| 243 | } | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | // ═══════════════════════════════════════════════════════ | ||
| 248 | // Phase: Fetch latest settings | ||
| 249 | // ═══════════════════════════════════════════════════════ | ||
| 250 | |||
| 251 | 31 | void SettingsSyncService::fetchLatestSettings() { | |
| 252 |
3/4✓ Branch 2 → 3 taken 30 times.
✓ Branch 2 → 4 taken 1 time.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 30 times.
|
31 | if (!m_imap || !m_enabled) |
| 253 | 1 | return; | |
| 254 |
4/4✓ Branch 5 → 6 taken 29 times.
✓ Branch 5 → 8 taken 1 time.
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 8 taken 28 times.
|
30 | if (m_phase != Phase::Idle && m_phase != Phase::WatchingIdle) return; |
| 255 | // Reserve admission before DONE completes. Another foreground request or | ||
| 256 | // IDLE notification must not replace this operation while it is queued. | ||
| 257 | 29 | m_phase = Phase::Fetching; | |
| 258 | |||
| 259 |
1/1✓ Branch 9 → 10 taken 29 times.
|
29 | afterIdle([this]() { |
| 260 | // Make sure we're in the right folder | ||
| 261 |
2/2✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 28 times.
|
29 | if (m_imap->selectedFolder() != m_syncFolder) { |
| 262 | 1 | m_phase = Phase::SelectingFolder; | |
| 263 | 1 | m_imap->selectFolder(m_syncFolder); | |
| 264 | } else { | ||
| 265 | 28 | doFetch(); | |
| 266 | } | ||
| 267 | 29 | }); | |
| 268 | } | ||
| 269 | |||
| 270 | 44 | void SettingsSyncService::doFetch() { | |
| 271 | 44 | m_phase = Phase::Fetching; | |
| 272 | 44 | m_currentSettingsUid = -1; | |
| 273 | |||
| 274 |
2/2✓ Branch 2 → 3 taken 14 times.
✓ Branch 2 → 16 taken 30 times.
|
44 | if (m_messageCount == 0) { |
| 275 |
5/5✓ Branch 3 → 4 taken 14 times.
✓ Branch 7 → 8 taken 14 times.
✓ Branch 8 → 9 taken 14 times.
✓ Branch 12 → 5 taken 14 times.
✓ Branch 12 → 13 taken 14 times.
|
28 | qCInfo(lcSync) << "Sync folder is empty, no settings to fetch"; |
| 276 | 14 | startIdleWatch(); | |
| 277 | 14 | emit fetchComplete(false); | |
| 278 | 14 | return; | |
| 279 | } | ||
| 280 | |||
| 281 | // Search for settings messages | ||
| 282 |
5/5✓ Branch 16 → 17 taken 30 times.
✓ Branch 20 → 21 taken 30 times.
✓ Branch 21 → 22 taken 30 times.
✓ Branch 25 → 18 taken 30 times.
✓ Branch 25 → 26 taken 30 times.
|
60 | qCInfo(lcSync) << "Searching for settings messages..."; |
| 283 |
2/2✓ Branch 30 → 31 taken 30 times.
✓ Branch 31 → 32 taken 30 times.
|
60 | m_imap->searchText(QString::fromLatin1(SYNC_SUBJECT), |
| 284 | 60 | QStringLiteral("SUBJECT")); | |
| 285 | } | ||
| 286 | |||
| 287 | 35 | void SettingsSyncService::onSearchResultReceived(const QList<qint64> &uids) { | |
| 288 |
2/2✓ Branch 2 → 3 taken 31 times.
✓ Branch 2 → 23 taken 4 times.
|
35 | if (m_phase == Phase::Fetching) { |
| 289 |
2/2✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 18 taken 30 times.
|
31 | if (uids.isEmpty()) { |
| 290 |
5/5✓ Branch 5 → 6 taken 1 time.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 14 → 7 taken 1 time.
✓ Branch 14 → 15 taken 1 time.
|
2 | qCInfo(lcSync) << "No settings messages found"; |
| 291 | 1 | startIdleWatch(); | |
| 292 | 1 | emit fetchComplete(false); | |
| 293 | 1 | return; | |
| 294 | } | ||
| 295 | |||
| 296 | // Newest (highest UID) first; older ones are fallbacks for a newest | ||
| 297 | // message that was delivered by mail instead of uploaded by a client. | ||
| 298 | 30 | m_candidateUids = uids; | |
| 299 | 30 | std::sort(m_candidateUids.begin(), m_candidateUids.end(), std::greater<qint64>()); | |
| 300 | 30 | fetchNextCandidate(); | |
| 301 | } | ||
| 302 | } | ||
| 303 | |||
| 304 | 32 | void SettingsSyncService::fetchNextCandidate() { | |
| 305 |
2/2✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 17 taken 31 times.
|
32 | if (m_candidateUids.isEmpty()) { |
| 306 |
5/5✓ Branch 4 → 5 taken 1 time.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 13 → 6 taken 1 time.
✓ Branch 13 → 14 taken 1 time.
|
2 | qCInfo(lcSync) << "No uploaded settings message found"; |
| 307 | 1 | startIdleWatch(); | |
| 308 | 1 | emit fetchComplete(false); | |
| 309 | 1 | return; | |
| 310 | } | ||
| 311 | 31 | m_currentSettingsUid = m_candidateUids.takeFirst(); | |
| 312 |
6/6✓ Branch 18 → 19 taken 31 times.
✓ Branch 22 → 23 taken 31 times.
✓ Branch 23 → 24 taken 31 times.
✓ Branch 24 → 25 taken 31 times.
✓ Branch 28 → 20 taken 31 times.
✓ Branch 28 → 29 taken 31 times.
|
62 | qCInfo(lcSync) << "Fetching settings message UID:" << m_currentSettingsUid; |
| 313 | 31 | m_imap->fetchBody(m_currentSettingsUid, SyncPayload::MaxRfcMessageBytes + 1); | |
| 314 | } | ||
| 315 | |||
| 316 | 37 | void SettingsSyncService::onRawBodyReceived(qint64 uid, | |
| 317 | const QByteArray &rawBody) { | ||
| 318 |
5/6✓ Branch 2 → 3 taken 36 times.
✓ Branch 2 → 5 taken 1 time.
✓ Branch 3 → 4 taken 36 times.
✗ Branch 3 → 5 not taken.
✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 6 taken 35 times.
|
37 | if (m_phase != Phase::Fetching || uid <= 0 || uid != m_currentSettingsUid) |
| 319 | 7 | return; | |
| 320 | |||
| 321 |
6/6✓ Branch 6 → 7 taken 35 times.
✓ Branch 10 → 11 taken 35 times.
✓ Branch 11 → 12 taken 35 times.
✓ Branch 12 → 13 taken 35 times.
✓ Branch 19 → 8 taken 35 times.
✓ Branch 19 → 20 taken 35 times.
|
70 | qCInfo(lcSync) << "Received settings message body, UID:" << uid |
| 322 |
2/2✓ Branch 13 → 14 taken 35 times.
✓ Branch 15 → 16 taken 35 times.
|
35 | << "size:" << rawBody.size(); |
| 323 | |||
| 324 |
2/2✓ Branch 21 → 22 taken 1 time.
✓ Branch 21 → 43 taken 34 times.
|
35 | if (rawBody.size() > SyncPayload::MaxRfcMessageBytes) { |
| 325 |
6/6✓ Branch 22 → 23 taken 1 time.
✓ Branch 26 → 27 taken 1 time.
✓ Branch 27 → 28 taken 1 time.
✓ Branch 29 → 30 taken 1 time.
✓ Branch 34 → 24 taken 1 time.
✓ Branch 34 → 35 taken 1 time.
|
2 | qCWarning(lcSync) << "Settings message too large:" << rawBody.size() |
| 326 |
1/1✓ Branch 30 → 31 taken 1 time.
|
1 | << "bytes"; |
| 327 | 1 | m_phase = Phase::Idle; | |
| 328 |
1/1✓ Branch 35 → 36 taken 1 time.
|
1 | startIdleWatch(); |
| 329 |
1/1✓ Branch 39 → 40 taken 1 time.
|
1 | emit syncError(QStringLiteral("Settings sync message too large")); |
| 330 | 1 | return; | |
| 331 | } | ||
| 332 | |||
| 333 |
3/3✓ Branch 43 → 44 taken 34 times.
✓ Branch 44 → 45 taken 2 times.
✓ Branch 44 → 58 taken 32 times.
|
34 | if (SyncPayload::deliveredByMailTransport(rawBody)) { |
| 334 |
6/6✓ Branch 45 → 46 taken 2 times.
✓ Branch 49 → 50 taken 2 times.
✓ Branch 50 → 51 taken 2 times.
✓ Branch 51 → 52 taken 2 times.
✓ Branch 55 → 47 taken 2 times.
✓ Branch 55 → 56 taken 2 times.
|
4 | qCWarning(lcSync) << "Ignored a settings message delivered by mail (Received header), UID:" << uid; |
| 335 |
1/1✓ Branch 56 → 57 taken 2 times.
|
2 | fetchNextCandidate(); |
| 336 | 2 | return; | |
| 337 | } | ||
| 338 |
1/1✓ Branch 58 → 59 taken 32 times.
|
32 | SyncPayload payload = SyncPayload::fromRfcMessage(rawBody); |
| 339 |
8/9✓ Branch 60 → 61 taken 31 times.
✓ Branch 60 → 64 taken 1 time.
✓ Branch 61 → 62 taken 30 times.
✓ Branch 61 → 64 taken 1 time.
✓ Branch 62 → 63 taken 30 times.
✗ Branch 63 → 64 not taken.
✓ Branch 63 → 65 taken 30 times.
✓ Branch 66 → 67 taken 2 times.
✓ Branch 66 → 85 taken 30 times.
|
32 | if (payload.clientId.isEmpty() || payload.version != 1 || !payload.lastModified.isValid()) { |
| 340 |
5/5✓ Branch 67 → 68 taken 2 times.
✓ Branch 71 → 72 taken 2 times.
✓ Branch 72 → 73 taken 2 times.
✓ Branch 76 → 69 taken 2 times.
✓ Branch 76 → 77 taken 2 times.
|
4 | qCWarning(lcSync) << "Failed to parse settings message"; |
| 341 |
1/1✓ Branch 77 → 78 taken 2 times.
|
2 | startIdleWatch(); |
| 342 |
1/1✓ Branch 81 → 82 taken 2 times.
|
2 | emit syncError(QStringLiteral("Invalid settings sync message")); |
| 343 | 2 | return; | |
| 344 | } | ||
| 345 | |||
| 346 | 30 | m_currentSettingsUid = uid; | |
| 347 | 30 | m_validatedSettingsUid = uid; | |
| 348 |
1/2✓ Branch 85 → 86 taken 30 times.
✗ Branch 85 → 87 not taken.
|
30 | m_validatedUidValidity = m_imap ? m_imap->selectedUidValidity() : 0; |
| 349 | // Reach the stable phase before notifying an owner that may immediately | ||
| 350 | // queue an upload, change account or retire this transport. | ||
| 351 |
1/1✓ Branch 88 → 89 taken 30 times.
|
30 | startIdleWatch(); |
| 352 | 30 | const auto generation = m_generation; | |
| 353 |
1/1✓ Branch 89 → 90 taken 30 times.
|
30 | const QPointer<SettingsSyncService> guard(this); |
| 354 |
1/1✓ Branch 90 → 91 taken 30 times.
|
30 | emit settingsReceived(payload); |
| 355 |
6/7✓ Branch 92 → 93 taken 30 times.
✗ Branch 92 → 96 not taken.
✓ Branch 94 → 95 taken 29 times.
✓ Branch 94 → 96 taken 1 time.
✓ Branch 97 → 98 taken 29 times.
✓ Branch 97 → 99 taken 1 time.
✓ Branch 98 → 99 taken 29 times.
|
30 | if (guard && guard->m_generation == generation) emit fetchComplete(true); |
| 356 | 32 | } | |
| 357 | |||
| 358 | // ═══════════════════════════════════════════════════════ | ||
| 359 | // Phase: Upload settings | ||
| 360 | // ═══════════════════════════════════════════════════════ | ||
| 361 | |||
| 362 | 22 | void SettingsSyncService::uploadSettings(const SyncPayload &payload) { | |
| 363 |
3/4✓ Branch 2 → 3 taken 21 times.
✓ Branch 2 → 4 taken 1 time.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 15 taken 21 times.
|
22 | if (!m_imap || !m_enabled) { |
| 364 |
5/5✓ Branch 4 → 5 taken 1 time.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 13 → 6 taken 1 time.
✓ Branch 13 → 14 taken 1 time.
|
2 | qCWarning(lcSync) << "Cannot upload: sync not enabled/connected"; |
| 365 | 1 | return; | |
| 366 | } | ||
| 367 | |||
| 368 |
4/4✓ Branch 15 → 16 taken 20 times.
✓ Branch 15 → 18 taken 1 time.
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 18 taken 19 times.
|
21 | if (m_phase != Phase::Idle && m_phase != Phase::WatchingIdle) return; |
| 369 | |||
| 370 |
1/1✓ Branch 18 → 19 taken 20 times.
|
20 | m_pendingUpload = payload.toRfcMessage(); |
| 371 |
2/2✓ Branch 22 → 23 taken 1 time.
✓ Branch 22 → 44 taken 19 times.
|
20 | if (m_pendingUpload.size() > SyncPayload::MaxRfcMessageBytes) { |
| 372 |
6/6✓ Branch 23 → 24 taken 1 time.
✓ Branch 27 → 28 taken 1 time.
✓ Branch 28 → 29 taken 1 time.
✓ Branch 30 → 31 taken 1 time.
✓ Branch 35 → 25 taken 1 time.
✓ Branch 35 → 36 taken 1 time.
|
2 | qCWarning(lcSync) << "Settings upload too large:" << m_pendingUpload.size() |
| 373 |
1/1✓ Branch 31 → 32 taken 1 time.
|
1 | << "bytes"; |
| 374 | 1 | m_pendingUpload.clear(); | |
| 375 |
1/1✓ Branch 40 → 41 taken 1 time.
|
1 | emit syncError(QStringLiteral("Settings sync upload too large")); |
| 376 | 1 | return; | |
| 377 | } | ||
| 378 | |||
| 379 | 19 | m_phase = Phase::Uploading; | |
| 380 |
1/1✓ Branch 45 → 46 taken 19 times.
|
19 | afterIdle([this]() { |
| 381 | 18 | doUpload(); | |
| 382 | 18 | }); | |
| 383 | } | ||
| 384 | |||
| 385 | 38 | bool SettingsSyncService::readyForOperation() const { | |
| 386 |
5/6✓ Branch 2 → 3 taken 37 times.
✓ Branch 2 → 6 taken 1 time.
✓ Branch 3 → 4 taken 37 times.
✗ Branch 3 → 6 not taken.
✓ Branch 4 → 5 taken 36 times.
✓ Branch 4 → 6 taken 1 time.
|
38 | return m_enabled && m_imap && m_phase == Phase::WatchingIdle; |
| 387 | } | ||
| 388 | |||
| 389 | 19 | void SettingsSyncService::doUpload() { | |
| 390 |
2/2✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 18 times.
|
19 | if (m_pendingUpload.isEmpty()) |
| 391 | 1 | return; | |
| 392 | |||
| 393 | 18 | m_phase = Phase::Uploading; | |
| 394 | 36 | m_uploadUidValidity = m_imap->selectedFolder() == m_syncFolder | |
| 395 |
1/2✓ Branch 7 → 8 taken 18 times.
✗ Branch 7 → 9 not taken.
|
18 | ? m_imap->selectedUidValidity() : 0; |
| 396 |
2/2✓ Branch 12 → 13 taken 7 times.
✓ Branch 12 → 14 taken 10 times.
|
17 | m_previousSettingsUid = m_uploadUidValidity != 0 && m_uploadUidValidity == m_validatedUidValidity |
| 397 |
2/2✓ Branch 11 → 12 taken 17 times.
✓ Branch 11 → 14 taken 1 time.
|
35 | ? m_validatedSettingsUid : -1; |
| 398 | |||
| 399 | // Make sure we're in the right folder | ||
| 400 | 18 | if (m_imap->selectedFolder() != m_syncFolder) { | |
| 401 | // After selecting, onFolderSelected will handle the rest | ||
| 402 | // but we need a different path – just append directly | ||
| 403 | } | ||
| 404 | |||
| 405 |
6/6✓ Branch 19 → 20 taken 18 times.
✓ Branch 23 → 24 taken 18 times.
✓ Branch 24 → 25 taken 18 times.
✓ Branch 26 → 27 taken 18 times.
✓ Branch 31 → 21 taken 18 times.
✓ Branch 31 → 32 taken 18 times.
|
36 | qCInfo(lcSync) << "Uploading settings (" << m_pendingUpload.size() |
| 406 |
1/1✓ Branch 27 → 28 taken 18 times.
|
18 | << "bytes)"; |
| 407 |
1/1✓ Branch 35 → 36 taken 18 times.
|
36 | m_imap->appendMessage(m_syncFolder, m_pendingUpload, |
| 408 | 36 | QStringLiteral("\\Seen")); | |
| 409 | } | ||
| 410 | |||
| 411 | 19 | void SettingsSyncService::onMessageAppended(const QString &folder, | |
| 412 | qint64 uid) { | ||
| 413 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 19 times.
|
19 | if (m_phase != Phase::Uploading) |
| 414 | ✗ | return; | |
| 415 |
2/2✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 18 times.
|
19 | if (folder != m_syncFolder) |
| 416 | 1 | return; | |
| 417 | |||
| 418 | // T-316 fix: the message count is otherwise only refreshed on SELECT, so | ||
| 419 | // after uploading into an initially empty folder a subsequent | ||
| 420 | // fetchLatestSettings() wrongly reported "sync folder is empty". | ||
| 421 | 18 | ++m_messageCount; | |
| 422 | |||
| 423 | 18 | m_newAppendedUid = uid; | |
| 424 | 18 | m_pendingUpload.clear(); | |
| 425 | |||
| 426 |
2/2✓ Branch 8 → 9 taken 17 times.
✓ Branch 8 → 32 taken 1 time.
|
18 | if (uid <= 0) { |
| 427 |
5/5✓ Branch 9 → 10 taken 17 times.
✓ Branch 13 → 14 taken 17 times.
✓ Branch 14 → 15 taken 17 times.
✓ Branch 18 → 11 taken 17 times.
✓ Branch 18 → 19 taken 17 times.
|
34 | qCInfo(lcSync) << "Settings uploaded without APPENDUID"; |
| 428 |
4/4✓ Branch 19 → 20 taken 17 times.
✓ Branch 23 → 24 taken 17 times.
✓ Branch 29 → 21 taken 17 times.
✓ Branch 29 → 30 taken 17 times.
|
34 | qCWarning(lcSync) |
| 429 |
1/1✓ Branch 24 → 25 taken 17 times.
|
17 | << "Settings upload returned no APPENDUID; skipping cleanup to avoid" |
| 430 |
1/1✓ Branch 25 → 26 taken 17 times.
|
17 | << "deleting the newly appended settings message"; |
| 431 | 17 | m_validatedSettingsUid = -1; | |
| 432 | 17 | m_validatedUidValidity = 0; | |
| 433 | 17 | finishUpload(); | |
| 434 | 17 | return; | |
| 435 | } | ||
| 436 | |||
| 437 |
6/6✓ Branch 32 → 33 taken 1 time.
✓ Branch 36 → 37 taken 1 time.
✓ Branch 37 → 38 taken 1 time.
✓ Branch 38 → 39 taken 1 time.
✓ Branch 43 → 34 taken 1 time.
✓ Branch 43 → 44 taken 1 time.
|
2 | qCInfo(lcSync) << "Settings uploaded, UID:" << uid |
| 438 |
1/1✓ Branch 39 → 40 taken 1 time.
|
1 | << "- cleaning up old versions"; |
| 439 | |||
| 440 | // Clean up old settings messages | ||
| 441 | 1 | cleanupOldSettings(uid); | |
| 442 | } | ||
| 443 | |||
| 444 | 2 | void SettingsSyncService::cleanupOldSettings(qint64 keepUid) { | |
| 445 | 2 | m_phase = Phase::CleaningUp; | |
| 446 | 2 | m_newAppendedUid = keepUid; | |
| 447 | |||
| 448 |
3/8✓ Branch 2 → 3 taken 2 times.
✗ Branch 2 → 6 not taken.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 2 times.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 7 not taken.
✓ Branch 8 → 9 taken 2 times.
✗ Branch 8 → 11 not taken.
|
2 | if (!m_imap || m_uploadUidValidity == 0 || !m_imap->hasUidExpungeCapability()) { |
| 449 | 2 | m_validatedSettingsUid = -1; | |
| 450 | 2 | m_validatedUidValidity = 0; | |
| 451 | 2 | finishUpload(); | |
| 452 | 2 | return; | |
| 453 | } | ||
| 454 | // Always reselect, even if this connection already selected the folder: | ||
| 455 | // the server may have recreated it since we read the prior version. | ||
| 456 | ✗ | m_imap->selectFolder(m_syncFolder); | |
| 457 | } | ||
| 458 | |||
| 459 | 3 | void SettingsSyncService::onAppendError(const QString &error) { | |
| 460 |
2/2✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 2 times.
|
3 | if (m_phase != Phase::Uploading) |
| 461 | 1 | return; | |
| 462 |
6/6✓ Branch 4 → 5 taken 2 times.
✓ Branch 8 → 9 taken 2 times.
✓ Branch 9 → 10 taken 2 times.
✓ Branch 10 → 11 taken 2 times.
✓ Branch 14 → 6 taken 2 times.
✓ Branch 14 → 15 taken 2 times.
|
4 | qCWarning(lcSync) << "Settings upload failed:" << error; |
| 463 | 2 | m_pendingUpload.clear(); | |
| 464 | 2 | m_phase = Phase::Idle; | |
| 465 | 2 | startIdleWatch(); | |
| 466 |
3/3✓ Branch 17 → 18 taken 2 times.
✓ Branch 19 → 20 taken 2 times.
✓ Branch 20 → 21 taken 2 times.
|
4 | emit syncError(tr("Settings upload failed: %1").arg(error)); |
| 467 | } | ||
| 468 | |||
| 469 | 2 | void SettingsSyncService::onFolderOperationError(const QString &op, | |
| 470 | const QString &error) { | ||
| 471 |
7/7✓ Branch 2 → 3 taken 2 times.
✓ Branch 6 → 7 taken 2 times.
✓ Branch 7 → 8 taken 2 times.
✓ Branch 8 → 9 taken 2 times.
✓ Branch 9 → 10 taken 2 times.
✓ Branch 13 → 4 taken 2 times.
✓ Branch 13 → 14 taken 2 times.
|
4 | qCWarning(lcSync) << "Folder operation failed:" << op << error; |
| 472 |
2/2✓ Branch 14 → 15 taken 1 time.
✓ Branch 14 → 22 taken 1 time.
|
2 | if (m_phase == Phase::CreatingFolder) { |
| 473 | 1 | m_phase = Phase::Idle; | |
| 474 |
1/1✓ Branch 18 → 19 taken 1 time.
|
1 | emit syncError( |
| 475 |
2/2✓ Branch 15 → 16 taken 1 time.
✓ Branch 17 → 18 taken 1 time.
|
3 | tr("Could not create sync folder: %1").arg(error)); |
| 476 | } | ||
| 477 | 2 | } | |
| 478 | |||
| 479 | // ═══════════════════════════════════════════════════════ | ||
| 480 | // Phase: IDLE watching | ||
| 481 | // ═══════════════════════════════════════════════════════ | ||
| 482 | |||
| 483 | 4 | void SettingsSyncService::startWatching() { | |
| 484 |
3/4✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 4 taken 1 time.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 3 times.
|
4 | if (!m_imap || !m_enabled) |
| 485 | 1 | return; | |
| 486 | |||
| 487 |
1/1✓ Branch 6 → 7 taken 3 times.
|
3 | afterIdle([this]() { |
| 488 |
2/2✓ Branch 5 → 6 taken 2 times.
✓ Branch 5 → 7 taken 1 time.
|
3 | if (m_imap->selectedFolder() != m_syncFolder) { |
| 489 | 2 | m_phase = Phase::SelectingFolder; | |
| 490 | 2 | m_imap->selectFolder(m_syncFolder); | |
| 491 | } else { | ||
| 492 | 1 | startIdleWatch(); | |
| 493 | } | ||
| 494 | 3 | }); | |
| 495 | } | ||
| 496 | |||
| 497 | 4 | void SettingsSyncService::stopWatching() { | |
| 498 |
2/2✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 9 taken 1 time.
|
4 | if (m_imap) { |
| 499 |
2/2✓ Branch 4 → 5 taken 1 time.
✓ Branch 4 → 6 taken 2 times.
|
3 | if (m_imap->isNotifying()) m_imap->stopNotify(); |
| 500 |
2/2✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 1 time.
|
2 | else if (m_imap->isIdling()) m_imap->stopIdle(); |
| 501 | } | ||
| 502 | 4 | m_phase = Phase::Idle; | |
| 503 | 4 | } | |
| 504 | |||
| 505 | 80 | void SettingsSyncService::startIdleWatch() { | |
| 506 |
4/4✓ Branch 2 → 3 taken 78 times.
✓ Branch 2 → 4 taken 2 times.
✓ Branch 3 → 4 taken 10 times.
✓ Branch 3 → 5 taken 68 times.
|
80 | if (!m_imap || !m_enabled) |
| 507 | 12 | return; | |
| 508 | |||
| 509 | 68 | m_phase = Phase::WatchingIdle; | |
| 510 | // T-324: Prefer NOTIFY over IDLE for sync folder | ||
| 511 |
1/2✗ Branch 6 → 7 not taken.
✓ Branch 6 → 25 taken 68 times.
|
68 | if (m_imap->usesNotifyPush()) { |
| 512 | ✗ | qCInfo(lcSync) << "Starting NOTIFY watch on sync folder"; | |
| 513 | ✗ | m_imap->startNotify({m_syncFolder}); | |
| 514 | } else { | ||
| 515 |
5/5✓ Branch 25 → 26 taken 68 times.
✓ Branch 29 → 30 taken 68 times.
✓ Branch 30 → 31 taken 68 times.
✓ Branch 34 → 27 taken 68 times.
✓ Branch 34 → 35 taken 68 times.
|
136 | qCInfo(lcSync) << "Starting IDLE watch on sync folder"; |
| 516 | 68 | m_imap->startIdle(); | |
| 517 | } | ||
| 518 | ✗ | } | |
| 519 | |||
| 520 | 6 | void SettingsSyncService::onExpungeComplete() { | |
| 521 |
2/2✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 4 taken 4 times.
|
6 | if (m_phase != Phase::CleaningUp) |
| 522 | 2 | return; | |
| 523 | |||
| 524 | 4 | finishUpload(); | |
| 525 | } | ||
| 526 | |||
| 527 | 1 | void SettingsSyncService::onExpungeFailed(const QString &error) { | |
| 528 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 1 time.
|
1 | if (m_phase != Phase::CleaningUp) return; |
| 529 | // APPEND was already confirmed. Failed history pruning must not report | ||
| 530 | // the latest settings as unsaved or leave the upload cycle stuck. | ||
| 531 |
6/6✓ Branch 4 → 5 taken 1 time.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 14 → 6 taken 1 time.
✓ Branch 14 → 15 taken 1 time.
|
2 | qCWarning(lcSync) << "Settings saved; previous version cleanup failed:" << error; |
| 532 | 1 | finishUpload(); | |
| 533 | } | ||
| 534 | |||
| 535 | 28 | void SettingsSyncService::finishUpload() { | |
| 536 |
2/2✓ Branch 2 → 3 taken 10 times.
✓ Branch 2 → 4 taken 18 times.
|
28 | m_currentSettingsUid = m_newAppendedUid > 0 ? m_newAppendedUid : -1; |
| 537 | 28 | m_phase = Phase::Idle; | |
| 538 | 28 | startIdleWatch(); | |
| 539 | // A second client may have appended a newer version before our reselect. | ||
| 540 | // That message will not produce a later IDLE notification. Read back after | ||
| 541 | // completion; a reconfigured owner or an already started operation wins. | ||
| 542 | 28 | const auto generation = m_generation; | |
| 543 |
1/1✓ Branch 6 → 7 taken 28 times.
|
28 | QTimer::singleShot(0, this, [this, generation] { |
| 544 |
3/4✓ Branch 2 → 3 taken 17 times.
✓ Branch 2 → 5 taken 1 time.
✓ Branch 3 → 4 taken 17 times.
✗ Branch 3 → 5 not taken.
|
18 | if (m_generation == generation && m_phase == Phase::WatchingIdle) |
| 545 | 17 | fetchLatestSettings(); | |
| 546 | 18 | }); | |
| 547 | 28 | emit uploadComplete(); | |
| 548 | 28 | } | |
| 549 | |||
| 550 | 3 | void SettingsSyncService::onIdleNewMessages(int newCount) { | |
| 551 |
2/2✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 4 taken 1 time.
|
3 | if (m_phase != Phase::WatchingIdle) |
| 552 | 2 | return; | |
| 553 | |||
| 554 |
6/6✓ Branch 4 → 5 taken 1 time.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 14 → 6 taken 1 time.
✓ Branch 14 → 15 taken 1 time.
|
2 | qCInfo(lcSync) << "IDLE: new messages in sync folder:" << newCount; |
| 555 | |||
| 556 | // T-79.F2/M14: keep the message count in sync with the push. When the | ||
| 557 | // folder was empty at SELECT time, m_messageCount stayed 0 and | ||
| 558 | // doFetch() dropped every remote push ("folder is empty") until the | ||
| 559 | // next reconnect — remote settings uploads never arrived. | ||
| 560 | 1 | m_messageCount += newCount; | |
| 561 | |||
| 562 | // Must use executeAfterIdle — stopIdle() sends DONE but state is still | ||
| 563 | // Idling until the OK response arrives. Direct calls would fail because | ||
| 564 | // searchText() etc. require state == Selected. | ||
| 565 | 1 | fetchLatestSettings(); | |
| 566 | } | ||
| 567 | |||
| 568 | // ═══════════════════════════════════════════════════════ | ||
| 569 | // Reconnect logic | ||
| 570 | // ═══════════════════════════════════════════════════════ | ||
| 571 | // | ||
| 572 | // T-720: handleDisconnect() + onReconnectTimer() have been removed. | ||
| 573 | // The dedicated IMAP connection is now wrapped in a | ||
| 574 | // ConnectionHealthMonitor (see configure()), which owns the periodic | ||
| 575 | // liveness probe, the exponential-backoff reconnect, and the | ||
| 576 | // suspend/network reactivity. The service keeps the Phase::Idle reset | ||
| 577 | // on Error/Disconnected so a stale WatchingIdle phase cannot receive | ||
| 578 | // push notifications on the next (reconnected) session. | ||
| 579 |