presentation/QuickMailBackendPredictions.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // Folder suggestions of QuickMailBackend (visible prediction, reason, | ||
| 2 | // demand batching on the prediction worker), split from QuickMailBackend.cpp | ||
| 3 | // for its line limit (Sprint 105 S105.3.8). | ||
| 4 | #include "QuickMailBackend.h" | ||
| 5 | |||
| 6 | #include "WorkspaceState.h" | ||
| 7 | #include "app/core/AccountSession.h" | ||
| 8 | #include "app/core/FolderPredictionSession.h" | ||
| 9 | #include "app/core/MailSessionCore.h" | ||
| 10 | #include "controller/UndoManager.h" | ||
| 11 | #include "data/MailCache.h" | ||
| 12 | #include "service/FolderPredictionService.h" | ||
| 13 | #include "service/ImapResponseParser.h" | ||
| 14 | |||
| 15 | #include <QElapsedTimer> | ||
| 16 | #include <QLoggingCategory> | ||
| 17 | #include <QPointer> | ||
| 18 | |||
| 19 | Q_DECLARE_LOGGING_CATEGORY(lcQuickBackend) | ||
| 20 | |||
| 21 | 445866 | FolderSuggestion::Candidate QuickMailBackend::visiblePrediction(const MailId &id) const { | |
| 22 |
8/13✓ Branch 6 → 7 taken 187721 times.
✓ Branch 6 → 19 taken 249593 times.
✓ Branch 7 → 8 taken 187721 times.
✓ Branch 9 → 10 taken 170385 times.
✓ Branch 9 → 19 taken 17336 times.
✓ Branch 23 → 24 taken 445866 times.
✗ Branch 23 → 25 not taken.
✓ Branch 25 → 26 taken 436604 times.
✓ Branch 25 → 27 taken 9262 times.
✗ Branch 60 → 61 not taken.
✗ Branch 60 → 62 not taken.
✗ Branch 64 → 65 not taken.
✗ Branch 64 → 66 not taken.
|
883180 | if (m_workspace.area() != "mail" || m_workspace.activeTab() != 0 || id.accountId != accountName() || |
| 23 |
8/8✓ Branch 4 → 5 taken 437314 times.
✓ Branch 4 → 19 taken 8552 times.
✓ Branch 12 → 13 taken 106005 times.
✓ Branch 12 → 19 taken 64380 times.
✓ Branch 14 → 15 taken 9326 times.
✓ Branch 14 → 19 taken 96679 times.
✓ Branch 21 → 22 taken 187721 times.
✓ Branch 21 → 23 taken 258145 times.
|
892506 | !m_session.predictions()->ready() || !m_predictions.contains(id) || |
| 24 |
3/3✓ Branch 17 → 18 taken 9326 times.
✓ Branch 18 → 19 taken 64 times.
✓ Branch 18 → 20 taken 9262 times.
|
445930 | m_predictionEpochs.value(id.folderId) != m_session.cache()->uidValidity(id.folderId)) return {}; |
| 25 | // Hints are viewport-scoped: the selected mail always shows one, list rows | ||
| 26 | // only while the suggestion column is open and the row is watched. The | ||
| 27 | // cache behind this may retain more, so a revisited mail answers instantly. | ||
| 28 |
2/2✓ Branch 27 → 28 taken 9262 times.
✓ Branch 28 → 29 taken 9262 times.
|
9262 | const int row = activeRowForIdentity(WorkspaceState::encode(id)); |
| 29 |
2/2✓ Branch 30 → 31 taken 420 times.
✓ Branch 30 → 32 taken 8842 times.
|
9262 | if (row < 0) return {}; |
| 30 |
2/2✓ Branch 34 → 35 taken 2298 times.
✓ Branch 34 → 43 taken 6544 times.
|
11140 | if (id != m_workspace.mailId() && |
| 31 |
3/4✓ Branch 36 → 37 taken 1689 times.
✓ Branch 36 → 42 taken 609 times.
✓ Branch 37 → 38 taken 1689 times.
✗ Branch 37 → 42 not taken.
|
2298 | !(suggestionColumnShown() && m_predictionFirst >= 0 && |
| 32 |
5/7✓ Branch 38 → 39 taken 1689 times.
✗ Branch 38 → 42 not taken.
✓ Branch 39 → 40 taken 1689 times.
✗ Branch 41 → 42 not taken.
✓ Branch 41 → 43 taken 1689 times.
✓ Branch 44 → 45 taken 609 times.
✓ Branch 44 → 46 taken 8233 times.
|
10531 | row >= m_predictionFirst && row <= qMin(m_predictionLast, activeRowCount() - 1))) return {}; |
| 33 | 8233 | const auto target = m_predictions.value(id).visible(m_predictionFolders.value(id.folderId), m_alternatePredictions.contains(id)); | |
| 34 |
3/3✓ Branch 52 → 53 taken 8233 times.
✓ Branch 53 → 54 taken 4906 times.
✓ Branch 53 → 55 taken 3327 times.
|
8233 | return m_folders.isSelectable(target.first) ? target : FolderSuggestion::Candidate{}; |
| 35 | 8233 | } | |
| 36 | 409187 | QString QuickMailBackend::predictionText(const MailId &id) const { | |
| 37 |
1/1✓ Branch 2 → 3 taken 409187 times.
|
409187 | const auto target = visiblePrediction(id); |
| 38 |
2/2✓ Branch 4 → 5 taken 406346 times.
✓ Branch 4 → 6 taken 2841 times.
|
409187 | if (target.first.isEmpty()) return {}; |
| 39 |
3/3✓ Branch 6 → 7 taken 2841 times.
✓ Branch 8 → 9 taken 2841 times.
✓ Branch 9 → 10 taken 2841 times.
|
8523 | return QString("%1 (%2%)%3").arg(ImapResponseParser::decodeMailboxName(target.first)) |
| 40 |
5/6✓ Branch 11 → 12 taken 2841 times.
✓ Branch 14 → 15 taken 95 times.
✓ Branch 14 → 19 taken 2746 times.
✓ Branch 17 → 18 taken 95 times.
✗ Branch 17 → 19 not taken.
✓ Branch 20 → 21 taken 2841 times.
|
14300 | .arg(int(target.second * 100)).arg(m_alternatePredictions.contains(id) && |
| 41 |
3/5✓ Branch 21 → 22 taken 2841 times.
✓ Branch 24 → 25 taken 95 times.
✓ Branch 24 → 26 taken 2746 times.
✗ Branch 36 → 37 not taken.
✗ Branch 36 → 38 not taken.
|
5777 | target == m_predictions.value(id).alternate ? " [ALT]" : ""); |
| 42 | 409187 | } | |
| 43 | 18135 | QVariantMap QuickMailBackend::suggestion() const { | |
| 44 | 18135 | const auto id = m_workspace.mailId(); | |
| 45 |
1/1✓ Branch 4 → 5 taken 18135 times.
|
18135 | const auto target = visiblePrediction(id); |
| 46 | ✗ | return {{"target", target.first}, {"text", predictionText(id)}, {"confidence", target.second}, | |
| 47 |
5/7✓ Branch 12 → 13 taken 18135 times.
✓ Branch 14 → 15 taken 18135 times.
✓ Branch 15 → 16 taken 18135 times.
✓ Branch 19 → 20 taken 90675 times.
✓ Branch 19 → 21 taken 18135 times.
✗ Branch 33 → 34 not taken.
✗ Branch 33 → 35 not taken.
|
145080 | {"identity", WorkspaceState::encode(id)}, {"reason", suggestionDetails(WorkspaceState::encode(id)).value("reason")}}; |
| 48 | 54405 | } | |
| 49 | 18301 | QVariantMap QuickMailBackend::suggestionDetails(const QVariantMap &identity) const { | |
| 50 |
1/1✓ Branch 2 → 3 taken 18301 times.
|
18301 | const auto id = WorkspaceState::decode(identity); |
| 51 |
1/1✓ Branch 3 → 4 taken 18301 times.
|
18301 | const auto target = visiblePrediction(id); |
| 52 |
2/2✓ Branch 5 → 6 taken 17215 times.
✓ Branch 5 → 7 taken 1086 times.
|
18301 | if (target.first.isEmpty()) return {}; |
| 53 | 1086 | const auto suggestion = m_predictions.value(id); | |
| 54 | // The reason explains the primary; a shown alternate says only that. | ||
| 55 |
4/4✓ Branch 9 → 10 taken 1002 times.
✓ Branch 9 → 11 taken 84 times.
✓ Branch 10 → 12 taken 1002 times.
✓ Branch 11 → 12 taken 84 times.
|
1086 | return {{"reason", target == suggestion.primary ? suggestionReason(suggestion) : tr("Second choice")}, |
| 56 |
2/4✓ Branch 16 → 17 taken 2172 times.
✓ Branch 16 → 18 taken 1086 times.
✗ Branch 25 → 26 not taken.
✗ Branch 25 → 27 not taken.
|
3258 | {"strong", target.second >= StrongSuggestion}}; |
| 57 | 20473 | } | |
| 58 | 1002 | QString QuickMailBackend::suggestionReason(const FolderSuggestion &suggestion) { | |
| 59 | // The full path, as the suggestion column names it. | ||
| 60 |
1/1✓ Branch 2 → 3 taken 1002 times.
|
1002 | const QString folder = ImapResponseParser::decodeMailboxName(suggestion.primary.first); |
| 61 |
2/7✓ Branch 3 → 4 taken 14 times.
✗ Branch 3 → 9 not taken.
✓ Branch 3 → 20 taken 988 times.
✗ Branch 3 → 31 not taken.
✗ Branch 3 → 36 not taken.
✗ Branch 3 → 41 not taken.
✗ Branch 3 → 43 not taken.
|
1002 | switch (suggestion.reason) { |
| 62 |
2/2✓ Branch 4 → 5 taken 14 times.
✓ Branch 6 → 7 taken 14 times.
|
28 | case FolderSuggestion::Thread: return tr("Earlier mails of this thread are in %1").arg(folder); |
| 63 | ✗ | case FolderSuggestion::List: | |
| 64 | ✗ | return tr("Mails of this list: %1 of %2 in %3").arg(suggestion.support).arg(suggestion.total).arg(folder); | |
| 65 | 988 | case FolderSuggestion::Sender: | |
| 66 |
4/4✓ Branch 20 → 21 taken 988 times.
✓ Branch 22 → 23 taken 988 times.
✓ Branch 24 → 25 taken 988 times.
✓ Branch 26 → 27 taken 988 times.
|
3952 | return tr("From this sender: %1 of %2 in %3").arg(suggestion.support).arg(suggestion.total).arg(folder); |
| 67 | ✗ | case FolderSuggestion::Alias: return tr("Mails to this address went to %1").arg(folder); | |
| 68 | ✗ | case FolderSuggestion::Subject: return tr("Words of the subject point to %1").arg(folder); | |
| 69 | ✗ | case FolderSuggestion::SpamFilter: return tr("Your server's spam filter marked it as spam"); | |
| 70 | ✗ | default: return tr("Words of the sender and subject point to %1").arg(folder); | |
| 71 | } | ||
| 72 | 1002 | } | |
| 73 | 8300 | void QuickMailBackend::invalidatePredictions() { | |
| 74 | 8300 | ++m_predictionGeneration; | |
| 75 | 8300 | FolderPredictionService::shared()->cancel(this); | |
| 76 | 8300 | m_predictionRequests.clear(); | |
| 77 | // Per-mail predictions stay: they are fenced by the folder epoch in | ||
| 78 | // visiblePrediction(), so keeping them lets a revisited mail answer from | ||
| 79 | // cache instantly instead of paying a worker round trip per selection. | ||
| 80 | 8300 | m_predictionDebounce.stop(); | |
| 81 |
13/15✓ Branch 6 → 7 taken 8300 times.
✓ Branch 7 → 8 taken 8245 times.
✓ Branch 7 → 17 taken 55 times.
✓ Branch 10 → 11 taken 7524 times.
✓ Branch 10 → 17 taken 721 times.
✓ Branch 12 → 13 taken 6047 times.
✓ Branch 12 → 17 taken 1477 times.
✓ Branch 15 → 16 taken 1328 times.
✓ Branch 15 → 17 taken 4719 times.
✓ Branch 18 → 19 taken 8245 times.
✓ Branch 18 → 20 taken 55 times.
✓ Branch 20 → 21 taken 1328 times.
✓ Branch 20 → 22 taken 6972 times.
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 28 not taken.
|
8300 | if (configured() && m_workspace.area() == "mail" && m_workspace.activeTab() == 0 && m_session.predictions()->ready()) |
| 82 | 1328 | m_predictionDebounce.start(); | |
| 83 | 8300 | m_mail.suggestionsChanged(); | |
| 84 | 8300 | m_threadList.suggestionsChanged(); | |
| 85 | 8300 | emit suggestionsChanged(); | |
| 86 | 8300 | } | |
| 87 | 10 | bool QuickMailBackend::canArmQuickMoveRetry(const MailId &selected) const { | |
| 88 | // Arm only while a prediction for this exact selection is still in flight: | ||
| 89 | // the 20 ms debounce running or a queued batch pending. Pending | ||
| 90 | // transport/selection work must never pile onto an armed retry. | ||
| 91 |
4/7✓ Branch 4 → 5 taken 9 times.
✓ Branch 6 → 7 taken 9 times.
✗ Branch 6 → 21 not taken.
✓ Branch 25 → 26 taken 1 time.
✓ Branch 25 → 27 taken 9 times.
✗ Branch 37 → 38 not taken.
✗ Branch 37 → 39 not taken.
|
19 | if (!selected.isValid() || selected.accountId != accountName() || |
| 92 |
4/8✓ Branch 9 → 10 taken 9 times.
✗ Branch 9 → 21 not taken.
✓ Branch 10 → 11 taken 9 times.
✗ Branch 10 → 21 not taken.
✓ Branch 12 → 13 taken 9 times.
✗ Branch 12 → 21 not taken.
✓ Branch 15 → 16 taken 9 times.
✗ Branch 15 → 21 not taken.
|
18 | m_workspace.mailId() != selected || m_actionPending || m_selection.pending() || |
| 93 |
4/4✓ Branch 3 → 4 taken 9 times.
✓ Branch 3 → 21 taken 1 time.
✓ Branch 23 → 24 taken 9 times.
✓ Branch 23 → 25 taken 1 time.
|
28 | m_session.undo()->busy() || |
| 94 |
5/7✓ Branch 16 → 17 taken 9 times.
✓ Branch 17 → 18 taken 5 times.
✓ Branch 17 → 22 taken 4 times.
✓ Branch 19 → 20 taken 5 times.
✗ Branch 19 → 22 not taken.
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 5 times.
|
9 | !(m_predictionDebounce.isActive() || !m_predictionRequests.isEmpty() || m_predictionBatchesRunning > 0)) |
| 95 | 1 | return false; | |
| 96 | // Epoch invariant (UIDVALIDITY admission): arming is only safe while the | ||
| 97 | // recorded folder epoch still matches the cache. An epoch that moved | ||
| 98 | // without a resync signal must keep the direct manual-move fallback — a | ||
| 99 | // batch computed under a changed epoch never grounds a quick move. | ||
| 100 |
1/2✓ Branch 28 → 29 taken 9 times.
✗ Branch 28 → 34 not taken.
|
18 | return m_predictionEpochs.contains(selected.folderId) && |
| 101 |
2/2✓ Branch 32 → 33 taken 1 time.
✓ Branch 32 → 34 taken 8 times.
|
18 | m_predictionEpochs.value(selected.folderId) == m_session.cache()->uidValidity(selected.folderId); |
| 102 | } | ||
| 103 | 1 | void QuickMailBackend::armQuickMoveRetry(const MailId &selected) { | |
| 104 | 1 | m_quickMoveArmed = selected; | |
| 105 | 1 | m_quickMoveFallback.start(); | |
| 106 | 1 | } | |
| 107 | 926 | void QuickMailBackend::disarmQuickMove() { | |
| 108 | 926 | m_quickMoveArmed = {}; | |
| 109 | 926 | m_quickMoveFallback.stop(); | |
| 110 | 926 | } | |
| 111 | 8571 | void QuickMailBackend::tryArmedQuickMove() { | |
| 112 |
6/8✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 7 taken 8570 times.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 7 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 1 time.
✓ Branch 9 → 10 taken 8570 times.
✓ Branch 9 → 11 taken 1 time.
|
8571 | if (!m_quickMoveArmed.isValid() || m_actionPending || m_selection.pending()) return; |
| 113 | // The press belonged to one selection; a different mail disarms silently. | ||
| 114 |
2/4✓ Branch 13 → 14 taken 1 time.
✗ Branch 13 → 19 not taken.
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 19 not taken.
|
2 | if (m_workspace.mailId() != m_quickMoveArmed || m_workspace.activeTab() != 0 || |
| 115 |
3/7✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 1 time.
✓ Branch 21 → 22 taken 1 time.
✗ Branch 21 → 23 not taken.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 26 taken 1 time.
✗ Branch 24 → 25 not taken.
|
2 | m_workspace.area() != "mail") { disarmQuickMove(); return; } |
| 116 |
1/1✓ Branch 26 → 27 taken 1 time.
|
1 | const auto target = visiblePrediction(m_quickMoveArmed).first; |
| 117 |
1/2✗ Branch 30 → 31 not taken.
✓ Branch 30 → 32 taken 1 time.
|
1 | if (target.isEmpty()) return; // still waiting; the timeout owns the fallback |
| 118 |
1/1✓ Branch 32 → 33 taken 1 time.
|
1 | disarmQuickMove(); |
| 119 |
1/1✓ Branch 33 → 34 taken 1 time.
|
1 | moveSelectedMail(target); |
| 120 | 1 | } | |
| 121 | 535 | void QuickMailBackend::requestPredictions() { | |
| 122 |
5/10✓ Branch 4 → 5 taken 535 times.
✗ Branch 4 → 10 not taken.
✓ Branch 7 → 8 taken 535 times.
✗ Branch 7 → 10 not taken.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 535 times.
✓ Branch 12 → 13 taken 535 times.
✗ Branch 12 → 14 not taken.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 36 taken 535 times.
|
535 | if (!m_session.predictions()->ready() || m_workspace.area() != "mail" || m_workspace.activeTab() != 0) { |
| 123 | ✗ | qCDebug(lcQuickBackend) << "predictions not requested: ready" << m_session.predictions()->ready() << "area" | |
| 124 | ✗ | << m_workspace.area() << "tab" << m_workspace.activeTab(); | |
| 125 | 142 | return; | |
| 126 | } | ||
| 127 | // A cached entry fenced by the folder epoch answers instantly; only | ||
| 128 | // uncached identities pay the worker round trip. | ||
| 129 | 898 | const auto cached = [this](const MailId &id) { | |
| 130 |
2/2✓ Branch 3 → 4 taken 112 times.
✓ Branch 3 → 9 taken 786 times.
|
1010 | return m_predictions.contains(id) && |
| 131 |
2/2✓ Branch 7 → 8 taken 105 times.
✓ Branch 7 → 9 taken 7 times.
|
1010 | m_predictionEpochs.value(id.folderId) == m_session.cache()->uidValidity(id.folderId); |
| 132 | 535 | }; | |
| 133 | 535 | QList<MailId> demand; | |
| 134 | // The demand budget accounts for the retained in-scope cache, so | ||
| 135 | // overlapping demands cannot push the projection past MaxRows. The scope | ||
| 136 | // prune on watch changes keeps that cache within the watched range. | ||
| 137 | 535 | const int budget = qMax(0, FolderPredictionService::MaxRows - m_predictions.size()); | |
| 138 | 535 | const auto selected = m_workspace.mailId(); | |
| 139 |
6/9✓ Branch 42 → 43 taken 470 times.
✓ Branch 42 → 52 taken 65 times.
✓ Branch 43 → 44 taken 470 times.
✓ Branch 45 → 46 taken 470 times.
✗ Branch 45 → 52 not taken.
✓ Branch 57 → 58 taken 380 times.
✓ Branch 57 → 59 taken 155 times.
✗ Branch 173 → 174 not taken.
✗ Branch 173 → 175 not taken.
|
1070 | if (budget > 0 && selected.isValid() && selected.accountId == accountName() && |
| 140 |
12/15✓ Branch 40 → 41 taken 535 times.
✗ Branch 40 → 52 not taken.
✓ Branch 46 → 47 taken 470 times.
✓ Branch 47 → 48 taken 470 times.
✓ Branch 48 → 49 taken 449 times.
✓ Branch 48 → 52 taken 21 times.
✓ Branch 49 → 50 taken 449 times.
✓ Branch 50 → 51 taken 380 times.
✓ Branch 50 → 52 taken 69 times.
✓ Branch 53 → 54 taken 470 times.
✓ Branch 53 → 55 taken 65 times.
✓ Branch 55 → 56 taken 470 times.
✓ Branch 55 → 57 taken 65 times.
✗ Branch 169 → 170 not taken.
✗ Branch 169 → 171 not taken.
|
1070 | activeRowForIdentity(WorkspaceState::encode(selected)) >= 0 && !cached(selected)) { |
| 141 | // The selected mail rides first, so pressing s waits for one 1-uid | ||
| 142 | // classification, never for the viewport backlog. | ||
| 143 |
1/1✓ Branch 58 → 59 taken 380 times.
|
380 | demand.append(selected); |
| 144 | } | ||
| 145 |
5/6✓ Branch 60 → 61 taken 33 times.
✓ Branch 60 → 63 taken 502 times.
✓ Branch 61 → 62 taken 33 times.
✗ Branch 61 → 63 not taken.
✓ Branch 64 → 65 taken 33 times.
✓ Branch 64 → 108 taken 502 times.
|
535 | if (suggestionColumnShown() && m_predictionFirst >= 0) { |
| 146 |
5/5✓ Branch 100 → 101 taken 482 times.
✓ Branch 102 → 103 taken 451 times.
✓ Branch 102 → 106 taken 31 times.
✓ Branch 107 → 66 taken 449 times.
✓ Branch 107 → 108 taken 33 times.
|
933 | for (int row = m_predictionFirst; row <= qMin(m_predictionLast, activeRowCount() - 1) && |
| 147 |
2/2✓ Branch 104 → 105 taken 449 times.
✓ Branch 104 → 106 taken 2 times.
|
451 | demand.size() < budget; ++row) { |
| 148 |
3/5✓ Branch 66 → 67 taken 449 times.
✓ Branch 67 → 68 taken 449 times.
✓ Branch 68 → 69 taken 449 times.
✗ Branch 181 → 182 not taken.
✗ Branch 181 → 183 not taken.
|
449 | const MailId id{accountName(), activeKeyAt(row).folderId, activeKeyAt(row).uid}; |
| 149 |
8/9✓ Branch 72 → 73 taken 449 times.
✗ Branch 72 → 77 not taken.
✓ Branch 73 → 74 taken 449 times.
✓ Branch 74 → 75 taken 413 times.
✓ Branch 74 → 77 taken 36 times.
✓ Branch 76 → 77 taken 17 times.
✓ Branch 76 → 78 taken 396 times.
✓ Branch 79 → 80 taken 53 times.
✓ Branch 79 → 81 taken 396 times.
|
449 | if (!id.isValid() || cached(id) || id == selected) continue; |
| 150 | 396 | bool known = false; | |
| 151 |
4/4✓ Branch 81 → 82 taken 396 times.
✓ Branch 82 → 83 taken 396 times.
✓ Branch 90 → 84 taken 18223 times.
✓ Branch 90 → 91 taken 396 times.
|
18619 | for (const auto &pending : demand) |
| 152 |
1/4✗ Branch 85 → 86 not taken.
✓ Branch 85 → 88 taken 18223 times.
✗ Branch 86 → 87 not taken.
✗ Branch 86 → 88 not taken.
|
18223 | if (pending.uid == id.uid && pending.folderId == id.folderId) { known = true; break; } |
| 153 |
2/3✓ Branch 91 → 92 taken 396 times.
✗ Branch 91 → 93 not taken.
✓ Branch 92 → 93 taken 396 times.
|
396 | if (!known) demand.append(id); |
| 154 | 449 | } | |
| 155 | } | ||
| 156 |
9/9✓ Branch 108 → 109 taken 535 times.
✓ Branch 112 → 113 taken 535 times.
✓ Branch 113 → 114 taken 535 times.
✓ Branch 115 → 116 taken 535 times.
✓ Branch 116 → 117 taken 535 times.
✓ Branch 117 → 118 taken 535 times.
✓ Branch 118 → 119 taken 535 times.
✓ Branch 130 → 110 taken 535 times.
✓ Branch 130 → 131 taken 535 times.
|
1070 | qCDebug(lcQuickBackend) << "prediction demand" << demand.size() << "rows" << m_predictionFirst << m_predictionLast |
| 157 |
6/6✓ Branch 119 → 120 taken 535 times.
✓ Branch 121 → 122 taken 535 times.
✓ Branch 122 → 123 taken 535 times.
✓ Branch 123 → 124 taken 535 times.
✓ Branch 124 → 125 taken 535 times.
✓ Branch 126 → 127 taken 535 times.
|
535 | << "queued" << m_predictionRequests.size() << "running" << m_predictionBatchesRunning << "cached" << m_predictions.size(); |
| 158 |
2/2✓ Branch 132 → 133 taken 142 times.
✓ Branch 132 → 134 taken 393 times.
|
535 | if (demand.isEmpty()) return; |
| 159 |
3/5✓ Branch 137 → 138 taken 393 times.
✓ Branch 140 → 141 taken 393 times.
✓ Branch 140 → 142 taken 393 times.
✗ Branch 197 → 198 not taken.
✗ Branch 197 → 199 not taken.
|
786 | appendPredictionRequests({demand.first()}); |
| 160 | // Small chunks publish the column progressively: the first rows are usable | ||
| 161 | // while the remaining batches are still classifying on the single worker. | ||
| 162 |
2/2✓ Branch 148 → 143 taken 31 times.
✓ Branch 148 → 149 taken 393 times.
|
424 | for (int begin = 1; begin < demand.size(); begin += ViewportPredictionChunk) |
| 163 |
2/2✓ Branch 143 → 144 taken 31 times.
✓ Branch 144 → 145 taken 31 times.
|
31 | appendPredictionRequests(demand.mid(begin, ViewportPredictionChunk)); |
| 164 |
1/1✓ Branch 149 → 150 taken 393 times.
|
393 | nextPredictionBatch(m_predictionGeneration); |
| 165 | 1070 | } | |
| 166 | 424 | void QuickMailBackend::appendPredictionRequests(const QList<MailId> &identities) { | |
| 167 | 424 | QHash<qint64, QList<qint64>> byFolder; | |
| 168 |
2/2✓ Branch 11 → 4 taken 776 times.
✓ Branch 11 → 12 taken 424 times.
|
1200 | for (const auto &id : identities) |
| 169 |
3/4✓ Branch 6 → 7 taken 776 times.
✗ Branch 6 → 9 not taken.
✓ Branch 7 → 8 taken 776 times.
✓ Branch 8 → 9 taken 776 times.
|
776 | if (id.isValid()) byFolder[id.folderId].append(id.uid); |
| 170 |
2/2✓ Branch 43 → 13 taken 424 times.
✓ Branch 43 → 44 taken 424 times.
|
848 | for (auto it = byFolder.cbegin(); it != byFolder.cend(); ++it) { |
| 171 |
1/1✓ Branch 15 → 16 taken 424 times.
|
424 | const auto path = m_session.cache()->folderPath(it.key()); |
| 172 |
1/2✗ Branch 17 → 18 not taken.
✓ Branch 17 → 19 taken 424 times.
|
424 | if (!m_availableFolders.contains(path)) continue; |
| 173 | 424 | m_predictionRequests.append({m_session.cache()->databasePath(), m_session.predictions()->databasePath(), | |
| 174 |
1/1✓ Branch 29 → 30 taken 424 times.
|
424 | accountName(), path, m_junkFolder, it.key(), m_session.cache()->uidValidity(it.key()), it.value()}); |
| 175 | 424 | } | |
| 176 | 1272 | } | |
| 177 | 661 | void QuickMailBackend::nextPredictionBatch(quint64 generation) { | |
| 178 |
6/6✓ Branch 2 → 3 taken 660 times.
✓ Branch 2 → 5 taken 1 time.
✓ Branch 4 → 5 taken 241 times.
✓ Branch 4 → 6 taken 419 times.
✓ Branch 7 → 8 taken 242 times.
✓ Branch 7 → 9 taken 419 times.
|
661 | if (generation != m_predictionGeneration || m_predictionRequests.isEmpty()) return; |
| 179 |
1/1✓ Branch 9 → 10 taken 419 times.
|
419 | const auto request = m_predictionRequests.takeFirst(); |
| 180 | 419 | ++m_predictionRequestCount; | |
| 181 | 419 | ++m_predictionBatchesRunning; | |
| 182 | 419 | const auto accountGeneration = m_session.accounts()->generation(); | |
| 183 | 419 | QElapsedTimer started; | |
| 184 | 419 | started.start(); | |
| 185 |
3/5✓ Branch 13 → 14 taken 419 times.
✓ Branch 15 → 16 taken 419 times.
✓ Branch 17 → 18 taken 419 times.
✗ Branch 32 → 33 not taken.
✗ Branch 32 → 34 not taken.
|
419 | FolderPredictionService::shared()->predict(this, request, [this, request, generation, accountGeneration, started](FolderPredictionResult result) { |
| 186 | 268 | --m_predictionBatchesRunning; | |
| 187 |
4/4✓ Branch 2 → 3 taken 268 times.
✓ Branch 6 → 7 taken 268 times.
✓ Branch 32 → 4 taken 268 times.
✓ Branch 32 → 33 taken 268 times.
|
1072 | qCDebug(lcQuickBackend).noquote() << QStringLiteral("prediction batch answered folder=%1 mails=%2 items=%3 ms=%4") |
| 188 |
5/5✓ Branch 12 → 13 taken 268 times.
✓ Branch 15 → 16 taken 268 times.
✓ Branch 18 → 19 taken 268 times.
✓ Branch 21 → 22 taken 268 times.
✓ Branch 22 → 23 taken 268 times.
|
1340 | .arg(request.folder).arg(request.uids.size()).arg(result.items.size()).arg(started.elapsed()); |
| 189 | // A new generation cleared the queue and started its own chain. | ||
| 190 |
1/2✗ Branch 33 → 34 not taken.
✓ Branch 33 → 35 taken 268 times.
|
273 | if (generation != m_predictionGeneration) return; |
| 191 | // F100-59: a result that no longer fits (account, cache, folder epoch or | ||
| 192 | // path changed meanwhile) is dropped, but the queue goes on — returning | ||
| 193 | // here stopped every later batch until the next demand. | ||
| 194 |
0/2✗ Branch 271 → 272 not taken.
✗ Branch 271 → 273 not taken.
|
268 | const QString stale = accountGeneration != m_session.accounts()->generation() ? QStringLiteral("account") |
| 195 |
3/10✗ Branch 44 → 45 not taken.
✓ Branch 44 → 48 taken 268 times.
✓ Branch 87 → 88 taken 268 times.
✗ Branch 87 → 89 not taken.
✗ Branch 89 → 90 not taken.
✓ Branch 89 → 91 taken 268 times.
✗ Branch 263 → 264 not taken.
✗ Branch 263 → 265 not taken.
✗ Branch 267 → 268 not taken.
✗ Branch 267 → 269 not taken.
|
804 | : request.cachePath != m_session.cache()->databasePath() ? QStringLiteral("cache") |
| 196 |
4/11✓ Branch 48 → 49 taken 268 times.
✗ Branch 50 → 51 not taken.
✓ Branch 50 → 54 taken 268 times.
✓ Branch 83 → 84 taken 268 times.
✗ Branch 83 → 85 not taken.
✗ Branch 85 → 86 not taken.
✓ Branch 85 → 87 taken 268 times.
✗ Branch 255 → 256 not taken.
✗ Branch 255 → 257 not taken.
✗ Branch 259 → 260 not taken.
✗ Branch 259 → 261 not taken.
|
804 | : request.account != accountName() ? QStringLiteral("account name") |
| 197 |
4/7✓ Branch 55 → 56 taken 268 times.
✓ Branch 56 → 57 taken 5 times.
✓ Branch 56 → 60 taken 263 times.
✗ Branch 81 → 82 not taken.
✓ Branch 81 → 83 taken 268 times.
✗ Branch 251 → 252 not taken.
✗ Branch 251 → 253 not taken.
|
536 | : request.uidValidity != m_session.cache()->uidValidity(request.folderId) ? QStringLiteral("folder epoch") |
| 198 |
6/11✓ Branch 61 → 62 taken 263 times.
✗ Branch 63 → 64 not taken.
✓ Branch 63 → 67 taken 263 times.
✓ Branch 77 → 78 taken 263 times.
✓ Branch 77 → 79 taken 5 times.
✓ Branch 79 → 80 taken 5 times.
✓ Branch 79 → 81 taken 263 times.
✗ Branch 243 → 244 not taken.
✗ Branch 243 → 245 not taken.
✗ Branch 247 → 248 not taken.
✗ Branch 247 → 249 not taken.
|
794 | : m_session.cache()->folderPath(request.folderId) != request.folder ? QStringLiteral("folder path") |
| 199 |
4/10✗ Branch 37 → 38 not taken.
✓ Branch 37 → 41 taken 268 times.
✗ Branch 68 → 69 not taken.
✓ Branch 68 → 72 taken 263 times.
✗ Branch 73 → 74 not taken.
✓ Branch 73 → 75 taken 268 times.
✗ Branch 75 → 76 not taken.
✓ Branch 75 → 77 taken 268 times.
✗ Branch 239 → 240 not taken.
✗ Branch 239 → 241 not taken.
|
1335 | : !m_availableFolders.contains(request.folder) ? QStringLiteral("folder not available") : QString(); |
| 200 |
2/2✓ Branch 92 → 93 taken 5 times.
✓ Branch 92 → 117 taken 263 times.
|
268 | if (!stale.isEmpty()) { |
| 201 |
4/4✓ Branch 93 → 94 taken 5 times.
✓ Branch 97 → 98 taken 5 times.
✓ Branch 114 → 95 taken 5 times.
✓ Branch 114 → 115 taken 5 times.
|
20 | qCInfo(lcQuickBackend).noquote() << QStringLiteral("prediction batch dropped reason=%1 folder=%2 mails=%3") |
| 202 |
3/3✓ Branch 102 → 103 taken 5 times.
✓ Branch 105 → 106 taken 5 times.
✓ Branch 106 → 107 taken 5 times.
|
15 | .arg(stale, request.folder).arg(request.uids.size()); |
| 203 |
1/1✓ Branch 115 → 116 taken 5 times.
|
5 | nextPredictionBatch(generation); |
| 204 | 5 | return; | |
| 205 | } | ||
| 206 |
1/2✗ Branch 118 → 119 not taken.
✓ Branch 118 → 138 taken 263 times.
|
263 | if (!result.error.isEmpty()) |
| 207 | ✗ | qCWarning(lcQuickBackend).noquote() << QStringLiteral("prediction batch failed folder=%1 error=%2").arg(request.folder, result.error); | |
| 208 |
1/2✓ Branch 139 → 140 taken 263 times.
✗ Branch 139 → 179 not taken.
|
263 | if (result.error.isEmpty()) { |
| 209 |
1/1✓ Branch 140 → 141 taken 263 times.
|
263 | m_predictionEpochs.insert(request.folderId, request.uidValidity); |
| 210 |
1/1✓ Branch 141 → 142 taken 263 times.
|
263 | m_predictionFolders.insert(request.folderId, request.folder); |
| 211 |
4/4✓ Branch 142 → 143 taken 263 times.
✓ Branch 143 → 144 taken 263 times.
✓ Branch 157 → 145 taken 480 times.
✓ Branch 157 → 158 taken 263 times.
|
743 | for (const auto &item : result.items) { |
| 212 |
2/6✗ Branch 147 → 148 not taken.
✓ Branch 147 → 151 taken 480 times.
✗ Branch 149 → 150 not taken.
✗ Branch 149 → 151 not taken.
✗ Branch 152 → 153 not taken.
✓ Branch 152 → 154 taken 480 times.
|
480 | if (m_predictions.size() >= MaximumCachedPredictions && !m_predictions.contains(item.mailId)) |
| 213 | ✗ | m_predictions.clear(); | |
| 214 |
1/1✓ Branch 154 → 155 taken 480 times.
|
480 | m_predictions.insert(item.mailId, item.suggestion); |
| 215 | } | ||
| 216 | // The viewport cache and alternate state never grow with scrolling. | ||
| 217 |
4/4✓ Branch 158 → 159 taken 263 times.
✓ Branch 175 → 176 taken 274 times.
✓ Branch 177 → 160 taken 11 times.
✓ Branch 177 → 178 taken 263 times.
|
274 | for (auto it = m_alternatePredictions.begin(); it != m_alternatePredictions.end();) |
| 218 |
6/7✓ Branch 162 → 163 taken 8 times.
✓ Branch 162 → 168 taken 3 times.
✓ Branch 166 → 167 taken 8 times.
✗ Branch 166 → 168 not taken.
✓ Branch 169 → 170 taken 8 times.
✓ Branch 169 → 174 taken 3 times.
✓ Branch 171 → 172 taken 8 times.
|
11 | if (!m_predictions.contains(*it) && *it != m_workspace.mailId()) it = m_alternatePredictions.erase(it); else ++it; |
| 219 | } | ||
| 220 |
1/1✓ Branch 179 → 180 taken 263 times.
|
263 | const QPointer<QuickMailBackend> guard(this); |
| 221 |
1/5✗ Branch 181 → 182 not taken.
✓ Branch 181 → 189 taken 263 times.
✗ Branch 182 → 183 not taken.
✗ Branch 184 → 185 not taken.
✗ Branch 185 → 186 not taken.
|
263 | if (!result.error.isEmpty()) setStatus(tr("Folder suggestions unavailable: %1").arg(result.error)); |
| 222 |
1/2✗ Branch 190 → 191 not taken.
✓ Branch 190 → 192 taken 263 times.
|
263 | if (!guard) return; |
| 223 |
1/1✓ Branch 192 → 193 taken 263 times.
|
263 | m_mail.suggestionsChanged(); |
| 224 |
1/1✓ Branch 193 → 194 taken 263 times.
|
263 | m_threadList.suggestionsChanged(); |
| 225 |
1/2✗ Branch 195 → 196 not taken.
✓ Branch 195 → 197 taken 263 times.
|
263 | if (!guard) return; |
| 226 |
1/1✓ Branch 197 → 198 taken 263 times.
|
263 | emit suggestionsChanged(); |
| 227 |
2/3✓ Branch 199 → 200 taken 263 times.
✗ Branch 199 → 201 not taken.
✓ Branch 200 → 201 taken 263 times.
|
263 | if (guard) nextPredictionBatch(generation); |
| 228 |
3/4✓ Branch 203 → 204 taken 263 times.
✗ Branch 203 → 205 not taken.
✓ Branch 207 → 208 taken 263 times.
✓ Branch 207 → 210 taken 5 times.
|
268 | }); |
| 229 | 419 | } | |
| 230 |