presentation/QuickContactMailHistory.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <QObject> | ||
| 4 | #include <QStringList> | ||
| 5 | #include <QThreadPool> | ||
| 6 | #include <QHash> | ||
| 7 | #include <QPair> | ||
| 8 | #include <QVariantList> | ||
| 9 | #include <functional> | ||
| 10 | |||
| 11 | // "Recent mails" of the contact card (Sprint 97 S97.5.3, CJ9): the last | ||
| 12 | // five mails from or to any of a person's addresses, newest first, read | ||
| 13 | // from the local search index on a worker (MailSearchReadService::readCache, | ||
| 14 | // one bounded page per address and direction). Latest wins: a newer | ||
| 15 | // request makes an older answer irrelevant (generation fence). | ||
| 16 | // | ||
| 17 | // "To" covers every To and Cc address since cache schema v24 (Sprint 95 | ||
| 18 | // S95.1.11); mails synced before that know only their first To address | ||
| 19 | // until they are fetched again. | ||
| 20 | // | ||
| 21 | // Sprint 103 S103.6.2/S103.6.4 (I-9, interaction.md §2.5): requestPerson() | ||
| 22 | // builds the card's history — mails with the person, mails linked to the | ||
| 23 | // card (D-15), events that name the person or invite their addresses, and | ||
| 24 | // the first meeting — upcoming events first, then the past newest first; | ||
| 25 | // "Last seen: <date> · <event>" for an event in the past 365 days. Edits of | ||
| 26 | // the card are never listed. | ||
| 27 | // | ||
| 28 | // Sprint 105 Phase 5 (D-7): mails come from the cache's address index in one | ||
| 29 | // query (MailAddressHistory) on a connection the worker keeps open, with | ||
| 30 | // the old per-address search only while the background backfill runs; the | ||
| 31 | // calendar store stays open on the worker too. The last 64 histories are | ||
| 32 | // kept (stamped with the mail and calendar data_version): a card opens with | ||
| 33 | // its cached items in the same frame and refreshes in the background only | ||
| 34 | // when a stamp moved. | ||
| 35 | class QuickContactMailHistory final : public QObject { | ||
| 36 | 172 | Q_OBJECT | |
| 37 | // [{subject, from, dateText, folder, messageId, sent}] — at most Limit. | ||
| 38 | Q_PROPERTY(QVariantList mails READ mails NOTIFY mailsChanged) | ||
| 39 | Q_PROPERTY(bool loading READ loading NOTIFY mailsChanged) | ||
| 40 | // [{kind ("mail", "linked", "event", "met"), dateText, title, detail, | ||
| 41 | // messageId, upcoming, event: {uid, accountId, calendarPath, startIso}}] | ||
| 42 | Q_PROPERTY(QVariantList items READ items NOTIFY mailsChanged) | ||
| 43 | Q_PROPERTY(QString lastSeen READ lastSeen NOTIFY mailsChanged) | ||
| 44 | public: | ||
| 45 | static constexpr int Limit = 5; | ||
| 46 | static constexpr int MaxAddresses = 4; | ||
| 47 | using DatabasePath = std::function<QString()>; | ||
| 48 | explicit QuickContactMailHistory(DatabasePath databasePath, QObject *parent = nullptr); | ||
| 49 | ~QuickContactMailHistory() override; | ||
| 50 | |||
| 51 | ✗ | QVariantList mails() const { return m_mails; } | |
| 52 | 1607 | QVariantList items() const { return m_items; } | |
| 53 | 453 | QString lastSeen() const { return m_lastSeen; } | |
| 54 | 795 | void setCalendarPath(const QString &path) { m_calendarPath = path; } | |
| 55 | // person: {addresses, uid, met: {date (ISO), text}, links: [{messageId, subject, date}]}; | ||
| 56 | // `limit` 5 on the card, 50 after *Show more*. | ||
| 57 | Q_INVOKABLE void requestPerson(const QVariantMap &person, int limit); | ||
| 58 | // S105.5.5: read a neighbour card's history into the cache (nothing shown). | ||
| 59 | Q_INVOKABLE void prefetchPerson(const QVariantMap &person, int limit); | ||
| 60 | struct History { | ||
| 61 | QVariantList items; | ||
| 62 | QString lastSeen; | ||
| 63 | qint64 mailVersion = -1; // stamps of the data the history was read from | ||
| 64 | qint64 calendarVersion = -1; | ||
| 65 | }; | ||
| 66 | static constexpr int CachedHistories = 64; | ||
| 67 | // The worker's stamps for `mailDatabase`/`calendarDatabase` (data_version). | ||
| 68 | static QPair<qint64, qint64> stamps(const QString &mailDatabase, const QString &calendarDatabase); | ||
| 69 | // Tests: how many histories are cached, and whether the last request was | ||
| 70 | // answered from the cache in the same frame. | ||
| 71 | 1 | int cachedHistoryCount() const { return int(m_cache.size()); } | |
| 72 | 1 | bool lastRequestFromCache() const { return m_lastFromCache; } | |
| 73 | // S105.5.2: one backfill chunk of the address index (worker-safe); true | ||
| 74 | // when the index is complete. | ||
| 75 | static bool backfillAddressIndex(const QString &mailDatabase, int chunk = 5000); | ||
| 76 | // S105.5.2: start the background backfill (chunks at idle pace). | ||
| 77 | void startAddressBackfill(); | ||
| 78 | // Worker-safe. | ||
| 79 | static History readHistory(const QString &mailDatabase, const QString &calendarDatabase, const QVariantMap &person, | ||
| 80 | int limit); | ||
| 81 | 866 | bool loading() const { return m_loading; } | |
| 82 | Q_INVOKABLE void request(const QStringList &addresses); | ||
| 83 | Q_INVOKABLE void clear(); | ||
| 84 | // Worker-safe: the merged, newest-first list for these addresses. | ||
| 85 | static QVariantList read(const QString &databasePath, const QStringList &addresses, int limit = Limit); | ||
| 86 | // The pre-index path (one LIKE search per address and direction); the | ||
| 87 | // fallback while the index is incomplete and the golden reference. | ||
| 88 | static QVariantList readByLike(const QString &databasePath, const QStringList &addresses, int limit = Limit); | ||
| 89 | |||
| 90 | signals: | ||
| 91 | void mailsChanged(); | ||
| 92 | |||
| 93 | private: | ||
| 94 | DatabasePath m_databasePath; | ||
| 95 | QThreadPool m_pool; | ||
| 96 | QVariantList m_mails; | ||
| 97 | QVariantList m_items; | ||
| 98 | QString m_lastSeen; | ||
| 99 | QString m_calendarPath; | ||
| 100 | bool m_loading = false; | ||
| 101 | quint64 m_generation = 0; | ||
| 102 | struct CacheEntry { | ||
| 103 | History history; | ||
| 104 | }; | ||
| 105 | QHash<QString, CacheEntry> m_cache; | ||
| 106 | QStringList m_cacheOrder; // least recently used first | ||
| 107 | bool m_lastFromCache = false; | ||
| 108 | bool m_backfillRunning = false; | ||
| 109 | void remember(const QString &key, const History &history); | ||
| 110 | QString cacheKey(const QVariantMap &person, int limit) const; | ||
| 111 | void scheduleBackfill(int delayMs); | ||
| 112 | }; | ||
| 113 |