feat(icons): propagate icon_custom through export / import / sync
Follow-up to 18fe13a: the custom-icon flag now survives round-trips instead
of resetting to fetched on a restore.
- Containers write icon_custom: manual export (doExport), auto-backup, sync
snapshot; parseEntriesFromJSON carries it back (default 0 when absent, e.g.
foreign CSV).
- encryptImportEntry ships it in the body (shared by bulk-import + sync
create/update).
- Server create + bulk-import INSERTs read icon_custom
(GetValue<Boolean>('icon_custom', False) → 0 default). An empty icon always
forces icon_custom=0.
Now a custom icon stays custom after export→import and across sync — it won't
be silently reclassified as cache and clobbered by Re-fetch-all on the other
device.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -270,10 +270,12 @@ La SEULE feature qui sort sur le réseau côté Delphi (HIBP est côté JS).
|
||||
skippe `icon_custom` (même sur "Re-fetch all"), `DELETE /entries/icons/all`
|
||||
filtre `COALESCE(icon_custom,0)=0`. Seul l'utilisateur change/retire une
|
||||
custom, par entrée (boutons Upload/Remove du slideover). `GET /entries`
|
||||
echoe `icon_custom`. **Non propagé** par import bulk / sync / export
|
||||
(l'INSERT bulk ne lit pas le flag) → une icône restaurée sur un device
|
||||
neuf compte comme fetchée ; assumé (les customs sont surtout des sites
|
||||
self-hosted, que DDG ne re-fetch pas de toute façon).
|
||||
echoe `icon_custom`. **Propagé** de bout en bout : export manuel,
|
||||
auto-backup, snapshot de sync l'écrivent ; `encryptImportEntry` le porte
|
||||
dans le body ; les INSERT **create + bulk-import** le lisent
|
||||
(`GetValue<Boolean>('icon_custom', False)` → 0 par défaut si absent, ex.
|
||||
CSV étranger). Round-trip export→import et sync préservent donc la
|
||||
provenance. Une icône vide (`icon_b64=''`) force toujours `icon_custom=0`.
|
||||
- Bulk : `DELETE /entries/icons/all` purge le cache auto (bouton "Clear
|
||||
cache") — préserve les uploads custom
|
||||
- Render : `entry-avatar` contient `<img class="entry-avatar-img">` si
|
||||
|
||||
@@ -333,6 +333,7 @@ var
|
||||
LTotpSec, LTotpIv, LKind, LCf, LCfIv, LIcon, LUuid,
|
||||
LTemplateEnc, LTemplateIv, LCreatedAt, LUpdatedAt,
|
||||
LSiteEnc, LSiteIv, LTitleEnc, LTitleIv, LTagsEnc, LTagsIv: string;
|
||||
LIconCustom: Boolean;
|
||||
LQ: TFDQuery;
|
||||
begin
|
||||
try
|
||||
@@ -371,6 +372,8 @@ begin
|
||||
LCf := LBody.GetValue<string>('custom_fields', '');
|
||||
LCfIv := LBody.GetValue<string>('custom_fields_iv', '');
|
||||
LIcon := LBody.GetValue<string>('icon_b64', '');
|
||||
// Icon provenance (0 = fetched cache, 1 = user upload). Absent → 0.
|
||||
LIconCustom := LBody.GetValue<Boolean>('icon_custom', False);
|
||||
LTemplateEnc := LBody.GetValue<string>('template_enc', '');
|
||||
LTemplateIv := LBody.GetValue<string>('template_iv', '');
|
||||
// Caller may bring its own UUID (sync restore / import preserving
|
||||
@@ -412,10 +415,10 @@ begin
|
||||
' site_enc, site_iv, title_enc, title_iv, tags_enc, tags_iv, ' +
|
||||
' encrypted_password, iv, encryption_method, ' +
|
||||
' folder, totp_secret, totp_iv, kind, custom_fields, custom_fields_iv,' +
|
||||
' icon_b64, template_enc, template_iv, uuid, created_at, updated_at, password_changed_at) ' +
|
||||
' icon_b64, icon_custom, template_enc, template_iv, uuid, created_at, updated_at, password_changed_at) ' +
|
||||
'VALUES (:uid, :uenc, :uiv, :senc, :siv, :tenc, :tiv2, :genc, :giv, ' +
|
||||
' :e, :i, ''client'', :f, :ts, :tiv, :k, ' +
|
||||
' :cf, :cfiv, :ic, :tplenc, :tpliv, :uuid, :c, :c2, :c)';
|
||||
' :cf, :cfiv, :ic, :iconcust, :tplenc, :tpliv, :uuid, :c, :c2, :c)';
|
||||
LQ.ParamByName('uid').AsInteger := LUserId;
|
||||
// site/username/title/tags cleartext columns dropped — only *_enc written.
|
||||
BindNullable(LQ, 'uenc', LUserEnc);
|
||||
@@ -452,6 +455,8 @@ begin
|
||||
if LCfIv = '' then LQ.ParamByName('cfiv').Clear else LQ.ParamByName('cfiv').Value := LCfIv;
|
||||
LQ.ParamByName('ic').DataType := ftMemo;
|
||||
if LIcon = '' then LQ.ParamByName('ic').Clear else LQ.ParamByName('ic').Value := LIcon;
|
||||
if (LIcon <> '') and LIconCustom then LQ.ParamByName('iconcust').AsInteger := 1
|
||||
else LQ.ParamByName('iconcust').AsInteger := 0;
|
||||
BindNullable(LQ, 'tplenc', LTemplateEnc);
|
||||
BindNullable(LQ, 'tpliv', LTemplateIv);
|
||||
LQ.ParamByName('uuid').AsString := LUuid;
|
||||
@@ -1203,6 +1208,7 @@ var
|
||||
LTotpIv, LNow, LKind, LCf, LCfIv, LIcon, LTemplateEnc, LTemplateIv, LUuid,
|
||||
LCreatedAt, LUpdatedAt,
|
||||
LSiteEnc, LSiteIv, LTitleEnc, LTitleIv, LTagsEnc, LTagsIv: string;
|
||||
LIconCustom: Boolean;
|
||||
LQ, LTomb: TFDQuery;
|
||||
begin
|
||||
try
|
||||
@@ -1259,10 +1265,10 @@ begin
|
||||
' site_enc, site_iv, title_enc, title_iv, tags_enc, tags_iv, ' +
|
||||
' encrypted_password, iv, encryption_method, ' +
|
||||
' folder, totp_secret, totp_iv, kind, custom_fields, custom_fields_iv,' +
|
||||
' icon_b64, template_enc, template_iv, uuid, created_at, updated_at) ' +
|
||||
' icon_b64, icon_custom, template_enc, template_iv, uuid, created_at, updated_at) ' +
|
||||
'VALUES (:uid, :uenc, :uiv, :senc, :siv, :tenc, :tiv2, :genc, :giv, ' +
|
||||
' :e, :i, ''client'', :f, :ts, :tiv, :k, ' +
|
||||
' :cf, :cfiv, :ic, :tplenc, :tpliv, :uuid, :c, :c2)';
|
||||
' :cf, :cfiv, :ic, :iconcust, :tplenc, :tpliv, :uuid, :c, :c2)';
|
||||
// Declare optional param types ONCE — the prepared statement is
|
||||
// reused across every imported entry, and FireDAC needs the
|
||||
// type set before the first .Clear call would otherwise fail
|
||||
@@ -1310,6 +1316,7 @@ begin
|
||||
LCf := LEntry.GetValue<string>('custom_fields', '');
|
||||
LCfIv := LEntry.GetValue<string>('custom_fields_iv', '');
|
||||
LIcon := LEntry.GetValue<string>('icon_b64', '');
|
||||
LIconCustom := LEntry.GetValue<Boolean>('icon_custom', False);
|
||||
LTemplateEnc := LEntry.GetValue<string>('template_enc', '');
|
||||
LTemplateIv := LEntry.GetValue<string>('template_iv', '');
|
||||
LUuid := Trim(LEntry.GetValue<string>('uuid', ''));
|
||||
@@ -1353,6 +1360,8 @@ begin
|
||||
if LCf = '' then LQ.ParamByName('cf').Clear else LQ.ParamByName('cf').Value := LCf;
|
||||
if LCfIv = '' then LQ.ParamByName('cfiv').Clear else LQ.ParamByName('cfiv').Value := LCfIv;
|
||||
if LIcon = '' then LQ.ParamByName('ic').Clear else LQ.ParamByName('ic').Value := LIcon;
|
||||
if (LIcon <> '') and LIconCustom then LQ.ParamByName('iconcust').AsInteger := 1
|
||||
else LQ.ParamByName('iconcust').AsInteger := 0;
|
||||
BindNullable(LQ, 'tplenc', LTemplateEnc);
|
||||
BindNullable(LQ, 'tpliv', LTemplateIv);
|
||||
LQ.ParamByName('uuid').AsString := LUuid;
|
||||
|
||||
@@ -213,6 +213,7 @@ async function runAutoBackupNow(silent) {
|
||||
custom_fields: plainCustom,
|
||||
attachments: attachments,
|
||||
icon_b64: e.icon_b64 || '',
|
||||
icon_custom: e.icon_custom ? 1 : 0,
|
||||
created_at: e.created_at, updated_at: e.updated_at,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -414,6 +414,7 @@ function parseEntriesFromJSON(text) {
|
||||
custom_fields: cf,
|
||||
attachments: atts,
|
||||
icon_b64: String(e.icon_b64 || '').trim(),
|
||||
icon_custom: e.icon_custom ? 1 : 0,
|
||||
created_at: String(e.created_at || '').trim(),
|
||||
updated_at: String(e.updated_at || '').trim(),
|
||||
});
|
||||
@@ -487,6 +488,7 @@ async function encryptImportEntry(plain) {
|
||||
custom_fields: cfEnc,
|
||||
custom_fields_iv: cfIv,
|
||||
icon_b64: plain.icon_b64 || '',
|
||||
icon_custom: plain.icon_custom ? 1 : 0,
|
||||
template: plain.template || '',
|
||||
// Preserve original timestamps on restore — bulk-import falls back
|
||||
// to now only when these are absent (foreign CSV imports).
|
||||
@@ -911,6 +913,7 @@ async function doExport() {
|
||||
custom_fields: plainCustom,
|
||||
attachments: attachments,
|
||||
icon_b64: e.icon_b64 || '',
|
||||
icon_custom: e.icon_custom ? 1 : 0,
|
||||
created_at: e.created_at,
|
||||
updated_at: e.updated_at,
|
||||
});
|
||||
|
||||
@@ -259,6 +259,7 @@ async function buildSyncSnapshot() {
|
||||
custom_fields: plainCustom,
|
||||
attachments,
|
||||
icon_b64: e.icon_b64 || '',
|
||||
icon_custom: e.icon_custom ? 1 : 0,
|
||||
created_at: e.created_at,
|
||||
updated_at: e.updated_at,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user