mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-13 20:03:46 +00:00
Merge branch 'main' into codex/fix-settings-script-path-picker
This commit is contained in:
27 files changed
+1210
-187
No files matched your search
@@ -37,10 +37,15 @@ class AppDeepLinkController extends AsyncNotifier<AppDeepLinkState> {
|
||||
|
||||
@override
|
||||
Future<AppDeepLinkState> build() async {
|
||||
final runtime = ref.watch(appRuntimeControllerProvider).value;
|
||||
if (runtime == null || _started || kIsWeb) {
|
||||
if (_started || kIsWeb) {
|
||||
return AppDeepLinkState(started: _started);
|
||||
}
|
||||
// Deep-link handling depends on the runtime being ready, but it must not
|
||||
// rebuild whenever runtime settings change. A rebuild disposes the active
|
||||
// platform subscriptions while `_started` remains true, leaving Android
|
||||
// unable to receive later browser takeover intents (for example after the
|
||||
// API server is started or stopped from Settings).
|
||||
await ref.read(appRuntimeControllerProvider.future);
|
||||
_started = true;
|
||||
final appLinks = AppLinks();
|
||||
_subscription = appLinks.uriLinkStream.listen((uri) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'task_event.dart';
|
||||
class LibgopeedChannel implements LibgopeedInterface {
|
||||
static const _channel = MethodChannel('gopeed.com/libgopeed');
|
||||
final _taskEvents = StreamController<TaskEvent>.broadcast();
|
||||
int _requestID = 0;
|
||||
|
||||
LibgopeedChannel() {
|
||||
_channel.setMethodCallHandler((call) async {
|
||||
@@ -50,7 +51,9 @@ class LibgopeedChannel implements LibgopeedInterface {
|
||||
|
||||
@override
|
||||
Future<String> invoke(String method, String path, {String query = '', String body = ''}) async {
|
||||
final requestID = _requestID++;
|
||||
final result = await _channel.invokeMethod<String>('invoke', {
|
||||
'requestID': requestID,
|
||||
'method': method,
|
||||
'path': path,
|
||||
'query': query,
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
import '../ffi/libgopeed_bind.dart';
|
||||
import '../ffi/libgopeed_worker.dart';
|
||||
import 'api_server_state.dart';
|
||||
import 'libgopeed_interface.dart';
|
||||
@@ -12,51 +8,22 @@ import 'start_config.dart';
|
||||
import 'task_event.dart';
|
||||
|
||||
class LibgopeedFFi implements LibgopeedInterface {
|
||||
late LibgopeedBind _libgopeed;
|
||||
LibgopeedFFi() {
|
||||
_worker.taskEvents.listen(_onTaskEvent, onError: _taskEvents.addError);
|
||||
}
|
||||
|
||||
final _taskEvents = StreamController<TaskEvent>.broadcast();
|
||||
final _worker = LibgopeedWorker();
|
||||
late final NativeCallable<Void Function(Pointer<Char>)> _taskEventCallback;
|
||||
|
||||
LibgopeedFFi(LibgopeedBind libgopeed) {
|
||||
_libgopeed = libgopeed;
|
||||
_taskEventCallback = NativeCallable<Void Function(Pointer<Char>)>.listener(_onTaskEvent);
|
||||
}
|
||||
|
||||
void _onTaskEvent(Pointer<Char> payload) {
|
||||
try {
|
||||
final decoded = jsonDecode(payload.cast<Utf8>().toDartString()) as Map<String, dynamic>;
|
||||
_taskEvents.add(TaskEvent.fromJson(decoded));
|
||||
} finally {
|
||||
_libgopeed.FreeCString(payload);
|
||||
}
|
||||
void _onTaskEvent(Map<String, dynamic> payload) {
|
||||
_taskEvents.add(TaskEvent.fromJson(payload));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> start(StartConfig cfg) {
|
||||
var completer = Completer<int>();
|
||||
final cfgPtr = jsonEncode(cfg).toNativeUtf8();
|
||||
try {
|
||||
final result = _libgopeed.Start(cfgPtr.cast());
|
||||
if (result.r1 != nullptr) {
|
||||
final message = result.r1.cast<Utf8>().toDartString();
|
||||
_libgopeed.FreeCString(result.r1);
|
||||
completer.completeError(Exception(message));
|
||||
} else {
|
||||
completer.complete(result.r0);
|
||||
}
|
||||
} finally {
|
||||
malloc.free(cfgPtr);
|
||||
}
|
||||
return completer.future;
|
||||
}
|
||||
Future<int> start(StartConfig cfg) => _worker.start(jsonEncode(cfg));
|
||||
|
||||
@override
|
||||
Future<void> stop() {
|
||||
var completer = Completer<void>();
|
||||
_libgopeed.Stop();
|
||||
completer.complete();
|
||||
return completer.future;
|
||||
}
|
||||
Future<void> stop() => _worker.stop();
|
||||
|
||||
Future<ApiServerOperationResult> _apiServerOperation(String operation) async {
|
||||
final payload = await _worker.apiServer(operation);
|
||||
@@ -77,33 +44,12 @@ class LibgopeedFFi implements LibgopeedInterface {
|
||||
|
||||
@override
|
||||
Future<String> invoke(String method, String path, {String query = '', String body = ''}) async {
|
||||
final methodPtr = method.toNativeUtf8();
|
||||
final pathPtr = path.toNativeUtf8();
|
||||
final queryPtr = query.toNativeUtf8();
|
||||
final bodyPtr = body.toNativeUtf8();
|
||||
Pointer<Char>? resultPtr;
|
||||
try {
|
||||
resultPtr = _libgopeed.Invoke(methodPtr.cast(), pathPtr.cast(), queryPtr.cast(), bodyPtr.cast());
|
||||
if (resultPtr == nullptr) {
|
||||
throw StateError('Gopeed Invoke returned a null response');
|
||||
}
|
||||
return resultPtr.cast<Utf8>().toDartString();
|
||||
} finally {
|
||||
malloc.free(methodPtr);
|
||||
malloc.free(pathPtr);
|
||||
malloc.free(queryPtr);
|
||||
malloc.free(bodyPtr);
|
||||
if (resultPtr != null && resultPtr != nullptr) {
|
||||
_libgopeed.FreeCString(resultPtr);
|
||||
}
|
||||
}
|
||||
return jsonEncode(await _worker.invoke(method, path, query, body));
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<TaskEvent> get taskEvents => _taskEvents.stream;
|
||||
|
||||
@override
|
||||
Future<void> subscribeTaskEvents(Set<TaskEventType> events) async {
|
||||
_libgopeed.SubscribeTaskEvents(events.mask, events.isEmpty ? 0 : _taskEventCallback.nativeFunction.address);
|
||||
}
|
||||
Future<void> subscribeTaskEvents(Set<TaskEventType> events) => _worker.subscribeTaskEvents(events.mask);
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
|
||||
import '../../util/util.dart';
|
||||
import '../common/libgopeed_channel.dart';
|
||||
@@ -9,7 +7,6 @@ import '../common/libgopeed_interface.dart';
|
||||
import '../common/api_server_state.dart';
|
||||
import '../common/start_config.dart';
|
||||
import '../common/task_event.dart';
|
||||
import '../ffi/libgopeed_bind.dart';
|
||||
import '../libgopeed_boot.dart';
|
||||
|
||||
LibgopeedBoot create() => LibgopeedBootNative();
|
||||
@@ -19,17 +16,7 @@ class LibgopeedBootNative implements LibgopeedBoot {
|
||||
|
||||
LibgopeedBootNative() {
|
||||
if (Util.isDesktop()) {
|
||||
var libName = "libgopeed.";
|
||||
if (Platform.isWindows) {
|
||||
libName += "dll";
|
||||
}
|
||||
if (Platform.isMacOS) {
|
||||
libName += "dylib";
|
||||
}
|
||||
if (Platform.isLinux) {
|
||||
libName += "so";
|
||||
}
|
||||
_libgopeed = LibgopeedFFi(LibgopeedBind(DynamicLibrary.open(libName)));
|
||||
_libgopeed = LibgopeedFFi();
|
||||
} else {
|
||||
_libgopeed = LibgopeedChannel();
|
||||
}
|
||||
|
||||
@@ -30,53 +30,67 @@ class LibgopeedBind {
|
||||
late final _StopPtr = _lookup<ffi.NativeFunction<ffi.Void Function()>>('Stop');
|
||||
late final _Stop = _StopPtr.asFunction<void Function()>();
|
||||
|
||||
ffi.Pointer<ffi.Char> GetAPIServerState() => _GetAPIServerState();
|
||||
ffi.Pointer<ffi.Char> GetAPIServerState() {
|
||||
return _GetAPIServerState();
|
||||
}
|
||||
|
||||
late final _GetAPIServerStatePtr = _lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function()>>('GetAPIServerState');
|
||||
late final _GetAPIServerState = _GetAPIServerStatePtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
|
||||
|
||||
ffi.Pointer<ffi.Char> StartAPIServer() => _StartAPIServer();
|
||||
ffi.Pointer<ffi.Char> StartAPIServer() {
|
||||
return _StartAPIServer();
|
||||
}
|
||||
|
||||
late final _StartAPIServerPtr = _lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function()>>('StartAPIServer');
|
||||
late final _StartAPIServer = _StartAPIServerPtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
|
||||
|
||||
ffi.Pointer<ffi.Char> StopAPIServer() => _StopAPIServer();
|
||||
ffi.Pointer<ffi.Char> StopAPIServer() {
|
||||
return _StopAPIServer();
|
||||
}
|
||||
|
||||
late final _StopAPIServerPtr = _lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function()>>('StopAPIServer');
|
||||
late final _StopAPIServer = _StopAPIServerPtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
|
||||
|
||||
ffi.Pointer<ffi.Char> RestartAPIServer() => _RestartAPIServer();
|
||||
ffi.Pointer<ffi.Char> RestartAPIServer() {
|
||||
return _RestartAPIServer();
|
||||
}
|
||||
|
||||
late final _RestartAPIServerPtr = _lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function()>>('RestartAPIServer');
|
||||
late final _RestartAPIServer = _RestartAPIServerPtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
|
||||
|
||||
ffi.Pointer<ffi.Char> Invoke(
|
||||
void InvokeAsync(
|
||||
ffi.Pointer<ffi.Char> method,
|
||||
ffi.Pointer<ffi.Char> path,
|
||||
ffi.Pointer<ffi.Char> query,
|
||||
ffi.Pointer<ffi.Char> body,
|
||||
int requestID,
|
||||
int callback,
|
||||
) {
|
||||
return _Invoke(method, path, query, body);
|
||||
return _InvokeAsync(method, path, query, body, requestID, callback);
|
||||
}
|
||||
|
||||
late final _InvokePtr =
|
||||
late final _InvokeAsyncPtr =
|
||||
_lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Pointer<ffi.Char> Function(
|
||||
ffi.Void Function(
|
||||
ffi.Pointer<ffi.Char>,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
ffi.UnsignedLongLong,
|
||||
ffi.UintPtr,
|
||||
)
|
||||
>
|
||||
>('Invoke');
|
||||
late final _Invoke =
|
||||
_InvokePtr.asFunction<
|
||||
ffi.Pointer<ffi.Char> Function(
|
||||
>('InvokeAsync');
|
||||
late final _InvokeAsync =
|
||||
_InvokeAsyncPtr.asFunction<
|
||||
void Function(
|
||||
ffi.Pointer<ffi.Char>,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
int,
|
||||
int,
|
||||
)
|
||||
>();
|
||||
|
||||
@@ -84,9 +98,8 @@ class LibgopeedBind {
|
||||
return _SubscribeTaskEvents(mask, callback);
|
||||
}
|
||||
|
||||
late final _SubscribeTaskEventsPtr = _lookup<ffi.NativeFunction<ffi.Void Function(ffi.Uint64, ffi.UintPtr)>>(
|
||||
'SubscribeTaskEvents',
|
||||
);
|
||||
late final _SubscribeTaskEventsPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.UnsignedLongLong, ffi.UintPtr)>>('SubscribeTaskEvents');
|
||||
late final _SubscribeTaskEvents = _SubscribeTaskEventsPtr.asFunction<void Function(int, int)>();
|
||||
|
||||
void FreeCString(ffi.Pointer<ffi.Char> value) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
@@ -7,38 +8,74 @@ import 'package:ffi/ffi.dart';
|
||||
|
||||
import 'libgopeed_bind.dart';
|
||||
|
||||
typedef _InvokeResultNative = Void Function(Uint64 requestID, Int32 success, Pointer<Char> payload);
|
||||
typedef _TaskEventNative = Void Function(Pointer<Char> payload);
|
||||
|
||||
class LibgopeedWorker {
|
||||
LibgopeedWorker() {
|
||||
factory LibgopeedWorker() => _instance;
|
||||
|
||||
LibgopeedWorker._() {
|
||||
_receivePort.listen(_handleWorkerMessage);
|
||||
_errorPort.listen(_handleWorkerError);
|
||||
_isolateReady = Isolate.spawn(_workerMain, _receivePort.sendPort, onError: _errorPort.sendPort);
|
||||
}
|
||||
|
||||
static final LibgopeedWorker _instance = LibgopeedWorker._();
|
||||
|
||||
final ReceivePort _receivePort = ReceivePort();
|
||||
final ReceivePort _errorPort = ReceivePort();
|
||||
final Map<int, Completer<String>> _pending = {};
|
||||
final Map<int, Completer<Object?>> _pending = {};
|
||||
final StreamController<Map<String, dynamic>> _taskEvents = StreamController<Map<String, dynamic>>.broadcast();
|
||||
late final Future<Isolate> _isolateReady;
|
||||
final Completer<SendPort> _sendPort = Completer<SendPort>();
|
||||
Future<void>? _stopFuture;
|
||||
int _requestID = 0;
|
||||
bool _stopping = false;
|
||||
bool _stopped = false;
|
||||
|
||||
Future<String> invoke(String method, String path, String query, String body) async {
|
||||
await _isolateReady;
|
||||
final sendPort = await _sendPort.future;
|
||||
final requestID = _requestID++;
|
||||
final completer = Completer<String>();
|
||||
_pending[requestID] = completer;
|
||||
sendPort.send(['invoke', requestID, method, path, query, body]);
|
||||
return completer.future;
|
||||
Stream<Map<String, dynamic>> get taskEvents => _taskEvents.stream;
|
||||
|
||||
Future<int> start(String config) => _request<int>('start', [config]);
|
||||
|
||||
Future<void> stop() => _stopFuture ??= _stop();
|
||||
|
||||
Future<void> _stop() async {
|
||||
_stopping = true;
|
||||
// The native bridge owns the bounded drain. Sending Stop immediately is
|
||||
// important because an InvokeAsync handler such as Resolve may never return.
|
||||
await _request<Object?>('stop', const [], allowWhileStopping: true);
|
||||
_stopped = true;
|
||||
final stoppingError = StateError('libgopeed worker stopped before the request completed');
|
||||
for (final completer in _pending.values) {
|
||||
completer.completeError(stoppingError);
|
||||
}
|
||||
_pending.clear();
|
||||
await _taskEvents.close();
|
||||
_receivePort.close();
|
||||
_errorPort.close();
|
||||
}
|
||||
|
||||
Future<String> apiServer(String operation) async {
|
||||
Future<Object?> invoke(String method, String path, String query, String body) {
|
||||
return _request<Object?>('invoke', [method, path, query, body]);
|
||||
}
|
||||
|
||||
Future<String> apiServer(String operation) => _request<String>('apiServer', [operation]);
|
||||
|
||||
Future<void> subscribeTaskEvents(int mask) async {
|
||||
await _request<Object?>('subscribeTaskEvents', [mask]);
|
||||
}
|
||||
|
||||
Future<T> _request<T>(String type, List<Object?> arguments, {bool allowWhileStopping = false}) async {
|
||||
if (_stopped || (_stopping && !allowWhileStopping)) {
|
||||
throw StateError('libgopeed worker is stopping');
|
||||
}
|
||||
await _isolateReady;
|
||||
final sendPort = await _sendPort.future;
|
||||
final requestID = _requestID++;
|
||||
final completer = Completer<String>();
|
||||
final completer = Completer<Object?>();
|
||||
_pending[requestID] = completer;
|
||||
sendPort.send(['apiServer', requestID, operation]);
|
||||
return completer.future;
|
||||
sendPort.send([type, requestID, ...arguments]);
|
||||
return (await completer.future) as T;
|
||||
}
|
||||
|
||||
void _handleWorkerMessage(dynamic message) {
|
||||
@@ -47,13 +84,18 @@ class LibgopeedWorker {
|
||||
return;
|
||||
}
|
||||
final response = message as List<dynamic>;
|
||||
final requestID = response[0] as int;
|
||||
final completer = _pending.remove(requestID);
|
||||
if (completer == null) return;
|
||||
if (response[1] as bool) {
|
||||
completer.complete(response[2] as String);
|
||||
} else {
|
||||
completer.completeError(StateError(response[2] as String));
|
||||
switch (response[0] as String) {
|
||||
case 'response':
|
||||
final requestID = response[1] as int;
|
||||
final completer = _pending.remove(requestID);
|
||||
if (completer == null) return;
|
||||
if (response[2] as bool) {
|
||||
completer.complete(response[3]);
|
||||
} else {
|
||||
completer.completeError(StateError(response[3] as String));
|
||||
}
|
||||
case 'taskEvent':
|
||||
_taskEvents.add((response[1] as Map<Object?, Object?>).cast<String, dynamic>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,35 +107,117 @@ class LibgopeedWorker {
|
||||
completer.completeError(error);
|
||||
}
|
||||
_pending.clear();
|
||||
_taskEvents.addError(error);
|
||||
}
|
||||
|
||||
static void _workerMain(SendPort mainSendPort) {
|
||||
final requests = ReceivePort();
|
||||
final bindings = LibgopeedBind(DynamicLibrary.open(_libraryName()));
|
||||
late final NativeCallable<_InvokeResultNative> invokeResultCallback;
|
||||
late final NativeCallable<_TaskEventNative> taskEventCallback;
|
||||
|
||||
invokeResultCallback = NativeCallable<_InvokeResultNative>.listener((
|
||||
int requestID,
|
||||
int success,
|
||||
Pointer<Char> payload,
|
||||
) {
|
||||
if (payload == nullptr) {
|
||||
mainSendPort.send(['response', requestID, false, 'Gopeed InvokeAsync returned a null response']);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final result = payload.cast<Utf8>().toDartString();
|
||||
mainSendPort.send(['response', requestID, success != 0, success != 0 ? jsonDecode(result) : result]);
|
||||
} catch (error) {
|
||||
mainSendPort.send(['response', requestID, false, error.toString()]);
|
||||
} finally {
|
||||
bindings.FreeCString(payload);
|
||||
}
|
||||
});
|
||||
|
||||
taskEventCallback = NativeCallable<_TaskEventNative>.listener((Pointer<Char> payload) {
|
||||
if (payload == nullptr) return;
|
||||
try {
|
||||
final event = jsonDecode(payload.cast<Utf8>().toDartString()) as Map<String, dynamic>;
|
||||
mainSendPort.send(['taskEvent', event]);
|
||||
} finally {
|
||||
bindings.FreeCString(payload);
|
||||
}
|
||||
});
|
||||
|
||||
mainSendPort.send(requests.sendPort);
|
||||
requests.listen((dynamic message) {
|
||||
final request = message as List<dynamic>;
|
||||
final type = request[0] as String;
|
||||
final requestID = request[1] as int;
|
||||
try {
|
||||
final response = switch (type) {
|
||||
'invoke' => _invoke(
|
||||
if (type == 'invoke') {
|
||||
try {
|
||||
_invokeAsync(
|
||||
bindings,
|
||||
invokeResultCallback.nativeFunction.address,
|
||||
requestID,
|
||||
request[2] as String,
|
||||
request[3] as String,
|
||||
request[4] as String,
|
||||
request[5] as String,
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
mainSendPort.send(['response', requestID, false, error.toString()]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final response = switch (type) {
|
||||
'start' => _start(bindings, request[2] as String),
|
||||
'stop' => _stopNative(bindings),
|
||||
'apiServer' => _apiServer(bindings, request[2] as String),
|
||||
'subscribeTaskEvents' => _subscribeTaskEvents(
|
||||
bindings,
|
||||
taskEventCallback.nativeFunction.address,
|
||||
request[2] as int,
|
||||
),
|
||||
_ => throw StateError('Unknown libgopeed worker request: $type'),
|
||||
};
|
||||
mainSendPort.send([requestID, true, response]);
|
||||
mainSendPort.send(['response', requestID, true, response]);
|
||||
if (type == 'stop') {
|
||||
invokeResultCallback.close();
|
||||
taskEventCallback.close();
|
||||
requests.close();
|
||||
}
|
||||
} catch (error) {
|
||||
mainSendPort.send([requestID, false, error.toString()]);
|
||||
mainSendPort.send(['response', requestID, false, error.toString()]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static int _start(LibgopeedBind bindings, String config) {
|
||||
final configPtr = config.toNativeUtf8();
|
||||
try {
|
||||
final result = bindings.Start(configPtr.cast());
|
||||
if (result.r1 != nullptr) {
|
||||
try {
|
||||
throw StateError(result.r1.cast<Utf8>().toDartString());
|
||||
} finally {
|
||||
bindings.FreeCString(result.r1);
|
||||
}
|
||||
}
|
||||
return result.r0;
|
||||
} finally {
|
||||
malloc.free(configPtr);
|
||||
}
|
||||
}
|
||||
|
||||
static Object? _stopNative(LibgopeedBind bindings) {
|
||||
bindings.Stop();
|
||||
return null;
|
||||
}
|
||||
|
||||
static Object? _subscribeTaskEvents(LibgopeedBind bindings, int callback, int mask) {
|
||||
bindings.SubscribeTaskEvents(mask, mask == 0 ? 0 : callback);
|
||||
return null;
|
||||
}
|
||||
|
||||
static String _apiServer(LibgopeedBind bindings, String operation) {
|
||||
final resultPtr = switch (operation) {
|
||||
'get' => bindings.GetAPIServerState(),
|
||||
@@ -112,26 +236,26 @@ class LibgopeedWorker {
|
||||
}
|
||||
}
|
||||
|
||||
static String _invoke(LibgopeedBind bindings, String method, String path, String query, String body) {
|
||||
static void _invokeAsync(
|
||||
LibgopeedBind bindings,
|
||||
int callback,
|
||||
int requestID,
|
||||
String method,
|
||||
String path,
|
||||
String query,
|
||||
String body,
|
||||
) {
|
||||
final methodPtr = method.toNativeUtf8();
|
||||
final pathPtr = path.toNativeUtf8();
|
||||
final queryPtr = query.toNativeUtf8();
|
||||
final bodyPtr = body.toNativeUtf8();
|
||||
Pointer<Char>? resultPtr;
|
||||
try {
|
||||
resultPtr = bindings.Invoke(methodPtr.cast(), pathPtr.cast(), queryPtr.cast(), bodyPtr.cast());
|
||||
if (resultPtr == nullptr) {
|
||||
throw StateError('Gopeed Invoke returned a null response');
|
||||
}
|
||||
return resultPtr.cast<Utf8>().toDartString();
|
||||
bindings.InvokeAsync(methodPtr.cast(), pathPtr.cast(), queryPtr.cast(), bodyPtr.cast(), requestID, callback);
|
||||
} finally {
|
||||
malloc.free(methodPtr);
|
||||
malloc.free(pathPtr);
|
||||
malloc.free(queryPtr);
|
||||
malloc.free(bodyPtr);
|
||||
if (resultPtr != null && resultPtr != nullptr) {
|
||||
bindings.FreeCString(resultPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ import 'dart:convert';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../../../libgopeed_boot.dart';
|
||||
import '../../../ffi/libgopeed_worker.dart';
|
||||
import '../../../../util/util.dart';
|
||||
import '../../../ffi/libgopeed_worker.dart';
|
||||
import '../../../libgopeed_boot.dart';
|
||||
import '../gopeed_transport.dart';
|
||||
|
||||
GopeedTransport createTransport(GopeedTransportConfig config) => NativeGopeedTransport();
|
||||
@@ -32,14 +32,16 @@ class NativeGopeedTransport implements GopeedTransport {
|
||||
}) async {
|
||||
final request = _RequestParts.from(path, queryParameters);
|
||||
final requestBody = data == null ? '' : jsonEncode(data);
|
||||
final payload = _ffiWorker != null
|
||||
? await _ffiWorker.invoke(method.toUpperCase(), request.path, request.query, requestBody)
|
||||
: await LibgopeedBoot.instance.invoke(
|
||||
method.toUpperCase(),
|
||||
request.path,
|
||||
query: request.query,
|
||||
body: requestBody,
|
||||
);
|
||||
final worker = _ffiWorker;
|
||||
if (worker != null) {
|
||||
return worker.invoke(method.toUpperCase(), request.path, request.query, requestBody);
|
||||
}
|
||||
final payload = await LibgopeedBoot.instance.invoke(
|
||||
method.toUpperCase(),
|
||||
request.path,
|
||||
query: request.query,
|
||||
body: requestBody,
|
||||
);
|
||||
return jsonDecode(payload);
|
||||
}
|
||||
|
||||
|
||||
@@ -93,11 +93,21 @@ class _CreateTaskWindowPageState extends ConsumerState<CreateTaskWindowPage> {
|
||||
super.initState();
|
||||
_urlController.addListener(_handleUrlChanged);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
final task = widget.initialTask ?? ref.read(pendingCreateTaskProvider);
|
||||
_applyInitialTask(task);
|
||||
if (widget.initialTask == null) {
|
||||
ref.read(pendingCreateTaskProvider.notifier).clear();
|
||||
}
|
||||
if (widget.windowController == null && widget.initialTask == null) {
|
||||
// Navigating to /create again reuses this page, so consume subsequent
|
||||
// browser/share requests as well as the initial pending task.
|
||||
ref.listenManual(pendingCreateTaskProvider, (_, next) {
|
||||
if (next == null) return;
|
||||
ref.read(pendingCreateTaskProvider.notifier).clear();
|
||||
_applyInitialTask(next);
|
||||
});
|
||||
}
|
||||
if (task == null || _urlController.text.trim().isEmpty) {
|
||||
unawaited(_loadClipboardUrl());
|
||||
}
|
||||
@@ -174,6 +184,13 @@ class _CreateTaskWindowPageState extends ConsumerState<CreateTaskWindowPage> {
|
||||
final opts = task.opts;
|
||||
setState(() {
|
||||
if (req?.url.isNotEmpty == true) {
|
||||
_fileDataUri = '';
|
||||
_renameController.clear();
|
||||
_httpMethod = 'GET';
|
||||
_httpBody = '';
|
||||
_replaceHttpHeaders(const {'User-Agent': '', 'Cookie': '', 'Referer': ''});
|
||||
_trackersController.clear();
|
||||
_protocolTab = 0;
|
||||
_urlController.text = req!.url;
|
||||
_initialRawUrl = req.rawUrl;
|
||||
_initialLabels = req.labels == null ? null : Map<String, String>.of(req.labels!);
|
||||
@@ -307,6 +324,7 @@ class _CreateTaskWindowPageState extends ConsumerState<CreateTaskWindowPage> {
|
||||
SizedBox(
|
||||
height: 120,
|
||||
child: AppTextField(
|
||||
key: const ValueKey('create-task-url-input'),
|
||||
controller: _urlController,
|
||||
hintText: context.l10n.pasteDownloadLinks,
|
||||
keyboardType: TextInputType.multiline,
|
||||
|
||||
Reference in new issue
Block a user