feat(android): an entry can be deleted from the phone
Both, or nothing: the row leaves the snapshot AND a tombstone takes its place. Removing the row alone does not delete - the desktop still holds its own copy, and the next pull would bring it back as an entry we never had. The time matters too: the desktop's arbitration keeps an entry edited AFTER the deletion rather than killing it a second time. No trash on the phone, deliberately. The desktop's is a `deleted` flag that does not travel in sync, so a deletion stopping there would propagate nowhere. Hence a button at the very bottom, in two taps, that disarms itself after four seconds - a button left armed gets touched by accident much later, and this is the one action with no way back. The confirmation is in the page because the engine cannot offer one: a JavaScript prompt needs a WebChromeClient FMX does not provide. Not exercised on the device: it would have destroyed a real entry.
This commit is contained in:
@@ -51,6 +51,20 @@ function NewEmptySnapshot(const AUsername: string): string;
|
||||
function AddEntry(const APlainJson, ATitle, ASite, AUsername, APassword,
|
||||
AKind: string): string;
|
||||
|
||||
/// Retire une entree de l'instantane et laisse une PIERRE TOMBALE a sa place.
|
||||
///
|
||||
/// LES DEUX, SINON RIEN. Retirer la ligne sans la tombstone ne supprime pas :
|
||||
/// le poste tient toujours la sienne, et le prochain tirage la ramenerait comme
|
||||
/// une entree qu'on n'a jamais eue. C'est la tombstone qui dit "celle-ci a ete
|
||||
/// supprimee, et a telle heure" - et l'heure compte, parce que l'arbitrage du
|
||||
/// poste garde une entree modifiee APRES la suppression plutot que de la tuer
|
||||
/// une seconde fois.
|
||||
///
|
||||
/// SANS RETOUR depuis le telephone : il n'y a pas de corbeille ici. Celle du
|
||||
/// poste est un drapeau `deleted` qui ne voyage pas dans la synchronisation ;
|
||||
/// une suppression qui s'y arreterait ne se propagerait donc nulle part.
|
||||
function DeleteEntry(const APlainJson, AUuid: string): string;
|
||||
|
||||
/// Horodatage UTC au format que tout le reste emploie (toISOString cote JS).
|
||||
/// MELANGER heure locale et UTC a deja decale l'arbitrage de la fusion une
|
||||
/// fois : il n'y a qu'une source d'heure ici, et elle est en UTC.
|
||||
@@ -86,6 +100,57 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
function DeleteEntry(const APlainJson, AUuid: string): string;
|
||||
var
|
||||
LRoot, LEntry, LStone: TJSONObject;
|
||||
LEntries, LStones: TJSONArray;
|
||||
I: Integer;
|
||||
LFound: Boolean;
|
||||
begin
|
||||
if AUuid = '' then
|
||||
raise EPMAPatch.Create('Entree sans uuid : rien a supprimer chez le distant');
|
||||
|
||||
LRoot := TJSONObject.ParseJSONValue(APlainJson) as TJSONObject;
|
||||
if LRoot = nil then
|
||||
raise EPMAPatch.Create('Instantane illisible');
|
||||
try
|
||||
if not LRoot.TryGetValue<TJSONArray>('entries', LEntries) then
|
||||
raise EPMAPatch.Create('Instantane sans tableau "entries"');
|
||||
|
||||
LFound := False;
|
||||
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;
|
||||
// Remove REND la valeur retiree : a l'appelant de la liberer.
|
||||
LEntries.Remove(I).Free;
|
||||
LFound := True;
|
||||
Break;
|
||||
end;
|
||||
|
||||
if not LFound then
|
||||
raise EPMAPatch.CreateFmt(
|
||||
'Entree %s absente du distant - supprimee ailleurs ?', [AUuid]);
|
||||
|
||||
// Le tableau peut manquer : un conteneur ecrit par un poste qui n'avait
|
||||
// encore rien supprime n'a aucune raison de le porter.
|
||||
if not LRoot.TryGetValue<TJSONArray>('tombstones', LStones) then
|
||||
begin
|
||||
LStones := TJSONArray.Create;
|
||||
LRoot.AddPair('tombstones', LStones);
|
||||
end;
|
||||
LStone := TJSONObject.Create;
|
||||
LStone.AddPair('uuid', AUuid);
|
||||
LStone.AddPair('deleted_at', NowUtcIso);
|
||||
LStones.AddElement(LStone);
|
||||
|
||||
Result := LRoot.ToJSON;
|
||||
finally
|
||||
LRoot.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
function AddEntry(const APlainJson, ATitle, ASite, AUsername, APassword,
|
||||
AKind: string): string;
|
||||
var
|
||||
|
||||
Reference in New Issue
Block a user