Add service restart
This commit is contained in:
@@ -7,18 +7,25 @@ import 'dart:isolate';
|
||||
import 'package:combine/combine.dart';
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
import 'package:hiddify/domain/connectivity/connectivity.dart';
|
||||
import 'package:hiddify/domain/singbox/config_options.dart';
|
||||
import 'package:hiddify/gen/singbox_generated_bindings.dart';
|
||||
import 'package:hiddify/services/singbox/shared.dart';
|
||||
import 'package:hiddify/services/singbox/singbox_service.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:loggy/loggy.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
final _logger = Loggy('FFISingboxService');
|
||||
|
||||
class FFISingboxService with InfraLogger implements SingboxService {
|
||||
class FFISingboxService
|
||||
with ServiceStatus, InfraLogger
|
||||
implements SingboxService {
|
||||
static final SingboxNativeLibrary _box = _gen();
|
||||
|
||||
late final ValueStream<ConnectionStatus> _connectionStatus;
|
||||
late final ReceivePort _connectionStatusReceiver;
|
||||
Stream<String>? _statusStream;
|
||||
Stream<String>? _groupsStream;
|
||||
|
||||
@@ -39,21 +46,37 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
return SingboxNativeLibrary(lib);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
loggy.debug("initializing");
|
||||
_connectionStatusReceiver = ReceivePort('service status receiver');
|
||||
final source = _connectionStatusReceiver
|
||||
.asBroadcastStream()
|
||||
.map((event) => jsonDecode(event as String) as Map<String, dynamic>)
|
||||
.map(mapEventToStatus);
|
||||
_connectionStatus = ValueConnectableStream.seeded(
|
||||
source,
|
||||
const ConnectionStatus.disconnected(),
|
||||
).autoConnect();
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> setup(
|
||||
String baseDir,
|
||||
String workingDir,
|
||||
String tempDir,
|
||||
) {
|
||||
final port = _connectionStatusReceiver.sendPort.nativePort;
|
||||
return TaskEither(
|
||||
() => CombineWorker().execute(
|
||||
() {
|
||||
_box.setupOnce(NativeApi.initializeApiDLData);
|
||||
_box.setup(
|
||||
baseDir.toNativeUtf8().cast(),
|
||||
workingDir.toNativeUtf8().cast(),
|
||||
tempDir.toNativeUtf8().cast(),
|
||||
port,
|
||||
);
|
||||
_box.setupOnce(NativeApi.initializeApiDLData);
|
||||
return right(unit);
|
||||
},
|
||||
),
|
||||
@@ -98,29 +121,14 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> create(String configPath) {
|
||||
return TaskEither(
|
||||
() => CombineWorker().execute(
|
||||
() async {
|
||||
final err = _box
|
||||
.create(configPath.toNativeUtf8().cast())
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
if (err.isNotEmpty) {
|
||||
return left(err);
|
||||
}
|
||||
return right(unit);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> start() {
|
||||
TaskEither<String, Unit> start(String configPath) {
|
||||
return TaskEither(
|
||||
() => CombineWorker().execute(
|
||||
() {
|
||||
final err = _box.start().cast<Utf8>().toDartString();
|
||||
final err = _box
|
||||
.start(configPath.toNativeUtf8().cast())
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
if (err.isNotEmpty) {
|
||||
return left(err);
|
||||
}
|
||||
@@ -146,7 +154,28 @@ class FFISingboxService with InfraLogger implements SingboxService {
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<String> watchStatus() {
|
||||
TaskEither<String, Unit> restart(String configPath) {
|
||||
return TaskEither(
|
||||
() => CombineWorker().execute(
|
||||
() {
|
||||
final err = _box
|
||||
.restart(configPath.toNativeUtf8().cast())
|
||||
.cast<Utf8>()
|
||||
.toDartString();
|
||||
if (err.isNotEmpty) {
|
||||
return left(err);
|
||||
}
|
||||
return right(unit);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<ConnectionStatus> watchConnectionStatus() => _connectionStatus;
|
||||
|
||||
@override
|
||||
Stream<String> watchStats() {
|
||||
if (_statusStream != null) return _statusStream!;
|
||||
final receiver = ReceivePort('status receiver');
|
||||
final statusStream = receiver.asBroadcastStream(
|
||||
|
||||
@@ -2,15 +2,36 @@ import 'dart:convert';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
import 'package:hiddify/domain/connectivity/connection_status.dart';
|
||||
import 'package:hiddify/domain/singbox/config_options.dart';
|
||||
import 'package:hiddify/services/singbox/shared.dart';
|
||||
import 'package:hiddify/services/singbox/singbox_service.dart';
|
||||
import 'package:hiddify/utils/utils.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
class MobileSingboxService with InfraLogger implements SingboxService {
|
||||
late final MethodChannel _methodChannel =
|
||||
const MethodChannel("com.hiddify.app/method");
|
||||
late final EventChannel _logsChannel =
|
||||
const EventChannel("com.hiddify.app/service.logs");
|
||||
class MobileSingboxService
|
||||
with ServiceStatus, InfraLogger
|
||||
implements SingboxService {
|
||||
late final _methodChannel = const MethodChannel("com.hiddify.app/method");
|
||||
late final _statusChannel =
|
||||
const EventChannel("com.hiddify.app/service.status");
|
||||
late final _alertsChannel =
|
||||
const EventChannel("com.hiddify.app/service.alerts");
|
||||
late final _logsChannel = const EventChannel("com.hiddify.app/service.logs");
|
||||
|
||||
late final ValueStream<ConnectionStatus> _connectionStatus;
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
loggy.debug("initializing");
|
||||
final status =
|
||||
_statusChannel.receiveBroadcastStream().map(mapEventToStatus);
|
||||
final alerts =
|
||||
_alertsChannel.receiveBroadcastStream().map(mapEventToStatus);
|
||||
_connectionStatus =
|
||||
ValueConnectableStream(Rx.merge([status, alerts])).autoConnect();
|
||||
await _connectionStatus.first;
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> setup(
|
||||
@@ -48,25 +69,14 @@ class MobileSingboxService with InfraLogger implements SingboxService {
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> create(String configPath) {
|
||||
return TaskEither(
|
||||
() async {
|
||||
loggy.debug("creating service for: $configPath");
|
||||
await _methodChannel.invokeMethod(
|
||||
"set_active_config_path",
|
||||
{"path": configPath},
|
||||
);
|
||||
return right(unit);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> start() {
|
||||
TaskEither<String, Unit> start(String configPath) {
|
||||
return TaskEither(
|
||||
() async {
|
||||
loggy.debug("starting");
|
||||
await _methodChannel.invokeMethod("start");
|
||||
await _methodChannel.invokeMethod(
|
||||
"start",
|
||||
{"path": configPath},
|
||||
);
|
||||
return right(unit);
|
||||
},
|
||||
);
|
||||
@@ -83,6 +93,20 @@ class MobileSingboxService with InfraLogger implements SingboxService {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TaskEither<String, Unit> restart(String configPath) {
|
||||
return TaskEither(
|
||||
() async {
|
||||
loggy.debug("restarting");
|
||||
await _methodChannel.invokeMethod(
|
||||
"restart",
|
||||
{"path": configPath},
|
||||
);
|
||||
return right(unit);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<String> watchOutbounds() {
|
||||
const channel = EventChannel("com.hiddify.app/groups");
|
||||
@@ -99,7 +123,10 @@ class MobileSingboxService with InfraLogger implements SingboxService {
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<String> watchStatus() {
|
||||
Stream<ConnectionStatus> watchConnectionStatus() => _connectionStatus;
|
||||
|
||||
@override
|
||||
Stream<String> watchStats() {
|
||||
// TODO: implement watchStatus
|
||||
return const Stream.empty();
|
||||
}
|
||||
|
||||
46
lib/services/singbox/shared.dart
Normal file
46
lib/services/singbox/shared.dart
Normal file
@@ -0,0 +1,46 @@
|
||||
import 'package:hiddify/domain/connectivity/connectivity.dart';
|
||||
import 'package:hiddify/domain/core_service_failure.dart';
|
||||
|
||||
mixin ServiceStatus {
|
||||
ConnectionStatus mapEventToStatus(dynamic event) {
|
||||
final status = event['status'] as String;
|
||||
late ConnectionStatus connectionStatus;
|
||||
switch (status) {
|
||||
case "Stopped":
|
||||
final failure = event["alert"] as String?;
|
||||
final message = event["message"] as String?;
|
||||
connectionStatus = ConnectionStatus.disconnected(
|
||||
switch (failure) {
|
||||
null => null,
|
||||
"RequestVPNPermission" => MissingVpnPermission(message),
|
||||
"RequestNotificationPermission" =>
|
||||
MissingNotificationPermission(message),
|
||||
"EmptyConfiguration" ||
|
||||
"StartCommandServer" ||
|
||||
"CreateService" ||
|
||||
"StartService" =>
|
||||
CoreConnectionFailure(fromServiceAlert(failure, message)),
|
||||
_ => const UnexpectedConnectionFailure(),
|
||||
},
|
||||
);
|
||||
case "Starting":
|
||||
connectionStatus = const Connecting();
|
||||
case "Started":
|
||||
connectionStatus = const Connected();
|
||||
case "Stopping":
|
||||
connectionStatus = const Disconnecting();
|
||||
}
|
||||
return connectionStatus;
|
||||
}
|
||||
|
||||
CoreServiceFailure fromServiceAlert(String key, String? message) {
|
||||
return switch (key) {
|
||||
"EmptyConfiguration" => InvalidConfig(message),
|
||||
"StartCommandServer" ||
|
||||
"CreateService" =>
|
||||
CoreServiceCreateFailure(message),
|
||||
"StartService" => CoreServiceStartFailure(message),
|
||||
_ => const CoreServiceOtherFailure(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fpdart/fpdart.dart';
|
||||
import 'package:hiddify/domain/connectivity/connectivity.dart';
|
||||
import 'package:hiddify/domain/singbox/singbox.dart';
|
||||
import 'package:hiddify/services/singbox/ffi_singbox_service.dart';
|
||||
import 'package:hiddify/services/singbox/mobile_singbox_service.dart';
|
||||
@@ -9,10 +10,14 @@ abstract interface class SingboxService {
|
||||
factory SingboxService() {
|
||||
if (Platform.isAndroid) {
|
||||
return MobileSingboxService();
|
||||
} else if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {
|
||||
return FFISingboxService();
|
||||
}
|
||||
return FFISingboxService();
|
||||
throw Exception("unsupported platform");
|
||||
}
|
||||
|
||||
Future<void> init();
|
||||
|
||||
TaskEither<String, Unit> setup(
|
||||
String baseDir,
|
||||
String workingDir,
|
||||
@@ -23,19 +28,21 @@ abstract interface class SingboxService {
|
||||
|
||||
TaskEither<String, Unit> changeConfigOptions(ConfigOptions options);
|
||||
|
||||
TaskEither<String, Unit> create(String configPath);
|
||||
|
||||
TaskEither<String, Unit> start();
|
||||
TaskEither<String, Unit> start(String configPath);
|
||||
|
||||
TaskEither<String, Unit> stop();
|
||||
|
||||
TaskEither<String, Unit> restart(String configPath);
|
||||
|
||||
Stream<String> watchOutbounds();
|
||||
|
||||
TaskEither<String, Unit> selectOutbound(String groupTag, String outboundTag);
|
||||
|
||||
TaskEither<String, Unit> urlTest(String groupTag);
|
||||
|
||||
Stream<String> watchStatus();
|
||||
Stream<ConnectionStatus> watchConnectionStatus();
|
||||
|
||||
Stream<String> watchStats();
|
||||
|
||||
Stream<String> watchLogs(String path);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user