From 01980ba28d14b5b5625c1376c458c2e1f39110ae Mon Sep 17 00:00:00 2001 From: problematicconsumer Date: Mon, 28 Aug 2023 13:15:57 +0330 Subject: [PATCH] Add status command receiver --- lib/data/repository/core_facade_impl.dart | 16 +++++++ lib/domain/singbox/core_status.dart | 20 +++++++++ lib/domain/singbox/singbox.dart | 1 + lib/domain/singbox/singbox_facade.dart | 3 ++ .../common/traffic/traffic_notifier.dart | 16 ++++--- lib/gen/singbox_generated_bindings.dart | 45 +++++++++++++++++++ lib/services/singbox/ffi_singbox_service.dart | 44 ++++++++++++++++++ .../singbox/mobile_singbox_service.dart | 6 +++ lib/services/singbox/singbox_service.dart | 2 + 9 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 lib/domain/singbox/core_status.dart diff --git a/lib/data/repository/core_facade_impl.dart b/lib/data/repository/core_facade_impl.dart index d10a61e6..fe4dc66a 100644 --- a/lib/data/repository/core_facade_impl.dart +++ b/lib/data/repository/core_facade_impl.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:fpdart/fpdart.dart'; import 'package:hiddify/data/api/clash_api.dart'; import 'package:hiddify/data/repository/exception_handlers.dart'; @@ -6,6 +8,7 @@ import 'package:hiddify/domain/connectivity/connection_status.dart'; import 'package:hiddify/domain/constants.dart'; import 'package:hiddify/domain/core_facade.dart'; import 'package:hiddify/domain/core_service_failure.dart'; +import 'package:hiddify/domain/singbox/singbox.dart'; import 'package:hiddify/services/connectivity/connectivity.dart'; import 'package:hiddify/services/files_editor_service.dart'; import 'package:hiddify/services/singbox/singbox_service.dart'; @@ -95,6 +98,19 @@ class CoreFacadeImpl with ExceptionHandler, InfraLogger implements CoreFacade { ); } + @override + Stream> watchCoreStatus() { + return singbox.watchStatus().map((event) { + final json = jsonDecode(event); + return CoreStatus.fromJson(json as Map); + }).handleExceptions( + (error, stackTrace) { + loggy.warning("error watching status", error, stackTrace); + return CoreServiceFailure.unexpected(error, stackTrace); + }, + ); + } + @override Stream> watchLogs() { return singbox diff --git a/lib/domain/singbox/core_status.dart b/lib/domain/singbox/core_status.dart new file mode 100644 index 00000000..b757e4ee --- /dev/null +++ b/lib/domain/singbox/core_status.dart @@ -0,0 +1,20 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'core_status.freezed.dart'; +part 'core_status.g.dart'; + +@freezed +class CoreStatus with _$CoreStatus { + @JsonSerializable(fieldRename: FieldRename.kebab) + const factory CoreStatus({ + required int connectionsIn, + required int connectionsOut, + required int uplink, + required int downlink, + required int uplinkTotal, + required int downlinkTotal, + }) = _CoreStatus; + + factory CoreStatus.fromJson(Map json) => + _$CoreStatusFromJson(json); +} diff --git a/lib/domain/singbox/singbox.dart b/lib/domain/singbox/singbox.dart index 5d2075d0..4a65fbbf 100644 --- a/lib/domain/singbox/singbox.dart +++ b/lib/domain/singbox/singbox.dart @@ -1 +1,2 @@ +export 'core_status.dart'; export 'singbox_facade.dart'; diff --git a/lib/domain/singbox/singbox_facade.dart b/lib/domain/singbox/singbox_facade.dart index bd389cae..189886f1 100644 --- a/lib/domain/singbox/singbox_facade.dart +++ b/lib/domain/singbox/singbox_facade.dart @@ -1,5 +1,6 @@ import 'package:fpdart/fpdart.dart'; import 'package:hiddify/domain/core_service_failure.dart'; +import 'package:hiddify/domain/singbox/core_status.dart'; abstract interface class SingboxFacade { TaskEither setup(); @@ -12,5 +13,7 @@ abstract interface class SingboxFacade { TaskEither stop(); + Stream> watchCoreStatus(); + Stream> watchLogs(); } diff --git a/lib/features/common/traffic/traffic_notifier.dart b/lib/features/common/traffic/traffic_notifier.dart index f7a7fb4c..75b64ed7 100644 --- a/lib/features/common/traffic/traffic_notifier.dart +++ b/lib/features/common/traffic/traffic_notifier.dart @@ -17,12 +17,16 @@ class TrafficNotifier extends _$TrafficNotifier with AppLogger { Stream> build() async* { final serviceRunning = await ref.watch(serviceRunningProvider.future); if (serviceRunning) { - yield* ref.watch(coreFacadeProvider).watchTraffic().map( - (event) => _mapToState( - event - .getOrElse((_) => const ClashTraffic(upload: 0, download: 0)), - ), - ); + // TODO: temporary! + yield* ref.watch(coreFacadeProvider).watchCoreStatus().map((event) { + return event.map( + (a) => ClashTraffic(upload: a.uplink, download: a.downlink), + ); + }).map( + (event) => _mapToState( + event.getOrElse((_) => const ClashTraffic(upload: 0, download: 0)), + ), + ); } else { yield* Stream.periodic(const Duration(seconds: 1)).asyncMap( (_) async { diff --git a/lib/gen/singbox_generated_bindings.dart b/lib/gen/singbox_generated_bindings.dart index 76cfdcdb..f37c436a 100644 --- a/lib/gen/singbox_generated_bindings.dart +++ b/lib/gen/singbox_generated_bindings.dart @@ -857,6 +857,20 @@ class SingboxNativeLibrary { late final __FCmulcr = __FCmulcrPtr.asFunction<_Fcomplex Function(_Fcomplex, double)>(); + void setupOnce( + ffi.Pointer api, + ) { + return _setupOnce( + api, + ); + } + + late final _setupOncePtr = + _lookup)>>( + 'setupOnce'); + late final _setupOnce = + _setupOncePtr.asFunction)>(); + void setup( ffi.Pointer baseDir, ffi.Pointer workingDir, @@ -920,6 +934,37 @@ class SingboxNativeLibrary { late final _stopPtr = _lookup Function()>>('stop'); late final _stop = _stopPtr.asFunction Function()>(); + + ffi.Pointer startCommandClient( + int command, + int port, + ) { + return _startCommandClient( + command, + port, + ); + } + + late final _startCommandClientPtr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Int, ffi.LongLong)>>('startCommandClient'); + late final _startCommandClient = _startCommandClientPtr + .asFunction Function(int, int)>(); + + ffi.Pointer stopCommandClient( + int command, + ) { + return _stopCommandClient( + command, + ); + } + + late final _stopCommandClientPtr = + _lookup Function(ffi.Int)>>( + 'stopCommandClient'); + late final _stopCommandClient = + _stopCommandClientPtr.asFunction Function(int)>(); } typedef va_list = ffi.Pointer; diff --git a/lib/services/singbox/ffi_singbox_service.dart b/lib/services/singbox/ffi_singbox_service.dart index 66ef7958..7045e6af 100644 --- a/lib/services/singbox/ffi_singbox_service.dart +++ b/lib/services/singbox/ffi_singbox_service.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:ffi'; import 'dart:io'; +import 'dart:isolate'; import 'package:combine/combine.dart'; import 'package:ffi/ffi.dart'; @@ -16,6 +17,8 @@ final _logger = Loggy('FFISingboxService'); class FFISingboxService with InfraLogger implements SingboxService { static final SingboxNativeLibrary _box = _gen(); + Stream? _statusStream; + static SingboxNativeLibrary _gen() { String fullPath = ""; if (Platform.environment.containsKey('FLUTTER_TEST')) { @@ -47,6 +50,7 @@ class FFISingboxService with InfraLogger implements SingboxService { workingDir.toNativeUtf8().cast(), tempDir.toNativeUtf8().cast(), ); + _box.setupOnce(NativeApi.initializeApiDLData); return right(unit); }, ), @@ -119,6 +123,46 @@ class FFISingboxService with InfraLogger implements SingboxService { ); } + @override + Stream watchStatus() { + if (_statusStream != null) return _statusStream!; + final receiver = ReceivePort('status receiver'); + final statusStream = receiver.asBroadcastStream( + onCancel: (_) { + _logger.debug("stopping status command client"); + final err = _box.stopCommandClient(1).cast().toDartString(); + if (err.isNotEmpty) { + _logger.warning("error stopping status client"); + } + receiver.close(); + _statusStream = null; + }, + ).map( + (event) { + if (event case String _) { + if (event.startsWith('error:')) { + loggy.warning("[status client] error received: $event"); + throw event.replaceFirst('error:', ""); + } + return event; + } + loggy.warning("[status client] unexpected type, msg: $event"); + throw "invalid type"; + }, + ); + + final err = _box + .startCommandClient(1, receiver.sendPort.nativePort) + .cast() + .toDartString(); + if (err.isNotEmpty) { + loggy.warning("error starting status command: $err"); + throw err; + } + + return _statusStream = statusStream; + } + @override Stream watchLogs(String path) { var linesRead = 0; diff --git a/lib/services/singbox/mobile_singbox_service.dart b/lib/services/singbox/mobile_singbox_service.dart index b8b76bb0..c92b5ffa 100644 --- a/lib/services/singbox/mobile_singbox_service.dart +++ b/lib/services/singbox/mobile_singbox_service.dart @@ -67,6 +67,12 @@ class MobileSingboxService with InfraLogger implements SingboxService { ); } + @override + Stream watchStatus() { + // TODO: implement watchStatus + return const Stream.empty(); + } + @override Stream watchLogs(String path) { return _logsChannel.receiveBroadcastStream().map( diff --git a/lib/services/singbox/singbox_service.dart b/lib/services/singbox/singbox_service.dart index a9ba353a..20d81fb8 100644 --- a/lib/services/singbox/singbox_service.dart +++ b/lib/services/singbox/singbox_service.dart @@ -26,5 +26,7 @@ abstract interface class SingboxService { TaskEither stop(); + Stream watchStatus(); + Stream watchLogs(String path); }