presentation/QuickContactActionsFamilies.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // Families are one vCard each (2026-10-07, replaces F103-21's families by | ||
| 2 | // name): a family's own entry carries X-MAILJD-KIND:family | ||
| 3 | // (ContactCard::isFamily) and its members as RELATED member lines — the only | ||
| 4 | // place a membership is stored. A person's editor still offers *Belongs to | ||
| 5 | // family*: a family chosen or typed there is written onto the family's entry, | ||
| 6 | // which is created on the spot when no family of that name exists. A family's | ||
| 7 | // entry has no relationships besides its members. migrateFamilies() converts | ||
| 8 | // older data once: member-of lines on people move onto the family's entry | ||
| 9 | // (created for a family that had a name only), and entries that list members | ||
| 10 | // without the marker get it. Same class as QuickContactActions.cpp. | ||
| 11 | |||
| 12 | #include "QuickContactActions.h" | ||
| 13 | |||
| 14 | #include "app/core/ContactOutbox.h" | ||
| 15 | #include "app/core/ContactSyncCoordinator.h" | ||
| 16 | #include "data/ContactQueries.h" | ||
| 17 | #include "data/ContactStore.h" | ||
| 18 | #include "data/RelationVocabulary.h" | ||
| 19 | #include "data/VCardCodec.h" | ||
| 20 | |||
| 21 | #include <QLoggingCategory> | ||
| 22 | #include <QScopedValueRollback> | ||
| 23 | #include <QSqlQuery> | ||
| 24 | |||
| 25 |
4/7✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 8 taken 7 times.
✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 2 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
|
9 | Q_LOGGING_CATEGORY(lcContactFamilies, "mailjd.contacts.families") |
| 26 | |||
| 27 | namespace { | ||
| 28 | |||
| 29 | ✗ | QVariantMap failure(const QString &error) { return {{QStringLiteral("ok"), false}, {QStringLiteral("error"), error}}; } | |
| 30 | |||
| 31 | 17 | bool isMemberOf(const QVariantMap &entry) { | |
| 32 |
3/3✓ Branch 7 → 8 taken 17 times.
✓ Branch 8 → 9 taken 17 times.
✓ Branch 9 → 10 taken 17 times.
|
34 | return RelationVocabulary::canonical(entry.value(QStringLiteral("type")).toString()) == QLatin1String("member-of"); |
| 33 | } | ||
| 34 | |||
| 35 | } // namespace | ||
| 36 | |||
| 37 | 65 | bool QuickContactActions::isFamily(const QString &key) const { | |
| 38 |
1/1✓ Branch 2 → 3 taken 65 times.
|
65 | ContactSyncCoordinator *sync = m_coordinator(); |
| 39 | 65 | qint64 bookId = 0; | |
| 40 | 65 | QString uid; | |
| 41 |
6/7✓ Branch 3 → 4 taken 65 times.
✗ Branch 3 → 6 not taken.
✓ Branch 4 → 5 taken 65 times.
✓ Branch 5 → 6 taken 2 times.
✓ Branch 5 → 7 taken 63 times.
✓ Branch 8 → 9 taken 2 times.
✓ Branch 8 → 10 taken 63 times.
|
65 | if (!sync || !ContactQueries::splitKey(key, &bookId, &uid)) |
| 42 | 2 | return false; | |
| 43 |
2/2✓ Branch 11 → 12 taken 63 times.
✓ Branch 12 → 13 taken 63 times.
|
63 | QSqlQuery q(sync->store()->database()); |
| 44 |
1/1✓ Branch 17 → 18 taken 63 times.
|
63 | q.prepare(QStringLiteral("SELECT kind FROM cards WHERE book_id = ? AND uid = ?")); |
| 45 |
1/1✓ Branch 22 → 23 taken 63 times.
|
63 | q.addBindValue(bookId); |
| 46 |
1/1✓ Branch 26 → 27 taken 63 times.
|
63 | q.addBindValue(uid); |
| 47 |
10/18✓ Branch 28 → 29 taken 63 times.
✓ Branch 29 → 30 taken 63 times.
✗ Branch 29 → 38 not taken.
✓ Branch 30 → 31 taken 63 times.
✓ Branch 31 → 32 taken 63 times.
✗ Branch 31 → 38 not taken.
✓ Branch 33 → 34 taken 63 times.
✓ Branch 34 → 35 taken 63 times.
✓ Branch 36 → 37 taken 6 times.
✓ Branch 36 → 38 taken 57 times.
✓ Branch 40 → 41 taken 63 times.
✗ Branch 40 → 42 not taken.
✓ Branch 42 → 43 taken 63 times.
✗ Branch 42 → 44 not taken.
✗ Branch 65 → 66 not taken.
✗ Branch 65 → 67 not taken.
✗ Branch 69 → 70 not taken.
✗ Branch 69 → 71 not taken.
|
63 | return q.exec() && q.next() && q.value(0).toString() == QLatin1String("family"); |
| 48 | 65 | } | |
| 49 | |||
| 50 | 70 | QVariantMap QuickContactActions::takeFamilyJoins(const QVariantMap &fields, QList<FamilyJoin> *joins) const { | |
| 51 |
3/3✓ Branch 5 → 6 taken 70 times.
✓ Branch 8 → 9 taken 39 times.
✓ Branch 8 → 10 taken 31 times.
|
70 | if (!fields.contains(QStringLiteral("relations"))) |
| 52 | 39 | return fields; | |
| 53 | 31 | QVariantList own; | |
| 54 |
6/6✓ Branch 14 → 15 taken 31 times.
✓ Branch 15 → 16 taken 31 times.
✓ Branch 20 → 21 taken 31 times.
✓ Branch 21 → 22 taken 31 times.
✓ Branch 118 → 23 taken 17 times.
✓ Branch 118 → 119 taken 31 times.
|
79 | for (const QVariant &item : fields.value(QStringLiteral("relations")).toList()) { |
| 55 |
1/1✓ Branch 24 → 25 taken 17 times.
|
17 | const QVariantMap entry = item.toMap(); |
| 56 | // A membership shown from the family's entry (mirrorOf) stays there; a | ||
| 57 | // read-only line of another program stays on the person. | ||
| 58 |
22/36✓ Branch 25 → 26 taken 17 times.
✓ Branch 26 → 27 taken 7 times.
✓ Branch 26 → 39 taken 10 times.
✓ Branch 30 → 31 taken 7 times.
✓ Branch 31 → 32 taken 7 times.
✗ Branch 31 → 39 not taken.
✓ Branch 36 → 37 taken 7 times.
✓ Branch 37 → 38 taken 7 times.
✗ Branch 38 → 39 not taken.
✓ Branch 38 → 40 taken 7 times.
✓ Branch 41 → 42 taken 7 times.
✓ Branch 41 → 43 taken 10 times.
✓ Branch 43 → 44 taken 7 times.
✓ Branch 43 → 45 taken 10 times.
✓ Branch 45 → 46 taken 7 times.
✓ Branch 45 → 47 taken 10 times.
✓ Branch 47 → 48 taken 7 times.
✓ Branch 47 → 49 taken 10 times.
✓ Branch 49 → 50 taken 7 times.
✓ Branch 49 → 51 taken 10 times.
✓ Branch 51 → 52 taken 7 times.
✓ Branch 51 → 53 taken 10 times.
✓ Branch 53 → 54 taken 10 times.
✓ Branch 53 → 58 taken 7 times.
✗ Branch 152 → 153 not taken.
✗ Branch 152 → 154 not taken.
✗ Branch 156 → 157 not taken.
✗ Branch 156 → 158 not taken.
✗ Branch 160 → 161 not taken.
✗ Branch 160 → 162 not taken.
✗ Branch 164 → 165 not taken.
✗ Branch 164 → 166 not taken.
✗ Branch 168 → 169 not taken.
✗ Branch 168 → 170 not taken.
✗ Branch 172 → 173 not taken.
✗ Branch 172 → 174 not taken.
|
31 | if (!isMemberOf(entry) || entry.contains(QStringLiteral("mirrorOf")) || entry.value(QStringLiteral("readOnly")).toBool()) { |
| 59 |
1/1✓ Branch 55 → 56 taken 10 times.
|
10 | own.append(entry); |
| 60 | 10 | continue; | |
| 61 | } | ||
| 62 | 7 | FamilyJoin join; | |
| 63 |
2/2✓ Branch 62 → 63 taken 7 times.
✓ Branch 63 → 64 taken 7 times.
|
14 | join.familyKey = entry.value(QStringLiteral("otherKey")).toString(); |
| 64 |
3/3✓ Branch 74 → 75 taken 7 times.
✓ Branch 75 → 76 taken 7 times.
✓ Branch 76 → 77 taken 7 times.
|
14 | join.name = entry.value(QStringLiteral("targetName")).toString().simplified(); |
| 65 |
1/2✗ Branch 85 → 86 not taken.
✓ Branch 85 → 101 taken 7 times.
|
7 | if (join.name.isEmpty()) |
| 66 | ✗ | join.name = entry.value(QStringLiteral("shownName")).toString().simplified(); | |
| 67 |
4/6✓ Branch 102 → 103 taken 6 times.
✓ Branch 102 → 105 taken 1 time.
✓ Branch 104 → 105 taken 6 times.
✗ Branch 104 → 106 not taken.
✓ Branch 107 → 108 taken 7 times.
✗ Branch 107 → 109 not taken.
|
7 | if (!join.familyKey.isEmpty() || !join.name.isEmpty()) |
| 68 |
1/1✓ Branch 108 → 109 taken 7 times.
|
7 | joins->append(join); |
| 69 | 48 | } | |
| 70 | 31 | QVariantMap out = fields; | |
| 71 |
1/1✓ Branch 125 → 126 taken 31 times.
|
62 | out.insert(QStringLiteral("relations"), own); |
| 72 | 31 | return out; | |
| 73 | 31 | } | |
| 74 | |||
| 75 | 8 | QString QuickContactActions::ensureFamily(const QString &name, qint64 bookId, QString *error) { | |
| 76 |
1/1✓ Branch 2 → 3 taken 8 times.
|
8 | ContactSyncCoordinator *sync = m_coordinator(); |
| 77 |
1/1✓ Branch 3 → 4 taken 8 times.
|
8 | const QString wanted = name.simplified(); |
| 78 |
3/6✓ Branch 4 → 5 taken 8 times.
✗ Branch 4 → 7 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 8 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 8 times.
|
8 | if (!sync || wanted.isEmpty()) |
| 79 | ✗ | return {}; | |
| 80 | // The family of that name: in the person's book first, else anywhere — | ||
| 81 | // but only in a book that can be written (S106-267: a same-named family in | ||
| 82 | // a read-only book was chosen, and adding the member then failed). | ||
| 83 |
2/2✓ Branch 11 → 12 taken 2 times.
✓ Branch 11 → 37 taken 6 times.
|
8 | if (m_familyIndex) { |
| 84 |
1/1✓ Branch 12 → 13 taken 2 times.
|
2 | const QList<QPair<qint64, QString>> named = m_familyIndex->value(wanted.toCaseFolded()); |
| 85 |
2/2✓ Branch 25 → 17 taken 1 time.
✓ Branch 25 → 26 taken 1 time.
|
2 | for (const auto &[book, key] : named) |
| 86 |
1/2✓ Branch 20 → 21 taken 1 time.
✗ Branch 20 → 23 not taken.
|
1 | if (book == bookId) |
| 87 | 1 | return key; | |
| 88 |
1/2✗ Branch 27 → 28 not taken.
✓ Branch 27 → 31 taken 1 time.
|
1 | if (!named.isEmpty()) |
| 89 | ✗ | return named.first().second; | |
| 90 |
2/2✓ Branch 33 → 34 taken 1 time.
✓ Branch 33 → 36 taken 1 time.
|
2 | } else { |
| 91 |
2/2✓ Branch 38 → 39 taken 6 times.
✓ Branch 39 → 40 taken 6 times.
|
6 | QSqlQuery q(sync->store()->database()); |
| 92 |
1/1✓ Branch 44 → 45 taken 6 times.
|
6 | q.prepare(QStringLiteral("SELECT c.book_id, c.uid, c.display_name FROM cards c " |
| 93 | "JOIN address_books b ON b.id = c.book_id " | ||
| 94 | "WHERE c.kind = 'family' AND b.read_only = 0 ORDER BY c.book_id <> ?, c.id")); | ||
| 95 |
1/1✓ Branch 49 → 50 taken 6 times.
|
6 | q.addBindValue(bookId); |
| 96 |
2/3✓ Branch 51 → 52 taken 6 times.
✓ Branch 52 → 53 taken 6 times.
✗ Branch 52 → 74 not taken.
|
6 | if (q.exec()) |
| 97 |
3/3✓ Branch 72 → 73 taken 11 times.
✓ Branch 73 → 54 taken 7 times.
✓ Branch 73 → 74 taken 4 times.
|
11 | while (q.next()) |
| 98 |
5/5✓ Branch 54 → 55 taken 7 times.
✓ Branch 55 → 56 taken 7 times.
✓ Branch 56 → 57 taken 7 times.
✓ Branch 61 → 62 taken 2 times.
✓ Branch 61 → 72 taken 5 times.
|
7 | if (q.value(2).toString().simplified().compare(wanted, Qt::CaseInsensitive) == 0) |
| 99 |
5/5✓ Branch 62 → 63 taken 2 times.
✓ Branch 63 → 64 taken 2 times.
✓ Branch 64 → 65 taken 2 times.
✓ Branch 65 → 66 taken 2 times.
✓ Branch 66 → 67 taken 2 times.
|
2 | return ContactQueries::cardKey(q.value(0).toLongLong(), q.value(1).toString()); |
| 100 | 6 | } | |
| 101 |
1/1✓ Branch 80 → 81 taken 5 times.
|
5 | ContactCard family; |
| 102 | 5 | family.kind = QStringLiteral("family"); | |
| 103 | 5 | family.formattedName = wanted; | |
| 104 |
1/1✓ Branch 89 → 90 taken 5 times.
|
5 | const QString key = sync->outbox()->createCard(bookId, family, error); |
| 105 |
1/2✗ Branch 91 → 92 not taken.
✓ Branch 91 → 93 taken 5 times.
|
5 | if (key.isEmpty()) |
| 106 | ✗ | return {}; | |
| 107 |
1/1✓ Branch 98 → 99 taken 5 times.
|
5 | pushUndo(tr("Undo new family"), {QStringLiteral("created"), {}, key, 0}); |
| 108 |
2/2✓ Branch 102 → 103 taken 1 time.
✓ Branch 102 → 110 taken 4 times.
|
5 | if (m_familyIndex) |
| 109 |
3/3✓ Branch 103 → 104 taken 1 time.
✓ Branch 104 → 105 taken 1 time.
✓ Branch 106 → 107 taken 1 time.
|
1 | (*m_familyIndex)[wanted.toCaseFolded()].append({bookId, key}); |
| 110 |
6/6✓ Branch 110 → 111 taken 5 times.
✓ Branch 114 → 115 taken 5 times.
✓ Branch 115 → 116 taken 5 times.
✓ Branch 116 → 117 taken 5 times.
✓ Branch 120 → 112 taken 5 times.
✓ Branch 120 → 121 taken 5 times.
|
10 | qCInfo(lcContactFamilies) << "Family entry created on the spot in book" << bookId; |
| 111 | 5 | return key; | |
| 112 | 18 | } | |
| 113 | |||
| 114 | 12 | QVariantMap QuickContactActions::addMember(const QString &familyKey, const QString &personKey) { | |
| 115 |
1/1✓ Branch 2 → 3 taken 12 times.
|
12 | ContactSyncCoordinator *sync = m_coordinator(); |
| 116 | 12 | qint64 bookId = 0; | |
| 117 | 12 | QString uid; | |
| 118 |
4/7✓ Branch 3 → 4 taken 12 times.
✗ Branch 3 → 6 not taken.
✓ Branch 4 → 5 taken 12 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 12 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 14 taken 12 times.
|
12 | if (!sync || !ContactQueries::splitKey(personKey, &bookId, &uid)) |
| 119 | ✗ | return failure(tr("The contact no longer exists.")); | |
| 120 |
3/3✓ Branch 14 → 15 taken 12 times.
✓ Branch 19 → 20 taken 12 times.
✓ Branch 20 → 21 taken 12 times.
|
24 | const QString name = personEntry(personKey).value(QStringLiteral("name")).toString(); |
| 121 | 12 | return editPeople( | |
| 122 | familyKey, | ||
| 123 | 24 | [&](ContactCard *card) -> QString { | |
| 124 | 12 | card->kind = QStringLiteral("family"); | |
| 125 |
2/2✓ Branch 27 → 11 taken 6 times.
✓ Branch 27 → 28 taken 12 times.
|
18 | for (const ContactRelation &relation : std::as_const(card->relations)) |
| 126 |
5/11✓ Branch 13 → 14 taken 6 times.
✓ Branch 15 → 16 taken 6 times.
✗ Branch 15 → 19 not taken.
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 19 taken 6 times.
✓ Branch 20 → 21 taken 6 times.
✗ Branch 20 → 22 not taken.
✗ Branch 22 → 23 not taken.
✓ Branch 22 → 25 taken 6 times.
✗ Branch 45 → 46 not taken.
✗ Branch 45 → 47 not taken.
|
12 | if (RelationVocabulary::canonical(relation.type) == QLatin1String("member") && |
| 127 | 6 | relation.targetUid.compare(uid, Qt::CaseInsensitive) == 0) | |
| 128 | ✗ | return {}; // already a member (the marker may still be new) | |
| 129 |
1/2✗ Branch 29 → 30 not taken.
✓ Branch 29 → 32 taken 12 times.
|
12 | if (card->relations.size() >= VCardCodec::MaxRelations) |
| 130 | ✗ | return tr("This family has too many members."); | |
| 131 | 12 | ContactRelation member; | |
| 132 | 12 | member.type = QStringLiteral("member"); | |
| 133 | 12 | member.targetUid = uid; | |
| 134 | 12 | member.targetName = name; | |
| 135 |
1/1✓ Branch 40 → 41 taken 12 times.
|
12 | card->relations.append(member); |
| 136 | 12 | return {}; | |
| 137 | 12 | }, | |
| 138 |
2/2✓ Branch 26 → 27 taken 12 times.
✓ Branch 28 → 29 taken 12 times.
|
36 | tr("Added to the family")); |
| 139 | 12 | } | |
| 140 | |||
| 141 | 58 | int QuickContactActions::applyFamilyJoins(const QString &personKey, const QList<FamilyJoin> &joins) { | |
| 142 | 58 | qint64 bookId = 0; | |
| 143 | 58 | QString uid; | |
| 144 |
6/7✓ Branch 3 → 4 taken 12 times.
✓ Branch 3 → 6 taken 46 times.
✓ Branch 4 → 5 taken 12 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 12 times.
✓ Branch 8 → 9 taken 46 times.
✓ Branch 8 → 10 taken 12 times.
|
58 | if (joins.isEmpty() || !ContactQueries::splitKey(personKey, &bookId, &uid)) |
| 145 | 46 | return 0; | |
| 146 | 12 | int failed = 0; | |
| 147 |
2/2✓ Branch 84 → 12 taken 12 times.
✓ Branch 84 → 85 taken 12 times.
|
24 | for (const FamilyJoin &join : joins) { |
| 148 | 12 | QString error; | |
| 149 | // A chosen entry is the family (an older one gets the marker with its member); a name finds or makes one. | ||
| 150 |
3/3✓ Branch 14 → 15 taken 8 times.
✓ Branch 14 → 16 taken 4 times.
✓ Branch 15 → 17 taken 8 times.
|
12 | const QString family = join.familyKey.isEmpty() ? ensureFamily(join.name, bookId, &error) : join.familyKey; |
| 151 |
3/6✓ Branch 18 → 19 taken 12 times.
✗ Branch 18 → 21 not taken.
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 22 taken 12 times.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 36 taken 12 times.
|
12 | if (family.isEmpty() || family == personKey) { |
| 152 | ✗ | qCWarning(lcContactFamilies) << "A family could not be found or created:" << error; | |
| 153 | ✗ | ++failed; | |
| 154 | ✗ | continue; | |
| 155 | ✗ | } | |
| 156 |
1/1✓ Branch 36 → 37 taken 12 times.
|
12 | const QVariantMap result = addMember(family, personKey); |
| 157 |
4/4✓ Branch 41 → 42 taken 12 times.
✓ Branch 42 → 43 taken 12 times.
✓ Branch 47 → 48 taken 2 times.
✓ Branch 47 → 71 taken 10 times.
|
24 | if (!result.value(QStringLiteral("ok")).toBool()) { |
| 158 |
8/8✓ Branch 48 → 49 taken 2 times.
✓ Branch 52 → 53 taken 2 times.
✓ Branch 53 → 54 taken 2 times.
✓ Branch 58 → 59 taken 2 times.
✓ Branch 59 → 60 taken 2 times.
✓ Branch 60 → 61 taken 2 times.
✓ Branch 69 → 50 taken 2 times.
✓ Branch 69 → 70 taken 2 times.
|
4 | qCWarning(lcContactFamilies) << "Membership not saved:" << result.value(QStringLiteral("error")).toString(); |
| 159 | 2 | ++failed; | |
| 160 | } | ||
| 161 | 12 | } | |
| 162 | 12 | return failed; | |
| 163 | 58 | } | |
| 164 | |||
| 165 | 728 | int QuickContactActions::migrateFamilies() { | |
| 166 |
1/1✓ Branch 2 → 3 taken 728 times.
|
728 | ContactSyncCoordinator *sync = m_coordinator(); |
| 167 |
2/4✓ Branch 3 → 4 taken 728 times.
✗ Branch 3 → 5 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 728 times.
|
728 | if (!sync || m_migratingFamilies) |
| 168 | ✗ | return 0; | |
| 169 | 728 | QScopedValueRollback<bool> guard(m_migratingFamilies, true); | |
| 170 | // Quiet: no toast, no undo step — the conversion is not the user's action. | ||
| 171 | 728 | QList<UndoPart> discarded; | |
| 172 | 728 | QScopedValueRollback<QList<UndoPart> *> quiet(m_collect, &discarded); | |
| 173 |
1/1✓ Branch 9 → 10 taken 728 times.
|
728 | QSqlDatabase db = sync->store()->database(); |
| 174 | 1456 | const auto keysOf = [&db](const QString &sql) { | |
| 175 | 1456 | QStringList keys; | |
| 176 |
1/1✓ Branch 2 → 3 taken 1456 times.
|
1456 | QSqlQuery q(db); |
| 177 |
2/3✓ Branch 3 → 4 taken 1456 times.
✓ Branch 4 → 5 taken 1456 times.
✗ Branch 4 → 19 not taken.
|
1456 | if (q.exec(sql)) |
| 178 |
3/3✓ Branch 17 → 18 taken 1462 times.
✓ Branch 18 → 6 taken 6 times.
✓ Branch 18 → 19 taken 1456 times.
|
1462 | while (q.next()) |
| 179 |
6/6✓ Branch 6 → 7 taken 6 times.
✓ Branch 7 → 8 taken 6 times.
✓ Branch 8 → 9 taken 6 times.
✓ Branch 9 → 10 taken 6 times.
✓ Branch 10 → 11 taken 6 times.
✓ Branch 11 → 12 taken 6 times.
|
6 | keys.append(ContactQueries::cardKey(q.value(0).toLongLong(), q.value(1).toString())); |
| 180 | 1456 | return keys; | |
| 181 | 1456 | }; | |
| 182 | 728 | FamilyIndex families; | |
| 183 | { | ||
| 184 |
1/1✓ Branch 10 → 11 taken 728 times.
|
728 | QSqlQuery q(db); |
| 185 |
2/3✓ Branch 14 → 15 taken 728 times.
✓ Branch 17 → 18 taken 728 times.
✗ Branch 17 → 46 not taken.
|
728 | if (q.exec(QStringLiteral("SELECT c.book_id, c.uid, c.display_name FROM cards c " |
| 186 | "JOIN address_books b ON b.id = c.book_id " | ||
| 187 | "WHERE c.kind = 'family' AND b.read_only = 0 ORDER BY c.id"))) | ||
| 188 |
3/3✓ Branch 44 → 45 taken 730 times.
✓ Branch 45 → 19 taken 2 times.
✓ Branch 45 → 46 taken 728 times.
|
730 | while (q.next()) |
| 189 |
6/6✓ Branch 19 → 20 taken 2 times.
✓ Branch 20 → 21 taken 2 times.
✓ Branch 21 → 22 taken 2 times.
✓ Branch 22 → 23 taken 2 times.
✓ Branch 23 → 24 taken 2 times.
✓ Branch 32 → 33 taken 2 times.
|
6 | families[q.value(2).toString().simplified().toCaseFolded()].append( |
| 190 |
7/7✓ Branch 24 → 25 taken 2 times.
✓ Branch 25 → 26 taken 2 times.
✓ Branch 26 → 27 taken 2 times.
✓ Branch 27 → 28 taken 2 times.
✓ Branch 28 → 29 taken 2 times.
✓ Branch 29 → 30 taken 2 times.
✓ Branch 30 → 31 taken 2 times.
|
4 | {q.value(0).toLongLong(), ContactQueries::cardKey(q.value(0).toLongLong(), q.value(1).toString())}); |
| 191 | 728 | } | |
| 192 | 728 | QScopedValueRollback<FamilyIndex *> indexed(m_familyIndex, &families); | |
| 193 | 728 | int converted = 0; | |
| 194 | 728 | int skipped = 0; | |
| 195 | // People that still name their family themselves (member-of lines). | ||
| 196 | 2184 | for (const QString &person : keysOf(QStringLiteral( | |
| 197 | "SELECT DISTINCT c.book_id, c.uid FROM card_relations r JOIN cards c ON c.id = r.card_id" | ||
| 198 | " JOIN address_books b ON b.id = c.book_id" | ||
| 199 |
5/5✓ Branch 51 → 52 taken 728 times.
✓ Branch 54 → 55 taken 728 times.
✓ Branch 55 → 56 taken 728 times.
✓ Branch 156 → 57 taken 6 times.
✓ Branch 156 → 157 taken 728 times.
|
2190 | " WHERE r.role = 'relation' AND r.type = 'member-of' AND b.read_only = 0"))) { |
| 200 | 6 | qint64 bookId = 0; | |
| 201 | 6 | QString uid; | |
| 202 |
3/4✓ Branch 58 → 59 taken 6 times.
✓ Branch 59 → 60 taken 6 times.
✗ Branch 59 → 62 not taken.
✓ Branch 61 → 63 taken 6 times.
|
6 | const auto stored = ContactQueries::splitKey(person, &bookId, &uid) ? sync->store()->card(bookId, uid) : std::nullopt; |
| 203 |
1/1✓ Branch 63 → 64 taken 6 times.
|
6 | ContactCard card; |
| 204 |
4/7✓ Branch 65 → 66 taken 6 times.
✗ Branch 65 → 69 not taken.
✓ Branch 67 → 68 taken 6 times.
✗ Branch 68 → 69 not taken.
✓ Branch 68 → 70 taken 6 times.
✗ Branch 71 → 72 not taken.
✓ Branch 71 → 73 taken 6 times.
|
6 | if (!stored || !VCardCodec::parse(stored->raw, &card)) |
| 205 | ✗ | continue; | |
| 206 | 6 | const size_t rawHash = qHash(stored->raw); | |
| 207 |
5/6✓ Branch 75 → 76 taken 1 time.
✓ Branch 75 → 78 taken 5 times.
✓ Branch 76 → 77 taken 1 time.
✗ Branch 76 → 78 not taken.
✓ Branch 79 → 80 taken 1 time.
✓ Branch 79 → 81 taken 5 times.
|
6 | if (m_familyJoinsFailed.value(person, 0) == rawHash && rawHash != 0) { |
| 208 | 1 | ++skipped; | |
| 209 | 1 | continue; | |
| 210 | } | ||
| 211 | 5 | QList<FamilyJoin> joins; | |
| 212 |
2/2✓ Branch 111 → 84 taken 5 times.
✓ Branch 111 → 112 taken 5 times.
|
10 | for (const ContactRelation &relation : std::as_const(card.relations)) { |
| 213 |
5/11✓ Branch 86 → 87 taken 5 times.
✗ Branch 86 → 91 not taken.
✓ Branch 88 → 89 taken 5 times.
✗ Branch 90 → 91 not taken.
✓ Branch 90 → 92 taken 5 times.
✓ Branch 93 → 94 taken 5 times.
✗ Branch 93 → 95 not taken.
✗ Branch 95 → 96 not taken.
✓ Branch 95 → 97 taken 5 times.
✗ Branch 262 → 263 not taken.
✗ Branch 262 → 264 not taken.
|
5 | if (relation.readOnly() || RelationVocabulary::canonical(relation.type) != QLatin1String("member-of")) |
| 214 | ✗ | continue; | |
| 215 | 5 | FamilyJoin join; | |
| 216 |
3/3✓ Branch 98 → 99 taken 2 times.
✓ Branch 98 → 100 taken 3 times.
✓ Branch 100 → 101 taken 3 times.
|
5 | join.familyKey = relation.targetUid.isEmpty() ? QString() : cardKeyForUid(relation.targetUid, bookId); |
| 217 |
1/1✓ Branch 103 → 104 taken 5 times.
|
5 | join.name = relation.targetName.simplified(); |
| 218 |
1/1✓ Branch 106 → 107 taken 5 times.
|
5 | joins.append(join); |
| 219 | 5 | } | |
| 220 |
3/3✓ Branch 112 → 113 taken 5 times.
✓ Branch 113 → 114 taken 2 times.
✓ Branch 113 → 116 taken 3 times.
|
5 | if (applyFamilyJoins(person, joins) > 0) { |
| 221 | // The person keeps the lines until every family has them; the next | ||
| 222 | // try waits for a change of the card (a read-only family stays | ||
| 223 | // read-only through every sync). | ||
| 224 |
1/1✓ Branch 114 → 115 taken 2 times.
|
2 | m_familyJoinsFailed.insert(person, rawHash); |
| 225 | 2 | continue; | |
| 226 | } | ||
| 227 |
1/1✓ Branch 116 → 117 taken 3 times.
|
3 | m_familyJoinsFailed.remove(person); |
| 228 | 6 | const QVariantMap removed = editPeople( | |
| 229 | person, | ||
| 230 | ✗ | [](ContactCard *edited) -> QString { | |
| 231 | 3 | edited->relations.removeIf([](const ContactRelation &relation) { | |
| 232 |
4/9✓ Branch 3 → 4 taken 3 times.
✗ Branch 3 → 9 not taken.
✓ Branch 5 → 6 taken 3 times.
✓ Branch 7 → 8 taken 3 times.
✗ Branch 7 → 9 not taken.
✓ Branch 11 → 12 taken 3 times.
✗ Branch 11 → 13 not taken.
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 17 not taken.
|
3 | return !relation.readOnly() && RelationVocabulary::canonical(relation.type) == QLatin1String("member-of"); |
| 233 | }); | ||
| 234 | 3 | return {}; | |
| 235 | }, | ||
| 236 |
1/1✓ Branch 119 → 120 taken 3 times.
|
9 | QString()); |
| 237 |
3/4✓ Branch 126 → 127 taken 3 times.
✓ Branch 127 → 128 taken 3 times.
✓ Branch 128 → 129 taken 3 times.
✗ Branch 128 → 130 not taken.
|
6 | converted += removed.value(QStringLiteral("ok")).toBool() ? 1 : 0; |
| 238 | 742 | } | |
| 239 | // Family entries from before the marker: they list members. | ||
| 240 | 2184 | for (const QString &family : keysOf(QStringLiteral( | |
| 241 | "SELECT DISTINCT c.book_id, c.uid FROM card_relations r JOIN cards c ON c.id = r.card_id" | ||
| 242 | " JOIN address_books b ON b.id = c.book_id" | ||
| 243 |
4/5✓ Branch 161 → 162 taken 728 times.
✓ Branch 164 → 165 taken 728 times.
✓ Branch 165 → 166 taken 728 times.
✗ Branch 189 → 167 not taken.
✓ Branch 189 → 190 taken 728 times.
|
2184 | " WHERE r.role = 'relation' AND r.type = 'member' AND c.kind <> 'family' AND b.read_only = 0"))) { |
| 244 | ✗ | const QVariantMap marked = editPeople( | |
| 245 | family, | ||
| 246 | ✗ | [](ContactCard *card) -> QString { | |
| 247 | ✗ | card->kind = QStringLiteral("family"); | |
| 248 | ✗ | return {}; | |
| 249 | }, | ||
| 250 | ✗ | QString()); | |
| 251 | ✗ | converted += marked.value(QStringLiteral("ok")).toBool() ? 1 : 0; | |
| 252 | 728 | } | |
| 253 |
4/4✓ Branch 191 → 192 taken 727 times.
✓ Branch 191 → 193 taken 1 time.
✓ Branch 192 → 193 taken 1 time.
✓ Branch 192 → 208 taken 726 times.
|
728 | if (converted > 0 || skipped > 0) |
| 254 |
8/8✓ Branch 193 → 194 taken 2 times.
✓ Branch 197 → 198 taken 2 times.
✓ Branch 198 → 199 taken 2 times.
✓ Branch 199 → 200 taken 2 times.
✓ Branch 200 → 201 taken 2 times.
✓ Branch 201 → 202 taken 2 times.
✓ Branch 206 → 195 taken 2 times.
✓ Branch 206 → 207 taken 2 times.
|
4 | qCInfo(lcContactFamilies) << "Families converted to one entry each:" << converted << "cards changed;" << skipped |
| 255 |
1/1✓ Branch 202 → 203 taken 2 times.
|
2 | << "people with failed joins left until their card changes"; |
| 256 | 728 | return converted; | |
| 257 | 728 | } | |
| 258 |