presentation/QuickContactActionsMirror.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // Sprint 103 F103-22: relationships mirror each other. "Anna is Ben's parent" | ||
| 2 | // saved on Ben's card is also "Ben is Anna's child" on Anna's card, and | ||
| 3 | // Anna's editor shows it as an ordinary row. Saving Anna's card then: | ||
| 4 | // - keeps an unchanged mirrored row where it is (nothing is written); | ||
| 5 | // - moves a changed row to Anna's card (and removes the line from Ben's); | ||
| 6 | // - removes a deleted row from Ben's card. | ||
| 7 | // The me card is the exception (D-6): its changed rows stay on the other | ||
| 8 | // card with the inverse type ("Ben is my child" → Ben's card: "parent"). | ||
| 9 | // The editor sends the mirrored rows as relation entries with `mirrorOf` | ||
| 10 | // {storedOnKey, sourceIndex, shownType} and the loaded ones as `mirrors`. | ||
| 11 | |||
| 12 | #include "QuickContactActions.h" | ||
| 13 | |||
| 14 | #include "data/ContactQueries.h" | ||
| 15 | #include "data/RelationVocabulary.h" | ||
| 16 | #include "data/VCardCodec.h" | ||
| 17 | |||
| 18 | #include <QLoggingCategory> | ||
| 19 | #include <QMap> | ||
| 20 | #include <QSet> | ||
| 21 | #include <algorithm> | ||
| 22 | |||
| 23 |
4/7✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 8 taken 4 times.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 1 time.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
|
5 | Q_LOGGING_CATEGORY(lcContactMirror, "mailjd.contacts.mirror") |
| 24 | |||
| 25 | namespace { | ||
| 26 | |||
| 27 | 16 | QString mirrorId(const QVariantMap &mirror) { | |
| 28 |
3/3✓ Branch 15 → 16 taken 16 times.
✓ Branch 16 → 17 taken 16 times.
✓ Branch 17 → 18 taken 16 times.
|
48 | return mirror.value(QStringLiteral("storedOnKey")).toString() + QLatin1Char('\n') + |
| 29 |
4/4✓ Branch 6 → 7 taken 16 times.
✓ Branch 7 → 8 taken 16 times.
✓ Branch 8 → 9 taken 16 times.
✓ Branch 18 → 19 taken 16 times.
|
64 | QString::number(mirror.value(QStringLiteral("sourceIndex"), -1).toInt()); |
| 30 | } | ||
| 31 | |||
| 32 | } // namespace | ||
| 33 | |||
| 34 | 57 | QVariantMap QuickContactActions::splitMirrors(const QString &key, const QVariantMap &fields, | |
| 35 | QList<MirrorEdit> *edits) const { | ||
| 36 |
14/24✓ Branch 5 → 6 taken 57 times.
✓ Branch 6 → 7 taken 25 times.
✓ Branch 6 → 12 taken 32 times.
✓ Branch 10 → 11 taken 25 times.
✓ Branch 11 → 12 taken 5 times.
✓ Branch 11 → 13 taken 20 times.
✓ Branch 14 → 15 taken 25 times.
✓ Branch 14 → 16 taken 32 times.
✓ Branch 16 → 17 taken 25 times.
✓ Branch 16 → 18 taken 32 times.
✓ Branch 18 → 19 taken 57 times.
✗ Branch 18 → 20 not taken.
✓ Branch 20 → 21 taken 57 times.
✗ Branch 20 → 22 not taken.
✓ Branch 22 → 23 taken 37 times.
✓ Branch 22 → 24 taken 20 times.
✗ Branch 330 → 331 not taken.
✗ Branch 330 → 332 not taken.
✗ Branch 334 → 335 not taken.
✗ Branch 334 → 336 not taken.
✗ Branch 338 → 339 not taken.
✗ Branch 338 → 340 not taken.
✗ Branch 342 → 343 not taken.
✗ Branch 342 → 344 not taken.
|
139 | if (!fields.contains(QStringLiteral("relations")) || !fields.contains(QStringLiteral("mirrors"))) |
| 37 | 37 | return fields; | |
| 38 |
5/9✓ Branch 25 → 26 taken 20 times.
✗ Branch 25 → 30 not taken.
✓ Branch 26 → 27 taken 20 times.
✓ Branch 28 → 29 taken 2 times.
✓ Branch 28 → 30 taken 18 times.
✓ Branch 31 → 32 taken 20 times.
✗ Branch 31 → 33 not taken.
✗ Branch 346 → 347 not taken.
✗ Branch 346 → 348 not taken.
|
20 | const bool onMe = !key.isEmpty() && key == meKey(); |
| 39 | 20 | QVariantList own; | |
| 40 | 20 | QSet<QString> listed; | |
| 41 |
6/6✓ Branch 38 → 39 taken 20 times.
✓ Branch 39 → 40 taken 20 times.
✓ Branch 44 → 45 taken 20 times.
✓ Branch 45 → 46 taken 20 times.
✓ Branch 253 → 47 taken 17 times.
✓ Branch 253 → 254 taken 20 times.
|
57 | for (const QVariant &item : fields.value(QStringLiteral("relations")).toList()) { |
| 42 |
1/1✓ Branch 48 → 49 taken 17 times.
|
17 | QVariantMap entry = item.toMap(); |
| 43 |
2/2✓ Branch 53 → 54 taken 17 times.
✓ Branch 54 → 55 taken 17 times.
|
34 | const QVariantMap mirror = entry.value(QStringLiteral("mirrorOf")).toMap(); |
| 44 |
3/3✓ Branch 59 → 60 taken 17 times.
✓ Branch 60 → 61 taken 10 times.
✓ Branch 60 → 65 taken 7 times.
|
17 | if (mirror.isEmpty()) { |
| 45 |
1/1✓ Branch 62 → 63 taken 10 times.
|
10 | own.append(entry); |
| 46 | 10 | continue; | |
| 47 | } | ||
| 48 |
2/2✓ Branch 65 → 66 taken 7 times.
✓ Branch 66 → 67 taken 7 times.
|
7 | listed.insert(mirrorId(mirror)); |
| 49 | 7 | MirrorEdit edit; | |
| 50 |
2/2✓ Branch 72 → 73 taken 7 times.
✓ Branch 73 → 74 taken 7 times.
|
14 | edit.storedOnKey = mirror.value(QStringLiteral("storedOnKey")).toString(); |
| 51 |
2/2✓ Branch 84 → 85 taken 7 times.
✓ Branch 85 → 86 taken 7 times.
|
14 | edit.sourceIndex = mirror.value(QStringLiteral("sourceIndex"), -1).toInt(); |
| 52 |
2/2✓ Branch 94 → 95 taken 7 times.
✓ Branch 95 → 96 taken 7 times.
|
14 | edit.otherKey = entry.value(QStringLiteral("otherKey")).toString(); |
| 53 |
3/3✓ Branch 106 → 107 taken 7 times.
✓ Branch 107 → 108 taken 7 times.
✓ Branch 108 → 109 taken 7 times.
|
14 | edit.type = RelationVocabulary::canonical(entry.value(QStringLiteral("type")).toString()); |
| 54 |
6/25✗ Branch 118 → 119 not taken.
✓ Branch 118 → 126 taken 7 times.
✗ Branch 123 → 124 not taken.
✗ Branch 124 → 125 not taken.
✗ Branch 125 → 127 not taken.
✗ Branch 129 → 130 not taken.
✓ Branch 129 → 131 taken 7 times.
✗ Branch 131 → 132 not taken.
✓ Branch 131 → 133 taken 7 times.
✗ Branch 133 → 134 not taken.
✓ Branch 133 → 135 taken 7 times.
✗ Branch 135 → 136 not taken.
✓ Branch 135 → 137 taken 7 times.
✗ Branch 137 → 138 not taken.
✓ Branch 137 → 139 taken 7 times.
✗ Branch 435 → 436 not taken.
✗ Branch 435 → 437 not taken.
✗ Branch 439 → 440 not taken.
✗ Branch 439 → 441 not taken.
✗ Branch 443 → 444 not taken.
✗ Branch 443 → 445 not taken.
✗ Branch 447 → 448 not taken.
✗ Branch 447 → 449 not taken.
✗ Branch 451 → 452 not taken.
✗ Branch 451 → 453 not taken.
|
7 | edit.label = edit.type == QLatin1String("custom") ? entry.value(QStringLiteral("label")).toString().trimmed() : QString(); |
| 55 |
13/28✓ Branch 143 → 144 taken 7 times.
✓ Branch 144 → 145 taken 7 times.
✓ Branch 146 → 147 taken 5 times.
✓ Branch 146 → 150 taken 2 times.
✓ Branch 148 → 149 taken 4 times.
✓ Branch 148 → 150 taken 1 time.
✓ Branch 151 → 152 taken 7 times.
✗ Branch 151 → 153 not taken.
✓ Branch 153 → 154 taken 7 times.
✗ Branch 153 → 155 not taken.
✓ Branch 155 → 156 taken 7 times.
✗ Branch 155 → 157 not taken.
✓ Branch 157 → 158 taken 7 times.
✗ Branch 157 → 159 not taken.
✓ Branch 159 → 160 taken 7 times.
✗ Branch 159 → 161 not taken.
✓ Branch 161 → 162 taken 4 times.
✓ Branch 161 → 163 taken 3 times.
✗ Branch 457 → 458 not taken.
✗ Branch 457 → 459 not taken.
✗ Branch 461 → 462 not taken.
✗ Branch 461 → 463 not taken.
✗ Branch 465 → 466 not taken.
✗ Branch 465 → 467 not taken.
✗ Branch 469 → 470 not taken.
✗ Branch 469 → 471 not taken.
✗ Branch 473 → 474 not taken.
✗ Branch 473 → 475 not taken.
|
14 | if (edit.type == mirror.value(QStringLiteral("shownType")).toString() && edit.otherKey == edit.storedOnKey) |
| 56 | 4 | continue; // unchanged: stays on the other card | |
| 57 |
1/1✓ Branch 163 → 164 taken 3 times.
|
3 | edits->append(edit); |
| 58 |
9/13✓ Branch 164 → 165 taken 2 times.
✓ Branch 164 → 171 taken 1 time.
✓ Branch 166 → 167 taken 2 times.
✗ Branch 166 → 171 not taken.
✓ Branch 167 → 168 taken 2 times.
✓ Branch 169 → 170 taken 2 times.
✗ Branch 169 → 171 not taken.
✓ Branch 172 → 173 taken 2 times.
✓ Branch 172 → 174 taken 1 time.
✓ Branch 174 → 175 taken 2 times.
✓ Branch 174 → 176 taken 1 time.
✗ Branch 477 → 478 not taken.
✗ Branch 477 → 479 not taken.
|
3 | if (onMe && !edit.otherKey.isEmpty() && !RelationVocabulary::inverseOf(edit.type).isEmpty()) |
| 59 | 2 | continue; // D-6: written on the other card with the inverse type | |
| 60 |
1/1✓ Branch 179 → 180 taken 1 time.
|
1 | entry.remove(QStringLiteral("mirrorOf")); |
| 61 |
1/1✓ Branch 186 → 187 taken 1 time.
|
2 | entry.insert(QStringLiteral("sourceIndex"), -1); |
| 62 | 1 | qint64 book = 0; | |
| 63 | 1 | QString uid; | |
| 64 |
4/7✓ Branch 191 → 192 taken 1 time.
✗ Branch 191 → 195 not taken.
✓ Branch 192 → 193 taken 1 time.
✓ Branch 193 → 194 taken 1 time.
✗ Branch 193 → 195 not taken.
✓ Branch 196 → 197 taken 1 time.
✗ Branch 196 → 233 not taken.
|
1 | if (edit.otherKey == edit.storedOnKey && ContactQueries::splitKey(edit.storedOnKey, &book, &uid)) { |
| 65 | // The row showed "Me" or the linked name; the line names the card. | ||
| 66 |
1/1✓ Branch 201 → 202 taken 1 time.
|
2 | entry.insert(QStringLiteral("targetUid"), uid); |
| 67 |
1/1✓ Branch 210 → 211 taken 1 time.
|
2 | entry.insert(QStringLiteral("targetUri"), QString()); |
| 68 |
3/3✓ Branch 215 → 216 taken 1 time.
✓ Branch 220 → 221 taken 1 time.
✓ Branch 224 → 225 taken 1 time.
|
3 | entry.insert(QStringLiteral("targetName"), personEntry(edit.storedOnKey).value(QStringLiteral("name"))); |
| 69 | } | ||
| 70 |
1/1✓ Branch 234 → 235 taken 1 time.
|
1 | own.append(entry); |
| 71 | 59 | } | |
| 72 |
6/6✓ Branch 259 → 260 taken 20 times.
✓ Branch 260 → 261 taken 20 times.
✓ Branch 265 → 266 taken 20 times.
✓ Branch 266 → 267 taken 20 times.
✓ Branch 307 → 268 taken 9 times.
✓ Branch 307 → 308 taken 20 times.
|
49 | for (const QVariant &item : fields.value(QStringLiteral("mirrors")).toList()) { |
| 73 |
1/1✓ Branch 269 → 270 taken 9 times.
|
9 | const QVariantMap mirror = item.toMap(); |
| 74 |
3/3✓ Branch 270 → 271 taken 9 times.
✓ Branch 273 → 274 taken 7 times.
✓ Branch 273 → 275 taken 2 times.
|
9 | if (listed.contains(mirrorId(mirror))) |
| 75 | 7 | continue; | |
| 76 | 2 | MirrorEdit removed; | |
| 77 |
2/2✓ Branch 279 → 280 taken 2 times.
✓ Branch 280 → 281 taken 2 times.
|
4 | removed.storedOnKey = mirror.value(QStringLiteral("storedOnKey")).toString(); |
| 78 |
2/2✓ Branch 291 → 292 taken 2 times.
✓ Branch 292 → 293 taken 2 times.
|
4 | removed.sourceIndex = mirror.value(QStringLiteral("sourceIndex"), -1).toInt(); |
| 79 |
1/1✓ Branch 297 → 298 taken 2 times.
|
2 | edits->append(removed); |
| 80 | 29 | } | |
| 81 | 20 | QVariantMap out = fields; | |
| 82 |
1/1✓ Branch 314 → 315 taken 20 times.
|
40 | out.insert(QStringLiteral("relations"), own); |
| 83 |
1/1✓ Branch 321 → 322 taken 20 times.
|
20 | out.remove(QStringLiteral("mirrors")); |
| 84 | 20 | return out; | |
| 85 | 20 | } | |
| 86 | |||
| 87 | // One write per other card: its line numbers (sourceIndex) are only valid | ||
| 88 | // for the card as it was loaded. | ||
| 89 | 52 | int QuickContactActions::applyMirrorEdits(const QString &key, const QList<MirrorEdit> &edits) { | |
| 90 | 52 | int failed = 0; | |
| 91 |
5/9✓ Branch 3 → 4 taken 52 times.
✗ Branch 3 → 8 not taken.
✓ Branch 4 → 5 taken 52 times.
✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 8 taken 50 times.
✓ Branch 9 → 10 taken 52 times.
✗ Branch 9 → 11 not taken.
✗ Branch 144 → 145 not taken.
✗ Branch 144 → 146 not taken.
|
52 | const bool onMe = !key.isEmpty() && key == meKey(); |
| 92 | 52 | QMap<QString, QList<MirrorEdit>> byCard; | |
| 93 |
2/2✓ Branch 18 → 13 taken 5 times.
✓ Branch 18 → 19 taken 52 times.
|
57 | for (const MirrorEdit &edit : edits) |
| 94 |
2/2✓ Branch 14 → 15 taken 5 times.
✓ Branch 15 → 16 taken 5 times.
|
5 | byCard[edit.storedOnKey].append(edit); |
| 95 | 52 | QList<MirrorEdit> elsewhere; | |
| 96 |
4/4✓ Branch 19 → 20 taken 52 times.
✓ Branch 116 → 117 taken 57 times.
✓ Branch 118 → 21 taken 5 times.
✓ Branch 118 → 119 taken 52 times.
|
57 | for (auto it = byCard.cbegin(); it != byCard.cend(); ++it) { |
| 97 | 5 | const QString holder = it.key(); | |
| 98 | 5 | const QList<MirrorEdit> list = it.value(); | |
| 99 | 5 | const QVariantMap result = editPeople( | |
| 100 | holder, | ||
| 101 |
1/1✓ Branch 26 → 27 taken 5 times.
|
5 | [&](ContactCard *card) -> QString { |
| 102 | 5 | QList<ContactRelation> kept; | |
| 103 |
4/4✓ Branch 2 → 3 taken 5 times.
✓ Branch 3 → 4 taken 5 times.
✓ Branch 40 → 5 taken 6 times.
✓ Branch 40 → 41 taken 5 times.
|
11 | for (const ContactRelation &relation : card->relations) { |
| 104 |
1/1✓ Branch 8 → 9 taken 6 times.
|
6 | const auto edit = std::find_if(list.cbegin(), list.cend(), [&](const MirrorEdit &candidate) { |
| 105 | 6 | return candidate.sourceIndex == relation.sourceIndex; | |
| 106 | }); | ||
| 107 |
5/6✓ Branch 11 → 12 taken 5 times.
✓ Branch 11 → 14 taken 1 time.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 5 times.
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 19 taken 5 times.
|
6 | if (edit == list.cend() || relation.readOnly()) { |
| 108 |
1/1✓ Branch 17 → 18 taken 1 time.
|
1 | kept.append(relation); |
| 109 | 1 | continue; | |
| 110 | } | ||
| 111 |
1/1✓ Branch 20 → 21 taken 5 times.
|
5 | const QString inverse = RelationVocabulary::inverseOf(edit->type); |
| 112 |
7/8✓ Branch 21 → 22 taken 2 times.
✓ Branch 21 → 28 taken 3 times.
✓ Branch 24 → 25 taken 1 time.
✓ Branch 24 → 28 taken 1 time.
✓ Branch 26 → 27 taken 1 time.
✗ Branch 26 → 28 not taken.
✓ Branch 29 → 30 taken 1 time.
✓ Branch 29 → 36 taken 4 times.
|
5 | if (onMe && edit->otherKey == holder && !inverse.isEmpty()) { |
| 113 | 1 | ContactRelation changed = relation; | |
| 114 | 1 | changed.type = inverse; | |
| 115 | 1 | changed.label.clear(); | |
| 116 |
1/1✓ Branch 33 → 34 taken 1 time.
|
1 | kept.append(changed); |
| 117 | 1 | } // otherwise the line is gone here (saved on the edited card, or removed) | |
| 118 | 5 | } | |
| 119 | 5 | card->relations = kept; | |
| 120 | 10 | return {}; | |
| 121 | 5 | }, | |
| 122 |
2/2✓ Branch 25 → 26 taken 5 times.
✓ Branch 27 → 28 taken 5 times.
|
15 | tr("Relationship changed")); |
| 123 |
8/8✓ Branch 30 → 31 taken 5 times.
✓ Branch 34 → 35 taken 5 times.
✓ Branch 35 → 36 taken 5 times.
✓ Branch 36 → 37 taken 5 times.
✓ Branch 37 → 38 taken 5 times.
✓ Branch 39 → 40 taken 5 times.
✓ Branch 57 → 32 taken 5 times.
✓ Branch 57 → 58 taken 5 times.
|
10 | qCInfo(lcContactMirror) << "Mirrored relations on" << holder << "changed:" << list.size() |
| 124 |
4/5✓ Branch 44 → 45 taken 5 times.
✓ Branch 45 → 46 taken 5 times.
✓ Branch 46 → 47 taken 5 times.
✗ Branch 46 → 48 not taken.
✓ Branch 49 → 50 taken 5 times.
|
10 | << (result.value(QStringLiteral("ok")).toBool() ? "done" : "refused"); |
| 125 |
3/4✓ Branch 62 → 63 taken 5 times.
✓ Branch 63 → 64 taken 5 times.
✗ Branch 68 → 69 not taken.
✓ Branch 68 → 92 taken 5 times.
|
10 | if (!result.value(QStringLiteral("ok")).toBool()) { |
| 126 | ✗ | ++failed; | |
| 127 | ✗ | qCWarning(lcContactMirror) << "Mirror write refused:" << result.value(QStringLiteral("error")).toString(); | |
| 128 | } | ||
| 129 |
2/2✓ Branch 111 → 94 taken 5 times.
✓ Branch 111 → 112 taken 5 times.
|
10 | for (const MirrorEdit &edit : list) |
| 130 |
5/6✓ Branch 95 → 96 taken 2 times.
✓ Branch 95 → 104 taken 3 times.
✓ Branch 97 → 98 taken 2 times.
✗ Branch 97 → 104 not taken.
✓ Branch 99 → 100 taken 1 time.
✓ Branch 99 → 104 taken 1 time.
|
6 | if (onMe && !edit.otherKey.isEmpty() && edit.otherKey != holder && |
| 131 |
6/9✓ Branch 100 → 101 taken 1 time.
✓ Branch 102 → 103 taken 1 time.
✗ Branch 102 → 104 not taken.
✓ Branch 105 → 106 taken 1 time.
✓ Branch 105 → 107 taken 4 times.
✓ Branch 107 → 108 taken 1 time.
✓ Branch 107 → 109 taken 4 times.
✗ Branch 205 → 206 not taken.
✗ Branch 205 → 207 not taken.
|
6 | !RelationVocabulary::inverseOf(edit.type).isEmpty()) |
| 132 |
1/1✓ Branch 108 → 109 taken 1 time.
|
1 | elsewhere.append(edit); |
| 133 | 5 | } | |
| 134 | // The me card's row now names another person: saved on that person's card (D-6). | ||
| 135 |
4/4✓ Branch 119 → 120 taken 52 times.
✓ Branch 120 → 121 taken 52 times.
✓ Branch 139 → 122 taken 1 time.
✓ Branch 139 → 140 taken 52 times.
|
53 | for (const MirrorEdit &edit : elsewhere) |
| 136 |
4/5✓ Branch 123 → 124 taken 1 time.
✓ Branch 128 → 129 taken 1 time.
✓ Branch 129 → 130 taken 1 time.
✗ Branch 135 → 136 not taken.
✓ Branch 135 → 137 taken 1 time.
|
2 | if (!addRelation(key, edit.otherKey, edit.type, edit.label).value(QStringLiteral("ok")).toBool()) ++failed; |
| 137 | 52 | return failed; | |
| 138 | 52 | } | |
| 139 |