[client] Keep track of changes to remote data for reverting actions

This commit is contained in:
Abhishek Shroff
2024-11-08 23:34:36 +05:30
parent 2b26dfd0cb
commit 4180ef76cd
5 changed files with 103 additions and 26 deletions

View File

@@ -20,19 +20,12 @@ class ResourceMoveAction extends PhylumAction with JsonApiAction {
final String resourceId;
final String? parent;
final String oldParent;
final String? resourceName;
final String oldName;
final DateTime modified;
final String description;
ResourceMoveAction._({
required this.resourceId,
required this.resourceName,
required this.parent,
required this.oldName,
required this.oldParent,
required this.description,
});
ResourceMoveAction._(
{required this.resourceId, required this.resourceName, required this.parent, required this.description, required this.modified});
ResourceMoveAction({
required Resource r,
@@ -42,9 +35,8 @@ class ResourceMoveAction extends PhylumAction with JsonApiAction {
resourceId: r.id,
resourceName: name,
parent: parent?.id,
oldName: r.name,
oldParent: r.parent!,
description: name == null ? 'Moving ${r.name}' : 'Renaming ${r.name} to $name',
modified: DateTime.now(),
);
static ResourceMoveAction fromMap(Map<String, dynamic> map) {
@@ -52,9 +44,8 @@ class ResourceMoveAction extends PhylumAction with JsonApiAction {
resourceId: map['resourceId'],
resourceName: map['resourceName'],
parent: map['parent'],
oldName: map['oldName'],
oldParent: map['oldParent'],
description: map['description'],
modified: DateTime.fromMillisecondsSinceEpoch(map['modified']),
);
}
@@ -70,6 +61,16 @@ class ResourceMoveAction extends PhylumAction with JsonApiAction {
};
}
@override
Future<void> initialize(PhylumAccount account) {
return account.resourceRepository.retainServerData(resourceId);
}
@override
Future<void> dispose(PhylumAccount account) {
return account.resourceRepository.discardServerData(resourceId);
}
@override
Future<void> applyOptimisticUpdate(PhylumAccount account) {
return account.resourceRepository.updateResource(
@@ -77,17 +78,20 @@ class ResourceMoveAction extends PhylumAction with JsonApiAction {
(o) => o(
name: Value.absentIfNull(resourceName),
parent: Value.absentIfNull(parent),
modified: Value(modified),
),
);
}
@override
Future<void> revertOptimisticUpdate(PhylumAccount account) {
final serverData = account.resourceRepository.getServerData(resourceId)!;
return account.resourceRepository.updateResource(
resourceId,
(o) => o(
name: Value.absentIfNull(oldName),
parent: Value.absentIfNull(oldParent),
name: resourceName == null ? Value.absent() : Value(serverData.name),
parent: parent == null ? Value.absent() : Value(serverData.parent),
modified: Value(serverData.modified),
),
);
}
@@ -97,9 +101,8 @@ class ResourceMoveAction extends PhylumAction with JsonApiAction {
'resourceId': resourceId,
'resourceName': resourceName,
'parent': parent,
'oldName': oldName,
'oldParent': oldParent,
'description': description,
'modified': modified.millisecondsSinceEpoch,
};
@override

View File

@@ -55,6 +55,8 @@ class PhylumAccount extends Account<PhylumAccount> {
// Set Authorization header
_accessToken = accessToken;
await resourceRepository.initialize();
void Function()? l;
l = apiClient.addResponseListener((request, errorResponse) {
if (errorResponse is PhylumApiErrorResponse) {
@@ -73,8 +75,8 @@ class PhylumAccount extends Account<PhylumAccount> {
}
@override
Future<void> clearData() async {
await super.clearData();
Future<void> cleanup() async {
await super.cleanup();
await db.dropDatabase();
}

View File

@@ -1,15 +1,48 @@
import 'dart:convert';
import 'package:drift/drift.dart';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:offtheline/offtheline.dart';
import 'package:phylum/libphylum/db/db.dart';
import 'package:phylum/libphylum/phylum_account.dart';
import 'package:phylum/libphylum/phylum_api_types.dart';
import 'package:phylum/libphylum/requests/resource_detail_request.dart';
class ResourceAdapter implements TypeAdapter<Resource> {
const ResourceAdapter();
@override
int get typeId => 1;
@override
Resource read(BinaryReader reader) {
reader.readByte();
return Resource.fromJson(json.decode(reader.readString()));
}
@override
void write(BinaryWriter writer, Resource obj) {
writer.writeByte(0); // Version marker, in case we need to change something in the future
writer.writeString(obj.toJsonString());
}
}
class ResourceRepository {
final PhylumAccount account;
late final Box<Resource> _serverDataBox;
late final Box<int> _serverDataCountsBox;
ResourceRepository({required this.account});
Future<void> initialize() async {
if (!Hive.isAdapterRegistered(const ResourceAdapter().typeId)) {
Hive.registerAdapter(const ResourceAdapter());
}
_serverDataBox = await account.openBox('server_data');
_serverDataCountsBox = await account.openBox('server_data_counts');
}
Future<ApiResult> requestResource(String id) async {
return account.apiClient.sendRequest(ResourceLsRequest(id), callback: (request, response) async {
if (response is PhylumApiSuccessResponse) {
@@ -46,6 +79,38 @@ class ResourceRepository {
return account.db.resources.select()..where((f) => f.id.isIn(ids));
}
Future<void> retainServerData(String id) async {
final count = _serverDataCountsBox.get(id) ?? 0;
if (count == 0) {
final r = await _selectResource(id).getSingle();
await Future.wait([
_serverDataCountsBox.put(id, 1),
_serverDataBox.put(id, r),
]);
} else {
return _serverDataCountsBox.put(id, count + 1);
}
}
Future<void> discardServerData(String id) async {
final count = _serverDataCountsBox.get(id) ?? 0;
if (count <= 0) {
throw Exception('Cannot discard server data without first retaining');
}
if (count == 1) {
await Future.wait([
_serverDataCountsBox.delete(id),
_serverDataBox.delete(id),
]);
} else {
return _serverDataCountsBox.put(id, count - 1);
}
}
Resource? getServerData(String id) {
return _serverDataBox.get(id);
}
Future<int> createResource(String id, bool dir, String parent, String name, String contentType) {
return account.db.resources.insertOne(
ResourcesCompanion.insert(
@@ -79,12 +144,19 @@ class ResourceRepository {
batch.deleteWhere(db.resources, (o) => o.id.isIn(deleted));
batch.insertAllOnConflictUpdate(db.resources, resources);
});
for (final r in resources) {
if (_serverDataBox.containsKey(r.id.value)) {
_serverDataBox.put(r.id.value, _serverDataBox.get(r.id.value)!.copyWithCompanion(r));
}
}
}
Future processResourceUpdateResponse(String id, Map<String, dynamic> data) async {
final resource = parsePartialResourceObject(data);
final r = parsePartialResourceObject(data);
debugPrint(r.toString());
final update = (account.db.resources.update()..where((o) => o.id.equals(id)));
return update.write(resource);
await update.write(r);
_serverDataBox.put(r.id.value, _serverDataBox.get(r.id.value)!.copyWithCompanion(r));
}
Future<(Iterable<ResourcesCompanion>, Iterable<String>)> parseFullResourceObject(Map<String, dynamic> data) async {
@@ -133,7 +205,7 @@ class ResourceRepository {
ResourcesCompanion parsePartialResourceObject(Map<String, dynamic> data) {
return ResourcesCompanion(
id: Value(data['id']),
id: Value.absentIfNull(data['id']),
parent: Value.absentIfNull(data['parent']),
name: Value.absentIfNull(data['name']),
dir: Value.absentIfNull(data['dir']),

View File

@@ -585,8 +585,8 @@ packages:
dependency: "direct main"
description:
path: "."
ref: a0bf6ed1e69a73e130b36436ec23437af76cd3fb
resolved-ref: a0bf6ed1e69a73e130b36436ec23437af76cd3fb
ref: b7d3c5c043a71ac1cfb4ba2548848e25187d9385
resolved-ref: b7d3c5c043a71ac1cfb4ba2548848e25187d9385
url: "https://codeberg.org/shroff/offtheline.git"
source: git
version: "0.16.0"

View File

@@ -22,7 +22,7 @@ dependencies:
offtheline:
git:
url: https://codeberg.org/shroff/offtheline.git
ref: a0bf6ed1e69a73e130b36436ec23437af76cd3fb
ref: b7d3c5c043a71ac1cfb4ba2548848e25187d9385
open_file:
path:
path_provider: