MailJDTourDownloadDocsQualityTestsCoverage

Which source lines and branches the tests execute. Lines 91.5 %, branches 75.0 % (minimum 85 % and 72 %). main @ 399ffe3d · 21 October 2026 · pipeline 7238.

GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 100.0% 1 / 0 / 1
Functions: 100.0% 1 / 0 / 1
Branches: -% 0 / 0 / 0

data/ContactStore.h
Line Branch Exec Source
1 #pragma once
2
3 #include <QHash>
4 #include <QList>
5 #include <QObject>
6 #include <QSqlDatabase>
7 #include <functional>
8 #include <optional>
9
10 #include "data/ConflictHistory.h"
11 #include "data/ContactModels.h"
12
13 // Card-centric contact storage (Sprint 97 S97.1.2, CONTACT-SCHEMA, D-1).
14 //
15 // contacts.db (separate from the mail cache, cross-account) holds, from
16 // schema version 1 on (PRAGMA user_version):
17 // address_books one row per CardDAV book and the local book
18 // cards one row per vCard: raw card, server base for merges,
19 // href/ETag, derived sort/search columns, sync state
20 // card_addresses the address index (lower-case key → card)
21 // card_groups CATEGORIES group names per card
22 // card_members member URIs of group cards (KIND:group / Apple)
23 // collected_addresses addresses written to that are in no address book
24 // address_usage send counts per address (completion ranking)
25 // contact_outbox queued write-backs (ContactOutbox)
26 // contact_meta small flags (first-start notice, …)
27 // card_relations, card_places, card_social, card_mail_links, place_cache,
28 // graph_dismissed v6 people rows and caches (ContactPeopleStore.h)
29 //
30 // First start of the new schema (D-5): server rows of the old one-row-per-
31 // address table are not converted — they become read-only "legacy" cards of
32 // their book, shown until the book's first complete fetch replaces them;
33 // usage counts move to address_usage; rows that only came from sending
34 // mail become collected addresses; rows the user created locally become
35 // cards of the local book and are listed for the "only on this computer"
36 // notice. The old table is dropped once no legacy card is left. The
37 // database is copied to contacts.db.pre-v1 before the conversion.
38 //
39 // Reads that only list or search live in ContactQueries; collected
40 // addresses and usage in CollectedAddressStore. Both work on database().
41 class ContactStore : public QObject {
42 Q_OBJECT
43
44 public:
45 static constexpr int SchemaVersion = 8; // v6: Sprint 103 people rows (ContactPeopleStore); v7: cards.anniversary;
46 // v8: Sprint 107 conflict_history, contact_outbox.base_raw
47
48 explicit ContactStore(QObject *parent = nullptr);
49 ~ContactStore() override;
50
51 bool open(const QString &dbPath);
52 void close();
53 bool isOpen() const;
54 29269 QSqlDatabase database() const { return m_db; }
55 bool beginTransaction();
56 bool commitTransaction();
57 void rollbackTransaction();
58
59 // ─── address books ───
60 // Creates the book row when missing; returns its id (0 on error).
61 qint64 ensureBook(const QString &accountId, const QString &path);
62 qint64 localBookId();
63 QList<AddressBookInfo> books() const;
64 std::optional<AddressBookInfo> book(qint64 id) const;
65 std::optional<AddressBookInfo> book(const QString &accountId, const QString &path) const;
66 // Writes the server-side properties and the sync state of a book.
67 bool updateBook(const AddressBookInfo &book);
68 bool setBookFlags(qint64 id, bool showInAll, bool useForCompletion);
69 // Removes a book and its cards (book deselected in the settings).
70 bool removeBook(qint64 id);
71
72 // ─── cards ───
73 std::optional<StoredCard> card(qint64 bookId, const QString &uid) const;
74 // Sprint 107 D-22: the books holding a card with this UID (a moved card).
75 QList<qint64> booksWithCard(const QString &uid) const;
76 std::optional<StoredCard> cardById(qint64 id) const;
77 // Every card of a book (S97.5.8 export), stored order.
78 QList<StoredCard> cards(qint64 bookId) const;
79 std::optional<StoredCard> cardByHref(qint64 bookId, const QString &href) const;
80 // href → ETag of the non-legacy cards of a book (sync comparison).
81 QHash<QString, QString> cardEtags(qint64 bookId) const;
82 // Inserts or replaces the card (book, UID); recomputes the derived
83 // columns and the address/group index. Returns the card id or 0.
84 qint64 putCard(const StoredCard &card);
85 bool removeCard(qint64 bookId, const QString &uid);
86 // The UID of the card at href (empty when none).
87 QString uidForHref(qint64 bookId, const QString &href) const;
88 // Removes the D-5 legacy cards of a book after its first complete fetch.
89 int removeLegacyCards(qint64 bookId);
90 bool setSyncState(qint64 bookId, const QString &uid, const QString &state);
91 // A new card in the local book ("On this computer"); returns its id.
92 qint64 addLocalCard(const ContactCard &card);
93
94 // ─── URI photos (S97.5.7, D-15) ───
95 // Cards of a book whose PHOTO is a URI that was not tried yet (or changed
96 // since): {cardId, uid, href, uri}, at most `limit`.
97 struct PendingPhoto {
98 qint64 cardId = 0;
99 QString uid, href, uri;
100 };
101 QList<PendingPhoto> pendingPhotos(qint64 bookId, int limit) const;
102 // Records the attempt for `uri`: the image bytes, or empty when the URI
103 // is foreign or the fetch failed (not tried again until the URI changes).
104 bool setFetchedPhoto(qint64 cardId, const QString &uri, const QByteArray &bytes);
105 // The fetched bytes of the card's current photo URI (empty when none).
106 QByteArray fetchedPhoto(qint64 bookId, const QString &uid) const;
107
108 // ─── outbox ───
109 // Adds the entry of (book, UID) or coalesces it into the queued one
110 // (revision + 1, keeps the base ETag of the first change). Returns its id.
111 qint64 enqueueOutbox(qint64 bookId, const QString &uid, const QString &operation, const QString &href,
112 const QString &baseEtag, qint64 dependsOn = 0);
113 QList<ContactOutboxEntry> outboxEntries() const;
114 // Sprint 107 D-21: the losing side of conflict resolutions (ConflictHistory).
115 bool addConflictHistory(const ConflictHistory::Entry &entry, qint64 now);
116 QList<ConflictHistory::Entry> conflictHistory(int limit = ConflictHistory::kLimit) const;
117 std::optional<ConflictHistory::Entry> conflictHistoryEntry(qint64 id) const;
118 bool removeConflictHistory(qint64 id);
119 std::optional<ContactOutboxEntry> outboxEntry(qint64 bookId, const QString &uid) const;
120 std::optional<ContactOutboxEntry> outboxEntryById(qint64 id) const;
121 bool updateOutbox(const ContactOutboxEntry &entry);
122 // Removes the entry; with revision >= 0 only while it still has that
123 // revision (a newer local change keeps it). True when removed.
124 bool removeOutbox(qint64 bookId, const QString &uid, int revision = -1);
125
126 // ─── meta ───
127 QString meta(const QString &key) const;
128 bool setMeta(const QString &key, const QString &value);
129
130 // ─── legacy (D-5) ───
131 // Cards of the local book created before Sprint 97 ("Local contacts")
132 // that wait for the user's choice (upload or keep).
133 QList<qint64> pendingLocalOnlyCards() const;
134 void clearPendingLocalOnlyCards();
135 // Drops the old table when no legacy card is left.
136 void dropLegacyTableWhenDone();
137 // F103-27: the placeholders of old rows that named no account or book live
138 // in the internal "legacy" book, which no sync ever fetches. Once every
139 // server book was fetched, a placeholder whose addresses all exist on real
140 // cards (or, without addresses, whose name does) is removed; the others
141 // become local contacts (editable, offered for upload like the other
142 // local-only cards) and the legacy book goes. Counts for the log and tests.
143 struct LegacyRetirement {
144 int removed = 0;
145 int moved = 0;
146 bool waiting = false; // a server book was not fetched yet
147 };
148 LegacyRetirement retireLegacyBook();
149
150 private:
151 bool inSavepoint(const std::function<bool()> &write);
152 qint64 writeCard(const StoredCard &card); // putCard() inside its savepoint
153 bool createSchema();
154 bool addMissingColumns();
155 // Adds a derived `cards` column and fills it once from the stored cards.
156 bool addDerivedColumn(const QString &column, const std::function<QString(const ContactCard &)> &value);
157 bool convertLegacyContacts();
158 bool writeDerived(qint64 cardId, const QString &raw);
159
160 QSqlDatabase m_db;
161 QString m_connectionName;
162 QString m_dbPath;
163 };
164