import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import '../../api/model/create_task.dart'; import '../../api/model/create_task_batch.dart'; import '../../api/model/downloader_config.dart'; import '../../api/model/extension.dart'; import '../../api/model/http_listen_result.dart'; import '../../api/model/info.dart'; import '../../api/model/install_extension.dart'; import '../../api/model/request.dart'; import '../../api/model/resolve_result.dart'; import '../../api/model/result.dart'; import '../../api/model/switch_extension.dart'; import '../../api/model/task.dart'; import '../../api/model/task_filter.dart'; import '../../api/model/update_check_extension_resp.dart'; import '../../api/model/update_extension_settings.dart'; import '../../util/extensions.dart'; import '../../util/util.dart'; import '../api_exception.dart'; import '../libgopeed_boot.dart'; import '../native/libgopeed_interface.dart'; import '../native/model/start_config.dart'; import 'libgopeed_boot_base.dart'; LibgopeedBoot create() => LibgopeedBootWeb(); class LibgopeedBootWeb with LibgopeedBootBase implements LibgopeedBoot, LibgopeedApi { late Dio _dio; static const String _apiPrefix = '/api/v1'; LibgopeedBootWeb(); Future _fetch( Future Function() fetch, T Function(dynamic json)? fromJsonT, ) async { try { var resp = await fetch(); fromJsonT ??= (json) => null as T; final result = Result.fromJson(resp.data, fromJsonT); return handleResult(result); } on DioException catch (e) { if (e.type == DioExceptionType.sendTimeout || e.type == DioExceptionType.receiveTimeout || e.type == DioExceptionType.connectionTimeout) { throw ApiException(1000, 'request timeout'); } throw ApiException(1000, e.message ?? ""); } } Map? _parseTaskFilter(TaskFilter? filter) { if (filter == null) { return null; } Map params = {}; if (filter.id != null) { params['id'] = filter.id; } if (filter.status != null) { params['status'] = filter.status; } if (filter.notStatus != null) { params['notStatus'] = filter.notStatus; } if (params.isEmpty) { return null; } return params; } @override Future init(StartConfig cfg) async { _dio = createDio(); _dio.options.baseUrl = kDebugMode ? 'http://127.0.0.1:9999' : ''; return this; } @override Future startHttp() async { throw UnimplementedError(); } @override Future stopHttp() async { throw UnimplementedError(); } @override Future restartHttp() async { throw UnimplementedError(); } @override Future info() { return _fetch( () => _dio.get("$_apiPrefix/info"), (data) => Info.fromJson(data)); } @override Future resolve(Request request) async { return _fetch( () => _dio.post("$_apiPrefix/resolve", data: request), (data) => ResolveResult.fromJson(data)); } @override Future createTask(CreateTask createTask) async { return _fetch( () => _dio.post("$_apiPrefix/tasks", data: createTask), (data) => data as String); } @override Future createTaskBatch(CreateTaskBatch createTask) async { return _fetch( () => _dio.post("$_apiPrefix/tasks", data: createTask), (data) => data as String); } @override Future pauseTask(String id) async { await _fetch(() => _dio.put("$_apiPrefix/tasks/$id/pause"), null); } @override Future pauseTasks(TaskFilter? filter) async { await _fetch( () => _dio.put("$_apiPrefix/tasks/pause", queryParameters: _parseTaskFilter(filter)), null); } @override Future continueTask(String id) async { await _fetch(() => _dio.put("$_apiPrefix/tasks/$id/continue"), null); } @override Future continueTasks(TaskFilter? filter) async { await _fetch( () => _dio.put("$_apiPrefix/tasks/continue", queryParameters: _parseTaskFilter(filter)), null); } @override Future deleteTask(String id, bool force) async { await _fetch( () => _dio .delete("$_apiPrefix/tasks/$id", queryParameters: {'force': force}), null); } @override Future deleteTasks(TaskFilter? filter, bool force) async { await _fetch( () => _dio.delete("$_apiPrefix/tasks", queryParameters: _parseTaskFilter(filter)?.apply((it) { it['force'] = force; })), null); } @override Future getTask(String id) async { return _fetch( () => _dio.get("$_apiPrefix/tasks/$id"), (data) => Task.fromJson(data)); } @override Future> getTasks(TaskFilter? filter) async { return _fetch>( () => _dio.get("$_apiPrefix/tasks", queryParameters: _parseTaskFilter(filter)), (data) => (data as List).map((e) => Task.fromJson(e)).toList()); } @override Future> getTaskStats(String id) async { return _fetch>( () => _dio.get("$_apiPrefix/tasks/$id/stats"), (data) => data as Map); } @override Future getConfig() async { return _fetch(() => _dio.get("$_apiPrefix/config"), (data) => DownloaderConfig.fromJson(data)); } @override Future putConfig(DownloaderConfig config) async { await _fetch( () => _dio.put("$_apiPrefix/config", data: config), null); } @override Future installExtension(InstallExtension installExtension) async { await _fetch( () => _dio.post("$_apiPrefix/extensions", data: installExtension), null); } @override Future> getExtensions() async { return _fetch>(() => _dio.get("$_apiPrefix/extensions"), (data) => (data as List).map((e) => Extension.fromJson(e)).toList()); } @override Future updateExtensionSettings( String identity, UpdateExtensionSettings updateExtensionSettings) async { await _fetch( () => _dio.put("$_apiPrefix/extensions/$identity/settings", data: updateExtensionSettings), null); } @override Future switchExtension( String identity, SwitchExtension switchExtension) async { await _fetch( () => _dio.put("$_apiPrefix/extensions/$identity/switch", data: switchExtension), null); } @override Future deleteExtension(String identity) async { await _fetch( () => _dio.delete("$_apiPrefix/extensions/$identity"), null); } @override Future upgradeCheckExtension( String identity) async { return _fetch( () => _dio.get("$_apiPrefix/extensions/$identity/upgrade"), (data) => UpdateCheckExtensionResp.fromJson(data)); } @override Future upgradeExtension(String identity) async { await _fetch( () => _dio.post("$_apiPrefix/extensions/$identity/upgrade"), null); } @override Future close() async { throw UnimplementedError(); } // To avoid CORS, use a backend proxy to forward the request @override Future> proxyRequest(String uri, {data, Options? options}) async { options ??= Options(); options.headers ??= {}; options.headers!["X-Target-Uri"] = uri; // add timestamp to avoid cache return _dio.request( "/api/v1/proxy?t=${DateTime.now().millisecondsSinceEpoch}", data: data, options: options); } String join(String path) { return "${_dio.options.baseUrl}/${Util.cleanPath(path)}"; } }