2023-08-19 22:27:23 +03:30
|
|
|
import 'dart:async';
|
2023-09-01 15:00:41 +03:30
|
|
|
import 'dart:convert';
|
2023-08-19 22:27:23 +03:30
|
|
|
import 'dart:ffi';
|
|
|
|
|
import 'dart:io';
|
2023-08-28 13:15:57 +03:30
|
|
|
import 'dart:isolate';
|
2023-08-19 22:27:23 +03:30
|
|
|
|
|
|
|
|
import 'package:ffi/ffi.dart';
|
|
|
|
|
import 'package:fpdart/fpdart.dart';
|
2023-12-01 12:56:24 +03:30
|
|
|
import 'package:hiddify/core/model/directories.dart';
|
2023-08-19 22:27:23 +03:30
|
|
|
import 'package:hiddify/gen/singbox_generated_bindings.dart';
|
2023-12-01 12:56:24 +03:30
|
|
|
import 'package:hiddify/singbox/model/singbox_config_option.dart';
|
|
|
|
|
import 'package:hiddify/singbox/model/singbox_outbound.dart';
|
|
|
|
|
import 'package:hiddify/singbox/model/singbox_stats.dart';
|
|
|
|
|
import 'package:hiddify/singbox/model/singbox_status.dart';
|
2024-02-18 12:35:11 +03:30
|
|
|
import 'package:hiddify/singbox/model/warp_account.dart';
|
2023-12-01 12:56:24 +03:30
|
|
|
import 'package:hiddify/singbox/service/singbox_service.dart';
|
2023-08-19 22:27:23 +03:30
|
|
|
import 'package:hiddify/utils/utils.dart';
|
2023-08-21 00:54:11 +03:00
|
|
|
import 'package:loggy/loggy.dart';
|
2023-08-19 22:27:23 +03:30
|
|
|
import 'package:path/path.dart' as p;
|
2023-09-10 20:25:04 +03:30
|
|
|
import 'package:rxdart/rxdart.dart';
|
2023-10-24 11:26:57 +02:00
|
|
|
import 'package:watcher/watcher.dart';
|
2023-08-19 22:27:23 +03:30
|
|
|
|
2023-08-21 00:54:11 +03:00
|
|
|
final _logger = Loggy('FFISingboxService');
|
|
|
|
|
|
2023-12-01 12:56:24 +03:30
|
|
|
class FFISingboxService with InfraLogger implements SingboxService {
|
2023-08-19 22:27:23 +03:30
|
|
|
static final SingboxNativeLibrary _box = _gen();
|
|
|
|
|
|
2023-12-01 12:56:24 +03:30
|
|
|
late final ValueStream<SingboxStatus> _status;
|
|
|
|
|
late final ReceivePort _statusReceiver;
|
|
|
|
|
Stream<SingboxStats>? _serviceStatsStream;
|
|
|
|
|
Stream<List<SingboxOutboundGroup>>? _outboundsStream;
|
2023-08-28 13:15:57 +03:30
|
|
|
|
2023-08-19 22:27:23 +03:30
|
|
|
static SingboxNativeLibrary _gen() {
|
|
|
|
|
String fullPath = "";
|
|
|
|
|
if (Platform.environment.containsKey('FLUTTER_TEST')) {
|
|
|
|
|
fullPath = "libcore";
|
|
|
|
|
}
|
|
|
|
|
if (Platform.isWindows) {
|
|
|
|
|
fullPath = p.join(fullPath, "libcore.dll");
|
|
|
|
|
} else if (Platform.isMacOS) {
|
|
|
|
|
fullPath = p.join(fullPath, "libcore.dylib");
|
|
|
|
|
} else {
|
|
|
|
|
fullPath = p.join(fullPath, "libcore.so");
|
|
|
|
|
}
|
2023-08-21 00:54:11 +03:00
|
|
|
_logger.debug('singbox native libs path: "$fullPath"');
|
2023-08-19 22:27:23 +03:30
|
|
|
final lib = DynamicLibrary.open(fullPath);
|
|
|
|
|
return SingboxNativeLibrary(lib);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-10 20:25:04 +03:30
|
|
|
@override
|
|
|
|
|
Future<void> init() async {
|
|
|
|
|
loggy.debug("initializing");
|
2023-12-01 12:56:24 +03:30
|
|
|
_statusReceiver = ReceivePort('service status receiver');
|
2024-05-31 13:41:35 +02:00
|
|
|
final source = _statusReceiver.asBroadcastStream().map((event) => jsonDecode(event as String)).map(SingboxStatus.fromEvent);
|
2023-12-01 12:56:24 +03:30
|
|
|
_status = ValueConnectableStream.seeded(
|
2023-09-10 20:25:04 +03:30
|
|
|
source,
|
2023-12-01 12:56:24 +03:30
|
|
|
const SingboxStopped(),
|
2023-09-10 20:25:04 +03:30
|
|
|
).autoConnect();
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-19 22:27:23 +03:30
|
|
|
@override
|
|
|
|
|
TaskEither<String, Unit> setup(
|
2023-12-01 12:56:24 +03:30
|
|
|
Directories directories,
|
2023-10-27 17:45:38 +03:30
|
|
|
bool debug,
|
2023-08-19 22:27:23 +03:30
|
|
|
) {
|
2023-12-01 12:56:24 +03:30
|
|
|
final port = _statusReceiver.sendPort.nativePort;
|
2023-08-19 22:27:23 +03:30
|
|
|
return TaskEither(
|
2024-05-31 13:41:35 +02:00
|
|
|
() => Future.microtask(
|
|
|
|
|
() async {
|
2023-09-10 20:25:04 +03:30
|
|
|
_box.setupOnce(NativeApi.initializeApiDLData);
|
2023-10-27 17:45:38 +03:30
|
|
|
final err = _box
|
|
|
|
|
.setup(
|
2023-12-01 12:56:24 +03:30
|
|
|
directories.baseDir.path.toNativeUtf8().cast(),
|
|
|
|
|
directories.workingDir.path.toNativeUtf8().cast(),
|
|
|
|
|
directories.tempDir.path.toNativeUtf8().cast(),
|
2023-10-27 17:45:38 +03:30
|
|
|
port,
|
|
|
|
|
debug ? 1 : 0,
|
|
|
|
|
)
|
|
|
|
|
.cast<Utf8>()
|
|
|
|
|
.toDartString();
|
|
|
|
|
if (err.isNotEmpty) {
|
|
|
|
|
return left(err);
|
|
|
|
|
}
|
2023-08-19 22:27:23 +03:30
|
|
|
return right(unit);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
2023-12-01 12:56:24 +03:30
|
|
|
TaskEither<String, Unit> validateConfigByPath(
|
2023-09-22 23:52:20 +03:30
|
|
|
String path,
|
|
|
|
|
String tempPath,
|
|
|
|
|
bool debug,
|
|
|
|
|
) {
|
2023-08-19 22:27:23 +03:30
|
|
|
return TaskEither(
|
2024-05-31 13:41:35 +02:00
|
|
|
() => Future.microtask(
|
|
|
|
|
() async {
|
2023-08-19 22:27:23 +03:30
|
|
|
final err = _box
|
2023-09-22 23:52:20 +03:30
|
|
|
.parse(
|
|
|
|
|
path.toNativeUtf8().cast(),
|
|
|
|
|
tempPath.toNativeUtf8().cast(),
|
|
|
|
|
debug ? 1 : 0,
|
|
|
|
|
)
|
2023-08-19 22:27:23 +03:30
|
|
|
.cast<Utf8>()
|
|
|
|
|
.toDartString();
|
|
|
|
|
if (err.isNotEmpty) {
|
|
|
|
|
return left(err);
|
|
|
|
|
}
|
|
|
|
|
return right(unit);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
2023-12-01 12:56:24 +03:30
|
|
|
TaskEither<String, Unit> changeOptions(SingboxConfigOption options) {
|
2023-08-19 22:27:23 +03:30
|
|
|
return TaskEither(
|
2024-05-31 13:41:35 +02:00
|
|
|
() => Future.microtask(
|
|
|
|
|
() async {
|
2024-02-17 11:58:34 +03:30
|
|
|
final json = jsonEncode(options.toJson());
|
2024-05-31 13:41:35 +02:00
|
|
|
final err = _box.changeConfigOptions(json.toNativeUtf8().cast()).cast<Utf8>().toDartString();
|
2023-09-01 15:00:41 +03:30
|
|
|
if (err.isNotEmpty) {
|
|
|
|
|
return left(err);
|
|
|
|
|
}
|
|
|
|
|
return right(unit);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-12 12:52:54 +03:30
|
|
|
@override
|
2023-12-01 12:56:24 +03:30
|
|
|
TaskEither<String, String> generateFullConfigByPath(
|
2023-11-12 12:52:54 +03:30
|
|
|
String path,
|
|
|
|
|
) {
|
|
|
|
|
return TaskEither(
|
2024-05-31 13:41:35 +02:00
|
|
|
() => Future.microtask(
|
|
|
|
|
() async {
|
2023-11-12 12:52:54 +03:30
|
|
|
final response = _box
|
|
|
|
|
.generateConfig(
|
|
|
|
|
path.toNativeUtf8().cast(),
|
|
|
|
|
)
|
|
|
|
|
.cast<Utf8>()
|
|
|
|
|
.toDartString();
|
|
|
|
|
if (response.startsWith("error")) {
|
2023-11-13 15:02:38 +03:30
|
|
|
return left(response.replaceFirst("error", ""));
|
2023-11-12 12:52:54 +03:30
|
|
|
}
|
|
|
|
|
return right(response);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-01 15:00:41 +03:30
|
|
|
@override
|
2023-12-14 14:50:10 +03:30
|
|
|
TaskEither<String, Unit> start(
|
|
|
|
|
String configPath,
|
|
|
|
|
String name,
|
|
|
|
|
bool disableMemoryLimit,
|
|
|
|
|
) {
|
2023-10-26 15:16:25 +03:30
|
|
|
loggy.debug("starting, memory limit: [${!disableMemoryLimit}]");
|
2023-09-01 15:00:41 +03:30
|
|
|
return TaskEither(
|
2024-05-31 13:41:35 +02:00
|
|
|
() => Future.microtask(
|
|
|
|
|
() async {
|
2023-08-19 22:27:23 +03:30
|
|
|
final err = _box
|
2023-10-26 15:16:25 +03:30
|
|
|
.start(
|
|
|
|
|
configPath.toNativeUtf8().cast(),
|
|
|
|
|
disableMemoryLimit ? 1 : 0,
|
|
|
|
|
)
|
2023-08-19 22:27:23 +03:30
|
|
|
.cast<Utf8>()
|
|
|
|
|
.toDartString();
|
|
|
|
|
if (err.isNotEmpty) {
|
|
|
|
|
return left(err);
|
|
|
|
|
}
|
|
|
|
|
return right(unit);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
2023-09-10 20:25:04 +03:30
|
|
|
TaskEither<String, Unit> stop() {
|
2023-08-19 22:27:23 +03:30
|
|
|
return TaskEither(
|
2024-05-31 13:41:35 +02:00
|
|
|
() => Future.microtask(
|
|
|
|
|
() async {
|
2023-09-10 20:25:04 +03:30
|
|
|
final err = _box.stop().cast<Utf8>().toDartString();
|
2023-08-19 22:27:23 +03:30
|
|
|
if (err.isNotEmpty) {
|
|
|
|
|
return left(err);
|
|
|
|
|
}
|
|
|
|
|
return right(unit);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
2023-12-14 14:50:10 +03:30
|
|
|
TaskEither<String, Unit> restart(
|
|
|
|
|
String configPath,
|
|
|
|
|
String name,
|
|
|
|
|
bool disableMemoryLimit,
|
|
|
|
|
) {
|
2023-10-26 15:16:25 +03:30
|
|
|
loggy.debug("restarting, memory limit: [${!disableMemoryLimit}]");
|
2023-08-19 22:27:23 +03:30
|
|
|
return TaskEither(
|
2024-05-31 13:41:35 +02:00
|
|
|
() => Future.microtask(
|
|
|
|
|
() async {
|
2023-09-10 20:25:04 +03:30
|
|
|
final err = _box
|
2023-10-26 15:16:25 +03:30
|
|
|
.restart(
|
|
|
|
|
configPath.toNativeUtf8().cast(),
|
|
|
|
|
disableMemoryLimit ? 1 : 0,
|
|
|
|
|
)
|
2023-09-10 20:25:04 +03:30
|
|
|
.cast<Utf8>()
|
|
|
|
|
.toDartString();
|
2023-08-19 22:27:23 +03:30
|
|
|
if (err.isNotEmpty) {
|
|
|
|
|
return left(err);
|
|
|
|
|
}
|
|
|
|
|
return right(unit);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-18 22:53:17 +03:30
|
|
|
@override
|
|
|
|
|
TaskEither<String, Unit> resetTunnel() {
|
|
|
|
|
throw UnimplementedError(
|
|
|
|
|
"reset tunnel function unavailable on platform",
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-28 13:15:57 +03:30
|
|
|
@override
|
2023-12-01 12:56:24 +03:30
|
|
|
Stream<SingboxStatus> watchStatus() => _status;
|
2023-09-10 20:25:04 +03:30
|
|
|
|
|
|
|
|
@override
|
2023-12-01 12:56:24 +03:30
|
|
|
Stream<SingboxStats> watchStats() {
|
2023-11-13 15:02:38 +03:30
|
|
|
if (_serviceStatsStream != null) return _serviceStatsStream!;
|
2024-02-13 17:02:10 +03:30
|
|
|
final receiver = ReceivePort('stats');
|
2023-08-28 13:15:57 +03:30
|
|
|
final statusStream = receiver.asBroadcastStream(
|
|
|
|
|
onCancel: (_) {
|
2023-11-13 15:02:38 +03:30
|
|
|
_logger.debug("stopping stats command client");
|
2023-08-28 13:15:57 +03:30
|
|
|
final err = _box.stopCommandClient(1).cast<Utf8>().toDartString();
|
|
|
|
|
if (err.isNotEmpty) {
|
2023-11-13 15:02:38 +03:30
|
|
|
_logger.error("error stopping stats client");
|
2023-08-28 13:15:57 +03:30
|
|
|
}
|
|
|
|
|
receiver.close();
|
2023-11-13 15:02:38 +03:30
|
|
|
_serviceStatsStream = null;
|
2023-08-28 13:15:57 +03:30
|
|
|
},
|
|
|
|
|
).map(
|
|
|
|
|
(event) {
|
|
|
|
|
if (event case String _) {
|
|
|
|
|
if (event.startsWith('error:')) {
|
2023-11-13 15:02:38 +03:30
|
|
|
loggy.error("[service stats client] error received: $event");
|
2023-08-28 13:15:57 +03:30
|
|
|
throw event.replaceFirst('error:', "");
|
|
|
|
|
}
|
2023-12-01 12:56:24 +03:30
|
|
|
return SingboxStats.fromJson(
|
|
|
|
|
jsonDecode(event) as Map<String, dynamic>,
|
|
|
|
|
);
|
2023-08-28 13:15:57 +03:30
|
|
|
}
|
2023-11-13 15:02:38 +03:30
|
|
|
loggy.error("[service status client] unexpected type, msg: $event");
|
2023-08-28 13:15:57 +03:30
|
|
|
throw "invalid type";
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
2024-05-31 13:41:35 +02:00
|
|
|
final err = _box.startCommandClient(1, receiver.sendPort.nativePort).cast<Utf8>().toDartString();
|
2023-08-28 13:15:57 +03:30
|
|
|
if (err.isNotEmpty) {
|
2023-10-05 22:47:24 +03:30
|
|
|
loggy.error("error starting status command: $err");
|
2023-08-28 13:15:57 +03:30
|
|
|
throw err;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-13 15:02:38 +03:30
|
|
|
return _serviceStatsStream = statusStream;
|
2023-08-28 13:15:57 +03:30
|
|
|
}
|
|
|
|
|
|
2023-08-29 19:32:31 +03:30
|
|
|
@override
|
2024-02-13 17:02:10 +03:30
|
|
|
Stream<List<SingboxOutboundGroup>> watchGroups() {
|
|
|
|
|
final logger = newLoggy("watchGroups");
|
2023-11-13 15:02:38 +03:30
|
|
|
if (_outboundsStream != null) return _outboundsStream!;
|
2024-02-13 17:02:10 +03:30
|
|
|
final receiver = ReceivePort('groups');
|
2023-11-13 15:02:38 +03:30
|
|
|
final outboundsStream = receiver.asBroadcastStream(
|
2023-08-29 19:32:31 +03:30
|
|
|
onCancel: (_) {
|
2024-02-13 17:02:10 +03:30
|
|
|
logger.debug("stopping");
|
|
|
|
|
receiver.close();
|
|
|
|
|
_outboundsStream = null;
|
2024-03-18 10:57:05 +01:00
|
|
|
final err = _box.stopCommandClient(5).cast<Utf8>().toDartString();
|
2023-08-29 19:32:31 +03:30
|
|
|
if (err.isNotEmpty) {
|
2023-10-05 22:47:24 +03:30
|
|
|
_logger.error("error stopping group client");
|
2023-08-29 19:32:31 +03:30
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
).map(
|
|
|
|
|
(event) {
|
|
|
|
|
if (event case String _) {
|
|
|
|
|
if (event.startsWith('error:')) {
|
2024-02-13 17:02:10 +03:30
|
|
|
logger.error("error received: $event");
|
2023-08-29 19:32:31 +03:30
|
|
|
throw event.replaceFirst('error:', "");
|
|
|
|
|
}
|
2024-02-13 17:02:10 +03:30
|
|
|
|
2023-12-01 12:56:24 +03:30
|
|
|
return (jsonDecode(event) as List).map((e) {
|
|
|
|
|
return SingboxOutboundGroup.fromJson(e as Map<String, dynamic>);
|
|
|
|
|
}).toList();
|
2023-08-29 19:32:31 +03:30
|
|
|
}
|
2024-02-13 17:02:10 +03:30
|
|
|
logger.error("unexpected type, msg: $event");
|
2023-08-29 19:32:31 +03:30
|
|
|
throw "invalid type";
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
2024-02-13 17:02:10 +03:30
|
|
|
try {
|
2024-05-31 13:41:35 +02:00
|
|
|
final err = _box.startCommandClient(5, receiver.sendPort.nativePort).cast<Utf8>().toDartString();
|
2024-02-13 17:02:10 +03:30
|
|
|
if (err.isNotEmpty) {
|
|
|
|
|
logger.error("error starting group command: $err");
|
|
|
|
|
throw err;
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
receiver.close();
|
|
|
|
|
rethrow;
|
2023-08-29 19:32:31 +03:30
|
|
|
}
|
|
|
|
|
|
2023-11-13 15:02:38 +03:30
|
|
|
return _outboundsStream = outboundsStream;
|
2023-08-29 19:32:31 +03:30
|
|
|
}
|
|
|
|
|
|
2024-02-09 12:02:52 +03:30
|
|
|
@override
|
2024-02-13 17:02:10 +03:30
|
|
|
Stream<List<SingboxOutboundGroup>> watchActiveGroups() {
|
2024-02-10 17:32:45 +03:30
|
|
|
final logger = newLoggy("[ActiveGroupsClient]");
|
2024-02-13 17:02:10 +03:30
|
|
|
final receiver = ReceivePort('active groups');
|
2024-02-10 17:32:45 +03:30
|
|
|
final outboundsStream = receiver.asBroadcastStream(
|
|
|
|
|
onCancel: (_) {
|
|
|
|
|
logger.debug("stopping");
|
2024-02-13 17:02:10 +03:30
|
|
|
receiver.close();
|
2024-03-18 10:57:05 +01:00
|
|
|
final err = _box.stopCommandClient(13).cast<Utf8>().toDartString();
|
2024-02-10 17:32:45 +03:30
|
|
|
if (err.isNotEmpty) {
|
|
|
|
|
logger.error("failed stopping: $err");
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
).map(
|
|
|
|
|
(event) {
|
|
|
|
|
if (event case String _) {
|
|
|
|
|
if (event.startsWith('error:')) {
|
|
|
|
|
logger.error(event);
|
|
|
|
|
throw event.replaceFirst('error:', "");
|
|
|
|
|
}
|
2024-02-13 17:02:10 +03:30
|
|
|
|
2024-02-10 17:32:45 +03:30
|
|
|
return (jsonDecode(event) as List).map((e) {
|
|
|
|
|
return SingboxOutboundGroup.fromJson(e as Map<String, dynamic>);
|
|
|
|
|
}).toList();
|
|
|
|
|
}
|
|
|
|
|
logger.error("unexpected type, msg: $event");
|
|
|
|
|
throw "invalid type";
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
2024-02-13 17:02:10 +03:30
|
|
|
try {
|
2024-05-31 13:41:35 +02:00
|
|
|
final err = _box.startCommandClient(13, receiver.sendPort.nativePort).cast<Utf8>().toDartString();
|
2024-02-13 17:02:10 +03:30
|
|
|
if (err.isNotEmpty) {
|
|
|
|
|
logger.error("error starting: $err");
|
|
|
|
|
throw err;
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
receiver.close();
|
|
|
|
|
rethrow;
|
2024-02-10 17:32:45 +03:30
|
|
|
}
|
2024-02-13 17:02:10 +03:30
|
|
|
|
2024-02-10 17:32:45 +03:30
|
|
|
return outboundsStream;
|
2024-02-09 12:02:52 +03:30
|
|
|
}
|
|
|
|
|
|
2023-08-29 19:32:31 +03:30
|
|
|
@override
|
|
|
|
|
TaskEither<String, Unit> selectOutbound(String groupTag, String outboundTag) {
|
|
|
|
|
return TaskEither(
|
2024-05-31 13:41:35 +02:00
|
|
|
() => Future.microtask(
|
|
|
|
|
() async {
|
2023-08-29 19:32:31 +03:30
|
|
|
final err = _box
|
|
|
|
|
.selectOutbound(
|
|
|
|
|
groupTag.toNativeUtf8().cast(),
|
|
|
|
|
outboundTag.toNativeUtf8().cast(),
|
|
|
|
|
)
|
|
|
|
|
.cast<Utf8>()
|
|
|
|
|
.toDartString();
|
|
|
|
|
if (err.isNotEmpty) {
|
|
|
|
|
return left(err);
|
|
|
|
|
}
|
|
|
|
|
return right(unit);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
|
|
|
TaskEither<String, Unit> urlTest(String groupTag) {
|
|
|
|
|
return TaskEither(
|
2024-05-31 13:41:35 +02:00
|
|
|
() => Future.microtask(
|
|
|
|
|
() async {
|
|
|
|
|
final err = _box.urlTest(groupTag.toNativeUtf8().cast()).cast<Utf8>().toDartString();
|
2023-08-29 19:32:31 +03:30
|
|
|
if (err.isNotEmpty) {
|
|
|
|
|
return left(err);
|
|
|
|
|
}
|
|
|
|
|
return right(unit);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-24 11:26:57 +02:00
|
|
|
final _logBuffer = <String>[];
|
|
|
|
|
int _logFilePosition = 0;
|
|
|
|
|
|
2023-08-19 22:27:23 +03:30
|
|
|
@override
|
2023-10-24 11:26:57 +02:00
|
|
|
Stream<List<String>> watchLogs(String path) async* {
|
|
|
|
|
yield await _readLogFile(File(path));
|
2024-05-31 13:41:35 +02:00
|
|
|
yield* Watcher(path, pollingDelay: const Duration(seconds: 1)).events.asyncMap((event) async {
|
2023-10-24 11:26:57 +02:00
|
|
|
if (event.type == ChangeType.MODIFY) {
|
|
|
|
|
await _readLogFile(File(path));
|
|
|
|
|
}
|
|
|
|
|
return _logBuffer;
|
|
|
|
|
});
|
2023-08-19 22:27:23 +03:30
|
|
|
}
|
|
|
|
|
|
2023-10-24 11:26:57 +02:00
|
|
|
@override
|
|
|
|
|
TaskEither<String, Unit> clearLogs() {
|
|
|
|
|
return TaskEither(
|
2024-05-31 13:41:35 +02:00
|
|
|
() => Future.microtask(
|
|
|
|
|
() async {
|
|
|
|
|
_logBuffer.clear();
|
|
|
|
|
return right(unit);
|
|
|
|
|
},
|
|
|
|
|
),
|
2023-08-19 22:27:23 +03:30
|
|
|
);
|
|
|
|
|
}
|
2023-10-24 11:26:57 +02:00
|
|
|
|
|
|
|
|
Future<List<String>> _readLogFile(File file) async {
|
|
|
|
|
if (_logFilePosition == 0 && file.lengthSync() == 0) return [];
|
2024-05-31 13:41:35 +02:00
|
|
|
final content = await file.openRead(_logFilePosition).transform(utf8.decoder).join();
|
2023-10-24 11:26:57 +02:00
|
|
|
_logFilePosition = file.lengthSync();
|
|
|
|
|
final lines = const LineSplitter().convert(content);
|
|
|
|
|
if (lines.length > 300) {
|
|
|
|
|
lines.removeRange(0, lines.length - 300);
|
|
|
|
|
}
|
|
|
|
|
for (final line in lines) {
|
|
|
|
|
_logBuffer.add(line);
|
|
|
|
|
if (_logBuffer.length > 300) {
|
|
|
|
|
_logBuffer.removeAt(0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return _logBuffer;
|
|
|
|
|
}
|
2024-02-18 12:35:11 +03:30
|
|
|
|
|
|
|
|
@override
|
2024-02-20 22:16:47 +03:30
|
|
|
TaskEither<String, WarpResponse> generateWarpConfig({
|
2024-02-18 12:35:11 +03:30
|
|
|
required String licenseKey,
|
|
|
|
|
required String previousAccountId,
|
|
|
|
|
required String previousAccessToken,
|
|
|
|
|
}) {
|
|
|
|
|
loggy.debug("generating warp config");
|
|
|
|
|
return TaskEither(
|
2024-05-31 13:41:35 +02:00
|
|
|
() => Future.microtask(
|
|
|
|
|
() async {
|
2024-02-18 12:35:11 +03:30
|
|
|
final response = _box
|
|
|
|
|
.generateWarpConfig(
|
|
|
|
|
licenseKey.toNativeUtf8().cast(),
|
|
|
|
|
previousAccountId.toNativeUtf8().cast(),
|
|
|
|
|
previousAccessToken.toNativeUtf8().cast(),
|
|
|
|
|
)
|
|
|
|
|
.cast<Utf8>()
|
|
|
|
|
.toDartString();
|
|
|
|
|
if (response.startsWith("error:")) {
|
|
|
|
|
return left(response.replaceFirst('error:', ""));
|
|
|
|
|
}
|
2024-02-20 22:16:47 +03:30
|
|
|
return right(warpFromJson(jsonDecode(response)));
|
2024-02-18 12:35:11 +03:30
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
2023-08-19 22:27:23 +03:30
|
|
|
}
|