From bb222e1903f97bc51d4b27e7f645a2bdaaea40f1 Mon Sep 17 00:00:00 2001 From: r-zakarya <82443831+r-zakarya@users.noreply.github.com> Date: Fri, 21 Aug 2026 15:00:46 +0100 Subject: [PATCH] perf(android): saving no longer pulls the vault it already holds The crypto is down to about 135 ms, so the wait was never the crypto: the log says HTTP 200 in 3244 ms for 266299 bytes. Every save fetched the whole vault, decrypted it, patched it, and sent it back. The fetch existed to be sure of modifying the latest published version. The sha proves that better and for free: publish against the snapshot in hand, and if another device published in between, Gitea refuses with a 409 - then, and only then, pull for real. The retry loop was already written for exactly that. The snapshot kept is the one RECEIVED, never a re-serialisation of TVault: that would drop everything the phone does not read - attachment bodies, the avatar, tombstones - which is the invariant PMA.VaultPatch exists for. It goes at lock, with the rest of the plaintext. Note this makes the 409 path load-bearing, and it has still never run. It is now what guarantees a save cannot clobber another device. The PUT still carries the whole container; that is inherent to the format. --- mainApk.pas | 51 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/mainApk.pas b/mainApk.pas index b548854..96f861f 100644 --- a/mainApk.pas +++ b/mainApk.pas @@ -130,6 +130,18 @@ type /// Elle part au verrouillage, avec lui. FKey, FKeySalt: TBytes; FKeyIters: Integer; + /// L'INSTANTANE BRUT, tel qu'il a ete recu. Pas une reserialisation de + /// TVault : celle-ci perdrait tout ce que le telephone ne lit pas - + /// le contenu des pieces jointes, l'avatar, les tombstones - c'est + /// l'invariant de PMA.VaultPatch. + /// + /// Il permet d'enregistrer SANS RETIRER le coffre d'abord. Le tirage + /// coutait 3,2 s pour 266 Ko, a chaque sauvegarde, alors qu'on tient deja + /// ce qu'il rapporte. Ce qu'il apportait vraiment, c'etait la certitude de + /// modifier la derniere version publiee - et le sha la donne aussi : si un + /// autre appareil a publie entre-temps, Gitea refuse en 409 et on retire + /// pour de bon. + FPlain: string; /// Code d'ouverture du cache local, choisi dans l'application. JAMAIS /// ENREGISTRE : il est vide a chaque lancement et doit etre retape. C'est /// ce qui le distingue du jeton, dont la persistance est un confort sans @@ -730,13 +742,30 @@ begin LClient := TSyncClient.Create(FUrl, FToken); for LAttempt := 1 to 3 do begin - Log(Format('save: attempt %d - GET', [LAttempt])); - // Sans empreinte connue : on VEUT le corps, pas un 304. - LGet := LClient.Fetch; - if LGet.Status <> ssOK then + // PREMIER ESSAI SANS TIRER. On tient l'instantane et le sha qui va + // avec ; le tirage ne servait qu'a s'assurer de modifier la + // derniere version publiee, et le sha le prouve mieux : s'il n'est + // plus le bon, Gitea refuse. On ne retire donc que lorsqu'on a + // vraiment quelque chose a apprendre. + if (LAttempt = 1) and (FPlain <> '') and (FETag <> '') then begin - LErr := Format('read failed (HTTP %d) %s', [LGet.HttpCode, LGet.Error]); - Break; + Log('save: publishing against the snapshot in hand'); + LGet.Status := ssOK; + LGet.Container := ''; + LGet.ETag := FETag; + LPlain := FPlain; + end + else + begin + Log(Format('save: attempt %d - GET', [LAttempt])); + // Sans empreinte connue : on VEUT le corps, pas un 304. + LGet := LClient.Fetch; + if LGet.Status <> ssOK then + begin + LErr := Format('read failed (HTTP %d) %s', [LGet.HttpCode, LGet.Error]); + Break; + end; + LPlain := ''; end; try @@ -745,7 +774,9 @@ begin // reutilise. Deriver deux fois le meme mot de passe coutait une // seconde fois quatre a six secondes, pour une cle equivalente. LSw := TStopwatch.StartNew; - LPlain := OpenContainer(LGet.Container, FSyncPwd); + // LPlain deja pose quand on publie contre l'instantane en main. + if LPlain = '' then + LPlain := OpenContainer(LGet.Container, FSyncPwd); LKey := FKey; LSalt := FKeySalt; LIter := FKeyIters; LNew := APatch(LPlain); Log(Format('save: decrypt+patch in %d ms', [LSw.ElapsedMilliseconds])); @@ -760,7 +791,10 @@ begin LSw := TStopwatch.StartNew; LBody := EncryptContainerWithKey(LNew, LKey, LSalt, LIter); Log(Format('save: re-encrypt in %d ms', [LSw.ElapsedMilliseconds])); + LSw := TStopwatch.StartNew; LPut := LClient.Put(LBody, LGet.ETag, AComment); + Log(Format('save: PUT %d chars in %d ms', + [Length(LBody), LSw.ElapsedMilliseconds])); if LPut.Status = ssOK then begin Log('save: published, new sha ' + LPut.ETag); @@ -781,6 +815,7 @@ begin if LPut.Status = ssConflict then begin Log('save: 409 - another device published, re-reading'); + LPlain := ''; Continue; end; @@ -1081,12 +1116,14 @@ begin FKey := nil; FKeySalt := nil; FKeyIters := 0; + FPlain := ''; ShowUnlock; end; procedure TForm8.AdoptVault(const APlainJson: string); begin FVault.LoadFromJson(APlainJson); + FPlain := APlainJson; Log(' ' + FVault.LiveCount.ToString + ' entries, ' + FVault.TrashCount.ToString + ' in trash'); FVaultUnreadable := False;