123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- import 'dart:io';
- import 'dart:async';
- import 'dart:convert';
- import 'package:dio/dio.dart';
- import 'package:get/get.dart';
- import 'package:naiyouwl/app/bean/ClashServiceInfo.dart';
- import 'package:naiyouwl/app/const/const.dart';
- import 'package:naiyouwl/app/controller/controllers.dart';
- import 'package:naiyouwl/app/utils/logger.dart';
- import 'package:naiyouwl/app/utils/shell.dart';
- import 'package:naiyouwl/app/utils/system_dns.dart';
- import 'package:naiyouwl/app/utils/system_proxy.dart';
- import 'package:naiyouwl/app/utils/utils.dart';
- import 'package:path/path.dart' as path;
- import 'package:flutter/foundation.dart';
- import 'package:bot_toast/bot_toast.dart';
- import 'package:web_socket_channel/io.dart';
- final headers = {"User-Agent": "naiyou-for-flutter/0.0.1"};
- class ServiceController extends GetxController {
- final dio = Dio(BaseOptions(baseUrl: 'http://127.0.0.1:9899', headers: headers));
- var serviceMode = false.obs;
- var coreStatus = RunningState.stoped.obs;
- var serviceStatus = RunningState.stoped.obs;
- Process? clashServiceProcess;
- bool get isRunning => serviceStatus.value == RunningState.running && coreStatus.value == RunningState.running;
- bool get isCanOperationService =>
- ![RunningState.starting, RunningState.stopping].contains(serviceStatus.value) &&
- ![RunningState.starting, RunningState.stopping].contains(coreStatus.value);
- bool get isCanOperationCore =>
- serviceStatus.value == RunningState.running && ![RunningState.starting, RunningState.stopping].contains(coreStatus.value);
- ServiceController() {
-
- }
- Future<void> startService() async {
- serviceStatus.value = RunningState.starting;
- if (Platform.isLinux) {
- await fixBinaryExecutePermissions(Files.assetsClashService);
- await fixBinaryExecutePermissions(Files.assetsClashCore);
- }
- try {
- final data = await fetchInfo();
- serviceMode.value = data.mode == 'service-mode';
- } catch (e) {
- await startUserModeService();
- if (serviceStatus.value == RunningState.error) return;
- }
- serviceStatus.value = RunningState.running;
- }
- Future<void> fixBinaryExecutePermissions(File file) async {
- final stat = await file.stat();
-
- final has = (stat.mode & 64) == 64;
- if (has) return;
- await Process.run('chmod', ['+x', file.path]);
- }
- Future<void> startUserModeService() async {
- serviceMode.value = false;
- try {
- int? exitCode;
- clashServiceProcess = await Process.start(Files.assetsClashService.path, ['user-mode'], mode: ProcessStartMode.inheritStdio);
- clashServiceProcess!.exitCode.then((code) => exitCode = code);
- while (true) {
- await Future.delayed(const Duration(milliseconds: 200));
- if (exitCode == 101) {
- BotToast.showText(text: 'clash-service exit with code: $exitCode,After 10 seconds, try to restart');
- log.error('After 10 seconds, try to restart');
- await Future.delayed(const Duration(seconds: 10));
- await startUserModeService();
- break;
- } else if (exitCode != null) {
- serviceStatus.value = RunningState.error;
- break;
- }
- try {
- await dio.post('/info');
- break;
- } catch (_) {}
- }
- } catch (e) {
- serviceStatus.value = RunningState.error;
- BotToast.showText(text: e.toString());
- }
- }
- Future<void> stopService() async {
- serviceStatus.value = RunningState.stopping;
- if (coreStatus.value == RunningState.running) await stopClashCore();
- if (!serviceMode.value) {
- if (clashServiceProcess != null) {
- clashServiceProcess!.kill();
- clashServiceProcess = null;
- } else if (kDebugMode) {
- await killProcess(path.basename(Files.assetsClashService.path));
- }
- }
- serviceStatus.value = RunningState.stoped;
- }
-
- Future<void> waitServiceStart() async {
- while (true) {
- await Future.delayed(const Duration(milliseconds: 100));
- try {
- await dio.post('/info');
- break;
- } catch (_) {}
- }
- }
-
- Future<void> waitServiceStop() async {
- while (true) {
- await Future.delayed(const Duration(milliseconds: 100));
- try {
- await dio.post('/info');
- } catch (e) {
- break;
- }
- }
- }
- Future<ClashServiceInfo> fetchInfo() async {
- final res = await dio.post('/info');
- return ClashServiceInfo.fromJson(res.data);
- }
- IOWebSocketChannel fetchLogWs() {
- return IOWebSocketChannel.connect(Uri.parse('ws://127.0.0.1:9089/logs'), headers: headers);
- }
- Future<void> fetchStart(String name) async {
- await fetchStop();
- final res = await dio.post<String>('/start', data: {
- "args": ['-d', Paths.config.path, '-f', path.join(Paths.config.path, name)]
- });
- if (json.decode(res.data!)["code"] != 0) throw json.decode(res.data!)["msg"];
- }
- Future<void> fetchStop() async {
- await dio.post('/stop');
- }
- Future<void> install() async {
- final res = await runAsAdmin(Files.assetsClashService.path, ["stop", "uninstall", "install", "start"]);
- log.debug('install', res.stdout, res.stderr);
- if (res.exitCode != 0) throw res.stderr;
- await waitServiceStart();
- }
- Future<void> uninstall() async {
- final res = await runAsAdmin(Files.assetsClashService.path, ["stop", "uninstall"]);
- log.debug('uninstall', res.stdout, res.stderr);
- if (res.exitCode != 0) throw res.stderr;
- await waitServiceStop();
- }
- Future<void> serviceModeSwitch(bool open) async {
- if (serviceStatus.value == RunningState.running) await stopService();
- try {
- open ? await install() : await uninstall();
- } catch (e) {
- BotToast.showText(text: e.toString());
- }
- await startService();
- await startClashCore();
- }
- Future<void> startClashCore() async {
- try {
- coreStatus.value = RunningState.starting;
- await fetchStart(controllers.config.config.value.selected);
- controllers.core.setApi(controllers.config.clashCoreApiAddress.value, controllers.config.clashCoreApiSecret.value);
- while (true) {
- await Future.delayed(const Duration(milliseconds: 200));
- final info = await fetchInfo();
- if (info.status == 'running') {
- try {
- await controllers.core.fetchHello();
- break;
- } catch (_) {}
- } else {
- throw 'clash-core start error';
- }
- }
- await controllers.core.updateConfig();
- if (Platform.isMacOS &&
- controllers.service.serviceMode.value &&
- controllers.config.clashCoreTunEnable.value &&
- controllers.config.clashCoreDns.isNotEmpty) {
- await MacSystemDns.instance.set([controllers.config.clashCoreDns.value]);
- }
- if (controllers.config.config.value.setSystemProxy) await SystemProxy.instance.set(controllers.core.proxyConfig);
- coreStatus.value = RunningState.running;
- } catch (e) {
- log.error(e);
- BotToast.showText(text: e.toString());
- coreStatus.value = RunningState.error;
- }
- }
- Future<void> stopClashCore() async {
- coreStatus.value = RunningState.stopping;
- if (Platform.isMacOS &&
- controllers.service.serviceMode.value &&
- controllers.config.clashCoreTunEnable.value &&
- controllers.config.clashCoreDns.isNotEmpty) {
- await MacSystemDns.instance.set([]);
- }
- if (controllers.config.config.value.setSystemProxy) await SystemProxy.instance.set(SystemProxyConfig());
- await fetchStop();
- coreStatus.value = RunningState.stoped;
- }
- Future<void> reloadClashCore() async {
- BotToast.showText(text: '正在重启 Clash Core ……');
- await stopClashCore();
- await controllers.config.readClashCoreApi();
- await startClashCore();
- if (coreStatus.value == RunningState.error) {
- BotToast.showText(text: '重启失败');
- } else {
- await controllers.core.updateVersion();
- BotToast.showText(text: '重启成功');
- }
- }
- }
|