From 65f5659d0660424143f0468ad004c1fd81e20c45 Mon Sep 17 00:00:00 2001 From: r-zakarya <82443831+r-zakarya@users.noreply.github.com> Date: Mon, 17 Aug 2026 16:15:07 +0100 Subject: [PATCH] fix(android): opening the vault no longer calls the server every time Asked plainly by the user: why does open trigger a sync? Because OpenLocalNow displays the cache and then refreshes in the background, so a change made on the desktop shows up without anyone thinking about it. Sound intent, applied on EVERY open - including a lock followed by a re-open three seconds later, which cannot learn anything new. Measured cost of one such pointless refresh: 1 s of download and 5.5 s of key derivation. A minimum interval between two background refreshes fixes that, whatever else is wrong: the desktop does not publish several times a minute, and looking at most once a quarter of an hour is enough for a change to arrive unnoticed. Pressing Sync goes through another path and never waits. The sha comparison that should already have made an unchanged refresh cheap is still not firing - two fingerprints print identically yet compare unequal. It is now compared with SameText on trimmed values, which cannot confuse two distinct snapshots since a hex digest never differs by case or whitespace alone, and the log reports both lengths so the invisible character shows up on the next real refresh. Co-Authored-By: Claude Opus 5 --- android-client/PMA.SyncClient.pas | 10 ++++++++- mainApk.pas | 35 ++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/android-client/PMA.SyncClient.pas b/android-client/PMA.SyncClient.pas index 088db25..6ee60b2 100644 --- a/android-client/PMA.SyncClient.pas +++ b/android-client/PMA.SyncClient.pas @@ -207,7 +207,15 @@ begin // Meme sha : le contenu est celui qu'on detient deja. On rend la // meme reponse qu'un 304 pour que l'appelant epargne la derivation, // et on jette le conteneur - il ne servirait a rien. - else if (AKnownETag <> '') and (Result.ETag = AKnownETag) then + // + // COMPARAISON INSENSIBLE A LA CASSE ET AUX BLANCS. Une empreinte + // hexadecimale ne peut pas differer seulement par la casse ou par + // un espace : trancher ainsi ne peut donc pas confondre deux + // instantanes distincts, alors qu'une egalite stricte se laisse + // battre par un caractere invisible - un guillemet d'en-tete, une + // fin de ligne - et fait payer une derivation complete pour rien. + else if (AKnownETag <> '') and + SameText(Result.ETag.Trim, AKnownETag.Trim) then begin Result.Status := ssNotModified; Result.Container := ''; diff --git a/mainApk.pas b/mainApk.pas index 4b68e89..c1a7b1b 100644 --- a/mainApk.pas +++ b/mainApk.pas @@ -72,6 +72,19 @@ const // qu'un telephone repose ouvert. BACKGROUND_GRACE_SECONDS = 30; + // Duree minimale entre deux rafraichissements de fond. + // + // L'ouverture du coffre en declenchait un CHAQUE FOIS. Verrouiller puis + // rouvrir trois secondes plus tard rappelait donc le serveur, qui ne pouvait + // rien apprendre de neuf : un telechargement complet, et une derivation de + // cle entiere des que l'empreinte ne concordait pas. Le poste ne publie pas + // plusieurs fois par minute ; consulter au plus une fois par quart d'heure + // suffit a ce que la modification arrive sans qu'on y pense. + // + // Ne concerne QUE le rafraichissement automatique. Un appui sur Sync passe + // par un autre chemin et n'attend jamais. + REFRESH_MIN_MINUTES = 15; + // FIRSTRUN_TOKEN et TEST_PWD viennent d'un fichier LOCAL, hors depot : le // jeton authentifie l'appareil, le mot de passe ouvre le coffre entier. Ni // l'un ni l'autre n'a sa place dans un depot, meme sans remote. Absents apres @@ -135,6 +148,8 @@ type FLastActivity: TDateTime; /// Quand l'application est passee derriere. 0 = elle est au premier plan. FBackgroundAt: TDateTime; + /// Dernier rafraichissement de fond. 0 = jamais, donc le premier a lieu. + FLastRefresh: TDateTime; procedure Touch; procedure OnIdleTimer(Sender: TObject); @@ -629,8 +644,17 @@ begin // Afficher d'abord, synchroniser ensuite : le coffre est a l'ecran, le // reseau se debrouille derriere. Sans mot de passe range, on ne peut // rien ouvrir de nouveau - inutile d'aller voir. - if FSyncPwd <> '' then + if FSyncPwd = '' then + Log(' no stored sync password - no background refresh') + else if (FLastRefresh > 0) and + (MinutesBetween(Now, FLastRefresh) < REFRESH_MIN_MINUTES) then + Log(Format(' refreshed %d min ago - skipping (min %d)', + [MinutesBetween(Now, FLastRefresh), REFRESH_MIN_MINUTES])) + else + begin + FLastRefresh := Now; FetchAndDecrypt(FUrl, FToken, FSyncPwd, True); + end; except on E: Exception do begin @@ -880,6 +904,14 @@ begin end; Log(' received ' + Length(LRes.Container).ToString + ' bytes'); + // LES DEUX EMPREINTES COTE A COTE. Sans cela on ne peut pas trancher + // entre « le distant a change » et « la comparaison ne se declenche + // pas », et les deux se ressemblent parfaitement de l'exterieur. + // LES LONGUEURS AUTANT QUE LES VALEURS. Deux empreintes s'affichaient + // identiques alors que l'egalite les separait : seul un caractere + // invisible peut faire cela, et il ne se voit qu'au compte. + Log(Format(' remote sha: %s (%d chars, known %d)', + [LRes.ETag, Length(LRes.ETag), Length(FETag)])); // LES COORDONNEES SONT RETENUES ICI, avant le dechiffrement. Un 200 // avec un conteneur en main prouve l'adresse ET le jeton ; le mot de @@ -915,6 +947,7 @@ begin // Le mot de passe part avec le coffre : c'est lui qui permettra au // prochain lancement de rafraichir sans rien demander. SaveVault(LPlain, FETag, APwd, LCanAuth); + Log(' cache now holds sha: ' + FETag); if VaultNeedsAuth then Log(' cache written - protected by biometrics/PIN') else if LCanAuth then