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:
@@ -136,22 +136,24 @@ export function ContactsView({ id }: { id?: string }) {
|
||||
* LDIF may arrive as .ldif, .ldi, .txt or with no extension at all, and
|
||||
* the name is the least reliable thing about it.
|
||||
*/
|
||||
const { created, skipped, alike } = /^\s*BEGIN:VCARD/im.test(text)
|
||||
const { created, updated, alike } = /^\s*BEGIN:VCARD/im.test(text)
|
||||
? await contacts.importVCard(text, book.id)
|
||||
: await contacts.importLdif(text, book.id);
|
||||
/*
|
||||
* The two counts kept apart, as the calendar import keeps them: "Imported
|
||||
* 3 contacts" over a file of two hundred reads as a failure when the rest
|
||||
* were simply already here, and a re-import of an unchanged export would
|
||||
* otherwise report importing nothing at all.
|
||||
* The counts kept apart, as the calendar import keeps them. "Imported 3
|
||||
* contacts" over a file of two hundred reads as a failure when the other
|
||||
* hundred and ninety-seven were updated, and a re-import of a corrected
|
||||
* export -- the reason for doing this at all -- creates nothing and would
|
||||
* otherwise report importing nothing.
|
||||
*/
|
||||
const imported = plural(created, { one: "Imported {n} contact", other: "Imported {n} contacts" });
|
||||
if (!created) toast.success(plural(skipped, { one: "Already here: {n} contact, nothing imported", other: "Already here: {n} contacts, nothing imported" }));
|
||||
else if (skipped) toast.success(`${imported} · ${plural(skipped, { one: "{n} was already here", other: "{n} were already here" })}`);
|
||||
const refreshed = plural(updated, { one: "{n} updated", other: "{n} updated" });
|
||||
if (!created) toast.success(plural(updated, { one: "Updated {n} contact, nothing new", other: "Updated {n} contacts, nothing new" }));
|
||||
else if (updated) toast.success(`${imported} · ${refreshed}`);
|
||||
else toast.success(imported);
|
||||
/*
|
||||
* Said separately, and after, because it is a different kind of fact.
|
||||
* LDIF has no UID to match on, so nothing was skipped and nothing was
|
||||
* LDIF has no UID to match on, so nothing was updated and nothing was
|
||||
* merged -- these are simply here twice now, and saying so is the whole
|
||||
* of what can honestly be said without guessing (#223).
|
||||
*/
|
||||
|
||||
@@ -17,14 +17,15 @@ export function VCardCard({ part, accountId }: { part: EmailBodyPart; accountId:
|
||||
const text = await client.fetchBlobText(accountId, part.blobId!, "text/vcard");
|
||||
const book = Object.values(contacts.books).find((b) => b.isDefault) ?? Object.values(contacts.books)[0];
|
||||
if (!book) throw new Error("No address book available");
|
||||
const { created, skipped } = await contacts.importVCard(text, book.id);
|
||||
const { created, updated } = await contacts.importVCard(text, book.id);
|
||||
setDone(true);
|
||||
/*
|
||||
* A card attached to a message is usually one you have already been sent
|
||||
* once. Saying "Added 0 contacts" for that would read as a failure; it is
|
||||
* the opposite -- there was nothing to do.
|
||||
* A card attached to a message is usually one you already have, and now
|
||||
* the newer copy wins rather than being dropped -- so the message says it
|
||||
* was brought up to date. "Added 0 contacts" would read as a failure when
|
||||
* the opposite happened.
|
||||
*/
|
||||
if (!created && skipped) toast.success(plural(skipped, { one: "Already in your contacts", other: "All {n} are already in your contacts" }));
|
||||
if (!created && updated) toast.success(plural(updated, { one: "Updated the contact you already had", other: "Updated {n} contacts you already had" }));
|
||||
else toast.success(plural(created, { one: "Added {n} contact", other: "Added {n} contacts" }));
|
||||
} catch (err) {
|
||||
toast.error((err as Error).message);
|
||||
|
||||
Reference in New Issue
Block a user