feat(android): attachments can be saved, section 3 item 1 is done
Two questions, and the code was only the second one. WHERE THE FILE LANDS. The private directory is useless - no file manager opens it and it leaves with the uninstall - and direct external storage has been closed since Android 10. Of the three remaining doors, MediaStore -> Downloads is the only one needing neither a permission nor an extra screen: since API 29 an app may write what it inserted itself. The destination picker would need an activity result, which FMX makes painful; sharing would need a FileProvider in the manifest. Below API 29 it would take WRITE_EXTERNAL_STORAGE - one of the ten permissions just removed - so it refuses and says so. WHERE THE CONTENT IS. TVault drops it on purpose: a single attachment took the cache from 13 to 174 Ko. It is re-read from the raw snapshot, which has been in memory ever since saving stopped fetching before publishing. The same data now serves two needs that had nothing to do with each other. Decoding and writing happen off the main thread, under the waiting veil: a few megabytes would freeze the screen, and the veil would not even paint. What leaves the application leaves its protection: the file lands in the clear in Downloads. The confirmation names the place rather than saying "saved". Release configuration compiles too - checked, since that is what section 5 caught out.
This commit is contained in:
@@ -73,6 +73,20 @@ function DeleteEntry(const APlainJson, AUuid: string): string;
|
||||
/// lourd a publier ; il se voit tout de suite, lui.
|
||||
function DuplicateEntry(const APlainJson, AUuid: string): string;
|
||||
|
||||
/// Le contenu base64 d'une piece jointe, relu dans l'instantane BRUT.
|
||||
///
|
||||
/// TVault le jette expres a la lecture - une seule piece jointe faisait passer
|
||||
/// le cache de 13 a 174 Ko, et le garder en memoire pour tout le coffre alors
|
||||
/// que rien ne l'affiche serait payer sans rien acheter. Il ne se relit donc
|
||||
/// qu'ici, au moment ou quelqu'un demande a l'enregistrer, et pour une seule
|
||||
/// entree.
|
||||
///
|
||||
/// Rend '' si l'entree, le rang ou le contenu manquent - une piece jointe
|
||||
/// listee sans contenu existe : le poste peut avoir echoue a le joindre a
|
||||
/// l'instantane.
|
||||
function AttachmentContent(const APlainJson, AUuid: string;
|
||||
AIndex: Integer): string;
|
||||
|
||||
/// Fond l'instantane LOCAL dans l'instantane DISTANT et rend le resultat.
|
||||
///
|
||||
/// LE DISTANT SERT DE BASE, jamais le local. Il porte ce que le telephone ne
|
||||
@@ -266,6 +280,36 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
function AttachmentContent(const APlainJson, AUuid: string;
|
||||
AIndex: Integer): string;
|
||||
var
|
||||
LRoot, LEntry: TJSONObject;
|
||||
LEntries, LAtt: TJSONArray;
|
||||
I: Integer;
|
||||
begin
|
||||
Result := '';
|
||||
if (AUuid = '') or (AIndex < 0) then Exit;
|
||||
LRoot := TJSONObject.ParseJSONValue(APlainJson) as TJSONObject;
|
||||
if LRoot = nil then Exit;
|
||||
try
|
||||
if not LRoot.TryGetValue<TJSONArray>('entries', LEntries) then Exit;
|
||||
for I := 0 to LEntries.Count - 1 do
|
||||
begin
|
||||
if not (LEntries.Items[I] is TJSONObject) then Continue;
|
||||
LEntry := LEntries.Items[I] as TJSONObject;
|
||||
if LEntry.GetValue<string>('uuid', '') <> AUuid then Continue;
|
||||
if not LEntry.TryGetValue<TJSONArray>('attachments', LAtt) then Exit;
|
||||
if AIndex >= LAtt.Count then Exit;
|
||||
if not (LAtt.Items[AIndex] is TJSONObject) then Exit;
|
||||
Result := (LAtt.Items[AIndex] as TJSONObject)
|
||||
.GetValue<string>('content_b64', '');
|
||||
Exit;
|
||||
end;
|
||||
finally
|
||||
LRoot.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
function DuplicateEntry(const APlainJson, AUuid: string): string;
|
||||
var
|
||||
LRoot, LEntry, LCopy: TJSONObject;
|
||||
|
||||
Reference in New Issue
Block a user