[client] restructure responses

This commit is contained in:
Abhishek Shroff
2025-01-21 00:40:31 +05:30
parent bebbdc6631
commit da3b83c211
21 changed files with 237 additions and 231 deletions
@@ -4,9 +4,9 @@ import 'package:offtheline/offtheline.dart';
import 'package:path/path.dart' as p;
import 'package:phylum/integrations/directories.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/libphylum/requests/resource_contents_request.dart';
import 'package:phylum/libphylum/responses/responses.dart';
import 'package:phylum/util/logging.dart';
import 'package:state_notifier/state_notifier.dart';
@@ -42,7 +42,7 @@ class DownloadManager extends StateNotifier<DownloadManagerState> {
task.status = const DownloadStatusStarting();
final response = await account.apiClient.dispatchRequestRaw(ResourceContentsRequest(task.resourceId));
if (response.statusCode < 200 || response.statusCode > 300) {
final error = parseErrorResponse(await response.bodyString());
final error = PhylumApiErrorResponse.fromResponseBody(await response.bodyString());
task.status = DownloadStatusError(error.message);
return;
}
@@ -2,8 +2,8 @@ import 'package:offtheline/offtheline.dart';
import 'package:phylum/libphylum/actions/action_resource_create.dart';
import 'package:phylum/libphylum/actions/action_resource_bookmark_remove.dart';
import 'package:phylum/libphylum/db/db.dart';
import 'package:phylum/libphylum/parsers/parsers.dart';
import 'package:phylum/libphylum/phylum_api_types.dart';
import 'package:phylum/libphylum/responses/responses.dart';
import 'action_resource.dart';
@@ -1,8 +1,8 @@
import 'package:drift/drift.dart';
import 'package:offtheline/offtheline.dart';
import 'package:phylum/libphylum/db/db.dart';
import 'package:phylum/libphylum/parsers/parsers.dart';
import 'package:phylum/libphylum/phylum_api_types.dart';
import 'package:phylum/libphylum/responses/responses.dart';
import 'action_resource.dart';
import 'action_resource_bookmark_add.dart';
+1 -46
View File
@@ -1,51 +1,6 @@
part of 'parsers.dart';
const keyLastBookmarkFetch = "lastBookmarksFetch";
class BookmarkListResponse extends PhylumApiSuccessResponse {
final Iterable<Bookmark> bookmarks;
final int until;
@override
Iterable<(Type, String)> get updatedObjects => bookmarks.map((b) => (Bookmark, b.resourceId));
BookmarkListResponse({required this.bookmarks, required this.until});
factory BookmarkListResponse.fromResponse(Map<String, dynamic> data) {
final bookmarks =
(data["bookmarks"] as List).cast<Map>().map((u) => parseBookmarkObject(u.cast<String, dynamic>()));
return BookmarkListResponse(bookmarks: bookmarks, until: data['until'] as int);
}
@override
Future process(PhylumAccount account) async {
await account.db.batch((batch) {
batch.insertAllOnConflictUpdate(account.db.bookmarks, bookmarks);
});
await account.persist(keyLastBookmarkFetch, until);
}
}
class BookmarkResponse extends PhylumApiSuccessResponse {
final Bookmark bookmark;
@override
Iterable<(Type, String)> get updatedObjects => [(Bookmark, bookmark.resourceId)];
BookmarkResponse({required this.bookmark});
factory BookmarkResponse.fromResponse(Map<String, dynamic> data) {
final bookmark = parseBookmarkObject((data as Map).cast<String, dynamic>());
return BookmarkResponse(bookmark: bookmark);
}
@override
Future process(PhylumAccount account) async {
return account.db.bookmarks.insertOnConflictUpdate(bookmark);
}
}
Bookmark parseBookmarkObject(Map<String, dynamic> data) {
Bookmark parseBookmark(Map<String, dynamic> data) {
return Bookmark(
resourceId: data['resource_id'],
name: data['name'],
+1 -26
View File
@@ -1,30 +1,5 @@
import 'dart:convert';
import 'package:drift/drift.dart';
import 'package:http/http.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';
part 'bookmarks.dart';
typedef JsonMapResponseParser = PhylumApiSuccessResponse Function(Map<String, dynamic> data);
Future<ApiResponse> parseJsonMapResponse(StreamedResponse response, JsonMapResponseParser parseResponse) async {
final body = await response.bodyString();
if (response.statusCode >= 200 && response.statusCode < 300) {
final responseMap = (jsonDecode(body) as Map<String, dynamic>?) ?? const {};
return parseResponse(responseMap);
}
return parseErrorResponse(body);
}
PhylumApiErrorResponse parseErrorResponse(String body) {
final map = (jsonDecode(body) as Map<String, dynamic>?) ?? const {};
return PhylumApiErrorResponse(
httpStatusCode: map['status'] ?? 500,
code: map['code'] ?? 'server_error',
message: map['msg'] ?? 'Server Error',
);
}
part 'resources.dart';
+3 -87
View File
@@ -1,90 +1,6 @@
import 'package:drift/drift.dart';
import 'package:phylum/libphylum/db/db.dart';
import 'package:phylum/libphylum/phylum_account.dart';
import 'package:phylum/libphylum/phylum_api_types.dart';
part of 'parsers.dart';
class FullResourceResponse extends PhylumApiSuccessResponse {
final Resource resource;
final Iterable<ResourcesCompanion> ancestors;
final Iterable<Resource> children;
late final Iterable<String> removedChildren;
@override
Iterable<(Type, String)> get updatedObjects => [
(Resource, resource.id),
...ancestors.map((r) => (Resource, r.id.value)),
...children.map((r) => (Resource, r.id)),
...removedChildren.map((id) => (Resource, id)),
];
FullResourceResponse({
required this.resource,
required this.ancestors,
required this.children,
});
factory FullResourceResponse.fromResponse(Map<String, dynamic> data) {
final resource = _parseFullResource(data['info'])..copyWith(lastRefresh: Value(DateTime.now()));
final ancestors =
(data['ancestors'] as List).cast<Map>().map((a) => _parseResourceAncestor(a.cast<String, dynamic>()));
final children = (data['children'] as List).cast<Map>().map((c) => _parseFullResource(c.cast<String, dynamic>()));
return FullResourceResponse(
resource: resource,
ancestors: ancestors,
children: children,
);
}
@override
Future process(PhylumAccount account) async {
final db = account.db;
final existing =
Set.of(await (db.select(db.resources)..where((t) => t.parent.equals(resource.id))).map((r) => r.id).get());
existing.removeAll(children.map((r) => r.id));
removedChildren = existing;
await db.batch((batch) {
for (final id in removedChildren) {
batch.update(db.resources, ResourcesCompanion(parent: Value(null)), where: (o) => o.id.equals(id));
}
batch.insertAllOnConflictUpdate(db.resources, ancestors);
batch.insert(db.resources, resource, mode: InsertMode.insertOrReplace);
batch.insertAll(db.resources, children, mode: InsertMode.insertOrReplace);
});
}
}
class PartialResourceResponse extends PhylumApiSuccessResponse {
final String id;
final ResourcesCompanion updates;
@override
Iterable<(Type, String)> get updatedObjects => [
(Resource, id),
];
PartialResourceResponse({
required this.id,
required this.updates,
});
factory PartialResourceResponse.fromResponse(String id, Map<String, dynamic> data) {
return PartialResourceResponse(
id: id,
updates: parsePartialResource(data),
);
}
@override
Future process(PhylumAccount account) async {
assert(id == updates.id.value);
final update = (account.db.resources.update()..where((o) => o.id.equals(id)));
await update.write(updates);
}
}
Resource _parseFullResource(Map<String, dynamic> data) {
Resource parseFullResource(Map<String, dynamic> data) {
return Resource(
id: data['id'],
parent: data['parent'],
@@ -101,7 +17,7 @@ Resource _parseFullResource(Map<String, dynamic> data) {
);
}
ResourcesCompanion _parseResourceAncestor(Map<String, dynamic> data) {
ResourcesCompanion parseResourceAncestor(Map<String, dynamic> data) {
return ResourcesCompanion(
id: Value(data['id']),
parent: Value(data['parent']),
+1 -1
View File
@@ -2,12 +2,12 @@ import 'dart:async';
import 'package:offtheline/offtheline.dart';
import 'package:phylum/libphylum/db/db.dart';
import 'package:phylum/libphylum/phylum_api_types.dart';
import 'package:phylum/libphylum/repositories/bookmark_repository.dart';
import 'package:phylum/libphylum/repositories/shared_resources_repository.dart';
import 'package:phylum/libphylum/repositories/resource_repository.dart';
import 'package:phylum/libphylum/repositories/user_repository.dart';
import 'package:phylum/libphylum/phylum_change_tracker.dart';
import 'package:phylum/libphylum/responses/responses.dart';
import 'package:phylum/util/logging.dart';
const _persistKeyAccessToken = 'accessToken';
@@ -4,33 +4,3 @@ import 'package:phylum/libphylum/phylum_account.dart';
typedef PhylumAction = OfflineAction<PhylumAccount>;
typedef PhylumActionQueue = ApiActionQueue<PhylumAccount>;
typedef PhylumActionQueueState = ApiActionQueueState<PhylumAccount>;
abstract class PhylumApiSuccessResponse extends ApiResponse<PhylumAccount> {
@override
bool get success => true;
@override
String get description => 'Success';
const PhylumApiSuccessResponse();
}
class PhylumApiErrorResponse extends ApiResponse<PhylumAccount> {
final int httpStatusCode;
final String code;
final String message;
@override
String get description => 'Api Error: $message';
@override
bool get success => false;
@override
Iterable<(Type, String)> get updatedObjects => const [];
@override
Future<void> process(PhylumAccount account) => Future.value();
PhylumApiErrorResponse({required this.httpStatusCode, required this.code, required this.message});
}
@@ -3,9 +3,9 @@ import 'package:offtheline/offtheline.dart';
import 'package:phylum/libphylum/actions/action_resource_bookmark_add.dart';
import 'package:phylum/libphylum/actions/action_resource_bookmark_remove.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/libphylum/requests/bookmarks_request.dart';
import 'package:phylum/libphylum/responses/responses.dart';
const keyLastBookmarkFetch = "lastBookmarksFetch";
@@ -10,10 +10,9 @@ import 'package:phylum/libphylum/actions/action_resource_upload.dart';
import 'package:phylum/libphylum/db/db.dart';
import 'package:phylum/libphylum/db/resource_helpers.dart';
import 'package:phylum/libphylum/name_conflict.dart';
import 'package:phylum/libphylum/parsers/parsers.dart';
import 'package:phylum/libphylum/parsers/resources.dart';
import 'package:phylum/libphylum/phylum_account.dart';
import 'package:phylum/libphylum/requests/resource_detail_request.dart';
import 'package:phylum/libphylum/responses/responses.dart';
import 'package:phylum/libphylum/util/uuid.dart';
class ResourceRepository {
@@ -1,6 +1,5 @@
import 'package:offtheline/offtheline.dart';
import 'package:phylum/libphylum/parsers/parsers.dart';
import 'package:phylum/libphylum/parsers/shared.dart';
import 'package:phylum/libphylum/responses/responses.dart';
import 'package:phylum/libphylum/phylum_account.dart';
import 'package:phylum/libphylum/requests/shared_resources_request.dart';
@@ -1,10 +1,9 @@
import 'package:drift/drift.dart';
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/libphylum/phylum_api_types.dart';
import 'package:phylum/libphylum/requests/users_list_request.dart';
import 'package:phylum/libphylum/responses/responses.dart';
class UserRepository {
final PhylumAccount account;
@@ -35,27 +34,3 @@ class UserRepository {
return _users[username]?.display ?? username;
}
}
class UserListResponse extends PhylumApiSuccessResponse {
final Iterable<User> users;
@override
Iterable<(Type, String)> get updatedObjects => const [];
@override
Future<void> process(PhylumAccount account) async {
await account.db.batch((batch) {
batch.insertAllOnConflictUpdate(account.db.users, users);
});
}
UserListResponse({required this.users});
factory UserListResponse.fromResponse(Map<String, dynamic> data) {
final users = (data["users"] as List).cast<Map>().map((u) => User(
username: data['username'],
display: data['display'],
));
return UserListResponse(users: users);
}
}
@@ -0,0 +1,26 @@
part of 'responses.dart';
const keyLastBookmarkFetch = "lastBookmarksFetch";
class BookmarkListResponse extends PhylumApiSuccessResponse {
final Iterable<Bookmark> bookmarks;
final int until;
@override
Iterable<(Type, String)> get updatedObjects => bookmarks.map((b) => (Bookmark, b.resourceId));
BookmarkListResponse({required this.bookmarks, required this.until});
factory BookmarkListResponse.fromResponse(Map<String, dynamic> data) {
final bookmarks = (data["bookmarks"] as List).cast<Map>().map((u) => parseBookmark(u.cast<String, dynamic>()));
return BookmarkListResponse(bookmarks: bookmarks, until: data['until'] as int);
}
@override
Future process(PhylumAccount account) async {
await account.db.batch((batch) {
batch.insertAllOnConflictUpdate(account.db.bookmarks, bookmarks);
});
await account.persist(keyLastBookmarkFetch, until);
}
}
@@ -0,0 +1,20 @@
part of 'responses.dart';
class BookmarkResponse extends PhylumApiSuccessResponse {
final Bookmark bookmark;
@override
Iterable<(Type, String)> get updatedObjects => [(Bookmark, bookmark.resourceId)];
BookmarkResponse({required this.bookmark});
factory BookmarkResponse.fromResponse(Map<String, dynamic> data) {
final bookmark = parseBookmark((data as Map).cast<String, dynamic>());
return BookmarkResponse(bookmark: bookmark);
}
@override
Future process(PhylumAccount account) async {
return account.db.bookmarks.insertOnConflictUpdate(bookmark);
}
}
@@ -0,0 +1,53 @@
part of 'responses.dart';
class FullResourceResponse extends PhylumApiSuccessResponse {
final Resource resource;
final Iterable<ResourcesCompanion> ancestors;
final Iterable<Resource> children;
late final Iterable<String> removedChildren;
@override
Iterable<(Type, String)> get updatedObjects => [
(Resource, resource.id),
...ancestors.map((r) => (Resource, r.id.value)),
...children.map((r) => (Resource, r.id)),
...removedChildren.map((id) => (Resource, id)),
];
FullResourceResponse({
required this.resource,
required this.ancestors,
required this.children,
});
factory FullResourceResponse.fromResponse(Map<String, dynamic> data) {
final resource = parseFullResource(data['info'])..copyWith(lastRefresh: Value(DateTime.now()));
final ancestors =
(data['ancestors'] as List).cast<Map>().map((a) => parseResourceAncestor(a.cast<String, dynamic>()));
final children = (data['children'] as List).cast<Map>().map((c) => parseFullResource(c.cast<String, dynamic>()));
return FullResourceResponse(
resource: resource,
ancestors: ancestors,
children: children,
);
}
@override
Future process(PhylumAccount account) async {
final db = account.db;
final existing =
Set.of(await (db.select(db.resources)..where((t) => t.parent.equals(resource.id))).map((r) => r.id).get());
existing.removeAll(children.map((r) => r.id));
removedChildren = existing;
await db.batch((batch) {
for (final id in removedChildren) {
batch.update(db.resources, ResourcesCompanion(parent: Value(null)), where: (o) => o.id.equals(id));
}
batch.insertAllOnConflictUpdate(db.resources, ancestors);
batch.insert(db.resources, resource, mode: InsertMode.insertOrReplace);
batch.insertAll(db.resources, children, mode: InsertMode.insertOrReplace);
});
}
}
@@ -0,0 +1,30 @@
part of 'responses.dart';
class PartialResourceResponse extends PhylumApiSuccessResponse {
final String id;
final ResourcesCompanion updates;
@override
Iterable<(Type, String)> get updatedObjects => [
(Resource, id),
];
PartialResourceResponse({
required this.id,
required this.updates,
});
factory PartialResourceResponse.fromResponse(String id, Map<String, dynamic> data) {
return PartialResourceResponse(
id: id,
updates: parsePartialResource(data),
);
}
@override
Future process(PhylumAccount account) async {
assert(id == updates.id.value);
final update = (account.db.resources.update()..where((o) => o.id.equals(id)));
await update.write(updates);
}
}
@@ -0,0 +1,65 @@
import 'dart:convert';
import 'package:drift/drift.dart';
import 'package:http/http.dart';
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';
part 'bookmark_list_response.dart';
part 'bookmark_response.dart';
part 'full_resource_response.dart';
part 'partial_resource_response.dart';
part 'shared_resources_response.dart';
part 'user_list_response.dart';
typedef JsonMapResponseParser = PhylumApiSuccessResponse Function(Map<String, dynamic> data);
Future<ApiResponse> parseJsonMapResponse(StreamedResponse response, JsonMapResponseParser parseResponse) async {
final body = await response.bodyString();
if (response.statusCode >= 200 && response.statusCode < 300) {
final responseMap = (jsonDecode(body) as Map<String, dynamic>?) ?? const {};
return parseResponse(responseMap);
}
return PhylumApiErrorResponse.fromResponseBody(body);
}
abstract class PhylumApiSuccessResponse extends ApiResponse<PhylumAccount> {
@override
bool get success => true;
@override
String get description => 'Success';
const PhylumApiSuccessResponse();
}
class PhylumApiErrorResponse extends ApiResponse<PhylumAccount> {
final int httpStatusCode;
final String code;
final String message;
@override
String get description => 'Api Error: $message';
@override
bool get success => false;
@override
Iterable<(Type, String)> get updatedObjects => const [];
@override
Future<void> process(PhylumAccount account) => Future.value();
PhylumApiErrorResponse({required this.httpStatusCode, required this.code, required this.message});
factory PhylumApiErrorResponse.fromResponseBody(String body) {
final map = (jsonDecode(body) as Map<String, dynamic>?) ?? const {};
return PhylumApiErrorResponse(
httpStatusCode: map['status'] ?? 500,
code: map['code'] ?? 'server_error',
message: map['msg'] ?? 'Server Error',
);
}
}
@@ -1,7 +1,4 @@
import 'package:phylum/libphylum/db/db.dart';
import 'package:phylum/libphylum/parsers/resources.dart';
import 'package:phylum/libphylum/phylum_account.dart';
import 'package:phylum/libphylum/phylum_api_types.dart';
part of 'responses.dart';
class SharedResourcesResponse extends PhylumApiSuccessResponse {
final Iterable<ResourcesCompanion> resources;
@@ -0,0 +1,25 @@
part of 'responses.dart';
class UserListResponse extends PhylumApiSuccessResponse {
final Iterable<User> users;
@override
Iterable<(Type, String)> get updatedObjects => const [];
@override
Future<void> process(PhylumAccount account) async {
await account.db.batch((batch) {
batch.insertAllOnConflictUpdate(account.db.users, users);
});
}
UserListResponse({required this.users});
factory UserListResponse.fromResponse(Map<String, dynamic> data) {
final users = (data["users"] as List).cast<Map>().map((u) => User(
username: data['username'],
display: data['display'],
));
return UserListResponse(users: users);
}
}
+2 -2
View File
@@ -10,9 +10,9 @@ import 'package:phylum/app_shortcuts.dart';
import 'package:phylum/integrations/download_manager.dart';
import 'package:phylum/libphylum/db/db.dart';
import 'package:phylum/libphylum/db/resource_helpers.dart';
import 'package:phylum/libphylum/parsers/parsers.dart';
import 'package:phylum/libphylum/phylum_account.dart';
import 'package:phylum/libphylum/requests/resource_contents_request.dart';
import 'package:phylum/libphylum/responses/responses.dart';
import 'package:phylum/ui/common/responsive_dialog.dart';
import 'package:phylum/ui/explorer/resource_info_view.dart';
import 'package:printing/printing.dart';
@@ -226,7 +226,7 @@ class _ResourcePreviewBuilderState extends State<ResourcePreviewBuilder> {
final account = context.read<PhylumAccount>();
final response = await account.apiClient.dispatchRequestRaw(ResourceContentsRequest(widget.resource.id));
if (response.statusCode < 200 || response.statusCode > 300) {
final error = parseErrorResponse(await response.bodyString()).message;
final error = PhylumApiErrorResponse.fromResponseBody(await response.bodyString()).message;
_error = error;
_downloading = false;
if (mounted) {
+1
View File
@@ -3,6 +3,7 @@ import 'package:offtheline/offtheline.dart';
import 'package:phylum/libphylum/actions/action_resource_bind.dart';
import 'package:phylum/libphylum/name_conflict.dart';
import 'package:phylum/libphylum/phylum_api_types.dart';
import 'package:phylum/libphylum/responses/responses.dart';
import 'package:phylum/util/dialogs.dart';
import 'package:provider/provider.dart';