data/SettingsCollector.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "data/SettingsCollector.h" | ||
| 2 | #include "data/DavSelectionList.h" | ||
| 3 | |||
| 4 | #include <QDir> | ||
| 5 | #include <QFile> | ||
| 6 | #include <QHash> | ||
| 7 | #include <QLoggingCategory> | ||
| 8 | #include <QRegularExpression> | ||
| 9 | #include <QSet> | ||
| 10 | #include <QSettings> | ||
| 11 | #include <QVariantMap> | ||
| 12 | |||
| 13 | #include "data/CalendarStore.h" | ||
| 14 | #include "data/ComposeSettingsSync.h" | ||
| 15 | #include "data/IdentityStore.h" | ||
| 16 | #include "data/TemplateStore.h" | ||
| 17 | #include "data/DavCredentials.h" | ||
| 18 | #include "data/MailCache.h" | ||
| 19 | #include "data/FolderPreferences.h" | ||
| 20 | |||
| 21 |
4/7✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 8 taken 46 times.
✓ Branch 4 → 5 taken 3 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 3 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
|
49 | Q_LOGGING_CATEGORY(lcSettingsSync, "mailjd.settingssync") |
| 22 | |||
| 23 | namespace { | ||
| 24 | |||
| 25 | constexpr int kMaxDavAccounts = 16; | ||
| 26 | constexpr int kMaxDavSelections = 256; | ||
| 27 | constexpr int kMaxSyncedMapEntries = 4096; | ||
| 28 | |||
| 29 | 8 | const QRegularExpression &iconNamePattern() { | |
| 30 | static const QRegularExpression pattern( | ||
| 31 |
4/7✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 15 taken 5 times.
✓ Branch 4 → 5 taken 3 times.
✗ Branch 4 → 15 not taken.
✓ Branch 9 → 10 taken 3 times.
✗ Branch 24 → 25 not taken.
✗ Branch 24 → 26 not taken.
|
11 | QStringLiteral("^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$")); |
| 32 | 8 | return pattern; | |
| 33 | } | ||
| 34 | |||
| 35 | 6 | const QRegularExpression &colorPattern() { | |
| 36 | static const QRegularExpression pattern( | ||
| 37 | 2 | QStringLiteral("^#(?:[0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|" | |
| 38 |
4/7✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 15 taken 5 times.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 15 not taken.
✓ Branch 9 → 10 taken 1 time.
✗ Branch 24 → 25 not taken.
✗ Branch 24 → 26 not taken.
|
8 | "[0-9A-Fa-f]{8})$")); |
| 39 | 6 | return pattern; | |
| 40 | } | ||
| 41 | |||
| 42 | 229 | QString normalizedCategory(const QString &category) { | |
| 43 | 229 | return SyncPayload::normalizedCategory(category); | |
| 44 | } | ||
| 45 | |||
| 46 | 16 | QStringList effectiveRemoteCategories(const SyncPayload &payload, | |
| 47 | const QSettings &settings) { | ||
| 48 | const QStringList configured = | ||
| 49 | settings | ||
| 50 |
1/1✓ Branch 8 → 9 taken 16 times.
|
48 | .value(QStringLiteral("sync/categories"), |
| 51 |
1/1✓ Branch 2 → 3 taken 16 times.
|
32 | SyncPayload::allCategories()) |
| 52 |
1/1✓ Branch 9 → 10 taken 16 times.
|
16 | .toStringList(); |
| 53 | 16 | QSet<QString> localPolicy; | |
| 54 |
2/2✓ Branch 24 → 18 taken 70 times.
✓ Branch 24 → 25 taken 16 times.
|
86 | for (const QString &category : configured) |
| 55 |
2/2✓ Branch 19 → 20 taken 70 times.
✓ Branch 20 → 21 taken 70 times.
|
70 | localPolicy.insert(normalizedCategory(category)); |
| 56 | |||
| 57 | // Privacy policy and the external-content whitelist are intentionally not | ||
| 58 | // remotely applicable. The IMAP message is only a transport container and | ||
| 59 | // does not establish the sender as an authorized device. | ||
| 60 | const QSet<QString> remotelyApplicable = { | ||
| 61 | 16 | QStringLiteral("folderIcons"), QStringLiteral("folderColors"), | |
| 62 | 16 | QStringLiteral("calendarColors"), QStringLiteral("hiddenFolders"), | |
| 63 | 16 | QStringLiteral("davAccounts"), QStringLiteral("general"), | |
| 64 |
2/4✓ Branch 51 → 52 taken 128 times.
✓ Branch 51 → 53 taken 16 times.
✗ Branch 106 → 107 not taken.
✗ Branch 106 → 108 not taken.
|
144 | QStringLiteral("identities"), QStringLiteral("templates")}; |
| 65 | |||
| 66 | 16 | QStringList result; | |
| 67 |
2/2✓ Branch 78 → 63 taken 37 times.
✓ Branch 78 → 79 taken 16 times.
|
53 | for (const QString &rawCategory : payload.enabledCategories) { |
| 68 |
1/1✓ Branch 64 → 65 taken 37 times.
|
37 | const QString category = normalizedCategory(rawCategory); |
| 69 |
1/2✓ Branch 68 → 69 taken 33 times.
✗ Branch 68 → 72 not taken.
|
70 | if (localPolicy.contains(category) && |
| 70 |
4/4✓ Branch 66 → 67 taken 33 times.
✓ Branch 66 → 72 taken 4 times.
✓ Branch 73 → 74 taken 32 times.
✓ Branch 73 → 75 taken 5 times.
|
70 | remotelyApplicable.contains(category) && |
| 71 |
2/2✓ Branch 70 → 71 taken 32 times.
✓ Branch 70 → 72 taken 1 time.
|
33 | !result.contains(category)) { |
| 72 |
1/1✓ Branch 74 → 75 taken 32 times.
|
32 | result.append(category); |
| 73 | } | ||
| 74 | 37 | } | |
| 75 | 16 | return result; | |
| 76 | 160 | } | |
| 77 | |||
| 78 | 43 | bool safeText(const QString &value, qsizetype maxLength) { | |
| 79 |
5/6✓ Branch 3 → 4 taken 41 times.
✓ Branch 3 → 6 taken 2 times.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 41 times.
✓ Branch 8 → 9 taken 2 times.
✓ Branch 8 → 10 taken 41 times.
|
43 | if (value.isEmpty() || value.size() > maxLength) |
| 80 | 2 | return false; | |
| 81 |
2/2✓ Branch 26 → 12 taken 233 times.
✓ Branch 26 → 27 taken 40 times.
|
273 | for (const QChar ch : value) { |
| 82 |
3/4✓ Branch 14 → 15 taken 233 times.
✗ Branch 14 → 21 not taken.
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 22 taken 232 times.
|
234 | if (ch == QChar::Null || (ch.category() == QChar::Other_Control && |
| 83 |
3/4✓ Branch 20 → 21 taken 1 time.
✗ Branch 20 → 22 not taken.
✓ Branch 23 → 24 taken 1 time.
✓ Branch 23 → 25 taken 232 times.
|
234 | ch != QLatin1Char('\t'))) |
| 84 | 1 | return false; | |
| 85 | } | ||
| 86 | 40 | return true; | |
| 87 | } | ||
| 88 | |||
| 89 | 9 | QStringList safeDavSelections(const QStringList &values) { | |
| 90 | 9 | QStringList result; | |
| 91 |
1/1✓ Branch 4 → 5 taken 9 times.
|
9 | result.reserve(qMin(values.size(), qsizetype{kMaxDavSelections})); |
| 92 |
2/2✓ Branch 21 → 7 taken 8 times.
✓ Branch 21 → 22 taken 9 times.
|
17 | for (const QString &value : values) { |
| 93 |
1/2✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 8 times.
|
8 | if (result.size() >= kMaxDavSelections) |
| 94 | ✗ | break; | |
| 95 |
6/6✓ Branch 12 → 13 taken 7 times.
✓ Branch 12 → 16 taken 1 time.
✓ Branch 14 → 15 taken 6 times.
✓ Branch 14 → 16 taken 1 time.
✓ Branch 17 → 18 taken 6 times.
✓ Branch 17 → 19 taken 2 times.
|
8 | if (safeText(value, 4096) && !result.contains(value)) |
| 96 |
1/1✓ Branch 18 → 19 taken 6 times.
|
6 | result.append(value); |
| 97 | } | ||
| 98 | 9 | return result; | |
| 99 | ✗ | } | |
| 100 | |||
| 101 | 8 | bool validDavAccount(const SyncPayload::CardDavAccount &account) { | |
| 102 |
1/4✓ Branch 7 → 8 taken 8 times.
✗ Branch 7 → 12 not taken.
✗ Branch 29 → 30 not taken.
✗ Branch 29 → 31 not taken.
|
16 | return safeText(account.id.trimmed(), 128) && |
| 103 |
4/8✓ Branch 2 → 3 taken 8 times.
✓ Branch 4 → 5 taken 8 times.
✗ Branch 4 → 12 not taken.
✓ Branch 5 → 6 taken 8 times.
✓ Branch 17 → 18 taken 8 times.
✗ Branch 17 → 19 not taken.
✗ Branch 25 → 26 not taken.
✗ Branch 25 → 27 not taken.
|
24 | safeText(account.username.trimmed(), 1024) && |
| 104 |
5/9✓ Branch 8 → 9 taken 8 times.
✓ Branch 10 → 11 taken 7 times.
✓ Branch 10 → 12 taken 1 time.
✓ Branch 13 → 14 taken 8 times.
✗ Branch 13 → 15 not taken.
✓ Branch 15 → 16 taken 8 times.
✗ Branch 15 → 17 not taken.
✗ Branch 21 → 22 not taken.
✗ Branch 21 → 23 not taken.
|
24 | !DavCredentials::canonicalOrigin(account.serverUrl).isEmpty(); |
| 105 | } | ||
| 106 | |||
| 107 | 4 | bool sameDavIdentity(const QVariantMap &local, | |
| 108 | const SyncPayload::CardDavAccount &remote) { | ||
| 109 | // The caller resolves `local` through a hash keyed by remote.id before | ||
| 110 | // invoking this helper. Rechecking that same key was a permanently-true | ||
| 111 | // arm; identity protection here therefore covers the two values a remote | ||
| 112 | // payload must never be allowed to change. | ||
| 113 |
8/25✓ Branch 7 → 8 taken 4 times.
✓ Branch 8 → 9 taken 4 times.
✓ Branch 9 → 10 taken 4 times.
✓ Branch 40 → 41 taken 4 times.
✗ Branch 40 → 42 not taken.
✓ Branch 42 → 43 taken 4 times.
✗ Branch 42 → 44 not taken.
✓ Branch 44 → 45 taken 4 times.
✗ Branch 44 → 46 not taken.
✓ Branch 46 → 47 taken 4 times.
✗ Branch 46 → 48 not taken.
✓ Branch 48 → 49 taken 4 times.
✗ Branch 48 → 50 not taken.
✗ Branch 82 → 83 not taken.
✗ Branch 82 → 84 not taken.
✗ Branch 86 → 87 not taken.
✗ Branch 86 → 88 not taken.
✗ Branch 90 → 91 not taken.
✗ Branch 90 → 92 not taken.
✗ Branch 94 → 95 not taken.
✗ Branch 94 → 96 not taken.
✗ Branch 98 → 99 not taken.
✗ Branch 98 → 100 not taken.
✗ Branch 102 → 103 not taken.
✗ Branch 102 → 104 not taken.
|
12 | return local.value(QStringLiteral("username")).toString().trimmed() == |
| 114 |
5/9✓ Branch 2 → 3 taken 4 times.
✓ Branch 11 → 12 taken 4 times.
✗ Branch 11 → 23 not taken.
✓ Branch 21 → 22 taken 3 times.
✓ Branch 21 → 23 taken 1 time.
✓ Branch 50 → 51 taken 4 times.
✗ Branch 50 → 52 not taken.
✗ Branch 106 → 107 not taken.
✗ Branch 106 → 108 not taken.
|
16 | remote.username.trimmed() && |
| 115 |
1/3✓ Branch 19 → 20 taken 4 times.
✗ Branch 54 → 55 not taken.
✗ Branch 54 → 56 not taken.
|
8 | DavCredentials::canonicalOrigin( |
| 116 |
7/22✓ Branch 17 → 18 taken 4 times.
✓ Branch 18 → 19 taken 4 times.
✓ Branch 26 → 27 taken 4 times.
✗ Branch 26 → 28 not taken.
✓ Branch 28 → 29 taken 4 times.
✗ Branch 28 → 30 not taken.
✓ Branch 30 → 31 taken 4 times.
✗ Branch 30 → 32 not taken.
✓ Branch 32 → 33 taken 4 times.
✗ Branch 32 → 34 not taken.
✓ Branch 34 → 35 taken 4 times.
✗ Branch 34 → 36 not taken.
✗ Branch 58 → 59 not taken.
✗ Branch 58 → 60 not taken.
✗ Branch 62 → 63 not taken.
✗ Branch 62 → 64 not taken.
✗ Branch 66 → 67 not taken.
✗ Branch 66 → 68 not taken.
✗ Branch 70 → 71 not taken.
✗ Branch 70 → 72 not taken.
✗ Branch 74 → 75 not taken.
✗ Branch 74 → 76 not taken.
|
16 | local.value(QStringLiteral("serverUrl")).toString()) == |
| 117 |
4/9✓ Branch 12 → 13 taken 4 times.
✓ Branch 24 → 25 taken 4 times.
✗ Branch 24 → 26 not taken.
✓ Branch 36 → 37 taken 4 times.
✗ Branch 36 → 38 not taken.
✓ Branch 38 → 39 taken 4 times.
✗ Branch 38 → 40 not taken.
✗ Branch 78 → 79 not taken.
✗ Branch 78 → 80 not taken.
|
16 | DavCredentials::canonicalOrigin(remote.serverUrl); |
| 118 | } | ||
| 119 | |||
| 120 | 10 | QList<QVariantMap> readSettingsArray(QSettings &settings, | |
| 121 | const QString &name) { | ||
| 122 | 10 | QList<QVariantMap> result; | |
| 123 |
1/1✓ Branch 3 → 4 taken 10 times.
|
10 | const int count = settings.beginReadArray(name); |
| 124 |
1/1✓ Branch 4 → 5 taken 10 times.
|
10 | result.reserve(count); |
| 125 |
2/2✓ Branch 23 → 6 taken 6 times.
✓ Branch 23 → 24 taken 10 times.
|
16 | for (int i = 0; i < count; ++i) { |
| 126 |
1/1✓ Branch 6 → 7 taken 6 times.
|
6 | settings.setArrayIndex(i); |
| 127 | 6 | QVariantMap fields; | |
| 128 |
5/5✓ Branch 7 → 8 taken 6 times.
✓ Branch 8 → 9 taken 6 times.
✓ Branch 9 → 10 taken 6 times.
✓ Branch 18 → 11 taken 21 times.
✓ Branch 18 → 19 taken 6 times.
|
27 | for (const QString &key : settings.childKeys()) |
| 129 |
2/2✓ Branch 13 → 14 taken 21 times.
✓ Branch 14 → 15 taken 21 times.
|
27 | fields.insert(key, settings.value(key)); |
| 130 |
1/1✓ Branch 20 → 21 taken 6 times.
|
6 | result.append(fields); |
| 131 | 6 | } | |
| 132 |
1/1✓ Branch 24 → 25 taken 10 times.
|
10 | settings.endArray(); |
| 133 | 10 | return result; | |
| 134 | ✗ | } | |
| 135 | |||
| 136 | 5 | void writeSettingsArray(QSettings &settings, const QString &name, | |
| 137 | const QList<QVariantMap> &items) { | ||
| 138 |
1/1✓ Branch 3 → 4 taken 5 times.
|
5 | settings.remove(name); |
| 139 |
1/1✓ Branch 6 → 7 taken 5 times.
|
5 | settings.beginWriteArray(name, items.size()); |
| 140 |
2/2✓ Branch 22 → 8 taken 8 times.
✓ Branch 22 → 23 taken 5 times.
|
13 | for (int i = 0; i < items.size(); ++i) { |
| 141 | 8 | settings.setArrayIndex(i); | |
| 142 | 8 | const QVariantMap &fields = items[i]; | |
| 143 |
4/4✓ Branch 10 → 11 taken 8 times.
✓ Branch 17 → 18 taken 43 times.
✓ Branch 19 → 12 taken 35 times.
✓ Branch 19 → 20 taken 8 times.
|
43 | for (auto it = fields.cbegin(); it != fields.cend(); ++it) |
| 144 |
1/1✓ Branch 15 → 16 taken 35 times.
|
35 | settings.setValue(it.key(), it.value()); |
| 145 | } | ||
| 146 | 5 | settings.endArray(); | |
| 147 | 5 | } | |
| 148 | |||
| 149 | } // namespace | ||
| 150 | |||
| 151 | 6 | QString SettingsCollector::calendarColorKey(const QString &accountId, | |
| 152 | const QString &calendarPath) { | ||
| 153 | 12 | const auto encode = [](const QString &part) { | |
| 154 | return QString::fromLatin1( | ||
| 155 |
2/2✓ Branch 2 → 3 taken 12 times.
✓ Branch 4 → 5 taken 12 times.
|
24 | part.toUtf8().toBase64(QByteArray::Base64UrlEncoding | |
| 156 |
1/1✓ Branch 5 → 6 taken 12 times.
|
24 | QByteArray::OmitTrailingEquals)); |
| 157 | }; | ||
| 158 |
3/3✓ Branch 5 → 6 taken 6 times.
✓ Branch 6 → 7 taken 6 times.
✓ Branch 7 → 8 taken 6 times.
|
6 | return QStringLiteral("v2:%1:%2").arg(encode(accountId), encode(calendarPath)); |
| 159 | } | ||
| 160 | |||
| 161 | 3 | static bool decodeCalendarColorKey(const QString &key, QString *accountId, | |
| 162 | QString *calendarPath) { | ||
| 163 |
3/3✓ Branch 5 → 6 taken 3 times.
✓ Branch 8 → 9 taken 2 times.
✓ Branch 8 → 10 taken 1 time.
|
3 | if (!key.startsWith(QStringLiteral("v2:"))) |
| 164 | 2 | return false; | |
| 165 | 1 | const qsizetype separator = key.indexOf(QLatin1Char(':'), 3); | |
| 166 |
1/2✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 1 time.
|
1 | if (separator < 0) |
| 167 | ✗ | return false; | |
| 168 | 2 | const auto decode = [](QStringView part) { | |
| 169 |
1/1✓ Branch 4 → 5 taken 2 times.
|
6 | return QString::fromUtf8(QByteArray::fromBase64( |
| 170 |
2/2✓ Branch 3 → 4 taken 2 times.
✓ Branch 5 → 6 taken 2 times.
|
6 | part.toLatin1(), QByteArray::Base64UrlEncoding)); |
| 171 | }; | ||
| 172 |
1/1✓ Branch 17 → 18 taken 1 time.
|
1 | *accountId = decode(QStringView{key}.mid(3, separator - 3)); |
| 173 |
1/1✓ Branch 22 → 23 taken 1 time.
|
1 | *calendarPath = decode(QStringView{key}.mid(separator + 1)); |
| 174 | 1 | return !calendarPath->isEmpty(); | |
| 175 | } | ||
| 176 | |||
| 177 | // ═══════════════════════════════════════════════════════ | ||
| 178 | // collectLocal – gather all local settings into SyncPayload | ||
| 179 | // ═══════════════════════════════════════════════════════ | ||
| 180 | |||
| 181 | 63 | SyncPayload SettingsCollector::collectLocal(MailCache *cache, | |
| 182 | const QString &configDir, | ||
| 183 | CalendarStore *calStore) { | ||
| 184 |
1/1✓ Branch 2 → 3 taken 63 times.
|
63 | SyncPayload p; |
| 185 | 63 | p.version = 1; | |
| 186 |
1/1✓ Branch 3 → 4 taken 63 times.
|
63 | p.lastModified = QDateTime::currentDateTimeUtc(); |
| 187 | |||
| 188 |
1/1✓ Branch 6 → 7 taken 63 times.
|
63 | QSettings s; |
| 189 | |||
| 190 | // --- enabledCategories --- | ||
| 191 | const QStringList configuredCategories = | ||
| 192 |
2/2✓ Branch 7 → 8 taken 63 times.
✓ Branch 13 → 14 taken 63 times.
|
189 | s.value(QStringLiteral("sync/categories"), SyncPayload::allCategories()) |
| 193 |
1/1✓ Branch 14 → 15 taken 63 times.
|
63 | .toStringList(); |
| 194 |
2/2✓ Branch 38 → 22 taken 122 times.
✓ Branch 38 → 39 taken 63 times.
|
185 | for (const QString &rawCategory : configuredCategories) { |
| 195 |
1/1✓ Branch 23 → 24 taken 122 times.
|
122 | const QString category = normalizedCategory(rawCategory); |
| 196 |
4/9✓ Branch 24 → 25 taken 122 times.
✓ Branch 26 → 27 taken 122 times.
✗ Branch 26 → 30 not taken.
✓ Branch 31 → 32 taken 122 times.
✗ Branch 31 → 33 not taken.
✓ Branch 33 → 34 taken 122 times.
✗ Branch 33 → 35 not taken.
✗ Branch 480 → 481 not taken.
✗ Branch 480 → 482 not taken.
|
244 | if (SyncPayload::allCategories().contains(category) && |
| 197 |
1/2✓ Branch 28 → 29 taken 122 times.
✗ Branch 28 → 30 not taken.
|
122 | !p.enabledCategories.contains(category)) { |
| 198 |
1/1✓ Branch 34 → 35 taken 122 times.
|
122 | p.enabledCategories.append(category); |
| 199 | } | ||
| 200 | 122 | } | |
| 201 | |||
| 202 | // clientId | ||
| 203 |
2/2✓ Branch 43 → 44 taken 63 times.
✓ Branch 44 → 45 taken 63 times.
|
63 | p.clientId = s.value(QStringLiteral("sync/clientId")).toString(); |
| 204 | |||
| 205 | // --- folderIcons --- | ||
| 206 | // Use allKeys() instead of childKeys() because QSettings treats '/' as | ||
| 207 | // a group separator. Folder paths with '/' delimiter (e.g. "Work/Projects") | ||
| 208 | // become nested subgroups that childKeys() would miss. | ||
| 209 |
2/2✓ Branch 56 → 57 taken 37 times.
✓ Branch 56 → 81 taken 26 times.
|
126 | if (p.enabledCategories.contains(QStringLiteral("folderIcons"))) { |
| 210 |
1/1✓ Branch 61 → 62 taken 37 times.
|
37 | s.beginGroup(QStringLiteral("folder/icon")); |
| 211 |
1/1✓ Branch 64 → 65 taken 37 times.
|
37 | const auto iconKeys = s.allKeys(); |
| 212 |
2/2✓ Branch 77 → 67 taken 34 times.
✓ Branch 77 → 78 taken 37 times.
|
71 | for (const auto &key : iconKeys) { |
| 213 |
3/3✓ Branch 69 → 70 taken 34 times.
✓ Branch 70 → 71 taken 34 times.
✓ Branch 71 → 72 taken 34 times.
|
34 | p.folderIcons[key] = s.value(key).toString(); |
| 214 | } | ||
| 215 |
1/1✓ Branch 78 → 79 taken 37 times.
|
37 | s.endGroup(); |
| 216 | 37 | } | |
| 217 | |||
| 218 | // --- folderColors --- | ||
| 219 |
2/2✓ Branch 87 → 88 taken 4 times.
✓ Branch 87 → 112 taken 59 times.
|
126 | if (p.enabledCategories.contains(QStringLiteral("folderColors"))) { |
| 220 |
1/1✓ Branch 92 → 93 taken 4 times.
|
4 | s.beginGroup(QStringLiteral("folder/color")); |
| 221 |
1/1✓ Branch 95 → 96 taken 4 times.
|
4 | const auto colorKeys = s.allKeys(); |
| 222 |
2/2✓ Branch 108 → 98 taken 1 time.
✓ Branch 108 → 109 taken 4 times.
|
5 | for (const auto &key : colorKeys) { |
| 223 |
3/3✓ Branch 100 → 101 taken 1 time.
✓ Branch 101 → 102 taken 1 time.
✓ Branch 102 → 103 taken 1 time.
|
1 | p.folderColors[key] = s.value(key).toString(); |
| 224 | } | ||
| 225 |
1/1✓ Branch 109 → 110 taken 4 times.
|
4 | s.endGroup(); |
| 226 | 4 | } | |
| 227 | |||
| 228 | // --- hiddenFolders --- | ||
| 229 |
2/2✓ Branch 118 → 119 taken 4 times.
✓ Branch 118 → 123 taken 59 times.
|
126 | if (p.enabledCategories.contains(QStringLiteral("hiddenFolders"))) |
| 230 |
1/1✓ Branch 119 → 120 taken 4 times.
|
4 | p.hiddenFolders = FolderPreferences::loadHidden(configDir); |
| 231 | |||
| 232 | // The external-content whitelist is intentionally local-only. A visible | ||
| 233 | // From header is spoofable, and an unsigned sync message must not expand | ||
| 234 | // the set of messages that may load remote resources automatically. | ||
| 235 | |||
| 236 | // --- carddavAccounts --- | ||
| 237 |
2/2✓ Branch 129 → 130 taken 6 times.
✓ Branch 129 → 188 taken 57 times.
|
126 | if (p.enabledCategories.contains(QStringLiteral("davAccounts"))) { |
| 238 |
1/1✓ Branch 134 → 135 taken 6 times.
|
6 | int count = s.beginReadArray(QStringLiteral("carddav/accounts")); |
| 239 |
2/2✓ Branch 186 → 138 taken 2 times.
✓ Branch 186 → 187 taken 6 times.
|
8 | for (int i = 0; i < count; ++i) { |
| 240 |
1/1✓ Branch 138 → 139 taken 2 times.
|
2 | s.setArrayIndex(i); |
| 241 | 2 | SyncPayload::CardDavAccount acc; | |
| 242 |
2/2✓ Branch 143 → 144 taken 2 times.
✓ Branch 144 → 145 taken 2 times.
|
2 | acc.id = s.value(QStringLiteral("id")).toString(); |
| 243 |
2/2✓ Branch 154 → 155 taken 2 times.
✓ Branch 155 → 156 taken 2 times.
|
2 | acc.serverUrl = s.value(QStringLiteral("serverUrl")).toString(); |
| 244 |
2/2✓ Branch 165 → 166 taken 2 times.
✓ Branch 166 → 167 taken 2 times.
|
2 | acc.username = s.value(QStringLiteral("username")).toString(); |
| 245 | // NOTE: password intentionally NOT collected (security) | ||
| 246 |
2/2✓ Branch 176 → 177 taken 2 times.
✓ Branch 177 → 178 taken 2 times.
|
2 | acc.selectedBooks = davPathList(s.value(QStringLiteral("selectedBooks"))); |
| 247 |
1/1✓ Branch 183 → 184 taken 2 times.
|
2 | p.carddavAccounts.append(acc); |
| 248 | 2 | } | |
| 249 |
1/1✓ Branch 187 → 188 taken 6 times.
|
6 | s.endArray(); |
| 250 | } | ||
| 251 | |||
| 252 | // --- caldavConfigs (T-334: references CardDAV accounts) --- | ||
| 253 |
2/2✓ Branch 194 → 195 taken 6 times.
✓ Branch 194 → 253 taken 57 times.
|
126 | if (p.enabledCategories.contains(QStringLiteral("davAccounts"))) { |
| 254 |
1/1✓ Branch 199 → 200 taken 6 times.
|
6 | int caCount = s.beginReadArray(QStringLiteral("caldav/configs")); |
| 255 |
2/2✓ Branch 251 → 203 taken 2 times.
✓ Branch 251 → 252 taken 6 times.
|
8 | for (int i = 0; i < caCount; ++i) { |
| 256 |
1/1✓ Branch 203 → 204 taken 2 times.
|
2 | s.setArrayIndex(i); |
| 257 | 2 | SyncPayload::CalDavSyncConfig cfg; | |
| 258 |
2/2✓ Branch 208 → 209 taken 2 times.
✓ Branch 209 → 210 taken 2 times.
|
2 | cfg.carddavAccountId = s.value(QStringLiteral("carddavAccountId")).toString(); |
| 259 |
2/2✓ Branch 219 → 220 taken 2 times.
✓ Branch 220 → 221 taken 2 times.
|
2 | cfg.selectedCalendars = davPathList(s.value(QStringLiteral("selectedCalendars"))); |
| 260 |
2/2✓ Branch 230 → 231 taken 2 times.
✓ Branch 231 → 232 taken 2 times.
|
2 | cfg.readOnlyCalendars = davPathList(s.value(QStringLiteral("readOnlyCalendars"))); |
| 261 |
2/2✓ Branch 242 → 243 taken 2 times.
✓ Branch 243 → 244 taken 2 times.
|
4 | cfg.syncIntervalMinutes = s.value(QStringLiteral("syncIntervalMin"), 15).toInt(); |
| 262 |
1/1✓ Branch 248 → 249 taken 2 times.
|
2 | p.caldavConfigs.append(cfg); |
| 263 | 2 | } | |
| 264 |
1/1✓ Branch 252 → 253 taken 6 times.
|
6 | s.endArray(); |
| 265 | } | ||
| 266 | |||
| 267 | // --- calendarColors (from CalendarStore SQLite) --- | ||
| 268 |
10/10✓ Branch 253 → 254 taken 8 times.
✓ Branch 253 → 260 taken 55 times.
✓ Branch 258 → 259 taken 2 times.
✓ Branch 258 → 260 taken 6 times.
✓ Branch 261 → 262 taken 8 times.
✓ Branch 261 → 263 taken 55 times.
✓ Branch 263 → 264 taken 8 times.
✓ Branch 263 → 265 taken 55 times.
✓ Branch 265 → 266 taken 2 times.
✓ Branch 265 → 284 taken 61 times.
|
71 | if (calStore && p.enabledCategories.contains(QStringLiteral("calendarColors"))) { |
| 269 |
1/1✓ Branch 266 → 267 taken 2 times.
|
2 | const auto calendars = calStore->allCalendars(); |
| 270 |
2/2✓ Branch 281 → 269 taken 3 times.
✓ Branch 281 → 282 taken 2 times.
|
5 | for (const auto &cal : calendars) { |
| 271 |
1/1✓ Branch 270 → 271 taken 3 times.
|
3 | QString color = calStore->calendarColor(cal.accountId, cal.path); |
| 272 |
1/2✓ Branch 272 → 273 taken 3 times.
✗ Branch 272 → 278 not taken.
|
3 | if (!color.isEmpty()) |
| 273 |
2/2✓ Branch 273 → 274 taken 3 times.
✓ Branch 274 → 275 taken 3 times.
|
3 | p.calendarColors[calendarColorKey(cal.accountId, cal.path)] = color; |
| 274 | 3 | } | |
| 275 | 2 | } | |
| 276 | |||
| 277 | // --- general --- | ||
| 278 |
2/2✓ Branch 290 → 291 taken 58 times.
✓ Branch 290 → 368 taken 5 times.
|
126 | if (p.enabledCategories.contains(QStringLiteral("general"))) { |
| 279 | p.general.defaultView = | ||
| 280 |
1/1✓ Branch 299 → 300 taken 58 times.
|
174 | s.value(QStringLiteral("view/defaultMode"), QStringLiteral("text")) |
| 281 |
1/1✓ Branch 300 → 301 taken 58 times.
|
116 | .toString(); |
| 282 | p.general.externalContent = | ||
| 283 |
1/1✓ Branch 317 → 318 taken 58 times.
|
174 | s.value(QStringLiteral("view/externalContent"), QStringLiteral("block")) |
| 284 |
1/1✓ Branch 318 → 319 taken 58 times.
|
116 | .toString(); |
| 285 | p.general.language = | ||
| 286 |
1/1✓ Branch 335 → 336 taken 58 times.
|
174 | s.value(QStringLiteral("i18n/language"), QStringLiteral("auto")) |
| 287 |
1/1✓ Branch 336 → 337 taken 58 times.
|
58 | .toString(); |
| 288 | 58 | p.general.carddavSyncInterval = | |
| 289 |
2/2✓ Branch 350 → 351 taken 58 times.
✓ Branch 351 → 352 taken 58 times.
|
116 | s.value(QStringLiteral("carddav/syncIntervalMin"), 0).toInt(); |
| 290 | 58 | p.general.caldavSyncInterval = | |
| 291 |
2/2✓ Branch 361 → 362 taken 58 times.
✓ Branch 362 → 363 taken 58 times.
|
116 | s.value(QStringLiteral("caldav/syncIntervalMin"), 15).toInt(); |
| 292 | } | ||
| 293 | |||
| 294 | // --- identities, templates (Sprint 108, ComposeSettingsSync) --- | ||
| 295 |
2/2✓ Branch 374 → 375 taken 4 times.
✓ Branch 374 → 393 taken 59 times.
|
126 | if (p.enabledCategories.contains(QStringLiteral("identities"))) { |
| 296 |
1/1✓ Branch 375 → 376 taken 4 times.
|
4 | IdentityStore identities(IdentityStore::pathFor(configDir)); |
| 297 |
3/4✓ Branch 378 → 379 taken 4 times.
✓ Branch 379 → 380 taken 4 times.
✗ Branch 379 → 384 not taken.
✓ Branch 380 → 381 taken 4 times.
|
4 | if (identities.load()) p.identities = ComposeSettingsSync::collectIdentities(identities); |
| 298 | ✗ | else p.enabledCategories.removeAll(QStringLiteral("identities")); | |
| 299 | 4 | } | |
| 300 |
2/2✓ Branch 399 → 400 taken 4 times.
✓ Branch 399 → 418 taken 59 times.
|
126 | if (p.enabledCategories.contains(QStringLiteral("templates"))) { |
| 301 |
1/1✓ Branch 400 → 401 taken 4 times.
|
4 | TemplateStore templates(TemplateStore::pathFor(configDir)); |
| 302 |
3/4✓ Branch 403 → 404 taken 4 times.
✓ Branch 404 → 405 taken 4 times.
✗ Branch 404 → 409 not taken.
✓ Branch 405 → 406 taken 4 times.
|
4 | if (templates.load()) p.templates = ComposeSettingsSync::collectTemplates(templates); |
| 303 | ✗ | else p.enabledCategories.removeAll(QStringLiteral("templates")); | |
| 304 | 4 | } | |
| 305 | // The sync message has a size limit: templates, then identities, stay | ||
| 306 | // out of an upload that would exceed it (never half of them — a missing | ||
| 307 | // template would read as deleted on the other installation). | ||
| 308 |
1/2✓ Branch 452 → 428 taken 63 times.
✗ Branch 452 → 453 not taken.
|
378 | for (const QString &category : {QStringLiteral("templates"), QStringLiteral("identities")}) { |
| 309 |
2/3✓ Branch 428 → 429 taken 63 times.
✓ Branch 431 → 432 taken 63 times.
✗ Branch 431 → 433 not taken.
|
63 | if (p.toRfcMessage().size() <= SyncPayload::MaxRfcMessageBytes) break; |
| 310 | ✗ | p.enabledCategories.removeAll(category); | |
| 311 | ✗ | (category == QLatin1String("templates") ? p.templates : p.identities).clear(); | |
| 312 | ✗ | qCWarning(lcSettingsSync) << "Left out of the upload, too large:" << category; | |
| 313 | 189 | } | |
| 314 | |||
| 315 | 63 | return p; | |
| 316 | 63 | } | |
| 317 | |||
| 318 | // ═══════════════════════════════════════════════════════ | ||
| 319 | // applyRemote – apply a remote SyncPayload to local settings | ||
| 320 | // ═══════════════════════════════════════════════════════ | ||
| 321 | |||
| 322 | 16 | QStringList SettingsCollector::applyRemote(const SyncPayload &payload, | |
| 323 | MailCache *cache, | ||
| 324 | const QString &configDir, | ||
| 325 | CalendarStore *calStore) { | ||
| 326 |
1/1✓ Branch 2 → 3 taken 16 times.
|
16 | QSettings s; |
| 327 |
1/1✓ Branch 3 → 4 taken 16 times.
|
16 | const QStringList cats = effectiveRemoteCategories(payload, s); |
| 328 | 16 | QStringList applied; | |
| 329 | |||
| 330 |
5/5✓ Branch 4 → 5 taken 16 times.
✓ Branch 8 → 9 taken 16 times.
✓ Branch 9 → 10 taken 16 times.
✓ Branch 21 → 6 taken 16 times.
✓ Branch 21 → 22 taken 16 times.
|
32 | qCInfo(lcSettingsSync) << "Accepted remote settings categories:" |
| 331 |
2/2✓ Branch 13 → 14 taken 16 times.
✓ Branch 14 → 15 taken 16 times.
|
16 | << cats.join(QStringLiteral(", ")); |
| 332 | |||
| 333 | // --- folderIcons --- | ||
| 334 |
6/12✓ Branch 26 → 27 taken 8 times.
✓ Branch 26 → 30 taken 8 times.
✓ Branch 31 → 32 taken 16 times.
✗ Branch 31 → 33 not taken.
✓ Branch 33 → 34 taken 16 times.
✗ Branch 33 → 35 not taken.
✓ Branch 35 → 36 taken 8 times.
✓ Branch 35 → 115 taken 8 times.
✗ Branch 897 → 898 not taken.
✗ Branch 897 → 899 not taken.
✗ Branch 901 → 902 not taken.
✗ Branch 901 → 903 not taken.
|
40 | if (cats.contains(QStringLiteral("folderIcons")) && |
| 335 |
2/3✓ Branch 27 → 28 taken 8 times.
✓ Branch 28 → 29 taken 8 times.
✗ Branch 28 → 30 not taken.
|
8 | payload.folderIcons.size() <= kMaxSyncedMapEntries) { |
| 336 | // Clear existing folder icons | ||
| 337 |
1/1✓ Branch 40 → 41 taken 8 times.
|
8 | s.beginGroup(QStringLiteral("folder/icon")); |
| 338 |
1/1✓ Branch 45 → 46 taken 8 times.
|
8 | s.remove(QString()); // removes all keys in the group |
| 339 |
1/1✓ Branch 47 → 48 taken 8 times.
|
8 | s.endGroup(); |
| 340 | // Write new ones | ||
| 341 |
1/1✓ Branch 48 → 49 taken 8 times.
|
8 | for (auto it = payload.folderIcons.constBegin(); |
| 342 |
3/3✓ Branch 92 → 93 taken 16 times.
✓ Branch 94 → 50 taken 8 times.
✓ Branch 94 → 95 taken 8 times.
|
16 | it != payload.folderIcons.constEnd(); ++it) { |
| 343 |
1/2✓ Branch 52 → 53 taken 8 times.
✗ Branch 52 → 60 not taken.
|
16 | if (safeText(it.key(), 4096) && |
| 344 |
8/11✓ Branch 53 → 54 taken 8 times.
✓ Branch 56 → 57 taken 8 times.
✓ Branch 57 → 58 taken 8 times.
✓ Branch 58 → 59 taken 7 times.
✓ Branch 58 → 60 taken 1 time.
✓ Branch 61 → 62 taken 8 times.
✗ Branch 61 → 63 not taken.
✓ Branch 63 → 64 taken 7 times.
✓ Branch 63 → 78 taken 1 time.
✗ Branch 916 → 917 not taken.
✗ Branch 916 → 918 not taken.
|
16 | iconNamePattern().match(it.value()).hasMatch()) { |
| 345 |
2/2✓ Branch 70 → 71 taken 7 times.
✓ Branch 72 → 73 taken 7 times.
|
14 | s.setValue(QStringLiteral("folder/icon/") + it.key(), it.value()); |
| 346 | } else { | ||
| 347 |
5/5✓ Branch 78 → 79 taken 1 time.
✓ Branch 82 → 83 taken 1 time.
✓ Branch 83 → 84 taken 1 time.
✓ Branch 89 → 80 taken 1 time.
✓ Branch 89 → 90 taken 1 time.
|
2 | qCWarning(lcSettingsSync) << "Rejected invalid folder icon mapping for" |
| 348 |
1/1✓ Branch 85 → 86 taken 1 time.
|
1 | << it.key(); |
| 349 | } | ||
| 350 | } | ||
| 351 |
7/7✓ Branch 95 → 96 taken 8 times.
✓ Branch 99 → 100 taken 8 times.
✓ Branch 100 → 101 taken 8 times.
✓ Branch 101 → 102 taken 8 times.
✓ Branch 102 → 103 taken 8 times.
✓ Branch 107 → 97 taken 8 times.
✓ Branch 107 → 108 taken 8 times.
|
16 | qCInfo(lcSettingsSync) << "Applied" << payload.folderIcons.size() |
| 352 |
1/1✓ Branch 103 → 104 taken 8 times.
|
8 | << "folder icons"; |
| 353 |
1/1✓ Branch 111 → 112 taken 8 times.
|
8 | applied.append(QStringLiteral("folderIcons")); |
| 354 | } | ||
| 355 | |||
| 356 | // --- folderColors --- | ||
| 357 |
6/12✓ Branch 119 → 120 taken 2 times.
✓ Branch 119 → 123 taken 14 times.
✓ Branch 124 → 125 taken 16 times.
✗ Branch 124 → 126 not taken.
✓ Branch 126 → 127 taken 16 times.
✗ Branch 126 → 128 not taken.
✓ Branch 128 → 129 taken 2 times.
✓ Branch 128 → 208 taken 14 times.
✗ Branch 952 → 953 not taken.
✗ Branch 952 → 954 not taken.
✗ Branch 956 → 957 not taken.
✗ Branch 956 → 958 not taken.
|
34 | if (cats.contains(QStringLiteral("folderColors")) && |
| 358 |
2/3✓ Branch 120 → 121 taken 2 times.
✓ Branch 121 → 122 taken 2 times.
✗ Branch 121 → 123 not taken.
|
2 | payload.folderColors.size() <= kMaxSyncedMapEntries) { |
| 359 |
1/1✓ Branch 133 → 134 taken 2 times.
|
2 | s.beginGroup(QStringLiteral("folder/color")); |
| 360 |
1/1✓ Branch 138 → 139 taken 2 times.
|
2 | s.remove(QString()); |
| 361 |
1/1✓ Branch 140 → 141 taken 2 times.
|
2 | s.endGroup(); |
| 362 |
1/1✓ Branch 141 → 142 taken 2 times.
|
2 | for (auto it = payload.folderColors.constBegin(); |
| 363 |
3/3✓ Branch 185 → 186 taken 5 times.
✓ Branch 187 → 143 taken 3 times.
✓ Branch 187 → 188 taken 2 times.
|
5 | it != payload.folderColors.constEnd(); ++it) { |
| 364 |
1/2✓ Branch 145 → 146 taken 3 times.
✗ Branch 145 → 153 not taken.
|
6 | if (safeText(it.key(), 4096) && |
| 365 |
8/11✓ Branch 146 → 147 taken 3 times.
✓ Branch 149 → 150 taken 3 times.
✓ Branch 150 → 151 taken 3 times.
✓ Branch 151 → 152 taken 2 times.
✓ Branch 151 → 153 taken 1 time.
✓ Branch 154 → 155 taken 3 times.
✗ Branch 154 → 156 not taken.
✓ Branch 156 → 157 taken 2 times.
✓ Branch 156 → 171 taken 1 time.
✗ Branch 971 → 972 not taken.
✗ Branch 971 → 973 not taken.
|
6 | colorPattern().match(it.value()).hasMatch()) { |
| 366 |
2/2✓ Branch 163 → 164 taken 2 times.
✓ Branch 165 → 166 taken 2 times.
|
4 | s.setValue(QStringLiteral("folder/color/") + it.key(), it.value()); |
| 367 | } else { | ||
| 368 |
5/5✓ Branch 171 → 172 taken 1 time.
✓ Branch 175 → 176 taken 1 time.
✓ Branch 176 → 177 taken 1 time.
✓ Branch 182 → 173 taken 1 time.
✓ Branch 182 → 183 taken 1 time.
|
2 | qCWarning(lcSettingsSync) << "Rejected invalid folder color mapping for" |
| 369 |
1/1✓ Branch 178 → 179 taken 1 time.
|
1 | << it.key(); |
| 370 | } | ||
| 371 | } | ||
| 372 |
7/7✓ Branch 188 → 189 taken 2 times.
✓ Branch 192 → 193 taken 2 times.
✓ Branch 193 → 194 taken 2 times.
✓ Branch 194 → 195 taken 2 times.
✓ Branch 195 → 196 taken 2 times.
✓ Branch 200 → 190 taken 2 times.
✓ Branch 200 → 201 taken 2 times.
|
4 | qCInfo(lcSettingsSync) << "Applied" << payload.folderColors.size() |
| 373 |
1/1✓ Branch 196 → 197 taken 2 times.
|
2 | << "folder colors"; |
| 374 |
1/1✓ Branch 204 → 205 taken 2 times.
|
2 | applied.append(QStringLiteral("folderColors")); |
| 375 | } | ||
| 376 | |||
| 377 | // --- hiddenFolders --- | ||
| 378 |
2/2✓ Branch 214 → 215 taken 2 times.
✓ Branch 214 → 257 taken 14 times.
|
16 | if (cats.contains(QStringLiteral("hiddenFolders"))) { |
| 379 | 2 | QStringList hidden; | |
| 380 |
1/1✓ Branch 217 → 218 taken 2 times.
|
2 | hidden.reserve(qMin(payload.hiddenFolders.size(), qsizetype{4096})); |
| 381 |
2/2✓ Branch 234 → 220 taken 5 times.
✓ Branch 234 → 235 taken 2 times.
|
7 | for (const QString &folder : payload.hiddenFolders) { |
| 382 |
1/2✗ Branch 222 → 223 not taken.
✓ Branch 222 → 224 taken 5 times.
|
5 | if (hidden.size() >= 4096) |
| 383 | ✗ | break; | |
| 384 |
6/6✓ Branch 225 → 226 taken 3 times.
✓ Branch 225 → 229 taken 2 times.
✓ Branch 227 → 228 taken 2 times.
✓ Branch 227 → 229 taken 1 time.
✓ Branch 230 → 231 taken 2 times.
✓ Branch 230 → 232 taken 3 times.
|
5 | if (safeText(folder, 4096) && !hidden.contains(folder)) |
| 385 |
1/1✓ Branch 231 → 232 taken 2 times.
|
2 | hidden.append(folder); |
| 386 | } | ||
| 387 |
1/1✓ Branch 235 → 236 taken 2 times.
|
2 | FolderPreferences::saveHidden(configDir, hidden); |
| 388 |
6/6✓ Branch 236 → 237 taken 2 times.
✓ Branch 240 → 241 taken 2 times.
✓ Branch 241 → 242 taken 2 times.
✓ Branch 243 → 244 taken 2 times.
✓ Branch 248 → 238 taken 2 times.
✓ Branch 248 → 249 taken 2 times.
|
4 | qCInfo(lcSettingsSync) << "Applied" << hidden.size() |
| 389 |
1/1✓ Branch 244 → 245 taken 2 times.
|
2 | << "hidden folders"; |
| 390 |
1/1✓ Branch 252 → 253 taken 2 times.
|
2 | applied.append(QStringLiteral("hiddenFolders")); |
| 391 | 2 | } | |
| 392 | |||
| 393 | Q_UNUSED(cache); | ||
| 394 | |||
| 395 | // --- carddavAccounts --- | ||
| 396 | 16 | QSet<QString> acceptedDavIds; | |
| 397 |
2/2✓ Branch 264 → 265 taken 6 times.
✓ Branch 264 → 450 taken 10 times.
|
16 | if (cats.contains(QStringLiteral("davAccounts"))) { |
| 398 | QList<QVariantMap> localAccounts = | ||
| 399 |
1/1✓ Branch 268 → 269 taken 6 times.
|
6 | readSettingsArray(s, QStringLiteral("carddav/accounts")); |
| 400 | 6 | QHash<QString, int> localById; | |
| 401 |
2/2✓ Branch 287 → 272 taken 5 times.
✓ Branch 287 → 288 taken 6 times.
|
11 | for (int i = 0; i < localAccounts.size(); ++i) |
| 402 |
4/4✓ Branch 272 → 273 taken 5 times.
✓ Branch 277 → 278 taken 5 times.
✓ Branch 278 → 279 taken 5 times.
✓ Branch 279 → 280 taken 5 times.
|
10 | localById.insert(localAccounts[i].value(QStringLiteral("id")).toString(), |
| 403 | i); | ||
| 404 | |||
| 405 | 6 | bool changed = false; | |
| 406 | 6 | QSet<QString> seenRemoteIds; | |
| 407 |
2/2✓ Branch 418 → 291 taken 8 times.
✓ Branch 418 → 419 taken 6 times.
|
14 | for (const auto &remote : payload.carddavAccounts) { |
| 408 |
1/1✓ Branch 292 → 293 taken 8 times.
|
8 | const QString remoteId = remote.id.trimmed(); |
| 409 |
7/7✓ Branch 293 → 294 taken 8 times.
✓ Branch 294 → 295 taken 7 times.
✓ Branch 294 → 297 taken 1 time.
✓ Branch 296 → 297 taken 1 time.
✓ Branch 296 → 298 taken 6 times.
✓ Branch 299 → 300 taken 2 times.
✓ Branch 299 → 312 taken 6 times.
|
8 | if (!validDavAccount(remote) || seenRemoteIds.contains(remoteId)) { |
| 410 |
5/5✓ Branch 300 → 301 taken 2 times.
✓ Branch 304 → 305 taken 2 times.
✓ Branch 305 → 306 taken 2 times.
✓ Branch 310 → 302 taken 2 times.
✓ Branch 310 → 311 taken 2 times.
|
4 | qCWarning(lcSettingsSync) << "Rejected invalid or duplicate DAV account" |
| 411 |
1/1✓ Branch 306 → 307 taken 2 times.
|
2 | << remoteId; |
| 412 | 2 | continue; | |
| 413 | 2 | } | |
| 414 |
1/1✓ Branch 312 → 313 taken 6 times.
|
6 | seenRemoteIds.insert(remoteId); |
| 415 | |||
| 416 | 6 | const auto localIt = localById.constFind(remoteId); | |
| 417 |
2/2✓ Branch 316 → 317 taken 4 times.
✓ Branch 316 → 345 taken 2 times.
|
6 | if (localIt != localById.cend()) { |
| 418 |
1/1✓ Branch 318 → 319 taken 4 times.
|
4 | QVariantMap &local = localAccounts[*localIt]; |
| 419 |
3/3✓ Branch 319 → 320 taken 4 times.
✓ Branch 320 → 321 taken 1 time.
✓ Branch 320 → 333 taken 3 times.
|
4 | if (!sameDavIdentity(local, remote)) { |
| 420 |
4/4✓ Branch 321 → 322 taken 1 time.
✓ Branch 325 → 326 taken 1 time.
✓ Branch 331 → 323 taken 1 time.
✓ Branch 331 → 332 taken 1 time.
|
2 | qCWarning(lcSettingsSync) |
| 421 |
2/2✓ Branch 326 → 327 taken 1 time.
✓ Branch 327 → 328 taken 1 time.
|
1 | << "Rejected remote DAV identity change for account" << remoteId; |
| 422 | 1 | continue; | |
| 423 | 1 | } | |
| 424 |
1/1✓ Branch 338 → 339 taken 3 times.
|
6 | local.insert(QStringLiteral("selectedBooks"), |
| 425 |
1/1✓ Branch 333 → 334 taken 3 times.
|
6 | safeDavSelections(remote.selectedBooks)); |
| 426 |
1/1✓ Branch 343 → 344 taken 3 times.
|
3 | acceptedDavIds.insert(remoteId); |
| 427 | 3 | changed = true; | |
| 428 | 3 | continue; | |
| 429 | 3 | } | |
| 430 | |||
| 431 |
1/2✗ Branch 346 → 347 not taken.
✓ Branch 346 → 358 taken 2 times.
|
2 | if (localAccounts.size() >= kMaxDavAccounts) { |
| 432 | ✗ | qCWarning(lcSettingsSync) << "DAV account limit reached"; | |
| 433 | ✗ | break; | |
| 434 | } | ||
| 435 | 2 | QVariantMap pending; | |
| 436 |
1/1✓ Branch 362 → 363 taken 2 times.
|
4 | pending.insert(QStringLiteral("id"), remoteId); |
| 437 |
2/2✓ Branch 366 → 367 taken 2 times.
✓ Branch 371 → 372 taken 2 times.
|
4 | pending.insert(QStringLiteral("serverUrl"), remote.serverUrl.trimmed()); |
| 438 |
2/2✓ Branch 376 → 377 taken 2 times.
✓ Branch 381 → 382 taken 2 times.
|
4 | pending.insert(QStringLiteral("username"), remote.username.trimmed()); |
| 439 |
1/1✓ Branch 391 → 392 taken 2 times.
|
4 | pending.insert(QStringLiteral("selectedBooks"), |
| 440 |
1/1✓ Branch 386 → 387 taken 2 times.
|
4 | safeDavSelections(remote.selectedBooks)); |
| 441 |
1/1✓ Branch 400 → 401 taken 2 times.
|
4 | pending.insert(QStringLiteral("requiresLocalAuthorization"), true); |
| 442 |
1/1✓ Branch 404 → 405 taken 2 times.
|
2 | localAccounts.append(pending); |
| 443 |
1/1✓ Branch 406 → 407 taken 2 times.
|
2 | localById.insert(remoteId, localAccounts.size() - 1); |
| 444 |
1/1✓ Branch 407 → 408 taken 2 times.
|
2 | acceptedDavIds.insert(remoteId); |
| 445 | 2 | changed = true; | |
| 446 | 8 | } | |
| 447 | |||
| 448 |
2/2✓ Branch 419 → 420 taken 4 times.
✓ Branch 419 → 446 taken 2 times.
|
6 | if (changed) { |
| 449 |
1/1✓ Branch 423 → 424 taken 4 times.
|
4 | writeSettingsArray(s, QStringLiteral("carddav/accounts"), localAccounts); |
| 450 |
1/1✓ Branch 429 → 430 taken 4 times.
|
4 | applied.append(QStringLiteral("davAccounts")); |
| 451 |
6/6✓ Branch 432 → 433 taken 4 times.
✓ Branch 436 → 437 taken 4 times.
✓ Branch 437 → 438 taken 4 times.
✓ Branch 439 → 440 taken 4 times.
✓ Branch 444 → 434 taken 4 times.
✓ Branch 444 → 445 taken 4 times.
|
8 | qCInfo(lcSettingsSync) << "Merged" << acceptedDavIds.size() |
| 452 | << "DAV account selections without changing" | ||
| 453 |
1/1✓ Branch 440 → 441 taken 4 times.
|
4 | " local endpoint identities"; |
| 454 | } | ||
| 455 | 6 | } | |
| 456 | |||
| 457 | // --- caldavConfigs (T-334: calendar selection per CardDAV account) --- | ||
| 458 |
6/8✓ Branch 454 → 455 taken 6 times.
✓ Branch 454 → 458 taken 10 times.
✓ Branch 459 → 460 taken 16 times.
✗ Branch 459 → 461 not taken.
✓ Branch 461 → 462 taken 16 times.
✗ Branch 461 → 463 not taken.
✓ Branch 463 → 464 taken 4 times.
✓ Branch 463 → 590 taken 12 times.
|
38 | if (cats.contains(QStringLiteral("davAccounts")) && |
| 459 |
2/2✓ Branch 456 → 457 taken 4 times.
✓ Branch 456 → 458 taken 2 times.
|
6 | !acceptedDavIds.isEmpty()) { |
| 460 | QList<QVariantMap> localConfigs = | ||
| 461 |
1/1✓ Branch 467 → 468 taken 4 times.
|
4 | readSettingsArray(s, QStringLiteral("caldav/configs")); |
| 462 | 4 | QHash<QString, int> localConfigById; | |
| 463 |
2/2✓ Branch 486 → 471 taken 1 time.
✓ Branch 486 → 487 taken 4 times.
|
5 | for (int i = 0; i < localConfigs.size(); ++i) { |
| 464 |
1/1✓ Branch 478 → 479 taken 1 time.
|
1 | localConfigById.insert( |
| 465 |
1/1✓ Branch 471 → 472 taken 1 time.
|
1 | localConfigs[i] |
| 466 |
1/1✓ Branch 476 → 477 taken 1 time.
|
3 | .value(QStringLiteral("carddavAccountId")) |
| 467 |
1/1✓ Branch 477 → 478 taken 1 time.
|
3 | .toString(), |
| 468 | i); | ||
| 469 | } | ||
| 470 | |||
| 471 | 4 | bool configsChanged = false; | |
| 472 |
2/2✓ Branch 578 → 489 taken 3 times.
✓ Branch 578 → 579 taken 4 times.
|
7 | for (const auto &remote : payload.caldavConfigs) { |
| 473 |
1/1✓ Branch 490 → 491 taken 3 times.
|
3 | const QString accountId = remote.carddavAccountId.trimmed(); |
| 474 |
2/2✓ Branch 492 → 493 taken 1 time.
✓ Branch 492 → 494 taken 2 times.
|
3 | if (!acceptedDavIds.contains(accountId)) |
| 475 | 1 | continue; | |
| 476 |
1/1✓ Branch 494 → 495 taken 2 times.
|
2 | const QStringList selected = safeDavSelections(remote.selectedCalendars); |
| 477 |
1/1✓ Branch 495 → 496 taken 2 times.
|
2 | QStringList readOnly = safeDavSelections(remote.readOnlyCalendars); |
| 478 |
4/4✓ Branch 496 → 497 taken 2 times.
✓ Branch 505 → 506 taken 4 times.
✓ Branch 507 → 498 taken 2 times.
✓ Branch 507 → 508 taken 2 times.
|
4 | for (auto it = readOnly.begin(); it != readOnly.end();) { |
| 479 |
2/2✓ Branch 500 → 501 taken 1 time.
✓ Branch 500 → 504 taken 1 time.
|
2 | if (!selected.contains(*it)) |
| 480 |
1/1✓ Branch 502 → 503 taken 1 time.
|
1 | it = readOnly.erase(it); |
| 481 | else | ||
| 482 | 1 | ++it; | |
| 483 | } | ||
| 484 | |||
| 485 | 2 | auto localIt = localConfigById.constFind(accountId); | |
| 486 |
2/2✓ Branch 511 → 512 taken 1 time.
✓ Branch 511 → 549 taken 1 time.
|
2 | if (localIt == localConfigById.cend()) { |
| 487 | 1 | QVariantMap config; | |
| 488 |
1/1✓ Branch 516 → 517 taken 1 time.
|
2 | config.insert(QStringLiteral("carddavAccountId"), accountId); |
| 489 |
1/1✓ Branch 524 → 525 taken 1 time.
|
2 | config.insert(QStringLiteral("selectedCalendars"), selected); |
| 490 |
1/1✓ Branch 532 → 533 taken 1 time.
|
2 | config.insert(QStringLiteral("readOnlyCalendars"), readOnly); |
| 491 | // Scheduling remains a local policy; do not copy the remote interval. | ||
| 492 |
1/1✓ Branch 540 → 541 taken 1 time.
|
2 | config.insert(QStringLiteral("syncIntervalMin"), 15); |
| 493 |
1/1✓ Branch 544 → 545 taken 1 time.
|
1 | localConfigs.append(config); |
| 494 |
1/1✓ Branch 546 → 547 taken 1 time.
|
1 | localConfigById.insert(accountId, localConfigs.size() - 1); |
| 495 | 1 | } else { | |
| 496 |
1/1✓ Branch 550 → 551 taken 1 time.
|
1 | QVariantMap &config = localConfigs[*localIt]; |
| 497 |
1/1✓ Branch 555 → 556 taken 1 time.
|
2 | config.insert(QStringLiteral("selectedCalendars"), selected); |
| 498 |
1/1✓ Branch 563 → 564 taken 1 time.
|
2 | config.insert(QStringLiteral("readOnlyCalendars"), readOnly); |
| 499 | } | ||
| 500 | 2 | configsChanged = true; | |
| 501 | 3 | } | |
| 502 |
2/2✓ Branch 579 → 580 taken 1 time.
✓ Branch 579 → 587 taken 3 times.
|
4 | if (configsChanged) |
| 503 |
1/1✓ Branch 583 → 584 taken 1 time.
|
1 | writeSettingsArray(s, QStringLiteral("caldav/configs"), localConfigs); |
| 504 | 4 | } | |
| 505 | |||
| 506 | // --- calendarColors (to CalendarStore SQLite) --- | ||
| 507 |
8/14✓ Branch 594 → 595 taken 2 times.
✓ Branch 594 → 599 taken 14 times.
✓ Branch 595 → 596 taken 1 time.
✓ Branch 595 → 599 taken 1 time.
✓ Branch 600 → 601 taken 16 times.
✗ Branch 600 → 602 not taken.
✓ Branch 602 → 603 taken 16 times.
✗ Branch 602 → 604 not taken.
✓ Branch 604 → 605 taken 1 time.
✓ Branch 604 → 658 taken 15 times.
✗ Branch 1262 → 1263 not taken.
✗ Branch 1262 → 1264 not taken.
✗ Branch 1266 → 1267 not taken.
✗ Branch 1266 → 1268 not taken.
|
33 | if (cats.contains(QStringLiteral("calendarColors")) && calStore && |
| 508 |
2/3✓ Branch 596 → 597 taken 1 time.
✓ Branch 597 → 598 taken 1 time.
✗ Branch 597 → 599 not taken.
|
1 | payload.calendarColors.size() <= kMaxSyncedMapEntries) { |
| 509 |
1/1✓ Branch 605 → 606 taken 1 time.
|
1 | for (auto it = payload.calendarColors.constBegin(); |
| 510 |
3/3✓ Branch 635 → 636 taken 4 times.
✓ Branch 637 → 607 taken 3 times.
✓ Branch 637 → 638 taken 1 time.
|
4 | it != payload.calendarColors.constEnd(); ++it) { |
| 511 | 3 | QString accountId; | |
| 512 | 3 | QString calendarPath; | |
| 513 | const bool accountAware = | ||
| 514 |
1/1✓ Branch 608 → 609 taken 3 times.
|
3 | decodeCalendarColorKey(it.key(), &accountId, &calendarPath); |
| 515 |
2/2✓ Branch 609 → 610 taken 1 time.
✓ Branch 609 → 611 taken 2 times.
|
3 | const QString validatedPath = accountAware ? calendarPath : it.key(); |
| 516 |
1/2✓ Branch 614 → 615 taken 3 times.
✗ Branch 614 → 622 not taken.
|
6 | if (safeText(validatedPath, 4096) && |
| 517 |
8/11✓ Branch 615 → 616 taken 3 times.
✓ Branch 618 → 619 taken 3 times.
✓ Branch 619 → 620 taken 3 times.
✓ Branch 620 → 621 taken 2 times.
✓ Branch 620 → 622 taken 1 time.
✓ Branch 623 → 624 taken 3 times.
✗ Branch 623 → 625 not taken.
✓ Branch 625 → 626 taken 2 times.
✓ Branch 625 → 631 taken 1 time.
✗ Branch 1270 → 1271 not taken.
✗ Branch 1270 → 1272 not taken.
|
6 | colorPattern().match(it.value()).hasMatch()) { |
| 518 |
2/2✓ Branch 626 → 627 taken 1 time.
✓ Branch 626 → 629 taken 1 time.
|
2 | if (accountAware) |
| 519 |
1/1✓ Branch 628 → 631 taken 1 time.
|
1 | calStore->setCalendarColor(accountId, calendarPath, it.value()); |
| 520 | else | ||
| 521 |
1/1✓ Branch 630 → 631 taken 1 time.
|
1 | calStore->setCalendarColor(validatedPath, it.value()); |
| 522 | } | ||
| 523 | 3 | } | |
| 524 |
7/7✓ Branch 638 → 639 taken 1 time.
✓ Branch 642 → 643 taken 1 time.
✓ Branch 643 → 644 taken 1 time.
✓ Branch 644 → 645 taken 1 time.
✓ Branch 645 → 646 taken 1 time.
✓ Branch 650 → 640 taken 1 time.
✓ Branch 650 → 651 taken 1 time.
|
2 | qCInfo(lcSettingsSync) << "Applied" << payload.calendarColors.size() |
| 525 |
1/1✓ Branch 646 → 647 taken 1 time.
|
1 | << "calendar colors"; |
| 526 |
1/1✓ Branch 654 → 655 taken 1 time.
|
1 | applied.append(QStringLiteral("calendarColors")); |
| 527 | } | ||
| 528 | |||
| 529 | // --- general --- | ||
| 530 |
2/2✓ Branch 664 → 665 taken 10 times.
✓ Branch 664 → 763 taken 6 times.
|
16 | if (cats.contains(QStringLiteral("general"))) { |
| 531 | 10 | bool generalApplied = false; | |
| 532 |
5/6✓ Branch 674 → 675 taken 2 times.
✓ Branch 674 → 680 taken 6 times.
✓ Branch 692 → 693 taken 10 times.
✗ Branch 692 → 694 not taken.
✓ Branch 694 → 695 taken 9 times.
✓ Branch 694 → 705 taken 1 time.
|
28 | if (payload.general.defaultView == QStringLiteral("text") || |
| 533 |
7/8✓ Branch 669 → 670 taken 8 times.
✓ Branch 669 → 680 taken 2 times.
✓ Branch 679 → 680 taken 1 time.
✓ Branch 679 → 681 taken 1 time.
✓ Branch 688 → 689 taken 8 times.
✓ Branch 688 → 690 taken 2 times.
✓ Branch 690 → 691 taken 10 times.
✗ Branch 690 → 692 not taken.
|
46 | payload.general.defaultView == QStringLiteral("light") || |
| 534 |
6/6✓ Branch 682 → 683 taken 2 times.
✓ Branch 682 → 684 taken 8 times.
✓ Branch 684 → 685 taken 2 times.
✓ Branch 684 → 686 taken 8 times.
✓ Branch 686 → 687 taken 8 times.
✓ Branch 686 → 688 taken 2 times.
|
12 | payload.general.defaultView == QStringLiteral("html")) { |
| 535 |
1/1✓ Branch 700 → 701 taken 9 times.
|
9 | s.setValue(QStringLiteral("view/defaultMode"), |
| 536 | 9 | payload.general.defaultView); | |
| 537 | 9 | generalApplied = true; | |
| 538 | } | ||
| 539 |
4/6✓ Branch 714 → 715 taken 4 times.
✗ Branch 714 → 720 not taken.
✓ Branch 732 → 733 taken 10 times.
✗ Branch 732 → 734 not taken.
✓ Branch 734 → 735 taken 9 times.
✓ Branch 734 → 745 taken 1 time.
|
24 | if (payload.general.language == QStringLiteral("auto") || |
| 540 |
7/8✓ Branch 709 → 710 taken 4 times.
✓ Branch 709 → 720 taken 6 times.
✓ Branch 719 → 720 taken 3 times.
✓ Branch 719 → 721 taken 1 time.
✓ Branch 728 → 729 taken 4 times.
✓ Branch 728 → 730 taken 6 times.
✓ Branch 730 → 731 taken 10 times.
✗ Branch 730 → 732 not taken.
|
38 | payload.general.language == QStringLiteral("en") || |
| 541 |
6/6✓ Branch 722 → 723 taken 4 times.
✓ Branch 722 → 724 taken 6 times.
✓ Branch 724 → 725 taken 4 times.
✓ Branch 724 → 726 taken 6 times.
✓ Branch 726 → 727 taken 4 times.
✓ Branch 726 → 728 taken 6 times.
|
14 | payload.general.language == QStringLiteral("de")) { |
| 542 |
1/1✓ Branch 740 → 741 taken 9 times.
|
18 | s.setValue(QStringLiteral("i18n/language"), payload.general.language); |
| 543 | 9 | generalApplied = true; | |
| 544 | } | ||
| 545 | // view/externalContent and all network scheduling intervals stay local. | ||
| 546 |
2/2✓ Branch 745 → 746 taken 9 times.
✓ Branch 745 → 763 taken 1 time.
|
10 | if (generalApplied) { |
| 547 |
1/1✓ Branch 749 → 750 taken 9 times.
|
9 | applied.append(QStringLiteral("general")); |
| 548 |
5/5✓ Branch 752 → 753 taken 9 times.
✓ Branch 756 → 757 taken 9 times.
✓ Branch 757 → 758 taken 9 times.
✓ Branch 761 → 754 taken 9 times.
✓ Branch 761 → 762 taken 9 times.
|
18 | qCInfo(lcSettingsSync) << "Applied non-sensitive general settings"; |
| 549 | } | ||
| 550 | } | ||
| 551 | |||
| 552 | // --- identities, templates: the composer's own files --- | ||
| 553 |
2/2✓ Branch 769 → 770 taken 1 time.
✓ Branch 769 → 801 taken 15 times.
|
16 | if (cats.contains(QStringLiteral("identities"))) { |
| 554 |
1/1✓ Branch 770 → 771 taken 1 time.
|
1 | IdentityStore identities(IdentityStore::pathFor(configDir)); |
| 555 |
7/11✓ Branch 773 → 774 taken 1 time.
✓ Branch 774 → 775 taken 1 time.
✗ Branch 774 → 780 not taken.
✓ Branch 775 → 776 taken 1 time.
✓ Branch 776 → 777 taken 1 time.
✗ Branch 776 → 780 not taken.
✓ Branch 777 → 778 taken 1 time.
✓ Branch 778 → 779 taken 1 time.
✗ Branch 778 → 780 not taken.
✓ Branch 781 → 782 taken 1 time.
✗ Branch 781 → 799 not taken.
|
1 | if (identities.load() && ComposeSettingsSync::applyIdentities(identities, payload.identities) && identities.save()) { |
| 556 |
1/1✓ Branch 785 → 786 taken 1 time.
|
1 | applied.append(QStringLiteral("identities")); |
| 557 |
5/5✓ Branch 788 → 789 taken 1 time.
✓ Branch 792 → 793 taken 1 time.
✓ Branch 793 → 794 taken 1 time.
✓ Branch 797 → 790 taken 1 time.
✓ Branch 797 → 798 taken 1 time.
|
2 | qCInfo(lcSettingsSync) << "Applied signatures and address settings"; |
| 558 | } | ||
| 559 | 1 | } | |
| 560 |
2/2✓ Branch 807 → 808 taken 1 time.
✓ Branch 807 → 842 taken 15 times.
|
16 | if (cats.contains(QStringLiteral("templates"))) { |
| 561 |
1/1✓ Branch 808 → 809 taken 1 time.
|
1 | TemplateStore templates(TemplateStore::pathFor(configDir)); |
| 562 |
7/11✓ Branch 811 → 812 taken 1 time.
✓ Branch 812 → 813 taken 1 time.
✗ Branch 812 → 818 not taken.
✓ Branch 813 → 814 taken 1 time.
✓ Branch 814 → 815 taken 1 time.
✗ Branch 814 → 818 not taken.
✓ Branch 815 → 816 taken 1 time.
✓ Branch 816 → 817 taken 1 time.
✗ Branch 816 → 818 not taken.
✓ Branch 819 → 820 taken 1 time.
✗ Branch 819 → 840 not taken.
|
1 | if (templates.load() && ComposeSettingsSync::applyTemplates(templates, payload.templates) && templates.save()) { |
| 563 |
1/1✓ Branch 823 → 824 taken 1 time.
|
1 | applied.append(QStringLiteral("templates")); |
| 564 |
6/6✓ Branch 826 → 827 taken 1 time.
✓ Branch 830 → 831 taken 1 time.
✓ Branch 831 → 832 taken 1 time.
✓ Branch 834 → 835 taken 1 time.
✓ Branch 838 → 828 taken 1 time.
✓ Branch 838 → 839 taken 1 time.
|
2 | qCInfo(lcSettingsSync) << "Applied templates" << templates.templates().size(); |
| 565 | } | ||
| 566 | 1 | } | |
| 567 | |||
| 568 |
2/2✓ Branch 843 → 844 taken 13 times.
✓ Branch 843 → 858 taken 3 times.
|
16 | if (!applied.isEmpty()) { |
| 569 |
1/1✓ Branch 851 → 852 taken 13 times.
|
26 | s.setValue(QStringLiteral("sync/lastSync"), |
| 570 |
2/2✓ Branch 844 → 845 taken 13 times.
✓ Branch 845 → 846 taken 13 times.
|
26 | QDateTime::currentDateTimeUtc().toString(Qt::ISODate)); |
| 571 | } | ||
| 572 | |||
| 573 |
1/1✓ Branch 858 → 859 taken 16 times.
|
16 | s.sync(); |
| 574 |
3/4✓ Branch 860 → 861 taken 16 times.
✓ Branch 861 → 862 taken 16 times.
✗ Branch 863 → 864 not taken.
✓ Branch 863 → 878 taken 16 times.
|
16 | if (!QFile::setPermissions(s.fileName(), |
| 575 | QFileDevice::ReadOwner | QFileDevice::WriteOwner)) { | ||
| 576 | ✗ | qCWarning(lcSettingsSync) << "Failed to restrict settings permissions for" | |
| 577 | ✗ | << s.fileName(); | |
| 578 | } | ||
| 579 | 16 | return applied; | |
| 580 | 16 | } | |
| 581 | |||
| 582 | // ═══════════════════════════════════════════════════════ | ||
| 583 | // categoryChanged – diff helper | ||
| 584 | // ═══════════════════════════════════════════════════════ | ||
| 585 | |||
| 586 | 33 | bool SettingsCollector::categoryChanged(const QString &category, | |
| 587 | const SyncPayload &local, | ||
| 588 | const SyncPayload &remote) { | ||
| 589 |
2/2✓ Branch 8 → 9 taken 3 times.
✓ Branch 8 → 11 taken 30 times.
|
33 | if (category == QStringLiteral("folderIcons")) |
| 590 | 3 | return local.folderIcons != remote.folderIcons; | |
| 591 |
2/2✓ Branch 17 → 18 taken 2 times.
✓ Branch 17 → 20 taken 28 times.
|
30 | if (category == QStringLiteral("folderColors")) |
| 592 | 2 | return local.folderColors != remote.folderColors; | |
| 593 |
2/2✓ Branch 26 → 27 taken 2 times.
✓ Branch 26 → 29 taken 26 times.
|
28 | if (category == QStringLiteral("hiddenFolders")) |
| 594 | 2 | return local.hiddenFolders != remote.hiddenFolders; | |
| 595 |
2/2✓ Branch 35 → 36 taken 3 times.
✓ Branch 35 → 57 taken 23 times.
|
26 | if (category == QStringLiteral("externalContentWhitelist")) { |
| 596 |
2/2✓ Branch 38 → 39 taken 1 time.
✓ Branch 38 → 40 taken 2 times.
|
6 | if (local.externalContentWhitelist.size() != |
| 597 | 3 | remote.externalContentWhitelist.size()) | |
| 598 | 1 | return true; | |
| 599 |
2/2✓ Branch 55 → 41 taken 2 times.
✓ Branch 55 → 56 taken 1 time.
|
3 | for (int i = 0; i < local.externalContentWhitelist.size(); ++i) { |
| 600 | 8 | if (local.externalContentWhitelist[i].type != | |
| 601 |
3/4✓ Branch 44 → 45 taken 2 times.
✗ Branch 44 → 49 not taken.
✓ Branch 51 → 52 taken 1 time.
✓ Branch 51 → 53 taken 1 time.
|
4 | remote.externalContentWhitelist[i].type || |
| 602 |
2/2✓ Branch 48 → 49 taken 1 time.
✓ Branch 48 → 50 taken 1 time.
|
2 | local.externalContentWhitelist[i].value != |
| 603 | 2 | remote.externalContentWhitelist[i].value) | |
| 604 | 1 | return true; | |
| 605 | } | ||
| 606 | 1 | return false; | |
| 607 | } | ||
| 608 |
2/2✓ Branch 63 → 64 taken 2 times.
✓ Branch 63 → 66 taken 21 times.
|
23 | if (category == QStringLiteral("calendarColors")) |
| 609 | 2 | return local.calendarColors != remote.calendarColors; | |
| 610 |
7/8✓ Branch 70 → 71 taken 15 times.
✓ Branch 70 → 76 taken 6 times.
✓ Branch 75 → 76 taken 4 times.
✓ Branch 75 → 77 taken 11 times.
✓ Branch 84 → 85 taken 21 times.
✗ Branch 84 → 86 not taken.
✓ Branch 86 → 87 taken 10 times.
✓ Branch 86 → 167 taken 11 times.
|
78 | if (category == QStringLiteral("davAccounts") || |
| 611 |
5/6✓ Branch 78 → 79 taken 15 times.
✓ Branch 78 → 80 taken 6 times.
✓ Branch 80 → 81 taken 15 times.
✓ Branch 80 → 82 taken 6 times.
✓ Branch 82 → 83 taken 21 times.
✗ Branch 82 → 84 not taken.
|
36 | category == QStringLiteral("carddavAccounts")) { |
| 612 | // F-73 (SPRINT-104): structural diff — compare the accounts and | ||
| 613 | // configs as id-keyed maps, order-insensitively. The former | ||
| 614 | // index-wise comparison reported a conflict forever for the same | ||
| 615 | // content stored in a different order, and neither resolution action | ||
| 616 | // could clear it (applyRemote merges by id and never reorders). | ||
| 617 | // syncIntervalMinutes is deliberately NOT compared: applyRemote never | ||
| 618 | // adopts it (scheduling is a local policy), so comparing it re-raised | ||
| 619 | // the conflict after every "use theirs". | ||
| 620 | // The raw list sizes catch duplicate-id entries the id map would | ||
| 621 | // silently deduplicate. | ||
| 622 |
2/2✓ Branch 89 → 90 taken 1 time.
✓ Branch 89 → 91 taken 9 times.
|
10 | if (local.carddavAccounts.size() != remote.carddavAccounts.size()) |
| 623 | 1 | return true; | |
| 624 | 9 | QHash<QString, SyncPayload::CardDavAccount> localAccounts, remoteAccounts; | |
| 625 |
2/2✓ Branch 97 → 93 taken 13 times.
✓ Branch 97 → 98 taken 9 times.
|
22 | for (const auto &entry : local.carddavAccounts) |
| 626 |
1/1✓ Branch 94 → 95 taken 13 times.
|
13 | localAccounts.insert(entry.id, entry); |
| 627 |
2/2✓ Branch 104 → 100 taken 13 times.
✓ Branch 104 → 105 taken 9 times.
|
22 | for (const auto &entry : remote.carddavAccounts) |
| 628 |
1/1✓ Branch 101 → 102 taken 13 times.
|
13 | remoteAccounts.insert(entry.id, entry); |
| 629 |
1/2✗ Branch 107 → 108 not taken.
✓ Branch 107 → 109 taken 9 times.
|
9 | if (localAccounts.size() != remoteAccounts.size()) |
| 630 | ✗ | return true; | |
| 631 |
2/2✓ Branch 136 → 110 taken 12 times.
✓ Branch 136 → 137 taken 6 times.
|
18 | for (auto it = localAccounts.cbegin(); it != localAccounts.cend(); ++it) { |
| 632 | 12 | const auto other = remoteAccounts.constFind(it.key()); | |
| 633 |
1/2✗ Branch 114 → 115 not taken.
✓ Branch 114 → 116 taken 12 times.
|
12 | if (other == remoteAccounts.cend()) |
| 634 | 3 | return true; | |
| 635 |
4/6✓ Branch 119 → 120 taken 12 times.
✗ Branch 119 → 128 not taken.
✓ Branch 123 → 124 taken 12 times.
✗ Branch 123 → 128 not taken.
✓ Branch 130 → 131 taken 3 times.
✓ Branch 130 → 132 taken 9 times.
|
24 | if (it->serverUrl != other->serverUrl || it->username != other->username || |
| 636 |
3/3✓ Branch 126 → 127 taken 12 times.
✓ Branch 127 → 128 taken 3 times.
✓ Branch 127 → 129 taken 9 times.
|
12 | it->selectedBooks != other->selectedBooks) |
| 637 | 3 | return true; | |
| 638 | } | ||
| 639 | // A config's identity is its account (1:1); compare the synced fields. | ||
| 640 |
2/2✓ Branch 139 → 140 taken 1 time.
✓ Branch 139 → 141 taken 5 times.
|
6 | if (local.caldavConfigs.size() != remote.caldavConfigs.size()) |
| 641 | 1 | return true; | |
| 642 | 7 | const auto configFor = [](const QList<SyncPayload::CalDavSyncConfig> &list, const QString &accountId) { | |
| 643 | 7 | return std::find_if(list.cbegin(), list.cend(), | |
| 644 | 9 | [&accountId](const SyncPayload::CalDavSyncConfig &config) { | |
| 645 | 9 | return config.carddavAccountId == accountId; | |
| 646 | 7 | }); | |
| 647 | }; | ||
| 648 |
2/2✓ Branch 162 → 143 taken 7 times.
✓ Branch 162 → 163 taken 5 times.
|
12 | for (const auto &config : local.caldavConfigs) { |
| 649 |
1/1✓ Branch 144 → 145 taken 7 times.
|
7 | const auto other = configFor(remote.caldavConfigs, config.carddavAccountId); |
| 650 |
1/2✗ Branch 147 → 148 not taken.
✓ Branch 147 → 149 taken 7 times.
|
7 | if (other == remote.caldavConfigs.cend()) |
| 651 | ✗ | return true; | |
| 652 |
3/5✓ Branch 150 → 151 taken 7 times.
✓ Branch 151 → 152 taken 7 times.
✗ Branch 151 → 155 not taken.
✗ Branch 157 → 158 not taken.
✓ Branch 157 → 159 taken 7 times.
|
14 | if (config.selectedCalendars != other->selectedCalendars || |
| 653 |
2/3✓ Branch 153 → 154 taken 7 times.
✗ Branch 154 → 155 not taken.
✓ Branch 154 → 156 taken 7 times.
|
7 | config.readOnlyCalendars != other->readOnlyCalendars) |
| 654 | ✗ | return true; | |
| 655 | } | ||
| 656 | 5 | return false; | |
| 657 | 9 | } | |
| 658 |
2/2✓ Branch 173 → 174 taken 2 times.
✓ Branch 173 → 198 taken 9 times.
|
11 | if (category == QStringLiteral("identities")) { |
| 659 | // Only addresses both installations have: applyRemote never adds one. | ||
| 660 |
4/4✓ Branch 174 → 175 taken 2 times.
✓ Branch 194 → 195 taken 3 times.
✓ Branch 196 → 176 taken 2 times.
✓ Branch 196 → 197 taken 1 time.
|
3 | for (auto it = local.identities.constBegin(); it != local.identities.constEnd(); ++it) |
| 661 |
11/18✓ Branch 177 → 178 taken 2 times.
✓ Branch 178 → 179 taken 2 times.
✗ Branch 178 → 186 not taken.
✓ Branch 180 → 181 taken 2 times.
✓ Branch 182 → 183 taken 2 times.
✓ Branch 183 → 184 taken 2 times.
✓ Branch 184 → 185 taken 1 time.
✓ Branch 184 → 186 taken 1 time.
✓ Branch 187 → 188 taken 2 times.
✗ Branch 187 → 189 not taken.
✓ Branch 189 → 190 taken 2 times.
✗ Branch 189 → 191 not taken.
✓ Branch 191 → 192 taken 1 time.
✓ Branch 191 → 193 taken 1 time.
✗ Branch 234 → 235 not taken.
✗ Branch 234 → 236 not taken.
✗ Branch 238 → 239 not taken.
✗ Branch 238 → 240 not taken.
|
2 | if (remote.identities.contains(it.key()) && remote.identities.value(it.key()) != it.value()) |
| 662 | 1 | return true; | |
| 663 | 1 | return false; | |
| 664 | } | ||
| 665 |
2/2✓ Branch 204 → 205 taken 1 time.
✓ Branch 204 → 207 taken 8 times.
|
9 | if (category == QStringLiteral("templates")) |
| 666 | 1 | return local.templates != remote.templates; | |
| 667 |
2/2✓ Branch 213 → 214 taken 7 times.
✓ Branch 213 → 221 taken 1 time.
|
8 | if (category == QStringLiteral("general")) { |
| 668 | // S106-205 (the F-73 family): only what applyRemote adopts — external | ||
| 669 | // content and the sync intervals stay local, so a difference there could | ||
| 670 | // never be cleared by either resolution. | ||
| 671 |
2/2✓ Branch 215 → 216 taken 6 times.
✓ Branch 215 → 218 taken 1 time.
|
13 | return local.general.defaultView != remote.general.defaultView || |
| 672 |
2/2✓ Branch 217 → 218 taken 2 times.
✓ Branch 217 → 219 taken 4 times.
|
13 | local.general.language != remote.general.language; |
| 673 | } | ||
| 674 | 1 | return false; | |
| 675 | } | ||
| 676 |