presentation/ServerDraftCleanup.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "ServerDraftCleanup.h" | ||
| 2 | |||
| 3 | #include "service/ImapService.h" | ||
| 4 | |||
| 5 | #include <QLoggingCategory> | ||
| 6 | |||
| 7 |
4/7✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 8 taken 2 times.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 1 time.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
|
3 | Q_LOGGING_CATEGORY(lcDraftCleanup, "mailjd.quick.draftcleanup") |
| 8 | |||
| 9 | namespace { | ||
| 10 |
3/3✓ Branch 2 → 3 taken 14 times.
✓ Branch 5 → 6 taken 14 times.
✓ Branch 6 → 7 taken 14 times.
|
14 | QString key(const QString &folder, qint64 uid) { return folder + QLatin1Char('\n') + QString::number(uid); } |
| 11 | } // namespace | ||
| 12 | |||
| 13 | 800 | ServerDraftCleanup::ServerDraftCleanup(std::function<ImapService *()> imap, | |
| 14 | 800 | std::function<QString()> currentFolder, QObject *parent) | |
| 15 |
1/1✓ Branch 11 → 12 taken 800 times.
|
800 | : QObject(parent), m_imap(std::move(imap)), m_currentFolder(std::move(currentFolder)) { |
| 16 |
1/1✓ Branch 12 → 13 taken 800 times.
|
800 | m_watchdog.setSingleShot(true); |
| 17 |
1/1✓ Branch 13 → 14 taken 800 times.
|
800 | m_watchdog.setInterval(WatchdogMs); |
| 18 |
1/1✓ Branch 14 → 15 taken 800 times.
|
800 | connect(&m_watchdog, &QTimer::timeout, this, [this] { |
| 19 | ✗ | qCWarning(lcDraftCleanup) << "Superseded server draft kept: the server did not answer the cleanup"; | |
| 20 | ✗ | finish(); | |
| 21 | ✗ | }); | |
| 22 | 800 | } | |
| 23 | |||
| 24 | 8 | void ServerDraftCleanup::remember(const QString &folder, qint64 uid, quint32 uidValidity) { | |
| 25 |
2/4✓ Branch 2 → 3 taken 8 times.
✗ Branch 2 → 8 not taken.
✓ Branch 3 → 4 taken 8 times.
✗ Branch 3 → 8 not taken.
|
8 | if (uid > 0 && uidValidity > 0) |
| 26 |
2/2✓ Branch 4 → 5 taken 8 times.
✓ Branch 5 → 6 taken 8 times.
|
8 | m_uidValidity.insert(key(folder, uid), uidValidity); |
| 27 | 8 | } | |
| 28 | |||
| 29 | 6 | void ServerDraftCleanup::remove(const QString &drafts, qint64 uid) { | |
| 30 | 6 | auto *imap = m_imap(); | |
| 31 |
4/8✓ Branch 3 → 4 taken 6 times.
✗ Branch 3 → 7 not taken.
✓ Branch 4 → 5 taken 6 times.
✗ Branch 4 → 7 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 6 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 6 times.
|
6 | if (!imap || uid <= 0 || drafts.isEmpty()) return; |
| 32 |
2/2✓ Branch 11 → 12 taken 6 times.
✓ Branch 12 → 13 taken 6 times.
|
6 | const quint32 uidValidity = m_uidValidity.take(key(drafts, uid)); |
| 33 |
1/2✗ Branch 14 → 15 not taken.
✓ Branch 14 → 26 taken 6 times.
|
6 | if (uidValidity == 0) { |
| 34 | ✗ | qCInfo(lcDraftCleanup) << "Superseded server draft kept: its mailbox identity is unknown (no APPENDUID)"; | |
| 35 | ✗ | return; | |
| 36 | } | ||
| 37 | // F-04: queue while another cleanup runs (dropped before); the next one | ||
| 38 | // starts from finish(). | ||
| 39 | 6 | m_pending.append({drafts, uid, uidValidity}); | |
| 40 |
1/2✓ Branch 29 → 30 taken 6 times.
✗ Branch 29 → 31 not taken.
|
6 | if (!m_active) startNext(); |
| 41 | 6 | } | |
| 42 | |||
| 43 | 11 | void ServerDraftCleanup::startNext() { | |
| 44 |
2/2✓ Branch 3 → 4 taken 5 times.
✓ Branch 3 → 5 taken 6 times.
|
12 | if (m_pending.isEmpty()) return; |
| 45 |
1/1✓ Branch 5 → 6 taken 6 times.
|
6 | const Pending next = m_pending.takeFirst(); |
| 46 |
1/1✓ Branch 6 → 7 taken 6 times.
|
6 | auto *imap = m_imap(); |
| 47 |
1/2✗ Branch 7 → 8 not taken.
✓ Branch 7 → 10 taken 6 times.
|
6 | if (!imap) { |
| 48 | ✗ | m_pending.clear(); | |
| 49 | ✗ | return; | |
| 50 | } | ||
| 51 | // F-04: every authenticated state may run the cleanup. With a folder | ||
| 52 | // open the connection is Selected or Idling; a disconnected connection | ||
| 53 | // drops the queue (the drafts stay; the next save's cleanup retries). | ||
| 54 | 6 | const auto state = imap->state(); | |
| 55 |
4/4✓ Branch 11 → 12 taken 2 times.
✓ Branch 11 → 16 taken 4 times.
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 16 taken 1 time.
|
6 | if (state != ImapService::State::Authenticated && |
| 56 |
1/2✓ Branch 13 → 14 taken 1 time.
✗ Branch 13 → 16 not taken.
|
1 | state != ImapService::State::Selected && |
| 57 | state != ImapService::State::Idling) { | ||
| 58 |
1/1✓ Branch 14 → 15 taken 1 time.
|
1 | m_pending.clear(); |
| 59 | 1 | return; | |
| 60 | } | ||
| 61 | 5 | m_active = true; | |
| 62 |
1/1✓ Branch 16 → 17 taken 5 times.
|
5 | m_watchdog.start(); |
| 63 |
1/1✓ Branch 17 → 18 taken 5 times.
|
5 | m_restore = m_currentFolder(); |
| 64 | 5 | m_reselect = m_restore != next.drafts; | |
| 65 |
1/2✗ Branch 21 → 22 not taken.
✓ Branch 21 → 24 taken 5 times.
|
5 | if (!m_reselect) { |
| 66 | ✗ | storeAndExpunge(next.drafts, next.uid, next.uidValidity); | |
| 67 | ✗ | return; | |
| 68 | } | ||
| 69 |
1/1✓ Branch 25 → 26 taken 5 times.
|
10 | m_steps << connect(imap, &ImapService::folderSelected, this, |
| 70 | 5 | [this, next](const QString &folder, int, quint32, quint64) { | |
| 71 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 5 times.
|
5 | if (folder != next.drafts) return; |
| 72 | 5 | disconnectSteps(); | |
| 73 | 5 | storeAndExpunge(next.drafts, next.uid, next.uidValidity); | |
| 74 |
1/1✓ Branch 26 → 27 taken 5 times.
|
10 | }); |
| 75 | // A failing SELECT of the Drafts folder (deleted elsewhere, refused) | ||
| 76 | // must not leave the cleanup active forever. S106-347: only that SELECT — | ||
| 77 | // another command's error is not the Drafts folder failing. | ||
| 78 |
1/1✓ Branch 30 → 31 taken 5 times.
|
10 | m_steps << connect(imap, &ImapService::folderSelectFailed, this, [this, next](const QString &folder) { |
| 79 | ✗ | if (folder != next.drafts) return; | |
| 80 | ✗ | qCWarning(lcDraftCleanup) << "Superseded server draft kept: selecting the Drafts folder failed"; | |
| 81 | ✗ | finish(); | |
| 82 |
1/1✓ Branch 31 → 32 taken 5 times.
|
10 | }); |
| 83 | 5 | const QString drafts = next.drafts; | |
| 84 |
1/2✗ Branch 35 → 36 not taken.
✓ Branch 35 → 42 taken 5 times.
|
5 | if (state == ImapService::State::Idling) |
| 85 | ✗ | imap->executeAfterIdle([imap, drafts] { imap->selectFolder(drafts); }); | |
| 86 | else | ||
| 87 |
1/1✓ Branch 42 → 43 taken 5 times.
|
5 | imap->selectFolder(drafts); |
| 88 | 6 | } | |
| 89 | |||
| 90 | 5 | void ServerDraftCleanup::storeAndExpunge(const QString &drafts, qint64 uid, quint32 uidValidity) { | |
| 91 | 5 | auto *imap = m_imap(); | |
| 92 |
5/6✓ Branch 3 → 4 taken 5 times.
✗ Branch 3 → 6 not taken.
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 4 times.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 21 taken 4 times.
|
5 | if (!imap || imap->selectedUidValidity() != uidValidity) { |
| 93 |
5/5✓ Branch 9 → 10 taken 1 time.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 14 → 15 taken 1 time.
✓ Branch 18 → 11 taken 1 time.
✓ Branch 18 → 19 taken 1 time.
|
2 | qCWarning(lcDraftCleanup) << "Superseded server draft kept: the Drafts UIDVALIDITY changed"; |
| 94 | 1 | finish(); | |
| 95 | 1 | return; | |
| 96 | } | ||
| 97 | // S106-345/346: the STORE carries its own token (far beyond the journal's | ||
| 98 | // row ids); its outcome — not any STORE's completion — decides the step. | ||
| 99 | 4 | const qint64 token = (qint64(1) << 62) + ++m_storeTokens; | |
| 100 | 4 | m_steps << connect(imap, &ImapService::mutationCompleted, this, | |
| 101 |
1/3✓ Branch 22 → 23 taken 4 times.
✗ Branch 43 → 44 not taken.
✗ Branch 43 → 45 not taken.
|
8 | [this, imap, drafts, uid, uidValidity, token](qint64 completed, ImapService::MutationOutcome outcome, |
| 102 | const QString &error, const QString &) { | ||
| 103 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 4 times.
|
4 | if (completed != token) return; |
| 104 | 4 | disconnectSteps(); | |
| 105 |
2/2✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 19 taken 3 times.
|
4 | if (outcome != ImapService::MutationOutcome::Confirmed) { |
| 106 |
6/6✓ Branch 6 → 7 taken 1 time.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 12 → 13 taken 1 time.
✓ Branch 16 → 8 taken 1 time.
✓ Branch 16 → 17 taken 1 time.
|
2 | qCWarning(lcDraftCleanup) << "Superseded server draft kept: marking it deleted failed:" << error; |
| 107 | 1 | finish(); | |
| 108 | 1 | return; | |
| 109 | } | ||
| 110 |
2/2✓ Branch 20 → 21 taken 1 time.
✓ Branch 20 → 33 taken 2 times.
|
3 | if (!imap->hasUidExpungeCapability()) { |
| 111 |
5/5✓ Branch 21 → 22 taken 1 time.
✓ Branch 25 → 26 taken 1 time.
✓ Branch 26 → 27 taken 1 time.
✓ Branch 30 → 23 taken 1 time.
✓ Branch 30 → 31 taken 1 time.
|
2 | qCInfo(lcDraftCleanup) << "Superseded server draft marked deleted; the server has no UID EXPUNGE"; |
| 112 | 1 | finish(); | |
| 113 | 1 | return; | |
| 114 | } | ||
| 115 |
2/2✓ Branch 33 → 34 taken 2 times.
✓ Branch 34 → 35 taken 2 times.
|
4 | m_steps << connect(imap, &ImapService::expungeComplete, this, [this] { finish(); }); |
| 116 | 2 | m_steps << connect(imap, &ImapService::expungeFailed, this, [this](const QString &error) { | |
| 117 | ✗ | qCWarning(lcDraftCleanup) << "Superseded server draft not expunged:" << error; | |
| 118 | ✗ | finish(); | |
| 119 |
2/2✓ Branch 36 → 37 taken 2 times.
✓ Branch 37 → 38 taken 2 times.
|
2 | }); |
| 120 |
2/2✓ Branch 39 → 40 taken 2 times.
✓ Branch 40 → 41 taken 2 times.
|
2 | imap->expungeUids({uid}, drafts, uidValidity); |
| 121 |
1/1✓ Branch 23 → 24 taken 4 times.
|
8 | }); |
| 122 |
1/1✓ Branch 29 → 30 taken 4 times.
|
8 | imap->storeFlag(uid, QStringLiteral("\\Deleted"), true, token); |
| 123 | } | ||
| 124 | |||
| 125 | 5 | void ServerDraftCleanup::finish() { | |
| 126 | 5 | m_watchdog.stop(); | |
| 127 | 5 | disconnectSteps(); | |
| 128 | 5 | auto *imap = m_imap(); | |
| 129 |
6/8✓ Branch 5 → 6 taken 5 times.
✗ Branch 5 → 10 not taken.
✓ Branch 6 → 7 taken 5 times.
✗ Branch 6 → 10 not taken.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 4 times.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 20 taken 4 times.
|
5 | if (m_reselect && imap && !m_restore.isEmpty()) { |
| 130 | 1 | const QString restore = m_restore; | |
| 131 |
2/2✓ Branch 14 → 15 taken 1 time.
✓ Branch 15 → 16 taken 1 time.
|
2 | imap->executeAfterIdle([imap, restore] { imap->selectFolder(restore); }); |
| 132 | 1 | } | |
| 133 | 5 | m_restore.clear(); | |
| 134 | 5 | m_reselect = false; | |
| 135 | 5 | m_active = false; | |
| 136 | 5 | startNext(); // F-04: the queued cleanup of the next revision | |
| 137 | 5 | } | |
| 138 | |||
| 139 | 1 | void ServerDraftCleanup::reset() { | |
| 140 | 1 | m_watchdog.stop(); | |
| 141 | 1 | disconnectSteps(); | |
| 142 | 1 | m_pending.clear(); | |
| 143 | 1 | m_restore.clear(); | |
| 144 | 1 | m_reselect = false; | |
| 145 | 1 | m_active = false; | |
| 146 | 1 | } | |
| 147 | |||
| 148 | 15 | void ServerDraftCleanup::disconnectSteps() { | |
| 149 |
3/3✓ Branch 6 → 7 taken 18 times.
✓ Branch 9 → 5 taken 18 times.
✓ Branch 9 → 10 taken 15 times.
|
33 | for (const auto &step : std::as_const(m_steps)) QObject::disconnect(step); |
| 150 | 15 | m_steps.clear(); | |
| 151 | 15 | } | |
| 152 |