mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-13 20:03:46 +00:00
feat: move native communication to FFI (#1457)
This commit is contained in:
86 files changed
+4558
-886
No files matched your search
@@ -29,6 +29,72 @@ 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();
|
||||
|
||||
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();
|
||||
|
||||
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();
|
||||
|
||||
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();
|
||||
|
||||
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(
|
||||
ffi.Pointer<ffi.Char> method,
|
||||
ffi.Pointer<ffi.Char> path,
|
||||
ffi.Pointer<ffi.Char> query,
|
||||
ffi.Pointer<ffi.Char> body,
|
||||
) {
|
||||
return _Invoke(method, path, query, body);
|
||||
}
|
||||
|
||||
late final _InvokePtr =
|
||||
_lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Pointer<ffi.Char> Function(
|
||||
ffi.Pointer<ffi.Char>,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
)
|
||||
>
|
||||
>('Invoke');
|
||||
late final _Invoke =
|
||||
_InvokePtr.asFunction<
|
||||
ffi.Pointer<ffi.Char> Function(
|
||||
ffi.Pointer<ffi.Char>,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
ffi.Pointer<ffi.Char>,
|
||||
)
|
||||
>();
|
||||
|
||||
void SubscribeTaskEvents(int mask, int callback) {
|
||||
return _SubscribeTaskEvents(mask, callback);
|
||||
}
|
||||
|
||||
late final _SubscribeTaskEventsPtr = _lookup<ffi.NativeFunction<ffi.Void Function(ffi.Uint64, ffi.UintPtr)>>(
|
||||
'SubscribeTaskEvents',
|
||||
);
|
||||
late final _SubscribeTaskEvents = _SubscribeTaskEventsPtr.asFunction<void Function(int, int)>();
|
||||
|
||||
void FreeCString(ffi.Pointer<ffi.Char> value) {
|
||||
return _FreeCString(value);
|
||||
}
|
||||
|
||||
late final _FreeCStringPtr = _lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Char>)>>('FreeCString');
|
||||
late final _FreeCString = _FreeCStringPtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
|
||||
}
|
||||
|
||||
final class Start_return extends ffi.Struct {
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:ffi/ffi.dart';
|
||||
|
||||
import 'libgopeed_bind.dart';
|
||||
|
||||
class LibgopeedWorker {
|
||||
LibgopeedWorker() {
|
||||
_receivePort.listen(_handleWorkerMessage);
|
||||
_errorPort.listen(_handleWorkerError);
|
||||
_isolateReady = Isolate.spawn(_workerMain, _receivePort.sendPort, onError: _errorPort.sendPort);
|
||||
}
|
||||
|
||||
final ReceivePort _receivePort = ReceivePort();
|
||||
final ReceivePort _errorPort = ReceivePort();
|
||||
final Map<int, Completer<String>> _pending = {};
|
||||
late final Future<Isolate> _isolateReady;
|
||||
final Completer<SendPort> _sendPort = Completer<SendPort>();
|
||||
int _requestID = 0;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Future<String> apiServer(String operation) async {
|
||||
await _isolateReady;
|
||||
final sendPort = await _sendPort.future;
|
||||
final requestID = _requestID++;
|
||||
final completer = Completer<String>();
|
||||
_pending[requestID] = completer;
|
||||
sendPort.send(['apiServer', requestID, operation]);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
void _handleWorkerMessage(dynamic message) {
|
||||
if (message is SendPort) {
|
||||
if (!_sendPort.isCompleted) _sendPort.complete(message);
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
void _handleWorkerError(dynamic message) {
|
||||
final parts = message is List<dynamic> ? message : <dynamic>[message];
|
||||
final error = StateError(parts.map((part) => part.toString()).join('\n'));
|
||||
if (!_sendPort.isCompleted) _sendPort.completeError(error);
|
||||
for (final completer in _pending.values) {
|
||||
completer.completeError(error);
|
||||
}
|
||||
_pending.clear();
|
||||
}
|
||||
|
||||
static void _workerMain(SendPort mainSendPort) {
|
||||
final requests = ReceivePort();
|
||||
final bindings = LibgopeedBind(DynamicLibrary.open(_libraryName()));
|
||||
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(
|
||||
bindings,
|
||||
request[2] as String,
|
||||
request[3] as String,
|
||||
request[4] as String,
|
||||
request[5] as String,
|
||||
),
|
||||
'apiServer' => _apiServer(bindings, request[2] as String),
|
||||
_ => throw StateError('Unknown libgopeed worker request: $type'),
|
||||
};
|
||||
mainSendPort.send([requestID, true, response]);
|
||||
} catch (error) {
|
||||
mainSendPort.send([requestID, false, error.toString()]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static String _apiServer(LibgopeedBind bindings, String operation) {
|
||||
final resultPtr = switch (operation) {
|
||||
'get' => bindings.GetAPIServerState(),
|
||||
'start' => bindings.StartAPIServer(),
|
||||
'stop' => bindings.StopAPIServer(),
|
||||
'restart' => bindings.RestartAPIServer(),
|
||||
_ => throw StateError('Unknown API server operation: $operation'),
|
||||
};
|
||||
if (resultPtr == nullptr) {
|
||||
throw StateError('Gopeed API server operation returned a null response');
|
||||
}
|
||||
try {
|
||||
return resultPtr.cast<Utf8>().toDartString();
|
||||
} finally {
|
||||
bindings.FreeCString(resultPtr);
|
||||
}
|
||||
}
|
||||
|
||||
static String _invoke(LibgopeedBind bindings, 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();
|
||||
} finally {
|
||||
malloc.free(methodPtr);
|
||||
malloc.free(pathPtr);
|
||||
malloc.free(queryPtr);
|
||||
malloc.free(bodyPtr);
|
||||
if (resultPtr != null && resultPtr != nullptr) {
|
||||
bindings.FreeCString(resultPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static String _libraryName() {
|
||||
if (Platform.isWindows) return 'libgopeed.dll';
|
||||
if (Platform.isMacOS) return 'libgopeed.dylib';
|
||||
if (Platform.isLinux) return 'libgopeed.so';
|
||||
throw UnsupportedError('Desktop FFI is unavailable on ${Platform.operatingSystem}');
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user