feat(android): the write path works, and settings survive a 304

Proven on device, against the real repository:

    GET first: HTTP 200, sha 8feb3b7d...
    PUT .../write-test.json
      OK 200 - new sha 67d6a4b2...

Read the sha, write with it, receive the next one. Section 4 step 3 is done.
The probe reads before writing because that is the real sequence: an empty sha
means "create", and Gitea answers 422 when the file is already there - which is
what it did, and rightly.

The second fix comes from an interaction between two changes made hours apart
today, and only visible once both were in place. Settings are stored on the
line after "received N bytes"; but now that the sha comparison works, a sync
answers 304 and returns before ever reaching it. So a freshly typed token was
never saved and vanished on close - which is exactly what the user reported.

They are now kept as soon as the transport answered at all, 200 or 304: both
prove the address and the token, only the body differs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-08-17 19:05:28 +01:00
parent 683655415f
commit 382e38913b
+31 -6
View File
@@ -32,7 +32,7 @@ interface
uses uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.SysUtils, System.Types, System.UITypes, System.Classes,
System.Diagnostics, System.Rtti, System.NetEncoding, System.DateUtils, System.Diagnostics, System.Rtti, System.NetEncoding, System.DateUtils,
System.Generics.Collections, System.IOUtils, System.Hash, System.Generics.Collections, System.IOUtils, System.Hash, System.StrUtils,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Platform, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Platform,
FMX.Controls.Presentation, FMX.StdCtrls, FMX.Memo, FMX.Memo.Types, FMX.Controls.Presentation, FMX.StdCtrls, FMX.Memo, FMX.Memo.Types,
FMX.ScrollBox, FMX.Layouts, FMX.Edit, FMX.ListBox, FMX.Objects, FMX.ScrollBox, FMX.Layouts, FMX.Edit, FMX.ListBox, FMX.Objects,
@@ -493,8 +493,8 @@ begin
procedure procedure
var var
LClient: TSyncClient; LClient: TSyncClient;
LRes: TSyncResult; LRes, LGet: TSyncResult;
LBody: string; LBody, LSha: string;
begin begin
LClient := TSyncClient.Create(LUrl, FToken); LClient := TSyncClient.Create(LUrl, FToken);
try try
@@ -503,12 +503,23 @@ begin
[FormatDateTime('yyyy-mm-dd"T"hh:nn:ss"Z"', [FormatDateTime('yyyy-mm-dd"T"hh:nn:ss"Z"',
TTimeZone.Local.ToUniversalTime(Now))]), TTimeZone.Local.ToUniversalTime(Now))]),
FSyncPwd, 1000); FSyncPwd, 1000);
// LIRE D'ABORD, ECRIRE ENSUITE - la sequence reelle. Un sha vide veut
// dire « cree », et Gitea refuse alors en 422 si le fichier est deja
// la ; c'est le cas de mise a jour qui compte, et il exige le sha
// courant. Un 404 au GET signifie simplement qu'il n'existe pas
// encore : on cree, sha vide.
LGet := LClient.Fetch;
if LGet.Status = ssOK then
LSha := LGet.ETag
else
LSha := '';
Log(Format('GET first: HTTP %d, sha %s', [LGet.HttpCode,
IfThen(LSha = '', '(aucun - creation)', LSha)]));
Log('PUT ' + LUrl); Log('PUT ' + LUrl);
Log(Format(' token %s (%d chars), body %d chars', Log(Format(' token %s (%d chars), body %d chars',
[Fingerprint(FToken), Length(FToken), Length(LBody)])); [Fingerprint(FToken), Length(FToken), Length(LBody)]));
// Sha vide : on cree le fichier, ou on echoue en 409 s'il existe deja - LRes := LClient.Put(LBody, LSha, 'write path probe from phone');
// ce qui prouverait le controle de concurrence aussi surement.
LRes := LClient.Put(LBody, '', 'write path probe from phone');
case LRes.Status of case LRes.Status of
ssOK: Log(' OK ' + LRes.HttpCode.ToString + ' - new sha ' + LRes.ETag); ssOK: Log(' OK ' + LRes.HttpCode.ToString + ' - new sha ' + LRes.ETag);
ssConflict: Log(' 409 - file already there, concurrency control works'); ssConflict: Log(' 409 - file already there, concurrency control works');
@@ -980,6 +991,20 @@ begin
else else
Log(' no HTTP reply after ' + LSw.ElapsedMilliseconds.ToString + ' ms'); Log(' no HTTP reply after ' + LSw.ElapsedMilliseconds.ToString + ' ms');
// LES COORDONNEES SONT RETENUES DES QUE LE TRANSPORT A REPONDU, 200
// comme 304 : les deux prouvent l'adresse et le jeton, seul le corps
// differe. Elles ne l'etaient que sur le chemin du 200, et depuis que
// la comparaison de sha fonctionne une synchronisation repond
// presque toujours 304 - donc un jeton fraichement saisi n'etait
// jamais enregistre, et disparaissait a la fermeture.
if LRes.Status in [ssOK, ssNotModified] then
try
SaveDeviceSettings(AUrl, ATok);
except
on E: Exception do
Log(' settings not saved: ' + E.ClassName + ' - ' + E.Message);
end;
case LRes.Status of case LRes.Status of
ssNotModified: ssNotModified:
begin begin