diff --git a/client/lib/libphylum/actions/action_resource.dart b/client/lib/libphylum/actions/action_resource.dart index 291894d5..f460907b 100644 --- a/client/lib/libphylum/actions/action_resource.dart +++ b/client/lib/libphylum/actions/action_resource.dart @@ -7,8 +7,8 @@ abstract class ResourceAction extends PhylumAction { String get resourceId => _resourceId; @override - ResponseParser get parseResponse => - (_, response) => parseJsonMapResponse(response, PartialResourceResponse.fromResponse); + ResponseParser get parseResponse => (_, response) => + parseJsonMapResponse(response, (response) => ResourceResponse.fromResponse(response, username: '')); ResourceAction({required String resourceId}) : _resourceId = resourceId; diff --git a/client/lib/libphylum/actions/action_resource_create.dart b/client/lib/libphylum/actions/action_resource_create.dart index 225894f3..cafcea5e 100644 --- a/client/lib/libphylum/actions/action_resource_create.dart +++ b/client/lib/libphylum/actions/action_resource_create.dart @@ -16,11 +16,11 @@ abstract class ResourceCreateAction extends ResourceBindAction { @override FutureOr processResponse(ApiResponse response) async { - if (response is PartialResourceResponse) { - if (response.id != resourceId) { + if (response is ResourceResponse) { + if (response.resource.id != resourceId) { account.actionQueue.updateActions( (PhylumAction action) => action is ResourceAction && action.resourceId == resourceId, - (action) => (action as ResourceAction).setResourceId(response.id), + (action) => (action as ResourceAction).setResourceId(response.resource.id), ); } } diff --git a/client/lib/libphylum/actions/action_resource_delete.dart b/client/lib/libphylum/actions/action_resource_delete.dart index 060fbbd3..23ae56e3 100644 --- a/client/lib/libphylum/actions/action_resource_delete.dart +++ b/client/lib/libphylum/actions/action_resource_delete.dart @@ -89,6 +89,6 @@ class ResourceDeleteAction extends ResourceAction with JsonApiAction { bool dependsOn(PhylumAction action) => action is ResourceAction && action.resourceId == resourceId; @override - ResponseParser get parseResponse => (_, response) => - parseJsonMapResponse(response, permanent ? EmptyResponse.fromResponse : PartialResourceResponse.fromResponse); + ResponseParser get parseResponse => + permanent ? (_, response) => parseJsonMapResponse(response, EmptyResponse.fromResponse) : super.parseResponse; } diff --git a/client/lib/libphylum/parsers/parsers.dart b/client/lib/libphylum/parsers/parsers.dart index 45f7c778..878ad05b 100644 --- a/client/lib/libphylum/parsers/parsers.dart +++ b/client/lib/libphylum/parsers/parsers.dart @@ -1,9 +1,4 @@ -import 'dart:convert'; - -import 'package:drift/drift.dart'; import 'package:phylum/libphylum/db/db.dart'; -import 'package:phylum/util/permissions.dart'; part 'bookmarks.dart'; part 'publinks.dart'; -part 'resources.dart'; diff --git a/client/lib/libphylum/parsers/resources.dart b/client/lib/libphylum/parsers/resources.dart deleted file mode 100644 index ea6f84cc..00000000 --- a/client/lib/libphylum/parsers/resources.dart +++ /dev/null @@ -1,72 +0,0 @@ -part of 'parsers.dart'; - -class FullResource { - final Resource resource; - final Iterable publinks; - final Map permissions; - - FullResource({required this.resource, required this.publinks, required this.permissions}); -} - -class PartialResource { - final ResourcesCompanion resource; - final Iterable? publinks; - - PartialResource({required this.resource, required this.publinks}); -} - -FullResource parseFullResource( - Map data, { - required String username, - required String? parent, - Map? inheritedPermissions, -}) { - inheritedPermissions ??= (data['inherited_permissions'] as Map).cast(); - final grants = (data['grants'] as Map) - .cast() - .map((k, v) => MapEntry(k, v.cast()['p'] as Permission)); - final permissions = Map.of(inheritedPermissions)..addAll(grants); - - final r = Resource( - id: data['id'], - parent: parent, - name: data['name'], - dir: data['dir'], - created: DateTime.fromMillisecondsSinceEpoch(data['created']), - modified: DateTime.fromMillisecondsSinceEpoch(data['modified']), - deleted: data['deleted'] == 0 ? null : DateTime.fromMillisecondsSinceEpoch(data['deleted']), - contentLength: data['c_len'], - contentSha256: data['c_sha256'], - contentType: data['c_type'], - inheritedPermissions: jsonEncode(inheritedPermissions), - grants: grants.isEmpty ? null : jsonEncode(grants), - userPermission: permissions[username] ?? 0, - ); - final publinks = parsePublinks(data['publinks'], r.id); - return FullResource(resource: r, publinks: publinks, permissions: permissions); -} - -ResourcesCompanion parseResourceAncestor(Map data) { - return ResourcesCompanion( - id: Value(data['id']), - name: Value(data['name']), - dir: Value(true), - userPermission: Value(data['user_permission']), - ); -} - -PartialResource parsePartialResource(Map data, [String? id]) { - final r = ResourcesCompanion( - id: Value(data['id'] ?? id), - name: Value(data['name']), - dir: Value(data['dir']), - created: Value(DateTime.fromMillisecondsSinceEpoch(data['created'])), - modified: Value(DateTime.fromMillisecondsSinceEpoch(data['modified'])), - deleted: data['deleted'] == 0 ? Value(null) : Value(DateTime.fromMillisecondsSinceEpoch(data['deleted'])), - contentLength: Value(data['c_len']), - contentSha256: Value(data['c_sha256']), - contentType: Value(data['c_type']), - grants: data['grants'].isEmpty ? Value('') : Value(jsonEncode(data['grants']))); - final publinks = data['publinks'] != null ? parsePublinks(data['publinks'], r.id.value) : null; - return PartialResource(resource: r, publinks: publinks); -} diff --git a/client/lib/libphylum/repositories/resource_repository.dart b/client/lib/libphylum/repositories/resource_repository.dart index 9c77af25..9204350e 100644 --- a/client/lib/libphylum/repositories/resource_repository.dart +++ b/client/lib/libphylum/repositories/resource_repository.dart @@ -53,7 +53,7 @@ class ResourceRepository extends Repository { return _account.apiClient.sendRequest( ResourceInfoRequest(id), (request, response) => - parseJsonMapResponse(response, (data) => FullResourceResponse.fromResponse(data, _account.userEmail)), + parseJsonMapResponse(response, (data) => ResourceInfoResponse.fromResponse(data, _account.userEmail)), ); } diff --git a/client/lib/libphylum/repositories/shared_resources_repository.dart b/client/lib/libphylum/repositories/shared_resources_repository.dart index 63f10268..a83aca72 100644 --- a/client/lib/libphylum/repositories/shared_resources_repository.dart +++ b/client/lib/libphylum/repositories/shared_resources_repository.dart @@ -11,7 +11,12 @@ class SharedResourcesRepository { Future refresh() async { return account.apiClient.sendRequest( SharedResourcesRequest(), - (request, response) => parseJsonMapResponse(response, SharedResourcesResponse.fromResponse), + (request, response) => parseJsonMapResponse( + response, + (response) => SharedResourcesResponse.fromResponse( + response, + account.userEmail, + )), ); } } diff --git a/client/lib/libphylum/repositories/trash_repository.dart b/client/lib/libphylum/repositories/trash_repository.dart index 9dcc2462..50a208c0 100644 --- a/client/lib/libphylum/repositories/trash_repository.dart +++ b/client/lib/libphylum/repositories/trash_repository.dart @@ -51,8 +51,10 @@ class TrashedResourceRepository extends Repository refresh({String? cursor}) async { return _account.apiClient.sendRequest( - TrashListRequest(cursor: cursor), - (request, response) => parseJsonMapResponse(response, TrashListResponse.fromResponse), - ); + TrashListRequest(cursor: cursor), + (request, response) => parseJsonMapResponse( + response, + (response) => TrashListResponse.fromResponse(response, _account.userEmail), + )); } } diff --git a/client/lib/libphylum/responses/partial_resource_response.dart b/client/lib/libphylum/responses/partial_resource_response.dart deleted file mode 100644 index e3280979..00000000 --- a/client/lib/libphylum/responses/partial_resource_response.dart +++ /dev/null @@ -1,44 +0,0 @@ -part of 'responses.dart'; - -class PartialResourceResponse extends PhylumApiSuccessResponse { - final String id; - final PartialResource updates; - - PartialResourceResponse({ - required this.id, - required this.updates, - }); - - factory PartialResourceResponse.fromResponse(Map data) { - final updates = parsePartialResource(data); - return PartialResourceResponse( - id: updates.resource.id.value, - updates: updates, - ); - } - - @override - Future process(PhylumAccount account) async { - final db = account.db; - - final publinks = updates.publinks; - Iterable? orphanedPublinks; - if (publinks != null) { - orphanedPublinks = Set.of(await (db.select(db.publinks) - ..where((l) => l.root.equals(id) & l.id.isNotIn(publinks.map((l) => l.id)))) - .map((r) => r.id) - .get()); - } - - await db.batch((batch) async { - batch.update(db.resources, updates.resource, where: (o) => o.id.equals(id)); - if (publinks != null) { - for (final id in orphanedPublinks!) { - batch.update(db.publinks, PublinksCompanion(root: Value('')), where: (o) => o.id.equals(id)); - } - batch.insertAll(db.publinks, publinks, mode: InsertMode.insertOrReplace); - } - }); - await account.datastore.get().reloadRemoteData(id); - } -} diff --git a/client/lib/libphylum/responses/full_resource_response.dart b/client/lib/libphylum/responses/resource_info_response.dart similarity index 70% rename from client/lib/libphylum/responses/full_resource_response.dart rename to client/lib/libphylum/responses/resource_info_response.dart index 1574c96e..540f0d6d 100644 --- a/client/lib/libphylum/responses/full_resource_response.dart +++ b/client/lib/libphylum/responses/resource_info_response.dart @@ -1,33 +1,31 @@ part of 'responses.dart'; -class FullResourceResponse extends PhylumApiSuccessResponse { - final FullResource resource; +class ResourceInfoResponse extends PhylumApiSuccessResponse { + final ResourceResponse root; final Iterable ancestors; - final Iterable children; + final Iterable children; - FullResourceResponse({ - required this.resource, + ResourceInfoResponse({ + required this.root, required this.ancestors, required this.children, }); - factory FullResourceResponse.fromResponse(Map data, String username) { + factory ResourceInfoResponse.fromResponse(Map data, String username) { final ancestors = (data['ancestors'] as List).cast().map((a) => parseResourceAncestor(a.cast())); - final resource = parseFullResource( + final resource = ResourceResponse.fromResponse( data['resource'], - parent: ancestors.isNotEmpty ? ancestors.first.id.value : null, username: username, ); - final children = (data['children'] as List).cast().map((c) => parseFullResource( + final children = (data['children'] as List).cast().map((c) => ResourceResponse.fromResponse( c.cast(), - parent: resource.resource.id, username: username, inheritedPermissions: resource.permissions, )); - return FullResourceResponse( - resource: resource, + return ResourceInfoResponse( + root: resource, ancestors: ancestors, children: children, ); @@ -37,7 +35,7 @@ class FullResourceResponse extends PhylumApiSuccessResponse { Future process(PhylumAccount account) async { final db = account.db; - final fullResources = [resource, ...children]; + final fullResources = [root, ...children]; final publinks = fullResources.map((r) => r.publinks).fold>([], (acc, e) => acc..addAll(e)); final orphanedPubinks = Set.of(await (db.select(db.publinks) ..where( @@ -45,8 +43,8 @@ class FullResourceResponse extends PhylumApiSuccessResponse { .map((r) => r.id) .get()); - final existing = Set.of( - await (db.select(db.resources)..where((t) => t.parent.equals(resource.resource.id))).map((r) => r.id).get()); + final existing = + Set.of(await (db.select(db.resources)..where((t) => t.parent.equals(root.resource.id))).map((r) => r.id).get()); existing.removeAll(children.map((r) => r.resource.id)); final removedChildren = List.of(existing, growable: false); @@ -55,7 +53,7 @@ class FullResourceResponse extends PhylumApiSuccessResponse { batch.update(db.resources, ResourcesCompanion(parent: Value(null)), where: (o) => o.id.equals(id)); } batch.insertAllOnConflictUpdate(db.resources, ancestors); - batch.insert(db.resources, resource.resource, mode: InsertMode.insertOrReplace); + batch.insert(db.resources, root.resource, mode: InsertMode.insertOrReplace); batch.insertAll(db.resources, children.map((r) => r.resource), mode: InsertMode.insertOrReplace); for (final id in orphanedPubinks) { @@ -73,9 +71,18 @@ class FullResourceResponse extends PhylumApiSuccessResponse { await repo.reloadRemoteData(a.id.value); } // This resource and its children are fully parsed, so the incoming object can replace the existing one - await repo.replaceRemoteData(resource.resource.id, resource.resource); + await repo.replaceRemoteData(root.resource.id, root.resource); for (final c in children) { await repo.replaceRemoteData(c.resource.id, c.resource); } } } + +ResourcesCompanion parseResourceAncestor(Map data) { + return ResourcesCompanion( + id: Value(data['id']), + name: Value(data['name']), + dir: Value(true), + userPermission: Value(data['user_permission']), + ); +} diff --git a/client/lib/libphylum/responses/resource_response.dart b/client/lib/libphylum/responses/resource_response.dart new file mode 100644 index 00000000..8a0ca9e0 --- /dev/null +++ b/client/lib/libphylum/responses/resource_response.dart @@ -0,0 +1,62 @@ +part of 'responses.dart'; + +class ResourceResponse extends PhylumApiSuccessResponse { + final Resource resource; + final Iterable publinks; + final Map permissions; + + ResourceResponse({required this.resource, required this.publinks, required this.permissions}); + + factory ResourceResponse.fromResponse( + Map data, { + required String username, + String? parent, + Map? inheritedPermissions, + }) { + inheritedPermissions ??= (data['inherited_permissions'] as Map).cast(); + final grants = (data['grants'] as Map) + .cast() + .map((k, v) => MapEntry(k, v.cast()['p'] as Permission)); + final permissions = Map.of(inheritedPermissions)..addAll(grants); + + final r = Resource( + id: data['id'], + parent: parent ?? data['parent'], + name: data['name'], + dir: data['dir'], + created: DateTime.fromMillisecondsSinceEpoch(data['created']), + modified: DateTime.fromMillisecondsSinceEpoch(data['modified']), + deleted: data['deleted'] == 0 ? null : DateTime.fromMillisecondsSinceEpoch(data['deleted']), + contentLength: data['c_len'], + contentSha256: data['c_sha256'], + contentType: data['c_type'], + inheritedPermissions: jsonEncode(inheritedPermissions), + grants: grants.isEmpty ? null : jsonEncode(grants), + userPermission: permissions[username] ?? 0, + ); + final publinks = parsePublinks(data['publinks'], r.id); + return ResourceResponse(resource: r, publinks: publinks, permissions: permissions); + } + + @override + Future process(PhylumAccount account) async { + final db = account.db; + + final orphanedPubinks = Set.of(await (db.select(db.publinks) + ..where((l) => l.root.equals(resource.id) & l.id.isNotIn(publinks.map((l) => l.id)))) + .map((r) => r.id) + .get()); + + await db.batch((batch) async { + batch.insert(db.resources, resource, mode: InsertMode.insertOrReplace); + + for (final id in orphanedPubinks) { + batch.update(db.publinks, PublinksCompanion(root: Value('')), where: (o) => o.id.equals(id)); + } + batch.insertAll(db.publinks, publinks, mode: InsertMode.insertOrReplace); + }); + + final repo = account.datastore.get(); + await repo.replaceRemoteData(resource.id, resource); + } +} diff --git a/client/lib/libphylum/responses/responses.dart b/client/lib/libphylum/responses/responses.dart index dae7985e..f7c5fd3e 100644 --- a/client/lib/libphylum/responses/responses.dart +++ b/client/lib/libphylum/responses/responses.dart @@ -6,12 +6,13 @@ import 'package:offtheline/offtheline.dart'; import 'package:phylum/libphylum/db/db.dart'; import 'package:phylum/libphylum/parsers/parsers.dart'; import 'package:phylum/libphylum/phylum_account.dart'; +import 'package:phylum/util/permissions.dart'; part 'bookmark_list_response.dart'; part 'bookmark_response.dart'; part 'empty_response.dart'; -part 'full_resource_response.dart'; -part 'partial_resource_response.dart'; +part 'resource_info_response.dart'; +part 'resource_response.dart'; part 'search_response.dart'; part 'shared_resources_response.dart'; part 'trash_list_response.dart'; diff --git a/client/lib/libphylum/responses/search_response.dart b/client/lib/libphylum/responses/search_response.dart index ff96d952..bca71f17 100644 --- a/client/lib/libphylum/responses/search_response.dart +++ b/client/lib/libphylum/responses/search_response.dart @@ -1,23 +1,24 @@ part of 'responses.dart'; class SearchResponse extends PhylumApiSuccessResponse { - final List resources; + final List resources; SearchResponse({required this.resources}); - factory SearchResponse.fromResponse(List data) { - final results = - data.cast().map((u) => parsePartialResource(u.cast())).toList(growable: false); + factory SearchResponse.fromResponse(List data, String username) { + final results = data + .cast() + .map((u) => ResourceResponse.fromResponse(u.cast(), username: username)) + .toList(growable: false); return SearchResponse(resources: results); } @override Future process(PhylumAccount account) async { final db = account.db; - final publinks = resources.map((r) => r.publinks).fold>([], (acc, e) => acc..addAll(e ?? const [])); + final publinks = resources.map((r) => r.publinks).fold>([], (acc, e) => acc..addAll(e)); final orphanedPublinks = Set.of(await (db.select(db.publinks) - ..where( - (l) => l.root.isIn(resources.map((r) => r.resource.id.value)) & l.id.isNotIn(publinks.map((l) => l.id)))) + ..where((l) => l.root.isIn(resources.map((r) => r.resource.id)) & l.id.isNotIn(publinks.map((l) => l.id)))) .map((r) => r.id) .get()); @@ -30,7 +31,7 @@ class SearchResponse extends PhylumApiSuccessResponse { }); final repo = account.datastore.get(); for (final r in resources) { - await repo.reloadRemoteData(r.resource.id.value); + await repo.reloadRemoteData(r.resource.id); } } } diff --git a/client/lib/libphylum/responses/shared_resources_response.dart b/client/lib/libphylum/responses/shared_resources_response.dart index 374a4a3f..4656057d 100644 --- a/client/lib/libphylum/responses/shared_resources_response.dart +++ b/client/lib/libphylum/responses/shared_resources_response.dart @@ -1,16 +1,15 @@ part of 'responses.dart'; class SharedResourcesResponse extends PhylumApiSuccessResponse { - final Iterable resources; + final Iterable resources; @override Future process(PhylumAccount account) async { final db = account.db; - final shared = resources.indexed.map((r) => SharedCompanion.insert(resourceId: r.$2.resource.id.value, seq: r.$1)); - final publinks = resources.map((r) => r.publinks).fold>([], (acc, e) => acc..addAll(e ?? const [])); + final shared = resources.indexed.map((r) => SharedCompanion.insert(resourceId: r.$2.resource.id, seq: r.$1)); + final publinks = resources.map((r) => r.publinks).fold>([], (acc, e) => acc..addAll(e)); final orphanedPublinks = Set.of(await (db.select(db.publinks) - ..where( - (l) => l.root.isIn(resources.map((r) => r.resource.id.value)) & l.id.isNotIn(publinks.map((l) => l.id)))) + ..where((l) => l.root.isIn(resources.map((r) => r.resource.id)) & l.id.isNotIn(publinks.map((l) => l.id)))) .map((r) => r.id) .get()); @@ -27,14 +26,17 @@ class SharedResourcesResponse extends PhylumApiSuccessResponse { final repo = account.datastore.get(); for (final r in resources) { - await repo.reloadRemoteData(r.resource.id.value); + await repo.reloadRemoteData(r.resource.id); } } SharedResourcesResponse({required this.resources}); - factory SharedResourcesResponse.fromResponse(Map data) { - final resources = (data["shared"] as List).cast().map((u) => parsePartialResource(u.cast())); + factory SharedResourcesResponse.fromResponse(Map data, String username) { + final resources = (data["shared"] as List).cast().map((u) => ResourceResponse.fromResponse( + u.cast(), + username: username, + )); return SharedResourcesResponse(resources: resources); } } diff --git a/client/lib/libphylum/responses/trash_list_response.dart b/client/lib/libphylum/responses/trash_list_response.dart index 48003706..0af38a5a 100644 --- a/client/lib/libphylum/responses/trash_list_response.dart +++ b/client/lib/libphylum/responses/trash_list_response.dart @@ -1,7 +1,7 @@ part of 'responses.dart'; class TrashListResponse extends PhylumApiSuccessResponse { - final Iterable resources; + final Iterable resources; final String? cursor; final bool clear; @@ -11,9 +11,10 @@ class TrashListResponse extends PhylumApiSuccessResponse { required this.clear, }); - factory TrashListResponse.fromResponse(Map data) { - final resources = - (data['resources'] as List).cast().map((a) => parsePartialResource(a.cast())); + factory TrashListResponse.fromResponse(Map data, String username) { + final resources = (data['resources'] as List) + .cast() + .map((a) => ResourceResponse.fromResponse(a.cast(), username: username)); final cursor = data['cursor'] as String; final clear = data['clear'] as bool; @@ -27,10 +28,9 @@ class TrashListResponse extends PhylumApiSuccessResponse { @override Future process(PhylumAccount account) async { final db = account.db; - final publinks = resources.map((r) => r.publinks).fold>([], (acc, e) => acc..addAll(e ?? const [])); + final publinks = resources.map((r) => r.publinks).fold>([], (acc, e) => acc..addAll(e)); final orphanedPublinks = Set.of(await (db.select(db.publinks) - ..where( - (l) => l.root.isIn(resources.map((r) => r.resource.id.value)) & l.id.isNotIn(publinks.map((l) => l.id)))) + ..where((l) => l.root.isIn(resources.map((r) => r.resource.id)) & l.id.isNotIn(publinks.map((l) => l.id)))) .map((r) => r.id) .get()); @@ -39,7 +39,7 @@ class TrashListResponse extends PhylumApiSuccessResponse { if (clear) { batch.deleteAll(db.trashedResources); } - batch.insertAll(db.trashedResources, resources.map((r) => TrashedResource(id: r.resource.id.value)), + batch.insertAll(db.trashedResources, resources.map((r) => TrashedResource(id: r.resource.id)), mode: InsertMode.insertOrReplace); for (final id in orphanedPublinks) { batch.update(db.publinks, PublinksCompanion(root: Value('')), where: (o) => o.id.equals(id)); @@ -49,7 +49,7 @@ class TrashListResponse extends PhylumApiSuccessResponse { final repo = account.datastore.get(); for (final r in resources) { - await repo.reloadRemoteData(r.resource.id.value); + await repo.reloadRemoteData(r.resource.id); } } } diff --git a/client/lib/ui/explorer/page.dart b/client/lib/ui/explorer/page.dart index 290ec5c9..19f18649 100644 --- a/client/lib/ui/explorer/page.dart +++ b/client/lib/ui/explorer/page.dart @@ -125,12 +125,14 @@ class ExplorerPageSearch extends ExplorerPage { return account.apiClient .sendRequest( SearchRequest(query: query), - (request, response) => parseJsonListResponse(response, SearchResponse.fromResponse), + (request, response) => parseJsonListResponse( + response, + (response) => SearchResponse.fromResponse(response, account.userEmail), + ), ) .then((result) { if (result is SearchResponse) { - // _streamController.add(result.resources.map((r) => r.resource).toList(growable: false)); - _streamController.add(const []); + _streamController.add(result.resources.map((r) => r.resource).toList(growable: false)); } return result is ApiSuccessResponse; });