presentation/RuleSuggestions.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <QDateTime> | ||
| 4 | #include <QHash> | ||
| 5 | #include <QSet> | ||
| 6 | #include <QString> | ||
| 7 | |||
| 8 | #include <optional> | ||
| 9 | |||
| 10 | // Rule suggestions from manual moves (Sprint 95 S95.4.5, D-12, RJ5). | ||
| 11 | // | ||
| 12 | // Counts manual moves per (sender address, target folder) of one account; | ||
| 13 | // mailing-list mail is counted per "list:<List-Id>" instead of its sender. | ||
| 14 | // The third move of the same pair within 30 days yields one suggestion; | ||
| 15 | // a pair is offered once, and "Not for this sender" silences the sender | ||
| 16 | // for good. Rule moves (apply to existing) are not recorded by the caller. | ||
| 17 | // State lives in a small JSON file next to the account's rule data | ||
| 18 | // (<profile>/sieve/<account-id>/suggestions.json, 0600); the predictor's | ||
| 19 | // training data is not touched. | ||
| 20 | // | ||
| 21 | // Invariants: | ||
| 22 | // - Addresses are compared lower case; folders as given (raw IMAP path). | ||
| 23 | // - At most kMaxPairs pairs are kept; the oldest are dropped first. | ||
| 24 | // - A malformed file is treated as empty and rewritten on the next change. | ||
| 25 | class RuleSuggestions { | ||
| 26 | public: | ||
| 27 | static constexpr int kThreshold = 3; | ||
| 28 | static constexpr int kWindowDays = 30; | ||
| 29 | static constexpr int kMaxPairs = 500; | ||
| 30 | |||
| 31 | struct Suggestion { | ||
| 32 | QString sender; | ||
| 33 | QString folder; | ||
| 34 | int count = 0; | ||
| 35 | }; | ||
| 36 | |||
| 37 | void setFile(const QString &path); // loads; empty path = memory only | ||
| 38 | QString file() const { return m_file; } | ||
| 39 | |||
| 40 | // Records one move; returns a suggestion when the pair reaches the | ||
| 41 | // threshold and was neither offered nor silenced. | ||
| 42 | std::optional<Suggestion> record(const QString &sender, const QString &folder, const QDateTime &now); | ||
| 43 | void markOffered(const QString &sender, const QString &folder); | ||
| 44 | void silenceSender(const QString &sender); | ||
| 45 |
1/1✓ Branch 2 → 3 taken 7 times.
|
7 | bool silenced(const QString &sender) const { return m_silenced.contains(sender.toLower()); } |
| 46 | int count(const QString &sender, const QString &folder) const; | ||
| 47 | |||
| 48 | private: | ||
| 49 | struct Pair { | ||
| 50 | int count = 0; | ||
| 51 | QDateTime first; | ||
| 52 | QDateTime last; | ||
| 53 | bool offered = false; | ||
| 54 | }; | ||
| 55 | static QString key(const QString &sender, const QString &folder); | ||
| 56 | void load(); | ||
| 57 | void save() const; | ||
| 58 | void prune(const QDateTime &now); | ||
| 59 | |||
| 60 | QString m_file; | ||
| 61 | QHash<QString, Pair> m_pairs; // "sender\nfolder" | ||
| 62 | QSet<QString> m_silenced; | ||
| 63 | }; | ||
| 64 |