feat(android): show attachments, without carrying their bytes
One attachment took the cache from 13 to 174 KB: the content travels base64 in the snapshot and is decrypted in memory with the rest. TVault reads name, type and size and DROPS content_b64 - holding it for the whole vault while nothing displays it would be paying for nothing. The detail now says a document exists. Saving it is what remains, and what blocks that is not code but where a file lands on Android 10+. When that is settled, the content re-reads from the raw JSON. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+16
-7
@@ -171,13 +171,22 @@ Par ordre de gravité, pas de taille.
|
||||
|
||||
Rangé par ce que ça apporte réellement sur un téléphone.
|
||||
|
||||
1. ~~**Pièces jointes**~~ — **abandonné** (2026-08-15) : le coffre n'en contient
|
||||
pas. Elles restent dans l'instantané et le client les ignore, ce qui ne
|
||||
coûte rien. À reprendre le jour où il y en aura, en tranchant d'abord où un
|
||||
fichier atterrit sur Android 10+ — le répertoire privé ne sert à rien à
|
||||
l'utilisateur, *Téléchargements* passe par `MediaStore` ou un partage par
|
||||
`Intent` — et en surveillant la mémoire, `content_b64` étant décodé en RAM
|
||||
avec le reste de l'instantané.
|
||||
1. **Pièces jointes** — *listées*, pas encore enregistrables.
|
||||
|
||||
Une seule pièce jointe a fait passer le cache de **13 à 174 Ko** : le
|
||||
contenu voyage en base64 dans l'instantané, et il est déchiffré en mémoire
|
||||
avec le reste. `TVault` lit donc nom, type et taille, et **jette
|
||||
`content_b64`** — le garder pour tout le coffre alors que rien ne l'affiche
|
||||
serait payer sans rien acheter.
|
||||
|
||||
Le détail affiche l'existence du document. Ce qui manque est
|
||||
l'enregistrement, et ce qui le bloque n'est pas le code mais **où un fichier
|
||||
atterrit sur Android 10+** : le répertoire privé ne sert à rien à
|
||||
l'utilisateur, *Téléchargements* passe par `MediaStore`, le partage par un
|
||||
`Intent`. Le jour venu, le contenu se relira depuis le JSON brut.
|
||||
|
||||
À surveiller : quelques PDF suffiraient à peser plusieurs Mo, retéléchargés
|
||||
entièrement à chaque instantané nouveau.
|
||||
2. ~~**Filtre dossier**~~ — **fait**, liste déroulante à côté de la recherche,
|
||||
alimentée par le tableau `folders`, et le choix survit à un rafraîchissement
|
||||
de fond. **Pas de filtre d'étiquette** : la recherche texte couvre déjà les
|
||||
|
||||
@@ -23,6 +23,16 @@ type
|
||||
Secret: Boolean;
|
||||
end;
|
||||
|
||||
/// Metadonnees d'une piece jointe. LE CONTENU N'EST PAS RETENU : il voyage
|
||||
/// en base64 dans l'instantane et pese lourd - une seule piece jointe a fait
|
||||
/// passer le cache de 13 a 174 Ko. Le garder en memoire pour tout le coffre
|
||||
/// alors que rien ne l'affiche serait payer sans rien acheter. Le jour ou
|
||||
/// l'enregistrement existera, il se relira depuis le JSON brut.
|
||||
TVaultAttachment = record
|
||||
FileName, Mime: string;
|
||||
SizeBytes: Int64;
|
||||
end;
|
||||
|
||||
TVaultEntry = record
|
||||
Uuid, Title, Site, Username, Password, Folder, Tags, Kind, Totp: string;
|
||||
/// Sous-type (carte, cle SSH, serveur...). Vide pour un identifiant ou
|
||||
@@ -34,6 +44,7 @@ type
|
||||
/// fusion le jour ou le client ecrira.
|
||||
UpdatedAt: string;
|
||||
Fields: TArray<TVaultField>;
|
||||
Attachments: TArray<TVaultAttachment>;
|
||||
Deleted: Boolean;
|
||||
/// Le corps d'une note EST son mot de passe cote stockage : il n'y a pas
|
||||
/// de colonne separee. L'affichage doit donc l'intituler autrement.
|
||||
@@ -160,6 +171,32 @@ begin
|
||||
SetLength(Result, N);
|
||||
end;
|
||||
|
||||
/// Pieces jointes : {filename, mime, size_bytes, content_b64}. On lit tout
|
||||
/// SAUF content_b64 - voir TVaultAttachment.
|
||||
function ParseAttachments(const AVal: TJSONValue): TArray<TVaultAttachment>;
|
||||
var
|
||||
LArr: TJSONArray;
|
||||
LObj: TJSONObject;
|
||||
I, N: Integer;
|
||||
begin
|
||||
Result := nil;
|
||||
if not (AVal is TJSONArray) then Exit;
|
||||
LArr := AVal as TJSONArray;
|
||||
SetLength(Result, LArr.Count);
|
||||
N := 0;
|
||||
for I := 0 to LArr.Count - 1 do
|
||||
begin
|
||||
if not (LArr.Items[I] is TJSONObject) then Continue;
|
||||
LObj := LArr.Items[I] as TJSONObject;
|
||||
Result[N].FileName := LObj.GetValue<string>('filename', '');
|
||||
Result[N].Mime := LObj.GetValue<string>('mime', '');
|
||||
Result[N].SizeBytes := LObj.GetValue<Int64>('size_bytes', 0);
|
||||
if Result[N].FileName = '' then Continue;
|
||||
Inc(N);
|
||||
end;
|
||||
SetLength(Result, N);
|
||||
end;
|
||||
|
||||
procedure TVault.LoadFromJson(const APlainJson: string);
|
||||
var
|
||||
LRoot, LObj: TJSONObject;
|
||||
@@ -209,6 +246,7 @@ begin
|
||||
FEntries[N].Totp := LObj.GetValue<string>('totp_secret', '');
|
||||
FEntries[N].Tags := TagsToString(LObj.GetValue('tags'));
|
||||
FEntries[N].Fields := ParseFields(LObj.GetValue('custom_fields'));
|
||||
FEntries[N].Attachments := ParseAttachments(LObj.GetValue('attachments'));
|
||||
FEntries[N].Template := LObj.GetValue<string>('template', '');
|
||||
FEntries[N].IconB64 := LObj.GetValue<string>('icon_b64', '');
|
||||
FEntries[N].UpdatedAt := LObj.GetValue<string>('updated_at', '');
|
||||
|
||||
Reference in New Issue
Block a user