[client] Upgrade offtheline to 0.15.0, cleaner abstractions

This commit is contained in:
Abhishek Shroff
2024-09-20 21:32:49 +05:30
parent 1600471915
commit a249f1079d
15 changed files with 150 additions and 149 deletions

View File

@@ -6,6 +6,7 @@ import 'package:path/path.dart' as p;
import 'package:phylum/integrations/directories.dart';
import 'package:phylum/libphylum/db/db.dart';
import 'package:phylum/libphylum/phylum_account.dart';
import 'package:phylum/libphylum/phylum_api_response.dart';
import 'package:phylum/libphylum/requests/resource_contents_request.dart';
import 'package:state_notifier/state_notifier.dart';
@@ -40,18 +41,13 @@ class DownloadManager extends StateNotifier<DownloadManagerState> {
task._status.addListener(l);
state = state.copyWith(running: [...state.running, task]);
final apiResponse = await account.api.sendRequestRaw(ResourceContentsRequest(r.id));
if (apiResponse.error != null) {
final msg = switch (apiResponse.error) {
const ApiClosedError() => 'Api closed',
const ApiOfflineError() => 'Cannot reach server',
ApiUnknownError(ex: dynamic ex) => 'Unknown Error - $ex',
ApiRequestError(err: PhylumApiErrorResponse err) => 'Request Error - ${err.message}',
_ => '',
};
task._status.value = DownloadStatusError(msg);
return;
} else {
try {
final response = await account.apiClient.dispatchRequestRaw(ResourceContentsRequest(r.id));
if (response.statusCode < 200 || response.statusCode > 300) {
final error = PhylumApiErrorResponse.fromResponseString(await response.bodyString());
task._status.value = DownloadStatusError(error.message);
return;
}
final output = _createDownloadFile(PhylumDirectories.instance.downloads!, r.name);
if (output == null) {
task._status.value = const DownloadStatusError('Unable to open output file');
@@ -59,10 +55,9 @@ class DownloadManager extends StateNotifier<DownloadManagerState> {
}
final sink = output.openWrite();
final httpResponse = apiResponse.result!;
final length = httpResponse.contentLength ?? r.size;
final length = response.contentLength ?? r.size;
int received = 0;
final stream = httpResponse.stream.map((s) {
final stream = response.stream.map((s) {
received += s.length;
task._status.value = DownloadStatusRunning(received, length);
return s;
@@ -73,6 +68,10 @@ class DownloadManager extends StateNotifier<DownloadManagerState> {
task._status.value = DownloadStatusError(err.toString());
output.deleteSync();
}).whenComplete(() => task._status.value = DownloadStatusFinished(output.path));
} on SocketException {
task._status.value = const DownloadStatusError('Cannot reach server');
} catch (e) {
task._status.value = DownloadStatusError('Unknown Error - $e');
}
}

View File

@@ -4,8 +4,9 @@ import 'package:drift/drift.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_response.dart';
class ResourceDeleteAction extends ApiAction<PhylumAccount> with JsonApiAction {
class ResourceDeleteAction extends ApiAction<PhylumApiResponse, PhylumApiErrorResponse, PhylumAccount> with JsonApiAction {
static const actionName = 'resourceDelete';
@override
String get name => actionName;

View File

@@ -2,9 +2,9 @@ import 'dart:async';
import 'package:offtheline/offtheline.dart';
import 'package:phylum/libphylum/phylum_account.dart';
import 'package:phylum/libphylum/phylum_datastore.dart';
import 'package:phylum/libphylum/phylum_api_response.dart';
class ResourceMkdirAction extends ApiAction<PhylumAccount> with JsonApiAction {
class ResourceMkdirAction extends ApiAction<PhylumApiResponse, PhylumApiErrorResponse, PhylumAccount> with JsonApiAction {
static const actionName = 'resourceMkdir';
@override
String get name => actionName;
@@ -15,9 +15,6 @@ class ResourceMkdirAction extends ApiAction<PhylumAccount> with JsonApiAction {
@override
String get endpoint => '/api/v1/resources/mkdir/$id';
@override
get tag => resourceSummaryResponse;
final String id;
final String parent;
final String resourceName;
@@ -65,4 +62,11 @@ class ResourceMkdirAction extends ApiAction<PhylumAccount> with JsonApiAction {
'resourceName': resourceName,
'parent': parent,
};
@override
FutureOr<void> processResponse(PhylumAccount account, ApiResponse<PhylumApiResponse, PhylumApiErrorResponse> response) async {
if (response is ApiSuccessResponse<PhylumApiResponse, PhylumApiErrorResponse>) {
await account.resourceRepository.parseResourceSummaryResponse(response.result);
}
}
}

View File

@@ -4,9 +4,9 @@ import 'package:drift/drift.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_datastore.dart';
import 'package:phylum/libphylum/phylum_api_response.dart';
class ResourceMoveAction extends ApiAction<PhylumAccount> with JsonApiAction {
class ResourceMoveAction extends ApiAction<PhylumApiResponse, PhylumApiErrorResponse, PhylumAccount> with JsonApiAction {
static const actionName = 'resourceMove';
@override
String get name => actionName;
@@ -17,9 +17,6 @@ class ResourceMoveAction extends ApiAction<PhylumAccount> with JsonApiAction {
@override
String get endpoint => '/api/v1/resources/move/$id';
@override
get tag => resourceSummaryResponse;
final String id;
final String parent;
final String? oldParent;
@@ -65,4 +62,11 @@ class ResourceMoveAction extends ApiAction<PhylumAccount> with JsonApiAction {
'oldParent': oldParent,
'description': description,
};
@override
FutureOr<void> processResponse(PhylumAccount account, ApiResponse<PhylumApiResponse, PhylumApiErrorResponse> response) async {
if (response is ApiSuccessResponse<PhylumApiResponse, PhylumApiErrorResponse>) {
await account.resourceRepository.parseResourceSummaryResponse(response.result);
}
}
}

View File

@@ -4,9 +4,9 @@ import 'package:drift/drift.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_datastore.dart';
import 'package:phylum/libphylum/phylum_api_response.dart';
class ResourceRenameAction extends ApiAction<PhylumAccount> with JsonApiAction {
class ResourceRenameAction extends ApiAction<PhylumApiResponse, PhylumApiErrorResponse, PhylumAccount> with JsonApiAction {
static const actionName = 'resourceRename';
@override
String get name => actionName;
@@ -17,9 +17,6 @@ class ResourceRenameAction extends ApiAction<PhylumAccount> with JsonApiAction {
@override
String get endpoint => '/api/v1/resources/rename/$id';
@override
get tag => resourceSummaryResponse;
final String id;
final String newName;
final String oldName;
@@ -71,4 +68,11 @@ class ResourceRenameAction extends ApiAction<PhylumAccount> with JsonApiAction {
'newName': newName,
'oldName': oldName,
};
@override
FutureOr<void> processResponse(PhylumAccount account, ApiResponse<PhylumApiResponse, PhylumApiErrorResponse> response) async {
if (response is ApiSuccessResponse<PhylumApiResponse, PhylumApiErrorResponse>) {
await account.resourceRepository.parseResourceSummaryResponse(response.result);
}
}
}

View File

@@ -6,10 +6,10 @@ import 'package:http_parser/http_parser.dart';
import 'package:mime/mime.dart';
import 'package:offtheline/offtheline.dart';
import 'package:phylum/libphylum/phylum_account.dart';
import 'package:phylum/libphylum/phylum_datastore.dart';
import 'package:phylum/libphylum/phylum_api_response.dart';
import 'package:uuid/uuid.dart';
class ResourceUploadAction extends ApiAction<PhylumAccount> with FileUploadApiAction {
class ResourceUploadAction extends ApiAction<PhylumApiResponse, PhylumApiErrorResponse, PhylumAccount> with FileUploadApiAction {
static const actionName = 'resourceUpload';
@override
String get name => actionName;
@@ -20,9 +20,6 @@ class ResourceUploadAction extends ApiAction<PhylumAccount> with FileUploadApiAc
@override
String get endpoint => '/api/v1/resources/upload/$id';
@override
get tag => resourceSummaryResponse;
@override
Future<MultipartFile> get file => MultipartFile.fromPath('contents', path, filename: resourceName, contentType: MediaType.parse(contentType));
@@ -96,4 +93,11 @@ class ResourceUploadAction extends ApiAction<PhylumAccount> with FileUploadApiAc
'name': resourceName,
};
}
@override
FutureOr<void> processResponse(PhylumAccount account, ApiResponse<PhylumApiResponse, PhylumApiErrorResponse> response) async {
if (response is ApiSuccessResponse<PhylumApiResponse, PhylumApiErrorResponse>) {
await account.resourceRepository.parseResourceSummaryResponse(response.result);
}
}
}

View File

@@ -1,43 +1,28 @@
import 'dart:convert';
import 'package:offtheline/offtheline.dart';
import 'package:phylum/libphylum/db/db.dart';
import 'package:phylum/libphylum/phylum_datastore.dart';
import 'package:phylum/libphylum/phylum_api_response.dart';
import 'package:phylum/libphylum/repositories/resource_repository.dart';
import 'package:phylum/util/logging.dart';
const _persistKeyAccessToken = 'accessToken';
const _persistKeyUserEmail = 'userEmail';
const _persistKeyUserName = 'userName';
const _persistKeyUserHome = 'userHome';
typedef PhylumApiResponse = Map<String, dynamic>;
class PhylumApiErrorResponse {
final String code;
final String message;
PhylumApiErrorResponse._({required this.code, required this.message});
factory PhylumApiErrorResponse.fromResponseString(String response) {
final map = (jsonDecode(response) as Map<String, dynamic>?) ?? const {};
return PhylumApiErrorResponse._(code: map['code'] ?? 'server_error', message: map['msg'] ?? 'Server Error');
}
}
Map<String, dynamic> _tansformResponse(String response) => (jsonDecode(response) as Map<String, dynamic>?) ?? const {};
class PhylumAccount extends Account<PhylumApiResponse, PhylumApiErrorResponse, PhylumAccount> {
late final db = AppDatabase(id: id);
late final resourceRepository = ResourceRepository(account: this);
final datastore = PhylumDatastore();
final actionQueue = ApiActionQueue<PhylumApiResponse, PhylumApiErrorResponse, PhylumAccount>();
late final _dispatcher = HttpClientDispatcher();
@override
Dispatcher get dispatcher => _dispatcher;
String? get accessToken => getPersisted(_persistKeyAccessToken);
set _accessToken(String? value) {
if (value != accessToken) {
persist(_persistKeyAccessToken, value);
}
api.setHeader('Authorization', 'bearer $value');
apiClient.setHeader('Authorization', 'bearer $value');
}
String get userEmail => getPersisted(_persistKeyUserEmail);
@@ -55,16 +40,9 @@ class PhylumAccount extends Account<PhylumApiResponse, PhylumApiErrorResponse, P
persist(_persistKeyUserHome, value);
}
PhylumAccount.create({required super.serverUri})
: super.create(
transformResponse: _tansformResponse,
transformErrorResponse: PhylumApiErrorResponse.fromResponseString,
);
PhylumAccount.restore({required super.id})
: super.restore(
transformResponse: _tansformResponse,
transformErrorResponse: PhylumApiErrorResponse.fromResponseString,
);
PhylumAccount.create({required super.serverUri}) : super.create(transformResponse: transformApiResponse);
PhylumAccount.restore({required super.id}) : super.restore(transformResponse: transformApiResponse);
@override
Future<void> initialize() async {
@@ -72,8 +50,16 @@ class PhylumAccount extends Account<PhylumApiResponse, PhylumApiErrorResponse, P
// Set Authorization header
_accessToken = accessToken;
await registerListener(datastore);
await registerListener(actionQueue);
void Function()? l;
l = apiClient.addResponseListener((request, errorResponse) {
if (errorResponse is ApiErrorResponse<PhylumApiResponse, PhylumApiErrorResponse>) {
if (errorResponse.err.code == "credentials_invalid") {
logger.i('Invalid Credentials - Logging out');
logOut();
l?.call();
}
}
});
}
@override
@@ -82,10 +68,6 @@ class PhylumAccount extends Account<PhylumApiResponse, PhylumApiErrorResponse, P
await db.dropDatabase();
}
Future<void> addAction(ApiAction<PhylumAccount> action) async {
return actionQueue.addAction(action);
}
static Future<PhylumAccount> createFromLoginResponse(Uri serverUri, Map<String, dynamic> response) async {
final account = PhylumAccount.create(serverUri: serverUri);
await account.initialized;

View File

@@ -0,0 +1,27 @@
import 'dart:convert';
import 'package:http/http.dart';
import 'package:offtheline/offtheline.dart';
typedef PhylumApiResponse = Map<String, dynamic>;
class PhylumApiErrorResponse {
final String code;
final String message;
PhylumApiErrorResponse._({required this.code, required this.message});
factory PhylumApiErrorResponse.fromResponseString(String response) {
final map = (jsonDecode(response) as Map<String, dynamic>?) ?? const {};
return PhylumApiErrorResponse._(code: map['code'] ?? 'server_error', message: map['msg'] ?? 'Server Error');
}
}
Future<ApiResponse<PhylumApiResponse, PhylumApiErrorResponse>> transformApiResponse(ApiRequest _, StreamedResponse response) async {
final body = await response.bodyString();
if (response.statusCode >= 200 && response.statusCode < 300) {
final apiResponse = (jsonDecode(body) as Map<String, dynamic>?) ?? const {};
return ApiSuccessResponse(apiResponse);
}
return ApiErrorResponse(statusCode: response.statusCode, err: PhylumApiErrorResponse.fromResponseString(body));
}

View File

@@ -1,61 +0,0 @@
import 'dart:async';
import 'package:drift/drift.dart';
import 'package:offtheline/offtheline.dart';
import 'package:phylum/libphylum/db/db.dart';
import 'package:phylum/libphylum/phylum_account.dart';
import 'package:phylum/util/logging.dart';
const resourceDetailsResponse = 'resourceDetails';
const resourceSummaryResponse = 'resourceSummary';
class PhylumDatastore with AccountListener<PhylumApiResponse, PhylumApiErrorResponse, PhylumAccount> {
@override
Future<void> initialize(PhylumAccount account) async {
super.initialize(account);
account.api.addResponseListener((data, tag) => account.db.transaction(() async => await _processResponse(data, tag, account.db)));
account.api.addErrorResponseListener((errorResponse) {
if (errorResponse.code == "credentials_invalid") {
logger.i('Invalid Credentials - Logging out');
account.logOut();
}
});
}
FutureOr _processResponse(PhylumApiResponse data, dynamic tag, AppDatabase db) {
switch (tag) {
case resourceDetailsResponse:
return parseResourceDetails(data, db);
case resourceSummaryResponse:
return parseResourceSummary(data, db);
}
}
Future parseResourceDetails(Map<String, dynamic> data, AppDatabase db) async {
final details = parseResourceSummary(data['metadata'], db).copyWith(lastFetch: Value(DateTime.now()));
final existing = Set.from(await db.managers.resources.filter((f) => f.parent.id.equals(data['metadata']['id'])).map((r) => r.id).get());
final children = data.containsKey('children')
? (data['children'] as List).cast<Map>().map((c) {
existing.remove(c['id']);
return parseResourceSummary(c.cast<String, dynamic>(), db);
})
: <ResourcesCompanion>[];
await db.resources.deleteWhere((o) => o.id.isIn(List.from(existing)));
return db.batch((batch) {
batch.insertAll(db.resources, [details, ...children], mode: InsertMode.insertOrReplace);
});
}
ResourcesCompanion parseResourceSummary(Map<String, dynamic> data, AppDatabase db) {
return ResourcesCompanion.insert(
id: data['id'],
parent: Value(data['parent']),
name: data['name'],
dir: data['dir'],
modified: DateTime.parse(data['modified']),
size: data['size'],
etag: data['etag'],
contentType: data['ctype'] ?? '',
);
}
}

View File

@@ -2,6 +2,7 @@ import 'package:drift/drift.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_response.dart';
import 'package:phylum/libphylum/requests/resource_detail_request.dart';
class ResourceRepository {
@@ -9,8 +10,13 @@ class ResourceRepository {
ResourceRepository({required this.account});
Future<ApiError<PhylumApiErrorResponse>?> requestResource(String id) {
return account.api.sendRequest(ResourceDetailRequest(id)).then((r) => r.error);
Future<ApiResult<PhylumApiResponse, PhylumApiErrorResponse>?> requestResource(String id) async {
final response = await account.apiClient.sendRequest(ResourceDetailRequest(id));
if (response is ApiSuccessResponse<PhylumApiResponse, PhylumApiErrorResponse>) {
await parseResourceDetailsResponse(response.result);
return null;
}
return response;
}
Future<Resource?> getResource(String id) {
@@ -61,4 +67,39 @@ class ResourceRepository {
Future<int> deleteResource(String id) {
return account.db.resources.deleteWhere((f) => f.id.equals(id));
}
Future parseResourceDetailsResponse(Map<String, dynamic> data) async {
final db = account.db;
final details = parseResourceSummaryObject(data['metadata']).copyWith(lastFetch: Value(DateTime.now()));
final existing = Set.from(await db.managers.resources.filter((f) => f.parent.id.equals(data['metadata']['id'])).map((r) => r.id).get());
final children = data.containsKey('children')
? (data['children'] as List).cast<Map>().map((c) {
existing.remove(c['id']);
return parseResourceSummaryObject(c.cast<String, dynamic>());
})
: <ResourcesCompanion>[];
return db.transaction(() async {
await db.resources.deleteWhere((o) => o.id.isIn(List.from(existing)));
await db.batch((batch) {
batch.insertAll(db.resources, [details, ...children], mode: InsertMode.insertOrReplace);
});
});
}
Future parseResourceSummaryResponse(Map<String, dynamic> data) async {
return account.db.into(account.db.resources).insert(parseResourceSummaryObject(data), mode: InsertMode.insertOrReplace);
}
ResourcesCompanion parseResourceSummaryObject(Map<String, dynamic> data) {
return ResourcesCompanion.insert(
id: data['id'],
parent: Value(data['parent']),
name: data['name'],
dir: data['dir'],
modified: DateTime.parse(data['modified']),
size: data['size'],
etag: data['etag'],
contentType: data['ctype'] ?? '',
);
}
}

View File

@@ -1,11 +1,7 @@
import 'package:http/http.dart';
import 'package:offtheline/offtheline.dart';
import 'package:phylum/libphylum/phylum_datastore.dart';
class ResourceDetailRequest extends ApiRequest {
@override
get tag => resourceDetailsResponse;
final String resourceId;
const ResourceDetailRequest(this.resourceId);

View File

@@ -70,7 +70,7 @@ class ExplorerActions extends StatelessWidget {
final account = context.read<PhylumAccount>();
final selected = context.read<ExplorerViewState>().selected;
final items = selected.map((r) {
final uri = account.api.createUriBuilder('/open');
final uri = account.apiClient.createUriBuilder('/open');
uri.queryParameters['id'] = r.id;
if (i.cut) {
uri.queryParameters['cut'] = 'y';
@@ -86,7 +86,7 @@ class ExplorerActions extends StatelessWidget {
// TODO: Move to top-level nav
final account = context.read<PhylumAccount>();
final folderId = context.read<ExplorerViewController>().folderId;
final openUri = account.api.createUri('/open');
final openUri = account.apiClient.createUri('/open');
final clipboard = SystemClipboard.instance;
if (clipboard == null) {
return;

View File

@@ -77,11 +77,11 @@ class _ResourcePreviewState extends State<ResourcePreview> {
}
Widget buildImagePreview(Resource r) {
final api = context.read<PhylumAccount>().api;
final api = context.read<PhylumAccount>().apiClient;
return SizedBox(
width: 800,
child: Image.network(
context.read<PhylumAccount>().api.createUri('/api/v1/resources/contents/${r.id}').toString(),
context.read<PhylumAccount>().apiClient.createUri('/api/v1/resources/contents/${r.id}').toString(),
headers: api.requestHeaders,
),
);

View File

@@ -577,11 +577,11 @@ packages:
dependency: "direct main"
description:
path: "."
ref: ad3d1e804990bcb9ca65a1e5d003b1cb680908e0
resolved-ref: ad3d1e804990bcb9ca65a1e5d003b1cb680908e0
ref: "7e062f421d95e48234bfd040ba20714f11e8c509"
resolved-ref: "7e062f421d95e48234bfd040ba20714f11e8c509"
url: "https://codeberg.org/shroff/offtheline.git"
source: git
version: "0.14.0"
version: "0.15.0"
open_file:
dependency: "direct main"
description:

View File

@@ -21,7 +21,7 @@ dependencies:
offtheline:
git:
url: https://codeberg.org/shroff/offtheline.git
ref: ad3d1e804990bcb9ca65a1e5d003b1cb680908e0
ref: 7e062f421d95e48234bfd040ba20714f11e8c509
open_file:
path:
path_provider: