refactor(downloader): remove opts parameter from Resolve (#1595)

This commit is contained in:
Levi authored and GitHub committed 2026-10-19 21:40:57 +08:00
1 parent 5462b14705
commit de65149b29
45 files changed
+1589 -384

No files matched your search

+2 -1
View File
@@ -10,6 +10,7 @@ import 'model/install_extension.dart';
import 'model/login.dart';
import 'model/resolve_result.dart';
import 'model/resolve_task.dart';
import 'model/patch_task.dart';
import 'model/result.dart';
import 'model/switch_extension.dart';
import 'model/task.dart';
@@ -91,7 +92,7 @@ Future<List<String>> createTaskBatch(CreateTaskBatch createTaskBatch) {
);
}
Future<void> patchTask(String id, ResolveTask patchTask) {
Future<void> patchTask(String id, PatchTask patchTask) {
return _parse<void>(() => _request('api/v1/tasks/$id', method: 'PATCH', data: patchTask), null);
}
+16
View File
@@ -0,0 +1,16 @@
import 'options.dart';
import 'request.dart';
class PatchTask {
final Request? req;
final Options? opts;
PatchTask({this.req, this.opts});
factory PatchTask.fromJson(Map<String, dynamic> json) => PatchTask(
req: json['req'] == null ? null : Request.fromJson(json['req'] as Map<String, dynamic>),
opts: json['opts'] == null ? null : Options.fromJson(json['opts'] as Map<String, dynamic>),
);
Map<String, dynamic> toJson() => {'req': req?.toJson(), 'opts': opts?.toJson()};
}
+1 -3
View File
@@ -1,6 +1,5 @@
import 'package:json_annotation/json_annotation.dart';
import 'options.dart';
import 'request.dart';
part 'resolve_task.g.dart';
@@ -8,9 +7,8 @@ part 'resolve_task.g.dart';
@JsonSerializable()
class ResolveTask {
Request? req;
Options? opts;
ResolveTask({this.req, this.opts});
ResolveTask({this.req});
factory ResolveTask.fromJson(Map<String, dynamic> json) => _$ResolveTaskFromJson(json);
Map<String, dynamic> toJson() => _$ResolveTaskToJson(this);
+5 -6
View File
@@ -7,11 +7,10 @@ part of 'resolve_task.dart';
// **************************************************************************
ResolveTask _$ResolveTaskFromJson(Map<String, dynamic> json) => ResolveTask(
req: json['req'] == null ? null : Request.fromJson(json['req'] as Map<String, dynamic>),
opts: json['opts'] == null ? null : Options.fromJson(json['opts'] as Map<String, dynamic>),
req: json['req'] == null
? null
: Request.fromJson(json['req'] as Map<String, dynamic>),
);
Map<String, dynamic> _$ResolveTaskToJson(ResolveTask instance) => <String, dynamic>{
'req': ?instance.req,
'opts': ?instance.opts,
};
Map<String, dynamic> _$ResolveTaskToJson(ResolveTask instance) =>
<String, dynamic>{'req': ?instance.req};
@@ -8,6 +8,7 @@ import '../../api/model/extension.dart';
import '../../api/model/install_extension.dart';
import '../../api/model/resolve_result.dart';
import '../../api/model/resolve_task.dart';
import '../../api/model/patch_task.dart';
import '../../api/model/switch_extension.dart';
import '../../api/model/task.dart';
import '../../api/model/update_check_extension_resp.dart';
@@ -85,7 +86,7 @@ void _bindGopeed(CapabilityRegistry registry) {
..bind(GopeedMethods.createTask, api.createTask)
..bind(GopeedMethods.createTaskBatch, api.createTaskBatch)
..bind(GopeedMethods.patchTask, (params) async {
await api.patchTask(params['id'] as String, ResolveTask.fromJson(params['request'] as Map<String, dynamic>));
await api.patchTask(params['id'] as String, PatchTask.fromJson(params['request'] as Map<String, dynamic>));
return const RpcUnit();
})
..bind(
@@ -5,6 +5,7 @@ import '../../api/model/extension.dart';
import '../../api/model/install_extension.dart';
import '../../api/model/resolve_result.dart';
import '../../api/model/resolve_task.dart';
import '../../api/model/patch_task.dart';
import '../../api/model/switch_extension.dart';
import '../../api/model/task.dart';
import '../../api/model/update_check_extension_resp.dart';
@@ -49,7 +50,7 @@ class GopeedService {
Future<List<String>> createTaskBatch(CreateTaskBatch request) =>
_invoker.invoke(GopeedMethods.createTaskBatch, request);
Future<void> patchTask(String id, ResolveTask request) async {
Future<void> patchTask(String id, PatchTask request) async {
await _invoker.invoke(GopeedMethods.patchTask, {'id': id, 'request': request.toJson()});
}
@@ -3,7 +3,7 @@ import 'dart:async';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../api/model/request.dart';
import '../../../api/model/resolve_task.dart';
import '../../../api/model/patch_task.dart';
import '../../../api/model/task.dart' as api_model;
import '../../../core/capabilities/app_capabilities.dart';
import '../domain/task_record.dart';
@@ -99,7 +99,7 @@ class TasksController extends AsyncNotifier<TasksState> {
}
Future<void> updateRequest(String id, Request request) async {
await ref.read(gopeedServiceProvider).patchTask(id, ResolveTask(req: request));
await ref.read(gopeedServiceProvider).patchTask(id, PatchTask(req: request));
await ref.read(gopeedServiceProvider).continueTask(id);
await refresh(silent: true);
}
@@ -927,8 +927,7 @@ class _CreateTaskWindowPageState extends ConsumerState<CreateTaskWindowPage> {
}
final request = _buildRequest(urls.first, protocolUrl: _protocolUrlFor(urls.first));
final options = _buildOptions();
final result = await ref.read(gopeedServiceProvider).resolve(ResolveTask(req: request, opts: options));
final result = await ref.read(gopeedServiceProvider).resolve(ResolveTask(req: request));
if (!mounted) return;
final created = await _showResolveDialog(request, result);
if (!created) {
+32 -7
View File
@@ -3,30 +3,49 @@ import 'dart:convert';
import 'package:dio/dio.dart' as dio;
import 'package:flutter_test/flutter_test.dart';
import 'package:gopeed/api/api.dart' as api;
import 'package:gopeed/api/model/create_task.dart';
import 'package:gopeed/api/model/options.dart';
import 'package:gopeed/api/model/patch_task.dart';
import 'package:gopeed/api/model/request.dart';
import 'package:gopeed/api/model/resolve_task.dart';
import 'package:gopeed/core/network/gopeed/gopeed_transport.dart';
void main() {
test('resolve sends the complete resolve task for magnet links', () async {
test('resolve omits download options and creation submits them separately', () async {
final transport = _RecordingTransport();
api.setTransportForTesting(transport);
final result = await api.resolve(
ResolveTask(
req: Request(url: 'magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567'),
opts: Options(path: '/downloads', selectFiles: const []),
),
ResolveTask(req: Request(url: 'magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567')),
);
expect(result.id, 'resolved-id');
final requestBody = transport.data as Map<String, dynamic>;
expect(requestBody.keys, containsAll(<String>['req', 'opts']));
expect(requestBody.keys, equals(<String>['req']));
expect(
(requestBody['req'] as Map<String, dynamic>)['url'],
'magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567',
);
expect((requestBody['opts'] as Map<String, dynamic>)['path'], '/downloads');
expect(requestBody, isNot(contains('opts')));
final id = await api.createTask(
CreateTask(
rid: result.id,
opts: Options(path: '/chosen-after-resolve', selectFiles: [1]),
),
);
expect(id, 'created-id');
final createBody = transport.data as Map<String, dynamic>;
expect(createBody['rid'], 'resolved-id');
expect((createBody['opts'] as Map<String, dynamic>)['path'], '/chosen-after-resolve');
expect((createBody['opts'] as Map<String, dynamic>)['selectFiles'], [1]);
});
test('patch preserves options in its independent request model', () async {
final transport = _RecordingTransport();
api.setTransportForTesting(transport);
await api.patchTask('task-id', PatchTask(opts: Options(selectFiles: [1])));
final requestBody = transport.data as Map<String, dynamic>;
expect((requestBody['opts'] as Map<String, dynamic>)['selectFiles'], [1]);
});
}
@@ -41,6 +60,12 @@ class _RecordingTransport implements GopeedTransport {
Map<String, dynamic>? queryParameters,
}) async {
this.data = jsonDecode(jsonEncode(data));
if (path == 'api/v1/tasks') {
return {'code': 0, 'data': 'created-id'};
}
if (method == 'PATCH') {
return {'code': 0, 'data': null};
}
return {
'code': 0,
'data': {
@@ -1,5 +1,13 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:gopeed/api/model/options.dart';
import 'package:gopeed/api/model/patch_task.dart';
import 'package:gopeed/api/model/request.dart';
import 'package:gopeed/api/model/resolve_result.dart';
import 'package:gopeed/api/model/resolve_task.dart';
import 'package:gopeed/api/model/resource.dart';
import 'package:gopeed/core/capabilities/app_capabilities.dart';
import 'package:gopeed/core/capabilities/capability_rpc.dart';
import 'package:gopeed/core/capabilities/gopeed_capability.dart';
import 'package:gopeed/core/window/app_window_appearance.dart';
class _Payload {
@@ -13,6 +21,33 @@ class _Payload {
}
void main() {
test('serialized resolve and patch capabilities keep separate request contracts', () async {
final codecs = createAppCapabilityCodecs();
String? resolvedUrl;
Map<String, dynamic>? patch;
final registry = CapabilityRegistry(codecs)
..bind(GopeedMethods.resolve, (request) {
resolvedUrl = request.req?.url;
return ResolveResult(id: 'resolved-id', res: Resource(files: []));
})
..bind(GopeedMethods.patchTask, (request) {
patch = request;
return const RpcUnit();
});
final request = ResolveTask(req: Request(url: 'magnet:?xt=urn:btih:hash'));
final wireRequest = codecs.encode(request) as Map<String, dynamic>;
expect(wireRequest.keys, ['req']);
final result = codecs.decode<ResolveResult>(await registry.invoke(GopeedMethods.resolve.name, wireRequest));
expect(result.id, 'resolved-id');
expect(resolvedUrl, request.req?.url);
final service = GopeedService(LocalCapabilityInvoker(registry));
await service.patchTask('task-id', PatchTask(opts: Options(selectFiles: [1])));
expect(patch?['id'], 'task-id');
final patchBody = patch?['request'] as Map<String, dynamic>;
expect((patchBody['opts'] as Map<String, dynamic>)['selectFiles'], [1]);
});
test('one method descriptor supports direct and serialized dispatch', () async {
const method = RpcMethod<_Payload, _Payload>('test.increment');
final codecs = RpcCodecRegistry()..register<_Payload>((json) => _Payload.fromJson(json! as Map<String, dynamic>));