fix(android): offline changes survive a refresh, and a restart
Two defects in what offline saving had just introduced, both losing data silently - found by re-reading rather than by testing, since the device was away. The background refresh REPLACED the cache with the repository's version. An edit made offline vanished at the next fetch, without a word, which is exactly what offline saving promises to prevent. It merges now, and keeps the flag: merging is not publishing. And the flag lived only in memory. An application killed between the offline save and the publish forgot it had anything to send - and the next refresh then clobbered the cache. It is a file now, read before any refresh can run. A file rather than a field of the sealed cache: the flag carries no secret, its whole content is yes or no, and the cache's signature is already nine parameters long.
This commit is contained in:
@@ -151,6 +151,20 @@ procedure SaveLocalContainer(const AContainer: string);
|
|||||||
/// c'est le geste qui fait passer l'autorite au depot, et il est sans retour.
|
/// c'est le geste qui fait passer l'autorite au depot, et il est sans retour.
|
||||||
procedure DropLocalContainer;
|
procedure DropLocalContainer;
|
||||||
|
|
||||||
|
/// DES MODIFICATIONS ATTENDENT D'ETRE PUBLIEES.
|
||||||
|
///
|
||||||
|
/// UN FICHIER A PART, ET NON UN CHAMP DU CACHE : ce drapeau ne porte aucun
|
||||||
|
/// secret - sa seule information est "oui" ou "non" -, il n'a donc rien a
|
||||||
|
/// faire dans un blob chiffre dont l'ecriture coute une derivation et dont la
|
||||||
|
/// signature est deja longue de neuf parametres.
|
||||||
|
///
|
||||||
|
/// IL DOIT SURVIVRE A UN REDEMARRAGE. En memoire seulement, une application
|
||||||
|
/// tuee entre l'enregistrement hors ligne et la publication oubliait qu'elle
|
||||||
|
/// avait quelque chose a envoyer - et le rafraichissement suivant remplacait
|
||||||
|
/// le cache par la version du depot, effacant la modification sans un mot.
|
||||||
|
function HasPendingFlag: Boolean;
|
||||||
|
procedure SetPendingFlag(AOn: Boolean);
|
||||||
|
|
||||||
/// Chemin du fichier, pour le journal et les tests.
|
/// Chemin du fichier, pour le journal et les tests.
|
||||||
function VaultPath: string;
|
function VaultPath: string;
|
||||||
|
|
||||||
@@ -184,6 +198,7 @@ uses
|
|||||||
const
|
const
|
||||||
VAULT_FILE = 'vault.bin';
|
VAULT_FILE = 'vault.bin';
|
||||||
LOCAL_FILE = 'vault-local.json';
|
LOCAL_FILE = 'vault-local.json';
|
||||||
|
PENDING_FILE = 'pending.flag';
|
||||||
SETTINGS_FILE = 'settings.bin';
|
SETTINGS_FILE = 'settings.bin';
|
||||||
// Un alias par schema : la contrainte d'authentification est figee a la
|
// Un alias par schema : la contrainte d'authentification est figee a la
|
||||||
// creation de la cle et ne peut pas etre ajoutee apres coup.
|
// creation de la cle et ne peut pas etre ajoutee apres coup.
|
||||||
@@ -259,6 +274,27 @@ begin
|
|||||||
TFile.Delete(LocalContainerPath);
|
TFile.Delete(LocalContainerPath);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function PendingPath: string;
|
||||||
|
begin
|
||||||
|
Result := TPath.Combine(TPath.GetHomePath, PENDING_FILE);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function HasPendingFlag: Boolean;
|
||||||
|
begin
|
||||||
|
Result := TFile.Exists(PendingPath);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure SetPendingFlag(AOn: Boolean);
|
||||||
|
begin
|
||||||
|
try
|
||||||
|
if AOn then TFile.WriteAllText(PendingPath, '1')
|
||||||
|
else if TFile.Exists(PendingPath) then TFile.Delete(PendingPath);
|
||||||
|
except
|
||||||
|
// Un drapeau qu'on n'a pas pu ecrire ne doit pas faire echouer un
|
||||||
|
// enregistrement qui, lui, a reussi.
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
function VaultPath: string;
|
function VaultPath: string;
|
||||||
begin
|
begin
|
||||||
// GetHomePath sur Android = /data/data/<paquet>/files, prive a l'app.
|
// GetHomePath sur Android = /data/data/<paquet>/files, prive a l'app.
|
||||||
|
|||||||
+19
@@ -390,6 +390,9 @@ begin
|
|||||||
IFMXApplicationEventService, LEvents) then
|
IFMXApplicationEventService, LEvents) then
|
||||||
LEvents.SetApplicationEventHandler(HandleAppEvent);
|
LEvents.SetApplicationEventHandler(HandleAppEvent);
|
||||||
|
|
||||||
|
// AVANT TOUT RAFRAICHISSEMENT : sans cela le premier tirage remplacerait le
|
||||||
|
// cache par la version du depot, et ce qui attend ici serait perdu.
|
||||||
|
FPending := HasPendingFlag;
|
||||||
PushConfig;
|
PushConfig;
|
||||||
ShowUnlock;
|
ShowUnlock;
|
||||||
{$IFNDEF DEBUG}
|
{$IFNDEF DEBUG}
|
||||||
@@ -976,6 +979,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
AdoptVault(LNew);
|
AdoptVault(LNew);
|
||||||
FPending := not LPublished;
|
FPending := not LPublished;
|
||||||
|
SetPendingFlag(FPending);
|
||||||
WebCall(Format('PM.setPending(%s)',
|
WebCall(Format('PM.setPending(%s)',
|
||||||
[LowerCase(BoolToStr(FPending, True))]));
|
[LowerCase(BoolToStr(FPending, True))]));
|
||||||
if LPublished then
|
if LPublished then
|
||||||
@@ -1141,6 +1145,7 @@ begin
|
|||||||
if LDone then
|
if LDone then
|
||||||
begin
|
begin
|
||||||
FPending := False;
|
FPending := False;
|
||||||
|
SetPendingFlag(False);
|
||||||
AdoptVault(LMerged);
|
AdoptVault(LMerged);
|
||||||
Log('pending changes published, new sha ' + FETag);
|
Log('pending changes published, new sha ' + FETag);
|
||||||
WebCall('PM.setPending(false)');
|
WebCall('PM.setPending(false)');
|
||||||
@@ -1930,6 +1935,8 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
WebCall(Format('PM.setPending(%s)',
|
||||||
|
[LowerCase(BoolToStr(FPending, True))]));
|
||||||
try
|
try
|
||||||
LSw := TStopwatch.StartNew;
|
LSw := TStopwatch.StartNew;
|
||||||
AdoptVault(LPlain);
|
AdoptVault(LPlain);
|
||||||
@@ -2286,6 +2293,18 @@ begin
|
|||||||
FETag := LRes.ETag;
|
FETag := LRes.ETag;
|
||||||
Log(' decrypted in ' + LSw.ElapsedMilliseconds.ToString + ' ms');
|
Log(' decrypted in ' + LSw.ElapsedMilliseconds.ToString + ' ms');
|
||||||
|
|
||||||
|
// CE QUI ATTEND ICI NE SE FAIT PAS ECRASER. Le rafraichissement
|
||||||
|
// remplacait le cache par la version du depot : une modification faite
|
||||||
|
// hors connexion disparaissait alors sans un mot, et c'est exactement
|
||||||
|
// ce que l'enregistrement hors ligne promet d'eviter.
|
||||||
|
//
|
||||||
|
// On garde le drapeau : fondre ne publie pas.
|
||||||
|
if FPending and (FPlain <> '') then
|
||||||
|
begin
|
||||||
|
LPlain := MergeSnapshots(FPlain, LPlain);
|
||||||
|
Log(' merged with what is waiting here - still to publish');
|
||||||
|
end;
|
||||||
|
|
||||||
// Ecrit APRES un dechiffrement reussi seulement, et avec son ETag :
|
// Ecrit APRES un dechiffrement reussi seulement, et avec son ETag :
|
||||||
// les deux doivent rester coherents.
|
// les deux doivent rester coherents.
|
||||||
try
|
try
|
||||||
|
|||||||
Reference in New Issue
Block a user