data/DavPushStore.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <QByteArray> | ||
| 4 | #include <QHash> | ||
| 5 | #include <QList> | ||
| 6 | #include <QSqlDatabase> | ||
| 7 | #include <QString> | ||
| 8 | #include <optional> | ||
| 9 | |||
| 10 | #include "DavPushModels.h" | ||
| 11 | |||
| 12 | // Sprint 96 S96.1.3: local, profile-bound DAV-Push metadata in | ||
| 13 | // <profile>/dav-push.sqlite (ADR-96-4). Never synchronised between | ||
| 14 | // installations; secrets (channel, private key, auth secret, provider | ||
| 15 | // token) are NOT stored here — only keyring references. | ||
| 16 | // | ||
| 17 | // A persisted registration is historical metadata: after a restart it is | ||
| 18 | // renewed/checked before anything is shown as "connected". | ||
| 19 | struct DavPushCollectionRecord { | ||
| 20 | QString accountId; | ||
| 21 | QString path; | ||
| 22 | DavPushOffer offer; | ||
| 23 | qint64 checkedAt = 0; // last completed capability check (epoch s) | ||
| 24 | QString registrationUrl; // confirmed Location ("" = none); confidential | ||
| 25 | QString registrationReceiver; // receiver id the registration points to | ||
| 26 | QString registrationEndpoint; // hash of the push endpoint registered ("" = unknown, schema v1) | ||
| 27 | qint64 expiresAt = 0; // confirmed Expires (epoch s) | ||
| 28 | qint64 registeredAt = 0; | ||
| 29 | qint64 renewedAt = 0; | ||
| 30 | QString lastError; // redacted technical reason | ||
| 31 | qint64 lastErrorAt = 0; | ||
| 32 | qint64 nextRetryAt = 0; | ||
| 33 | int failures = 0; | ||
| 34 | qint64 lastPushAt = 0; // last valid, mapped push message | ||
| 35 | qint64 lastSyncAt = 0; // last complete data sync of this collection | ||
| 36 | }; | ||
| 37 | |||
| 38 | struct DavPushReceiverRecord { | ||
| 39 | QString accountId; | ||
| 40 | QString receiverId; // random; names the keyring entry | ||
| 41 | QString provider; // "ntfy" | ||
| 42 | QString server; // https origin of the push service | ||
| 43 | qint64 createdAt = 0; | ||
| 44 | int generation = 0; // incremented on every replacement | ||
| 45 | }; | ||
| 46 | |||
| 47 | struct DavPushCleanupEntry { | ||
| 48 | qint64 id = 0; | ||
| 49 | QString accountId; | ||
| 50 | QString registrationUrl; | ||
| 51 | qint64 createdAt = 0; | ||
| 52 | int attempts = 0; | ||
| 53 | }; | ||
| 54 | |||
| 55 | class DavPushStore { | ||
| 56 | public: | ||
| 57 | // v2 (2026-10-04): collections.endpoint_hash for push services whose | ||
| 58 | // endpoint can change (UnifiedPush distributors). | ||
| 59 | static constexpr int SchemaVersion = 2; | ||
| 60 | // Bounded history (SPRINT-96.md §5.3). | ||
| 61 | static constexpr int kMaxLatencySamples = 256; | ||
| 62 | static constexpr int kMaxEvents = 50; | ||
| 63 | static constexpr int kHourlyRetentionHours = 7 * 24; | ||
| 64 | static constexpr int kMaxCleanupEntries = 200; | ||
| 65 | static constexpr int kMaxCleanupAttempts = 10; // S106-078 | ||
| 66 | |||
| 67 | 868 | DavPushStore() = default; | |
| 68 | ~DavPushStore(); | ||
| 69 | DavPushStore(const DavPushStore &) = delete; | ||
| 70 | DavPushStore &operator=(const DavPushStore &) = delete; | ||
| 71 | |||
| 72 | bool open(const QString &path); | ||
| 73 | // One transaction around a batch of writes (statistics flush): one | ||
| 74 | // commit instead of one per statement. | ||
| 75 | bool beginTransaction(); | ||
| 76 | bool commitTransaction(); | ||
| 77 | void rollbackTransaction(); | ||
| 78 | 5364 | bool isOpen() const { return m_db.isOpen(); } | |
| 79 | 3 | QString lastError() const { return m_lastError; } | |
| 80 | |||
| 81 | // Random per-profile installation id (names keyring entries). | ||
| 82 | QString installationId(); | ||
| 83 | |||
| 84 | // --- collections --- | ||
| 85 | std::optional<DavPushCollectionRecord> collection(const QString &accountId, const QString &path) const; | ||
| 86 | QList<DavPushCollectionRecord> collections(const QString &accountId = {}) const; | ||
| 87 | bool saveCollection(const DavPushCollectionRecord &record); | ||
| 88 | bool removeCollection(const QString &accountId, const QString &path); | ||
| 89 | |||
| 90 | // --- receivers --- | ||
| 91 | std::optional<DavPushReceiverRecord> receiver(const QString &accountId) const; | ||
| 92 | bool saveReceiver(const DavPushReceiverRecord &record); | ||
| 93 | bool removeReceiver(const QString &accountId); | ||
| 94 | QStringList receiverAccounts() const; | ||
| 95 | |||
| 96 | // --- registrations waiting for an unsubscribe (bounded) --- | ||
| 97 | bool queueCleanup(const QString &accountId, const QString ®istrationUrl, qint64 now); | ||
| 98 | QList<DavPushCleanupEntry> cleanupEntries(const QString &accountId = {}) const; | ||
| 99 | bool updateCleanupAttempt(qint64 id); | ||
| 100 | bool removeCleanup(qint64 id); | ||
| 101 | bool removeCleanupForAccount(const QString &accountId); | ||
| 102 | |||
| 103 | // --- statistics (scope = "account:<id>" or "collection:<key>") --- | ||
| 104 | bool addCounter(const QString &scope, const QString &name, qint64 delta, qint64 now); | ||
| 105 | qint64 counter(const QString &scope, const QString &name) const; | ||
| 106 | // Sum of the hourly aggregates of `name` with hour >= since. | ||
| 107 | qint64 periodSum(const QString &scope, const QString &name, qint64 since) const; | ||
| 108 | bool addLatency(const QString &scope, const QString &kind, qint64 millis, qint64 now); | ||
| 109 | QList<qint64> latencies(const QString &scope, const QString &kind) const; | ||
| 110 | bool addEvent(const QString &scope, const QString &kind, const QString &detail, qint64 now); | ||
| 111 | struct Event { qint64 at = 0; QString kind; QString detail; }; | ||
| 112 | QList<Event> events(const QString &scope) const; | ||
| 113 | // Local reset: counters, hourly aggregates, samples and events of the | ||
| 114 | // scope. Registrations, tokens and credentials are untouched. | ||
| 115 | bool resetStatistics(const QString &scope, qint64 now); | ||
| 116 | qint64 statisticsSince(const QString &scope) const; | ||
| 117 | bool pruneHourly(qint64 now); | ||
| 118 | |||
| 119 | private: | ||
| 120 | bool createSchema(); | ||
| 121 | bool exec(const QString &sql, const QVariantList &values = {}) const; | ||
| 122 | |||
| 123 | QSqlDatabase m_db; | ||
| 124 | QString m_connectionName; | ||
| 125 | mutable QString m_lastError; | ||
| 126 | }; | ||
| 127 |