mirror of
https://codeberg.org/shroff/phylum.git
synced 2025-12-31 08:20:09 -06:00
[client] Keep track of changes to remote data for reverting actions
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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']),
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -22,7 +22,7 @@ dependencies:
|
||||
offtheline:
|
||||
git:
|
||||
url: https://codeberg.org/shroff/offtheline.git
|
||||
ref: a0bf6ed1e69a73e130b36436ec23437af76cd3fb
|
||||
ref: b7d3c5c043a71ac1cfb4ba2548848e25187d9385
|
||||
open_file:
|
||||
path:
|
||||
path_provider:
|
||||
|
||||
Reference in New Issue
Block a user