presentation/RuleSuggestions.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "RuleSuggestions.h" | ||
| 2 | |||
| 3 | #include <QDir> | ||
| 4 | #include <QFile> | ||
| 5 | #include <QFileInfo> | ||
| 6 | #include <QJsonArray> | ||
| 7 | #include <QJsonDocument> | ||
| 8 | #include <QJsonObject> | ||
| 9 | #include <QLoggingCategory> | ||
| 10 | #include <QSaveFile> | ||
| 11 | |||
| 12 | #include <algorithm> | ||
| 13 | |||
| 14 |
4/7✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 8 taken 3 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.
|
4 | Q_LOGGING_CATEGORY(lcRuleSuggestions, "mailjd.sieve.suggest") |
| 15 | |||
| 16 | 1082 | QString RuleSuggestions::key(const QString &sender, const QString &folder) { | |
| 17 |
3/3✓ Branch 4 → 5 taken 1082 times.
✓ Branch 5 → 6 taken 1082 times.
✓ Branch 6 → 7 taken 1082 times.
|
1082 | return sender.toLower() + QLatin1Char('\n') + folder; |
| 18 | } | ||
| 19 | |||
| 20 | 20 | void RuleSuggestions::setFile(const QString &path) { | |
| 21 |
2/2✓ Branch 3 → 4 taken 10 times.
✓ Branch 3 → 5 taken 10 times.
|
20 | if (path == m_file) |
| 22 | 10 | return; | |
| 23 | 10 | m_file = path; | |
| 24 | 10 | m_pairs.clear(); | |
| 25 | 10 | m_silenced.clear(); | |
| 26 | 10 | load(); | |
| 27 | } | ||
| 28 | |||
| 29 | 10 | void RuleSuggestions::load() { | |
| 30 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 10 times.
|
10 | if (m_file.isEmpty()) |
| 31 | 7 | return; | |
| 32 |
1/1✓ Branch 5 → 6 taken 10 times.
|
10 | QFile file(m_file); |
| 33 |
3/3✓ Branch 7 → 8 taken 10 times.
✓ Branch 8 → 9 taken 7 times.
✓ Branch 8 → 10 taken 3 times.
|
10 | if (!file.open(QIODevice::ReadOnly)) |
| 34 | 7 | return; | |
| 35 |
3/3✓ Branch 10 → 11 taken 3 times.
✓ Branch 11 → 12 taken 3 times.
✓ Branch 12 → 13 taken 3 times.
|
3 | const QJsonObject root = QJsonDocument::fromJson(file.readAll()).object(); |
| 36 |
7/7✓ Branch 18 → 19 taken 3 times.
✓ Branch 19 → 20 taken 3 times.
✓ Branch 23 → 24 taken 3 times.
✓ Branch 24 → 25 taken 3 times.
✓ Branch 27 → 28 taken 5 times.
✓ Branch 124 → 26 taken 5 times.
✓ Branch 124 → 125 taken 3 times.
|
8 | for (const QJsonValue &value : root.value(QStringLiteral("pairs")).toArray()) { |
| 37 |
1/1✓ Branch 28 → 29 taken 5 times.
|
5 | const QJsonObject o = value.toObject(); |
| 38 | 5 | Pair pair; | |
| 39 |
2/2✓ Branch 33 → 34 taken 5 times.
✓ Branch 34 → 35 taken 5 times.
|
5 | pair.count = o.value(QStringLiteral("count")).toInt(); |
| 40 |
3/3✓ Branch 41 → 42 taken 5 times.
✓ Branch 42 → 43 taken 5 times.
✓ Branch 43 → 44 taken 5 times.
|
5 | pair.first = QDateTime::fromString(o.value(QStringLiteral("first")).toString(), Qt::ISODate); |
| 41 |
3/3✓ Branch 53 → 54 taken 5 times.
✓ Branch 54 → 55 taken 5 times.
✓ Branch 55 → 56 taken 5 times.
|
5 | pair.last = QDateTime::fromString(o.value(QStringLiteral("last")).toString(), Qt::ISODate); |
| 42 |
2/2✓ Branch 65 → 66 taken 5 times.
✓ Branch 66 → 67 taken 5 times.
|
5 | pair.offered = o.value(QStringLiteral("offered")).toBool(); |
| 43 |
2/2✓ Branch 73 → 74 taken 5 times.
✓ Branch 74 → 75 taken 5 times.
|
5 | const QString sender = o.value(QStringLiteral("sender")).toString(); |
| 44 |
2/2✓ Branch 81 → 82 taken 5 times.
✓ Branch 82 → 83 taken 5 times.
|
5 | const QString folder = o.value(QStringLiteral("folder")).toString(); |
| 45 |
9/11✓ Branch 87 → 88 taken 4 times.
✓ Branch 87 → 93 taken 1 time.
✓ Branch 89 → 90 taken 4 times.
✗ Branch 89 → 93 not taken.
✓ Branch 90 → 91 taken 3 times.
✓ Branch 90 → 93 taken 1 time.
✓ Branch 91 → 92 taken 3 times.
✗ Branch 92 → 93 not taken.
✓ Branch 92 → 94 taken 3 times.
✓ Branch 95 → 96 taken 2 times.
✓ Branch 95 → 97 taken 3 times.
|
5 | if (sender.isEmpty() || folder.isEmpty() || pair.count <= 0 || !pair.last.isValid()) |
| 46 | 2 | continue; | |
| 47 |
2/2✓ Branch 97 → 98 taken 3 times.
✓ Branch 98 → 99 taken 3 times.
|
3 | m_pairs.insert(key(sender, folder), pair); |
| 48 | 16 | } | |
| 49 |
7/7✓ Branch 129 → 130 taken 3 times.
✓ Branch 130 → 131 taken 3 times.
✓ Branch 134 → 135 taken 3 times.
✓ Branch 135 → 136 taken 3 times.
✓ Branch 138 → 139 taken 1 time.
✓ Branch 147 → 137 taken 1 time.
✓ Branch 147 → 148 taken 3 times.
|
4 | for (const QJsonValue &value : root.value(QStringLiteral("silenced")).toArray()) |
| 50 |
3/3✓ Branch 139 → 140 taken 1 time.
✓ Branch 140 → 141 taken 1 time.
✓ Branch 141 → 142 taken 1 time.
|
4 | m_silenced.insert(value.toString().toLower()); |
| 51 |
9/9✓ Branch 149 → 150 taken 3 times.
✓ Branch 153 → 154 taken 3 times.
✓ Branch 154 → 155 taken 3 times.
✓ Branch 156 → 157 taken 3 times.
✓ Branch 157 → 158 taken 3 times.
✓ Branch 159 → 160 taken 3 times.
✓ Branch 160 → 161 taken 3 times.
✓ Branch 164 → 151 taken 3 times.
✓ Branch 164 → 165 taken 3 times.
|
6 | qCDebug(lcRuleSuggestions) << "Loaded" << m_pairs.size() << "move pairs," << m_silenced.size() << "silenced senders"; |
| 52 | 10 | } | |
| 53 | |||
| 54 | 539 | void RuleSuggestions::save() const { | |
| 55 |
2/2✓ Branch 3 → 4 taken 3 times.
✓ Branch 3 → 5 taken 536 times.
|
539 | if (m_file.isEmpty()) |
| 56 | 4 | return; | |
| 57 |
1/1✓ Branch 5 → 6 taken 536 times.
|
536 | QJsonArray pairs; |
| 58 |
2/2✓ Branch 72 → 7 taken 127792 times.
✓ Branch 72 → 73 taken 536 times.
|
128328 | for (auto it = m_pairs.cbegin(); it != m_pairs.cend(); ++it) { |
| 59 | 127792 | const int split = it.key().indexOf(QLatin1Char('\n')); | |
| 60 |
2/2✓ Branch 15 → 16 taken 127792 times.
✓ Branch 47 → 48 taken 127792 times.
|
383376 | pairs.append(QJsonObject{{QStringLiteral("sender"), it.key().left(split)}, |
| 61 |
1/1✓ Branch 21 → 22 taken 127792 times.
|
255584 | {QStringLiteral("folder"), it.key().mid(split + 1)}, |
| 62 | 255584 | {QStringLiteral("count"), it->count}, | |
| 63 |
1/1✓ Branch 32 → 33 taken 127792 times.
|
255584 | {QStringLiteral("first"), it->first.toString(Qt::ISODate)}, |
| 64 |
1/1✓ Branch 38 → 39 taken 127792 times.
|
255584 | {QStringLiteral("last"), it->last.toString(Qt::ISODate)}, |
| 65 |
2/4✓ Branch 51 → 52 taken 766752 times.
✓ Branch 51 → 53 taken 127792 times.
✗ Branch 166 → 167 not taken.
✗ Branch 166 → 168 not taken.
|
1150128 | {QStringLiteral("offered"), it->offered}}); |
| 66 | } | ||
| 67 |
1/1✓ Branch 75 → 76 taken 536 times.
|
536 | QStringList silenced(m_silenced.cbegin(), m_silenced.cend()); |
| 68 |
1/1✓ Branch 76 → 77 taken 536 times.
|
536 | silenced.sort(); |
| 69 |
4/4✓ Branch 78 → 79 taken 536 times.
✓ Branch 79 → 80 taken 536 times.
✓ Branch 80 → 81 taken 536 times.
✓ Branch 81 → 82 taken 536 times.
|
536 | QDir().mkpath(QFileInfo(m_file).absolutePath()); |
| 70 |
1/1✓ Branch 86 → 87 taken 536 times.
|
536 | QSaveFile file(m_file); |
| 71 |
3/3✓ Branch 88 → 89 taken 536 times.
✓ Branch 89 → 90 taken 1 time.
✓ Branch 89 → 104 taken 535 times.
|
536 | if (!file.open(QIODevice::WriteOnly)) { |
| 72 |
7/7✓ Branch 90 → 91 taken 1 time.
✓ Branch 94 → 95 taken 1 time.
✓ Branch 95 → 96 taken 1 time.
✓ Branch 96 → 97 taken 1 time.
✓ Branch 97 → 98 taken 1 time.
✓ Branch 102 → 92 taken 1 time.
✓ Branch 102 → 103 taken 1 time.
|
2 | qCWarning(lcRuleSuggestions) << "Cannot write rule suggestions:" << file.errorString(); |
| 73 | 1 | return; | |
| 74 | } | ||
| 75 |
1/1✓ Branch 105 → 106 taken 535 times.
|
535 | file.setPermissions(QFile::ReadOwner | QFile::WriteOwner); |
| 76 |
1/1✓ Branch 118 → 119 taken 535 times.
|
2140 | file.write(QJsonDocument(QJsonObject{{QStringLiteral("pairs"), pairs}, |
| 77 |
4/6✓ Branch 113 → 114 taken 535 times.
✓ Branch 116 → 117 taken 535 times.
✓ Branch 123 → 124 taken 1070 times.
✓ Branch 123 → 125 taken 535 times.
✗ Branch 256 → 257 not taken.
✗ Branch 256 → 258 not taken.
|
2675 | {QStringLiteral("silenced"), QJsonArray::fromStringList(silenced)}}) |
| 78 |
1/1✓ Branch 117 → 118 taken 535 times.
|
1070 | .toJson(QJsonDocument::Compact)); |
| 79 |
2/3✓ Branch 130 → 131 taken 535 times.
✗ Branch 131 → 132 not taken.
✓ Branch 131 → 146 taken 535 times.
|
535 | if (!file.commit()) |
| 80 | ✗ | qCWarning(lcRuleSuggestions) << "Cannot write rule suggestions:" << file.errorString(); | |
| 81 | 896687 | } | |
| 82 | |||
| 83 | 532 | void RuleSuggestions::prune(const QDateTime &now) { | |
| 84 |
3/3✓ Branch 2 → 3 taken 532 times.
✓ Branch 18 → 4 taken 127792 times.
✓ Branch 18 → 19 taken 532 times.
|
128324 | for (auto it = m_pairs.begin(); it != m_pairs.end();) { |
| 85 |
6/7✓ Branch 5 → 6 taken 127792 times.
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 10 taken 127791 times.
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 10 not taken.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 15 taken 127791 times.
|
127792 | if (it->last.daysTo(now) > kWindowDays && !it->offered) |
| 86 |
1/1✓ Branch 13 → 14 taken 1 time.
|
1 | it = m_pairs.erase(it); |
| 87 | else | ||
| 88 | 127791 | ++it; | |
| 89 | } | ||
| 90 |
2/2✓ Branch 20 → 21 taken 527 times.
✓ Branch 20 → 22 taken 5 times.
|
532 | if (m_pairs.size() <= kMaxPairs) |
| 91 | 527 | return; | |
| 92 | 5 | QList<QPair<QDateTime, QString>> byAge; | |
| 93 |
2/2✓ Branch 31 → 23 taken 2505 times.
✓ Branch 31 → 32 taken 5 times.
|
2510 | for (auto it = m_pairs.cbegin(); it != m_pairs.cend(); ++it) |
| 94 |
1/1✓ Branch 26 → 27 taken 2505 times.
|
2505 | byAge.append({it->last, it.key()}); |
| 95 |
3/3✓ Branch 32 → 33 taken 5 times.
✓ Branch 33 → 34 taken 5 times.
✓ Branch 34 → 35 taken 5 times.
|
5 | std::sort(byAge.begin(), byAge.end()); |
| 96 |
2/2✓ Branch 40 → 36 taken 5 times.
✓ Branch 40 → 41 taken 5 times.
|
10 | for (int i = 0; i < byAge.size() - kMaxPairs; ++i) |
| 97 |
1/1✓ Branch 37 → 38 taken 5 times.
|
5 | m_pairs.remove(byAge.at(i).second); |
| 98 | 5 | } | |
| 99 | |||
| 100 | 534 | std::optional<RuleSuggestions::Suggestion> RuleSuggestions::record(const QString &sender, const QString &folder, | |
| 101 | const QDateTime &now) { | ||
| 102 |
2/2✓ Branch 2 → 3 taken 534 times.
✓ Branch 3 → 4 taken 534 times.
|
534 | const QString address = sender.trimmed().toLower(); |
| 103 |
4/5✓ Branch 6 → 7 taken 534 times.
✓ Branch 7 → 8 taken 3 times.
✓ Branch 7 → 11 taken 531 times.
✓ Branch 9 → 10 taken 3 times.
✗ Branch 9 → 11 not taken.
|
534 | const bool list = address.startsWith(QLatin1String("list:")) && address.size() > 5; |
| 104 |
10/11✓ Branch 13 → 14 taken 534 times.
✗ Branch 13 → 21 not taken.
✓ Branch 14 → 15 taken 531 times.
✓ Branch 14 → 19 taken 3 times.
✓ Branch 17 → 18 taken 531 times.
✓ Branch 18 → 19 taken 530 times.
✓ Branch 18 → 21 taken 1 time.
✓ Branch 20 → 21 taken 1 time.
✓ Branch 20 → 22 taken 532 times.
✓ Branch 23 → 24 taken 2 times.
✓ Branch 23 → 25 taken 532 times.
|
534 | if (address.isEmpty() || (!list && !address.contains(QLatin1Char('@'))) || folder.isEmpty()) |
| 105 | 2 | return std::nullopt; | |
| 106 |
1/1✓ Branch 25 → 26 taken 532 times.
|
532 | Pair pair = m_pairs.value(key(address, folder)); |
| 107 | // Moves older than the window start a new count. | ||
| 108 |
8/9✓ Branch 28 → 29 taken 17 times.
✓ Branch 28 → 33 taken 515 times.
✓ Branch 29 → 30 taken 17 times.
✓ Branch 30 → 31 taken 1 time.
✓ Branch 30 → 33 taken 16 times.
✓ Branch 31 → 32 taken 1 time.
✗ Branch 31 → 33 not taken.
✓ Branch 34 → 35 taken 1 time.
✓ Branch 34 → 39 taken 531 times.
|
532 | if (pair.count > 0 && pair.first.daysTo(now) > kWindowDays && !pair.offered) { |
| 109 | 1 | pair.count = 0; | |
| 110 | 1 | pair.first = {}; | |
| 111 | } | ||
| 112 | 532 | ++pair.count; | |
| 113 |
3/3✓ Branch 39 → 40 taken 532 times.
✓ Branch 40 → 41 taken 516 times.
✓ Branch 40 → 42 taken 16 times.
|
532 | if (!pair.first.isValid()) |
| 114 | 516 | pair.first = now; | |
| 115 | 532 | pair.last = now; | |
| 116 |
2/2✓ Branch 43 → 44 taken 532 times.
✓ Branch 44 → 45 taken 532 times.
|
532 | m_pairs.insert(key(address, folder), pair); |
| 117 |
1/1✓ Branch 46 → 47 taken 532 times.
|
532 | prune(now); |
| 118 | 532 | std::optional<Suggestion> result; | |
| 119 |
9/9✓ Branch 47 → 48 taken 9 times.
✓ Branch 47 → 52 taken 523 times.
✓ Branch 48 → 49 taken 6 times.
✓ Branch 48 → 52 taken 3 times.
✓ Branch 49 → 50 taken 6 times.
✓ Branch 50 → 51 taken 5 times.
✓ Branch 50 → 52 taken 1 time.
✓ Branch 53 → 54 taken 5 times.
✓ Branch 53 → 59 taken 527 times.
|
532 | if (pair.count >= kThreshold && !pair.offered && !silenced(address)) |
| 120 | 5 | result = Suggestion{address, folder, pair.count}; | |
| 121 |
1/1✓ Branch 59 → 60 taken 532 times.
|
532 | save(); |
| 122 | 532 | return result; | |
| 123 | 534 | } | |
| 124 | |||
| 125 | 6 | void RuleSuggestions::markOffered(const QString &sender, const QString &folder) { | |
| 126 |
2/2✓ Branch 2 → 3 taken 6 times.
✓ Branch 3 → 4 taken 6 times.
|
6 | auto it = m_pairs.find(key(sender, folder)); |
| 127 |
2/2✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 5 times.
|
6 | if (it == m_pairs.end()) |
| 128 | 1 | return; | |
| 129 | 5 | it->offered = true; | |
| 130 |
1/1✓ Branch 10 → 11 taken 5 times.
|
5 | save(); |
| 131 | } | ||
| 132 | |||
| 133 | 2 | void RuleSuggestions::silenceSender(const QString &sender) { | |
| 134 |
3/3✓ Branch 2 → 3 taken 2 times.
✓ Branch 3 → 4 taken 2 times.
✓ Branch 4 → 5 taken 2 times.
|
2 | m_silenced.insert(sender.trimmed().toLower()); |
| 135 | 2 | save(); | |
| 136 | 2 | } | |
| 137 | |||
| 138 | 9 | int RuleSuggestions::count(const QString &sender, const QString &folder) const { | |
| 139 |
1/1✓ Branch 2 → 3 taken 9 times.
|
9 | return m_pairs.value(key(sender, folder)).count; |
| 140 | } | ||
| 141 |