service/FolderPredictionService.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "FolderPredictionService.h" | ||
| 2 | #include "data/FolderPredictor.h" | ||
| 3 | #include "data/FolderSuggestionStore.h" | ||
| 4 | #include "data/MailCache.h" | ||
| 5 | #include <QCoreApplication> | ||
| 6 | #include <QElapsedTimer> | ||
| 7 | #include <QFileInfo> | ||
| 8 | #include <QFutureWatcher> | ||
| 9 | #include <QLoggingCategory> | ||
| 10 | #include <QSet> | ||
| 11 | #include <QtConcurrent> | ||
| 12 | #include <algorithm> | ||
| 13 | |||
| 14 |
4/7✓ Branch 2 → 3 taken 13 times.
✓ Branch 2 → 8 taken 376 times.
✓ Branch 4 → 5 taken 13 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 13 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
|
389 | Q_LOGGING_CATEGORY(lcFolderPrediction, "mailjd.predictor.service") |
| 15 | |||
| 16 |
2/2✓ Branch 3 → 4 taken 35 times.
✓ Branch 8 → 9 taken 35 times.
|
35 | FolderPredictionService::FolderPredictionService(QObject *parent) : QObject(parent) { |
| 17 |
1/1✓ Branch 9 → 10 taken 35 times.
|
35 | m_pool.setMaxThreadCount(1); |
| 18 |
1/1✓ Branch 10 → 11 taken 35 times.
|
35 | m_pool.setExpiryTimeout(1000); |
| 19 |
1/1✓ Branch 12 → 13 taken 35 times.
|
35 | connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, |
| 20 | 36 | this, [this] { stop(); }); | |
| 21 | 35 | } | |
| 22 | 57 | FolderPredictionService::~FolderPredictionService() { stop(); } | |
| 23 | 10348 | FolderPredictionService *FolderPredictionService::shared() { | |
| 24 |
3/4✓ Branch 2 → 3 taken 21 times.
✓ Branch 2 → 7 taken 10327 times.
✓ Branch 4 → 5 taken 21 times.
✗ Branch 4 → 7 not taken.
|
10348 | static QPointer<FolderPredictionService> service; |
| 25 |
5/8✓ Branch 8 → 9 taken 21 times.
✓ Branch 8 → 16 taken 10327 times.
✓ Branch 11 → 12 taken 21 times.
✓ Branch 12 → 13 taken 21 times.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 21 times.
✗ Branch 18 → 19 not taken.
✗ Branch 18 → 20 not taken.
|
10348 | if (!service) service = new FolderPredictionService(QCoreApplication::instance()); |
| 26 | 10348 | return service; | |
| 27 | } | ||
| 28 | 36 | void FolderPredictionService::stop() { | |
| 29 | 36 | m_stopping = true; | |
| 30 |
5/5✓ Branch 2 → 3 taken 36 times.
✓ Branch 3 → 4 taken 36 times.
✓ Branch 6 → 7 taken 6 times.
✓ Branch 9 → 5 taken 6 times.
✓ Branch 9 → 10 taken 36 times.
|
42 | for (const auto &request : m_pending) disconnect(request.destroyed); |
| 31 | 36 | m_pending.clear(); | |
| 32 |
2/2✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 18 taken 35 times.
|
36 | if (m_active) { |
| 33 | 1 | m_active->cancelled->store(true); | |
| 34 | 1 | disconnect(m_active->destroyed); | |
| 35 | } | ||
| 36 |
1/1✓ Branch 19 → 20 taken 36 times.
|
36 | m_pool.waitForDone(); |
| 37 | 36 | } | |
| 38 | 10396 | void FolderPredictionService::cancel(QObject *owner) { | |
| 39 |
6/6✓ Branch 3 → 4 taken 618 times.
✓ Branch 3 → 8 taken 9778 times.
✓ Branch 6 → 7 taken 343 times.
✓ Branch 6 → 8 taken 275 times.
✓ Branch 9 → 10 taken 343 times.
✓ Branch 9 → 13 taken 10053 times.
|
10396 | if (m_active && m_active->owner == owner) m_active->cancelled->store(true); |
| 40 | 10396 | m_pending.removeIf([this, owner](const Request &request) { | |
| 41 |
6/6✓ Branch 3 → 4 taken 506 times.
✓ Branch 3 → 7 taken 1 time.
✓ Branch 5 → 6 taken 486 times.
✓ Branch 5 → 7 taken 20 times.
✓ Branch 8 → 9 taken 486 times.
✓ Branch 8 → 10 taken 21 times.
|
507 | if (request.owner && request.owner != owner) return false; |
| 42 | 21 | disconnect(request.destroyed); | |
| 43 | 21 | return true; | |
| 44 | }); | ||
| 45 | 10396 | } | |
| 46 | 468 | void FolderPredictionService::predict(QObject *owner, FolderPredictionRequest input, | |
| 47 | Callback callback) { | ||
| 48 |
1/1✓ Branch 2 → 3 taken 468 times.
|
468 | cancel(owner); |
| 49 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 468 times.
|
474 | if (!owner) return; |
| 50 | 2322 | const auto invalidText = [](const QString &text) { | |
| 51 |
5/5✓ Branch 3 → 4 taken 2321 times.
✓ Branch 3 → 7 taken 1 time.
✓ Branch 5 → 6 taken 2321 times.
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 8 taken 2320 times.
|
2322 | return text.size() > 4096 || text.contains(QChar::Null); |
| 52 | }; | ||
| 53 |
4/6✓ Branch 7 → 8 taken 467 times.
✓ Branch 7 → 35 taken 1 time.
✓ Branch 8 → 9 taken 467 times.
✗ Branch 8 → 35 not taken.
✓ Branch 10 → 11 taken 467 times.
✗ Branch 10 → 35 not taken.
|
935 | if (m_stopping || m_pending.size() >= MaxPending || input.folderId <= 0 || |
| 54 |
2/4✓ Branch 12 → 13 taken 467 times.
✗ Branch 12 → 35 not taken.
✓ Branch 14 → 15 taken 467 times.
✗ Branch 14 → 35 not taken.
|
1401 | input.cachePath.isEmpty() || input.predictorPath.isEmpty() || |
| 55 |
3/4✓ Branch 16 → 17 taken 467 times.
✗ Branch 16 → 35 not taken.
✓ Branch 18 → 19 taken 466 times.
✓ Branch 18 → 35 taken 1 time.
|
1401 | input.account.isEmpty() || input.folder.isEmpty() || |
| 56 |
2/2✓ Branch 20 → 21 taken 465 times.
✓ Branch 20 → 35 taken 1 time.
|
933 | input.uids.isEmpty() || input.uids.size() > MaxRows || |
| 57 |
4/6✓ Branch 21 → 22 taken 465 times.
✓ Branch 22 → 23 taken 465 times.
✗ Branch 22 → 35 not taken.
✓ Branch 23 → 24 taken 465 times.
✓ Branch 24 → 25 taken 465 times.
✗ Branch 24 → 35 not taken.
|
465 | invalidText(input.cachePath) || invalidText(input.predictorPath) || |
| 58 |
11/13✓ Branch 5 → 6 taken 468 times.
✗ Branch 5 → 35 not taken.
✓ Branch 25 → 26 taken 465 times.
✓ Branch 26 → 27 taken 464 times.
✓ Branch 26 → 35 taken 1 time.
✓ Branch 27 → 28 taken 464 times.
✓ Branch 28 → 29 taken 463 times.
✓ Branch 28 → 35 taken 1 time.
✓ Branch 29 → 30 taken 463 times.
✓ Branch 30 → 31 taken 463 times.
✗ Branch 30 → 35 not taken.
✓ Branch 37 → 38 taken 6 times.
✓ Branch 37 → 45 taken 462 times.
|
1399 | invalidText(input.account) || invalidText(input.folder) || invalidText(input.junkFolder) || |
| 59 |
3/3✓ Branch 33 → 34 taken 463 times.
✓ Branch 34 → 35 taken 1 time.
✓ Branch 34 → 36 taken 462 times.
|
1280 | std::any_of(input.uids.cbegin(), input.uids.cend(), [](qint64 uid) { return uid <= 0; })) { |
| 60 | 6 | callback({{}, QStringLiteral("Folder prediction is unavailable or exceeds its identity budget"), input.uidValidity}); | |
| 61 | 6 | return; | |
| 62 | } | ||
| 63 |
3/11✓ Branch 45 → 46 taken 462 times.
✓ Branch 50 → 51 taken 462 times.
✓ Branch 51 → 52 taken 462 times.
✗ Branch 78 → 79 not taken.
✗ Branch 78 → 80 not taken.
✗ Branch 81 → 82 not taken.
✗ Branch 81 → 83 not taken.
✗ Branch 84 → 85 not taken.
✗ Branch 84 → 86 not taken.
✗ Branch 87 → 88 not taken.
✗ Branch 87 → 89 not taken.
|
462 | Request request{owner, std::move(input), std::move(callback)}; |
| 64 | 462 | const auto cancelled = request.cancelled; | |
| 65 |
1/1✓ Branch 54 → 55 taken 462 times.
|
536 | request.destroyed = connect(owner, &QObject::destroyed, this, [cancelled] { cancelled->store(true); }); |
| 66 |
1/1✓ Branch 59 → 60 taken 462 times.
|
462 | m_pending.append(std::move(request)); |
| 67 |
1/1✓ Branch 60 → 61 taken 462 times.
|
462 | startNext(); |
| 68 | 474 | } | |
| 69 | 896 | void FolderPredictionService::startNext() { | |
| 70 |
5/6✓ Branch 2 → 3 taken 896 times.
✗ Branch 2 → 5 not taken.
✓ Branch 4 → 5 taken 65 times.
✓ Branch 4 → 6 taken 831 times.
✓ Branch 7 → 8 taken 65 times.
✓ Branch 7 → 9 taken 831 times.
|
1292 | if (m_stopping || m_active) return; |
| 71 |
1/2✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 831 times.
|
831 | if (m_warming) return; |
| 72 |
1/1✓ Branch 11 → 12 taken 831 times.
|
831 | cancel(nullptr); |
| 73 |
3/3✓ Branch 13 → 14 taken 396 times.
✓ Branch 13 → 16 taken 435 times.
✓ Branch 14 → 15 taken 396 times.
|
831 | if (m_pending.isEmpty()) { startWarm(); return; } |
| 74 |
1/1✓ Branch 16 → 17 taken 435 times.
|
435 | m_active = m_pending.takeFirst(); |
| 75 |
3/6✓ Branch 19 → 20 taken 435 times.
✓ Branch 20 → 21 taken 435 times.
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 435 times.
✗ Branch 47 → 48 not taken.
✗ Branch 47 → 49 not taken.
|
435 | auto *watcher = new QFutureWatcher<FolderPredictionResult>(this); |
| 76 |
1/1✓ Branch 23 → 24 taken 435 times.
|
435 | connect(watcher, &QFutureWatcher<FolderPredictionResult>::finished, this, [this, watcher] { |
| 77 | 434 | auto request = std::move(*m_active); | |
| 78 | 434 | m_active.reset(); | |
| 79 |
1/1✓ Branch 6 → 7 taken 434 times.
|
434 | auto result = watcher->result(); |
| 80 |
3/4✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 14 taken 433 times.
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 14 not taken.
|
434 | if (result.provisional && !m_stopping) |
| 81 | 1 | m_warm.append({request.input.cachePath, request.input.account}); | |
| 82 |
1/1✓ Branch 14 → 15 taken 434 times.
|
434 | watcher->deleteLater(); |
| 83 |
1/1✓ Branch 15 → 16 taken 434 times.
|
434 | disconnect(request.destroyed); |
| 84 |
1/1✓ Branch 16 → 17 taken 434 times.
|
434 | QPointer<FolderPredictionService> guard(this); |
| 85 |
7/8✓ Branch 17 → 18 taken 434 times.
✗ Branch 17 → 24 not taken.
✓ Branch 19 → 20 taken 368 times.
✓ Branch 19 → 24 taken 66 times.
✓ Branch 22 → 23 taken 282 times.
✓ Branch 22 → 24 taken 86 times.
✓ Branch 25 → 26 taken 282 times.
✓ Branch 25 → 31 taken 152 times.
|
434 | if (!m_stopping && request.owner && !request.cancelled->load()) |
| 86 |
1/1✓ Branch 28 → 29 taken 282 times.
|
282 | request.callback(std::move(result)); |
| 87 |
3/3✓ Branch 32 → 33 taken 433 times.
✓ Branch 32 → 34 taken 1 time.
✓ Branch 33 → 34 taken 433 times.
|
434 | if (guard) startNext(); |
| 88 |
1/3✓ Branch 11 → 12 taken 1 time.
✗ Branch 40 → 41 not taken.
✗ Branch 40 → 42 not taken.
|
435 | }); |
| 89 | 435 | const auto input = m_active->input; | |
| 90 | 435 | const auto cancelled = m_active->cancelled; | |
| 91 | 435 | const auto statistics = m_statistics; | |
| 92 |
1/1✓ Branch 30 → 31 taken 435 times.
|
435 | const auto model = modelFor(input.cachePath, input.account); |
| 93 |
2/8✓ Branch 35 → 36 taken 435 times.
✓ Branch 36 → 37 taken 435 times.
✗ Branch 56 → 57 not taken.
✗ Branch 56 → 58 not taken.
✗ Branch 59 → 60 not taken.
✗ Branch 59 → 61 not taken.
✗ Branch 62 → 63 not taken.
✗ Branch 62 → 64 not taken.
|
435 | watcher->setFuture(QtConcurrent::run(&m_pool, [input, cancelled, statistics, model] { |
| 94 | 435 | return classify(input, *cancelled, statistics, *model); | |
| 95 | })); | ||
| 96 | 435 | } | |
| 97 | |||
| 98 | // Runs on the worker. Evidence first (S105.3.1); the Naive Bayes word model | ||
| 99 | // answers only for mails the evidence knows nothing about (an empty or not | ||
| 100 | // yet synchronised filing history), as before. | ||
| 101 | 435 | FolderPredictionResult FolderPredictionService::classify(const FolderPredictionRequest &input, | |
| 102 | const std::atomic_bool &cancelled, | ||
| 103 | const std::shared_ptr<FolderPredictor::StatisticsCache> &statistics, | ||
| 104 | FolderEvidenceModel &model) { | ||
| 105 | 435 | FolderPredictionResult result; | |
| 106 | 435 | result.uidValidity = input.uidValidity; | |
| 107 |
2/2✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 6 taken 434 times.
|
435 | if (cancelled.load()) return result; |
| 108 | 434 | QElapsedTimer timer; | |
| 109 | 434 | timer.start(); | |
| 110 | // The training session owns the predictor file; without it there is | ||
| 111 | // no model to answer from (and nothing is created here). | ||
| 112 |
4/4✓ Branch 7 → 8 taken 434 times.
✓ Branch 8 → 9 taken 434 times.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 19 taken 433 times.
|
434 | if (!QFileInfo(input.predictorPath).isFile()) { |
| 113 | 1 | result.error = QStringLiteral("Folder prediction database is unavailable"); | |
| 114 | 1 | return result; | |
| 115 | } | ||
| 116 |
1/1✓ Branch 19 → 20 taken 433 times.
|
433 | FolderSuggestionStore store; |
| 117 | 433 | QString storeError; | |
| 118 |
1/1✓ Branch 20 → 21 taken 433 times.
|
433 | const bool stored = store.open(input.predictorPath, &storeError); |
| 119 |
1/8✗ Branch 21 → 22 not taken.
✓ Branch 21 → 35 taken 433 times.
✗ Branch 22 → 23 not taken.
✗ Branch 26 → 27 not taken.
✗ Branch 28 → 29 not taken.
✗ Branch 29 → 30 not taken.
✗ Branch 33 → 24 not taken.
✗ Branch 33 → 34 not taken.
|
433 | if (!stored) qCWarning(lcFolderPrediction).noquote() << "suggestion store unavailable:" << storeError; |
| 120 | // S105.3.5: until the model is built, stored suggestions answer at once. | ||
| 121 |
6/7✓ Branch 35 → 36 taken 433 times.
✓ Branch 36 → 37 taken 264 times.
✓ Branch 36 → 39 taken 169 times.
✓ Branch 37 → 38 taken 264 times.
✗ Branch 37 → 39 not taken.
✓ Branch 40 → 41 taken 264 times.
✓ Branch 40 → 100 taken 169 times.
|
433 | if (!model.built(input.account) && stored) { |
| 122 |
1/1✓ Branch 41 → 42 taken 264 times.
|
264 | const auto known = store.read(input.account, input.folderId, input.uidValidity, input.uids, &storeError); |
| 123 |
1/1✓ Branch 44 → 45 taken 264 times.
|
264 | QSet<qint64> wanted(input.uids.cbegin(), input.uids.cend()); |
| 124 |
5/6✓ Branch 46 → 47 taken 1 time.
✓ Branch 46 → 51 taken 263 times.
✓ Branch 49 → 50 taken 1 time.
✗ Branch 49 → 51 not taken.
✓ Branch 52 → 53 taken 1 time.
✓ Branch 52 → 90 taken 263 times.
|
264 | if (!known.isEmpty() && known.size() == wanted.size()) { |
| 125 |
2/2✓ Branch 63 → 54 taken 1 time.
✓ Branch 63 → 64 taken 1 time.
|
2 | for (auto it = known.cbegin(); it != known.cend(); ++it) |
| 126 | 1 | result.items.append({{input.account, input.folderId, it.key()}, it.value()}); | |
| 127 | 1 | result.provisional = true; | |
| 128 |
4/4✓ Branch 64 → 65 taken 1 time.
✓ Branch 68 → 69 taken 1 time.
✓ Branch 87 → 66 taken 1 time.
✓ Branch 87 → 88 taken 1 time.
|
2 | qCInfo(lcFolderPrediction).noquote() |
| 129 |
3/3✓ Branch 75 → 76 taken 1 time.
✓ Branch 78 → 79 taken 1 time.
✓ Branch 79 → 80 taken 1 time.
|
3 | << QStringLiteral("stored suggestions answered mails=%1 ms=%2").arg(known.size()).arg(timer.elapsed()); |
| 130 | 1 | return result; | |
| 131 | } | ||
| 132 | 265 | } | |
| 133 |
1/1✓ Branch 100 → 101 taken 432 times.
|
432 | MailCache cache; |
| 134 |
2/4✓ Branch 101 → 102 taken 432 times.
✗ Branch 102 → 103 not taken.
✓ Branch 102 → 108 taken 432 times.
✗ Branch 103 → 104 not taken.
|
432 | if (!cache.openReadOnly(input.cachePath)) { result.error = cache.lastError(); return result; } |
| 135 | 432 | QString modelError; | |
| 136 |
1/1✓ Branch 108 → 109 taken 432 times.
|
432 | const bool modelReady = model.built(input.account) |
| 137 |
3/3✓ Branch 109 → 110 taken 169 times.
✓ Branch 109 → 113 taken 263 times.
✓ Branch 111 → 112 taken 169 times.
|
432 | ? model.update(cache.database(), &modelError) |
| 138 |
1/1✓ Branch 114 → 115 taken 263 times.
|
263 | : model.build(cache.database(), input.account, &modelError); |
| 139 |
1/8✗ Branch 116 → 117 not taken.
✓ Branch 116 → 130 taken 432 times.
✗ Branch 117 → 118 not taken.
✗ Branch 121 → 122 not taken.
✗ Branch 123 → 124 not taken.
✗ Branch 124 → 125 not taken.
✗ Branch 128 → 119 not taken.
✗ Branch 128 → 129 not taken.
|
432 | if (!modelReady) qCWarning(lcFolderPrediction).noquote() << "evidence model unavailable:" << modelError; |
| 140 | 432 | const auto page = cache.training().trainingHeaders(input.account, input.folderId, input.folder, | |
| 141 |
1/1✓ Branch 131 → 132 taken 432 times.
|
432 | input.uidValidity, input.uids); |
| 142 |
2/2✓ Branch 133 → 134 taken 9 times.
✓ Branch 133 → 137 taken 423 times.
|
432 | if (!page.error.isEmpty()) { result.error = page.error; return result; } |
| 143 |
2/2✓ Branch 138 → 139 taken 33 times.
✓ Branch 138 → 141 taken 390 times.
|
423 | if (cancelled.load()) return result; |
| 144 | 390 | std::optional<FolderPredictor> predictor; | |
| 145 | 390 | QList<QPair<qint64, FolderSuggestion>> computed; | |
| 146 |
2/2✓ Branch 193 → 143 taken 617 times.
✓ Branch 193 → 194 taken 388 times.
|
1005 | for (const auto &header : page.headers) { |
| 147 |
2/2✓ Branch 145 → 146 taken 1 time.
✓ Branch 145 → 147 taken 616 times.
|
618 | if (cancelled.load()) return FolderPredictionResult{}; |
| 148 | 616 | FolderSuggestion suggestion; | |
| 149 |
2/3✓ Branch 147 → 148 taken 616 times.
✗ Branch 147 → 152 not taken.
✓ Branch 148 → 149 taken 616 times.
|
616 | if (modelReady) suggestion = model.suggest(header, input.junkFolder); |
| 150 |
2/2✓ Branch 153 → 154 taken 282 times.
✓ Branch 153 → 179 taken 334 times.
|
616 | if (suggestion.primary.first.isEmpty()) { |
| 151 |
2/2✓ Branch 155 → 156 taken 278 times.
✓ Branch 155 → 170 taken 4 times.
|
282 | if (!predictor) { |
| 152 |
1/1✓ Branch 156 → 157 taken 278 times.
|
278 | predictor.emplace(); |
| 153 |
2/3✓ Branch 159 → 160 taken 278 times.
✗ Branch 161 → 162 not taken.
✓ Branch 161 → 170 taken 278 times.
|
278 | if (!predictor->openReadOnly(input.predictorPath, statistics)) { |
| 154 | ✗ | result.error = QStringLiteral("Folder prediction database is unavailable"); | |
| 155 | ✗ | return result; | |
| 156 | } | ||
| 157 | } | ||
| 158 |
1/1✓ Branch 171 → 172 taken 282 times.
|
282 | suggestion = predictor->suggestions(header, input.junkFolder, &result.error); |
| 159 |
3/3✓ Branch 175 → 176 taken 1 time.
✓ Branch 175 → 179 taken 281 times.
✓ Branch 176 → 177 taken 1 time.
|
282 | if (!result.error.isEmpty()) { result.items.clear(); return result; } |
| 160 | } | ||
| 161 | 615 | result.items.append({{input.account, input.folderId, header.uid}, suggestion}); | |
| 162 |
1/1✓ Branch 184 → 185 taken 615 times.
|
615 | computed.append({header.uid, suggestion}); |
| 163 | 616 | } | |
| 164 |
2/3✓ Branch 195 → 196 taken 388 times.
✗ Branch 196 → 197 not taken.
✓ Branch 196 → 205 taken 388 times.
|
388 | if (!cache.training().trainingIdentityCurrent(input.account, input.folderId, input.folder, input.uidValidity)) { |
| 165 | ✗ | result.items.clear(); | |
| 166 | ✗ | result.error = QStringLiteral("Folder prediction mailbox identity changed"); | |
| 167 | ✗ | return result; | |
| 168 | } | ||
| 169 |
4/7✓ Branch 205 → 206 taken 388 times.
✗ Branch 205 → 209 not taken.
✓ Branch 206 → 207 taken 388 times.
✗ Branch 207 → 208 not taken.
✓ Branch 207 → 209 taken 388 times.
✗ Branch 210 → 211 not taken.
✓ Branch 210 → 224 taken 388 times.
|
388 | if (stored && !store.write(input.account, input.folderId, input.uidValidity, computed, &storeError)) |
| 170 | ✗ | qCWarning(lcFolderPrediction).noquote() << "suggestions not stored:" << storeError; | |
| 171 |
4/4✓ Branch 224 → 225 taken 388 times.
✓ Branch 228 → 229 taken 388 times.
✓ Branch 247 → 226 taken 388 times.
✓ Branch 247 → 248 taken 388 times.
|
776 | qCDebug(lcFolderPrediction).noquote() |
| 172 |
3/3✓ Branch 235 → 236 taken 388 times.
✓ Branch 238 → 239 taken 388 times.
✓ Branch 239 → 240 taken 388 times.
|
1164 | << QStringLiteral("classified mails=%1 ms=%2").arg(computed.size()).arg(timer.elapsed()); |
| 173 | 388 | return result; | |
| 174 | 1051 | } | |
| 175 | |||
| 176 | 436 | std::shared_ptr<FolderEvidenceModel> FolderPredictionService::modelFor(const QString &cachePath, | |
| 177 | const QString &account) { | ||
| 178 |
3/3✓ Branch 4 → 5 taken 436 times.
✓ Branch 5 → 6 taken 436 times.
✓ Branch 6 → 7 taken 436 times.
|
436 | auto &model = m_models[cachePath + QLatin1Char('\n') + account]; |
| 179 |
3/3✓ Branch 10 → 11 taken 265 times.
✓ Branch 10 → 15 taken 171 times.
✓ Branch 11 → 12 taken 265 times.
|
436 | if (!model) model = std::make_shared<FolderEvidenceModel>(); |
| 180 | 436 | return model; | |
| 181 | } | ||
| 182 | |||
| 183 | 40 | void FolderPredictionService::recordFiling(const QString &cachePath, const QString &account, | |
| 184 | const MailHeader &header, const QString &folder) { | ||
| 185 |
2/2✓ Branch 4 → 5 taken 40 times.
✓ Branch 5 → 6 taken 40 times.
|
40 | const auto model = m_models.value(cachePath + QLatin1Char('\n') + account); |
| 186 | // An unbuilt model reads the filing from the cache when it is built. | ||
| 187 |
7/8✓ Branch 10 → 11 taken 37 times.
✓ Branch 10 → 15 taken 3 times.
✓ Branch 12 → 13 taken 37 times.
✓ Branch 13 → 14 taken 37 times.
✗ Branch 13 → 15 not taken.
✓ Branch 16 → 17 taken 37 times.
✓ Branch 16 → 19 taken 3 times.
✓ Branch 18 → 19 taken 37 times.
|
40 | if (model && model->built(account)) model->correct(header, folder); |
| 188 | 40 | } | |
| 189 | |||
| 190 | ✗ | void FolderPredictionService::forgetAccount(const QString &account) { | |
| 191 | ✗ | m_models.removeIf([&account](const auto &entry) { return entry.key().endsWith(QLatin1Char('\n') + account); }); | |
| 192 | ✗ | } | |
| 193 | |||
| 194 | // S105.3.5: after a provisional answer the model is built in the | ||
| 195 | // background, then consumers are told to ask again. | ||
| 196 | 396 | void FolderPredictionService::startWarm() { | |
| 197 |
8/12✓ Branch 2 → 3 taken 396 times.
✗ Branch 2 → 10 not taken.
✓ Branch 4 → 5 taken 396 times.
✗ Branch 4 → 10 not taken.
✓ Branch 5 → 6 taken 396 times.
✗ Branch 5 → 10 not taken.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 10 taken 395 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 1 time.
✓ Branch 12 → 13 taken 395 times.
✓ Branch 12 → 14 taken 1 time.
|
396 | if (m_stopping || m_active || m_warming || m_warm.isEmpty() || !m_pending.isEmpty()) return; |
| 198 |
1/1✓ Branch 14 → 15 taken 1 time.
|
1 | const Warm warm = m_warm.takeFirst(); |
| 199 |
1/1✓ Branch 15 → 16 taken 1 time.
|
1 | const auto model = modelFor(warm.cachePath, warm.account); |
| 200 |
2/4✓ Branch 17 → 18 taken 1 time.
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 21 taken 1 time.
✗ Branch 19 → 20 not taken.
|
1 | if (model->built(warm.account)) { startWarm(); return; } |
| 201 | 1 | m_warming = true; | |
| 202 |
3/6✓ Branch 21 → 22 taken 1 time.
✓ Branch 22 → 23 taken 1 time.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 1 time.
✗ Branch 46 → 47 not taken.
✗ Branch 46 → 48 not taken.
|
1 | auto *watcher = new QFutureWatcher<bool>(this); |
| 203 |
1/1✓ Branch 26 → 27 taken 1 time.
|
1 | connect(watcher, &QFutureWatcher<bool>::finished, this, [this, watcher, account = warm.account] { |
| 204 |
1/1✓ Branch 2 → 3 taken 1 time.
|
1 | const bool built = watcher->result(); |
| 205 |
1/1✓ Branch 3 → 4 taken 1 time.
|
1 | watcher->deleteLater(); |
| 206 | 1 | m_warming = false; | |
| 207 |
1/2✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 1 time.
|
1 | if (m_stopping) return; |
| 208 |
1/1✓ Branch 6 → 7 taken 1 time.
|
1 | QPointer<FolderPredictionService> guard(this); |
| 209 |
2/3✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 9 not taken.
✓ Branch 8 → 9 taken 1 time.
|
1 | if (built) emit evidenceRebuilt(account); |
| 210 |
1/2✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 1 time.
|
1 | if (!guard) return; |
| 211 |
1/1✓ Branch 12 → 13 taken 1 time.
|
1 | startNext(); |
| 212 |
1/2✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 18 not taken.
|
1 | }); |
| 213 |
2/4✓ Branch 31 → 32 taken 1 time.
✓ Branch 32 → 33 taken 1 time.
✗ Branch 57 → 58 not taken.
✗ Branch 57 → 59 not taken.
|
1 | watcher->setFuture(QtConcurrent::run(&m_pool, [warm, model] { |
| 214 |
1/1✓ Branch 2 → 3 taken 1 time.
|
1 | MailCache cache; |
| 215 |
2/3✓ Branch 3 → 4 taken 1 time.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 1 time.
|
1 | if (!cache.openReadOnly(warm.cachePath)) return false; |
| 216 | 1 | QString error; | |
| 217 |
1/1✓ Branch 8 → 9 taken 1 time.
|
1 | const bool built = model->build(cache.database(), warm.account, &error); |
| 218 |
1/8✗ Branch 9 → 10 not taken.
✓ Branch 9 → 23 taken 1 time.
✗ Branch 10 → 11 not taken.
✗ Branch 14 → 15 not taken.
✗ Branch 16 → 17 not taken.
✗ Branch 17 → 18 not taken.
✗ Branch 21 → 12 not taken.
✗ Branch 21 → 22 not taken.
|
1 | if (!built) qCWarning(lcFolderPrediction).noquote() << "evidence model not built:" << error; |
| 219 | 1 | return built; | |
| 220 | 1 | })); | |
| 221 | 1 | } | |
| 222 |