data/ContactQueries.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <QHash> | ||
| 4 | #include <QList> | ||
| 5 | #include <QSqlDatabase> | ||
| 6 | #include <QString> | ||
| 7 | #include <QStringList> | ||
| 8 | #include <algorithm> | ||
| 9 | #include <optional> | ||
| 10 | |||
| 11 | // Read-only contact queries (Sprint 97 S97.1.2/S97.2.2): paged lists per | ||
| 12 | // view, letter sections, search, completion and the address index. Works on | ||
| 13 | // a contacts.db connection owned by the caller, so the list worker and the | ||
| 14 | // GUI thread each use their own connection (worker-safe). | ||
| 15 | // | ||
| 16 | // A list row is a card (cardId > 0) or, in "Collected addresses" and | ||
| 17 | // "Frequently contacted", a collected address (cardId == 0). Its key is | ||
| 18 | // "<bookId>/<uid>" or "collected:<address>" (S97.1.7, identity not rows). | ||
| 19 | namespace ContactQueries { | ||
| 20 | |||
| 21 | // S106-310: sort keys in SQLite BINARY order (UTF-8 bytes = code point | ||
| 22 | // order). QString's < compares UTF-16 units, which puts a supplementary | ||
| 23 | // character (surrogates 0xD800…) before U+E000…U+FFFF; at the first | ||
| 24 | // differing unit surrogates move above 0xE000…0xFFFF (ICU's code point | ||
| 25 | // order fix-up). Every in-memory order next to a keyset page uses this. | ||
| 26 | 642 | inline bool sortKeyLess(const QString &a, const QString &b) { | |
| 27 | 1038 | const auto rank = [](char16_t unit) { | |
| 28 |
6/6✓ Branch 2 → 3 taken 52 times.
✓ Branch 2 → 5 taken 986 times.
✓ Branch 3 → 4 taken 22 times.
✓ Branch 3 → 5 taken 30 times.
✓ Branch 5 → 6 taken 30 times.
✓ Branch 5 → 7 taken 986 times.
|
1038 | return unit >= 0xD800 && unit <= 0xDFFF ? int(unit) + 0x2000 : unit >= 0xE000 ? int(unit) - 0x800 : int(unit); |
| 29 | }; | ||
| 30 | 642 | const qsizetype common = std::min(a.size(), b.size()); | |
| 31 |
2/2✓ Branch 19 → 6 taken 1344 times.
✓ Branch 19 → 20 taken 123 times.
|
1467 | for (qsizetype i = 0; i < common; ++i) |
| 32 |
2/2✓ Branch 9 → 10 taken 519 times.
✓ Branch 9 → 18 taken 825 times.
|
1344 | if (a.at(i) != b.at(i)) |
| 33 | 519 | return rank(a.at(i).unicode()) < rank(b.at(i).unicode()); | |
| 34 | 123 | return a.size() < b.size(); | |
| 35 | } | ||
| 36 | |||
| 37 | // Duplicates (S97.5.9): cards that share the folded name or an address with | ||
| 38 | // another card of the same book, in the books shown in "All contacts". | ||
| 39 | // Ungrouped (F100-11): cards of a book that are in no group. | ||
| 40 | enum class ViewKind { All, Frequent, Collected, Book, Group, Duplicates, Ungrouped }; | ||
| 41 | |||
| 42 | struct View { | ||
| 43 | 9 | ViewKind kind = ViewKind::All; | |
| 44 | ✗ | qint64 bookId = 0; // Book, Group | |
| 45 | ✗ | QString group; // Group: CATEGORIES name or the group card's name | |
| 46 | ✗ | bool byFamilyName = false; // "View ▸ Sort by last name" (D-13) | |
| 47 |
1/8✓ Branch 2 → 3 taken 9 times.
✗ Branch 2 → 4 not taken.
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 6 not taken.
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 9 not taken.
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 11 not taken.
|
9 | bool operator==(const View &) const = default; |
| 48 | }; | ||
| 49 | |||
| 50 | struct Row { | ||
| 51 | QString key; | ||
| 52 | qint64 cardId = 0; | ||
| 53 | qint64 bookId = 0; | ||
| 54 | QString uid; | ||
| 55 | QString displayName; | ||
| 56 | QString sortKey; // folded, keyset paging | ||
| 57 | QString section; // "A"…"Z", "#" | ||
| 58 | QString organization; | ||
| 59 | QString primaryAddress; | ||
| 60 | int addressCount = 0; | ||
| 61 | QString bookName; // server display name, "" for the local book | ||
| 62 | QString accountId; | ||
| 63 | bool bookReadOnly = false; | ||
| 64 | bool localBook = false; | ||
| 65 | QString kind; // individual, group, org | ||
| 66 | QString syncState; // "", pending, conflict, legacy | ||
| 67 | bool writable = true; | ||
| 68 | bool hasPhoto = false; | ||
| 69 | QString photoTag; // changes when the photo does (avatar URL) | ||
| 70 | bool collected = false; // a collected address, not a card | ||
| 71 | int useCount = 0; | ||
| 72 | // F100-10: optional list columns. | ||
| 73 | QString phone; | ||
| 74 | QString title; | ||
| 75 | QString locality; | ||
| 76 | QString birthday; // ISO "YYYY-MM-DD" or "--MM-DD" | ||
| 77 | QString groups; // group names, ", "-separated | ||
| 78 | bool operator==(const Row &) const = default; | ||
| 79 | }; | ||
| 80 | |||
| 81 | struct Group { | ||
| 82 | qint64 bookId = 0; | ||
| 83 | QString name; | ||
| 84 | qint64 cardId = 0; // group card (KIND:group / Apple), 0 for a CATEGORIES group | ||
| 85 | int memberCount = 0; | ||
| 86 | }; | ||
| 87 | |||
| 88 | struct Completion { | ||
| 89 | QString key; // card key or "collected:<address>" | ||
| 90 | QString name; | ||
| 91 | QString address; | ||
| 92 | QString bookName; // "" for collected addresses | ||
| 93 | bool collected = false; | ||
| 94 | bool group = false; // S97.5.4: a group that expands into members | ||
| 95 | QStringList members; // group: "Name <address>" of each member | ||
| 96 | int useCount = 0; | ||
| 97 | QString uid; | ||
| 98 | int avatarColor = 0; | ||
| 99 | }; | ||
| 100 | |||
| 101 | struct Lookup { | ||
| 102 | bool known = false; // the address belongs to a card | ||
| 103 | bool collected = false; | ||
| 104 | QString key; | ||
| 105 | QString displayName; | ||
| 106 | QString bookName; | ||
| 107 | qint64 bookId = 0; | ||
| 108 | QString uid; | ||
| 109 | }; | ||
| 110 | |||
| 111 | // Words of `filter` all match the folded search text. | ||
| 112 | int count(QSqlDatabase db, const View &view, const QString &filter); | ||
| 113 | // F-61: afterRowKey is the collected rows' tie-break (their cardId is 0). | ||
| 114 | QList<Row> page(QSqlDatabase db, const View &view, const QString &filter, int limit, const QString &afterKey = {}, | ||
| 115 | qint64 afterId = 0, const QString &afterRowKey = {}); | ||
| 116 | // Rows of the given card ids in this view (incremental updates); a card | ||
| 117 | // that no longer belongs to the view is missing from the result. | ||
| 118 | QList<Row> rowsForCards(QSqlDatabase db, const View &view, const QString &filter, const QList<qint64> &cardIds); | ||
| 119 | // Sidebar counts: book id → card count, plus the smart lists. | ||
| 120 | QHash<qint64, int> bookCounts(QSqlDatabase db); | ||
| 121 | // F100-11: per book, the cards in no group (books with groups only). | ||
| 122 | QHash<qint64, int> ungroupedCounts(QSqlDatabase db); | ||
| 123 | int allCount(QSqlDatabase db); | ||
| 124 | int frequentCount(QSqlDatabase db); | ||
| 125 | int collectedCount(QSqlDatabase db); | ||
| 126 | QList<Group> groups(QSqlDatabase db, qint64 bookId = 0); | ||
| 127 | // Recipient completion (usage first, then name); one suggestion per address. | ||
| 128 | // Groups whose name starts with the fragment come first (S97.5.4) and carry | ||
| 129 | // their members. | ||
| 130 | QList<Completion> completions(QSqlDatabase db, const QString &fragment, int limit = 8); | ||
| 131 | // "Name <address>" (preferred address) of every member of a group | ||
| 132 | // (CATEGORIES name or group card) in one book; members without an address | ||
| 133 | // are skipped. | ||
| 134 | QStringList groupMembers(QSqlDatabase db, qint64 bookId, const QString &group); | ||
| 135 | // Card keys of a group's members (CATEGORIES name or group card), and the | ||
| 136 | // group card's own key ("" for a CATEGORIES-only group). | ||
| 137 | QStringList groupMemberKeys(QSqlDatabase db, qint64 bookId, const QString &group); | ||
| 138 | QString groupCardKey(QSqlDatabase db, qint64 bookId, const QString &group); | ||
| 139 | // S97.5.9: other cards that are probably the same person — same address or | ||
| 140 | // the same folded display name — in any book (sameBook tells them apart). | ||
| 141 | struct Duplicate { | ||
| 142 | QString key; | ||
| 143 | QString displayName; | ||
| 144 | QString bookName; | ||
| 145 | bool sameBook = false; | ||
| 146 | }; | ||
| 147 | QList<Duplicate> duplicatesOf(QSqlDatabase db, const QString &cardKey); | ||
| 148 | Lookup lookup(QSqlDatabase db, const QString &address); | ||
| 149 | // Same-address duplicates inside one book (S97.1.8): card keys that already | ||
| 150 | // use the address there, excluding `exceptKey`. | ||
| 151 | QList<Lookup> sameBookOwners(QSqlDatabase db, qint64 bookId, const QString &address, const QString &exceptKey = {}); | ||
| 152 | // Sprint 108 S108.5.3: every address of a card, the preferred one first, | ||
| 153 | // then in the card's order (the address book sheet asks which one). | ||
| 154 | QStringList cardAddresses(QSqlDatabase db, qint64 cardId); | ||
| 155 | QString cardKey(qint64 bookId, const QString &uid); | ||
| 156 | bool splitKey(const QString &key, qint64 *bookId, QString *uid); | ||
| 157 | |||
| 158 | } // namespace ContactQueries | ||
| 159 |