Update a contact on re-import rather than skipping it

#228 skipped a vCard whose UID the book already held. The reporter asked for
the opposite on #174 and he is right: the reason to import a file a second
time is usually that the first one was not right, so skipping means a
corrected export corrects nothing.

A merge, not a replacement. Properties the file carries overwrite what is
here; properties it does not mention are left alone, so a phone number added
in ihasmail after the first import survives a re-import of the original file.
The cost is that a field genuinely deleted at the source stays here, which is
the better way to be wrong -- the other way round loses work nobody asked to
lose. Worth confirming with him rather than assuming.

`addressBookIds` is left off the patch. The card is already in this book, so
saying it again says nothing, and saying it on a card that is also in another
book would move it.

Creates and updates now share one batch budget. Stalwart counts every object
in a /set together, so batching the halves separately would send 300 new and
300 changed as two calls of 300 and be refused for a limit of 500 that neither
half exceeds.

LDIF is untouched and still reports look-alikes without acting on them, since
what it should match on is the question still open on #223. Both imports keep
one answer shape so a caller need not know which it called; LDIF's `updated`
is always 0, which is the honest number rather than a missing field.

The message a vCard attached to a message shows changes with it: the newer
copy now wins instead of being dropped, so it says the contact was brought up
to date rather than that nothing was added.

Refs #223.
This commit is contained in:
2026-09-02 15:06:04 -07:00
parent af0e8b2be2
commit 9f4bd65fd5
16 changed files with 210 additions and 99 deletions
+3 -2
View File
@@ -1049,10 +1049,11 @@ export const catalog: Catalog = {
"Nothing unread here": "Тут немає непрочитаного",
},
plurals: {
"Updated {n} contacts, nothing new": { one: "Оновлено {n} контакт, нових немає", few: "Оновлено {n} контакти, нових немає", many: "Оновлено {n} контактів, нових немає", other: "Оновлено {n} контакти, нових немає" },
"{n} updated": { one: "{n} оновлено", few: "{n} оновлено", many: "{n} оновлено", other: "{n} оновлено" },
"Updated {n} contacts you already had": { one: "Оновлено контакт, який уже був", few: "Оновлено {n} контакти, які вже були", many: "Оновлено {n} контактів, які вже були", other: "Оновлено {n} контакти, які вже були" },
"{n} of them look like contacts you already had": { one: "{n} з них схожий на контакт, який уже був", few: "{n} з них схожі на контакти, які вже були", many: "{n} з них схожі на контакти, які вже були", other: "{n} з них схожі на контакти, які вже були" },
"Your administrator changed {n} settings": { one: "Адміністратор змінив {n} налаштування", few: "Адміністратор змінив {n} налаштування", many: "Адміністратор змінив {n} налаштувань", other: "Адміністратор змінив {n} налаштування" },
"Already here: {n} contacts, nothing imported": { one: "Уже є: {n} контакт, нічого не імпортовано", few: "Уже є: {n} контакти, нічого не імпортовано", many: "Уже є: {n} контактів, нічого не імпортовано", other: "Уже є: {n} контакти, нічого не імпортовано" },
"All {n} are already in your contacts": { one: "Уже у ваших контактах", few: "Усі {n} уже у ваших контактах", many: "Усі {n} уже у ваших контактах", other: "Усі {n} уже у ваших контактах" },
"Exported {n} events": { one: "Експортовано {n} подію", few: "Експортовано {n} події", many: "Експортовано {n} подій", other: "Експортовано {n} події" },
"Imported {n} events": { one: "Імпортовано {n} подію", few: "Імпортовано {n} події", many: "Імпортовано {n} подій", other: "Імпортовано {n} події" },
"Already here: {n} events, nothing imported": { one: "Уже є: {n} подія, нічого не імпортовано", few: "Уже є: {n} події, нічого не імпортовано", many: "Уже є: {n} подій, нічого не імпортовано", other: "Уже є: {n} події, нічого не імпортовано" },