Add status command receiver
This commit is contained in:
@@ -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<Either<CoreServiceFailure, CoreStatus>> watchCoreStatus() {
|
||||
return singbox.watchStatus().map((event) {
|
||||
final json = jsonDecode(event);
|
||||
return CoreStatus.fromJson(json as Map<String, dynamic>);
|
||||
}).handleExceptions(
|
||||
(error, stackTrace) {
|
||||
loggy.warning("error watching status", error, stackTrace);
|
||||
return CoreServiceFailure.unexpected(error, stackTrace);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<Either<CoreServiceFailure, String>> watchLogs() {
|
||||
return singbox
|
||||
|
||||
20
lib/domain/singbox/core_status.dart
Normal file
20
lib/domain/singbox/core_status.dart
Normal file
@@ -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<String, dynamic> json) =>
|
||||
_$CoreStatusFromJson(json);
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export 'core_status.dart';
|
||||
export '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<CoreServiceFailure, Unit> setup();
|
||||
@@ -12,5 +13,7 @@ abstract interface class SingboxFacade {
|
||||
|
||||
TaskEither<CoreServiceFailure, Unit> stop();
|
||||
|
||||
Stream<Either<CoreServiceFailure, CoreStatus>> watchCoreStatus();
|
||||
|
||||
Stream<Either<CoreServiceFailure, String>> watchLogs();
|
||||
}
|
||||
|
||||
@@ -17,12 +17,16 @@ class TrafficNotifier extends _$TrafficNotifier with AppLogger {
|
||||
Stream<List<Traffic>> 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 {
|
||||
|
||||
@@ -857,6 +857,20 @@ class SingboxNativeLibrary {
|
||||
late final __FCmulcr =
|
||||
__FCmulcrPtr.asFunction<_Fcomplex Function(_Fcomplex, double)>();
|
||||
|
||||
void setupOnce(
|
||||
ffi.Pointer<ffi.Void> api,
|
||||
) {
|
||||
return _setupOnce(
|
||||
api,
|
||||
);
|
||||
}
|
||||
|
||||
late final _setupOncePtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
|
||||
'setupOnce');
|
||||
late final _setupOnce =
|
||||
_setupOncePtr.asFunction<void Function(ffi.Pointer<ffi.Void>)>();
|
||||
|
||||
void setup(
|
||||
ffi.Pointer<ffi.Char> baseDir,
|
||||
ffi.Pointer<ffi.Char> workingDir,
|
||||
@@ -920,6 +934,37 @@ class SingboxNativeLibrary {
|
||||
late final _stopPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function()>>('stop');
|
||||
late final _stop = _stopPtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
|
||||
|
||||
ffi.Pointer<ffi.Char> startCommandClient(
|
||||
int command,
|
||||
int port,
|
||||
) {
|
||||
return _startCommandClient(
|
||||
command,
|
||||
port,
|
||||
);
|
||||
}
|
||||
|
||||
late final _startCommandClientPtr = _lookup<
|
||||
ffi.NativeFunction<
|
||||
ffi.Pointer<ffi.Char> Function(
|
||||
ffi.Int, ffi.LongLong)>>('startCommandClient');
|
||||
late final _startCommandClient = _startCommandClientPtr
|
||||
.asFunction<ffi.Pointer<ffi.Char> Function(int, int)>();
|
||||
|
||||
ffi.Pointer<ffi.Char> stopCommandClient(
|
||||
int command,
|
||||
) {
|
||||
return _stopCommandClient(
|
||||
command,
|
||||
);
|
||||
}
|
||||
|
||||
late final _stopCommandClientPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function(ffi.Int)>>(
|
||||
'stopCommandClient');
|
||||
late final _stopCommandClient =
|
||||
_stopCommandClientPtr.asFunction<ffi.Pointer<ffi.Char> Function(int)>();
|
||||
}
|
||||
|
||||
typedef va_list = ffi.Pointer<ffi.Char>;
|
||||
|
||||
@@ -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<String>? _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<String> 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<Utf8>().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<Utf8>()
|
||||
.toDartString();
|
||||
if (err.isNotEmpty) {
|
||||
loggy.warning("error starting status command: $err");
|
||||
throw err;
|
||||
}
|
||||
|
||||
return _statusStream = statusStream;
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<String> watchLogs(String path) {
|
||||
var linesRead = 0;
|
||||
|
||||
@@ -67,6 +67,12 @@ class MobileSingboxService with InfraLogger implements SingboxService {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<String> watchStatus() {
|
||||
// TODO: implement watchStatus
|
||||
return const Stream.empty();
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<String> watchLogs(String path) {
|
||||
return _logsChannel.receiveBroadcastStream().map(
|
||||
|
||||
@@ -26,5 +26,7 @@ abstract interface class SingboxService {
|
||||
|
||||
TaskEither<String, Unit> stop();
|
||||
|
||||
Stream<String> watchStatus();
|
||||
|
||||
Stream<String> watchLogs(String path);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user