// ignore_for_file: avoid_init_to_null import 'dart:async'; import 'dart:io'; import 'dart:convert'; import 'dart:math'; import 'package:dart_framework/charset/euc_kr.dart'; import 'package:dart_framework/utils/system_util.dart'; class ProcessData { late Process process; late File file; late bool isRunning; late int exitCode; Function(ProcessData)? _endListener; late StringBuffer stdout; late StringBuffer stderr; bool _enableStdout; bool _enableStderr; late Converter, String> _decoder = utf8.decoder; // ignore: no_leading_underscores_for_local_identifiers ProcessData(this._enableStdout, this._enableStderr); Function(ProcessData)? get endListener { return _endListener; } bool get isSuccess { return ProcessExecutor.getSuccess(exitCode); } List get stdoutArr { return getBufferToArray(stdout); } List get stderrArr { return getBufferToArray(stderr); } List getBufferToArray(StringBuffer buffer) { var list = []; var arr = buffer.toString().split('\n'); var length = arr.length; for (var i = 0; i < length; ++i) { if (arr[i].isNotEmpty) { list.add(arr[i]); } } return list; } set decoder(Converter, String> value) { _decoder = value; } set processValue(Process value) { process = value; stdout = StringBuffer(); stderr = StringBuffer(); process.stdout.transform(_decoder).listen((event) { stdout.writeln(event); if (_enableStdout) { print(event); } }); process.stderr.transform(_decoder).listen((event) { stderr.writeln(event); if (_enableStderr) { print('======================= $event'); } }); isRunning = true; checkExit(); } void addEndListener(Function(ProcessData) listener) { _endListener = listener; } void removeEndListener() { _endListener = null; } void checkExit() async { exitCode = await process.exitCode; deleteFile(); isRunning = false; if (_endListener != null) _endListener!(this); } void terminate() { process.kill(); } void deleteFile() async { if (await file.exists()) { await file.delete(); } } void dispose() async { deleteFile(); } Future waitForExit() async { await process.exitCode; return simpleFuture; } } class ProcessExecutor { static Future startExe(String exe, List args, {String? workspace, bool? printStdout, bool? printStderr}) async { var c = Completer(); var arg = '$exe ${args.join(' ')}'; var processData = ProcessData(printStdout ?? true, printStderr ?? true); if (Platform.isMacOS || Platform.isWindows) { workspace = workspace ?? Directory.systemTemp.path; print('Execute Shell: $arg'); try { processData.processValue = await Process.start(exe, args, workingDirectory: workspace); } on Exception catch (e) { print(e); } } else { print('This platform not support process executor.'); } c.complete(processData); return c.future; } static Future start(StringBuffer shell, {Converter, String>? decoder, String? workspace, bool? printStdout, bool? printStderr}) async { var c = Completer(); try { var processData = ProcessData(printStdout ?? true, printStderr ?? true); if (decoder != null) { processData.decoder = decoder; } if (Platform.isMacOS || Platform.isLinux) { workspace = workspace ?? Directory.systemTemp.path; var file = File('$workspace/$tempFileName'); processData.file = file; print('Execute Shell: ${file.path} \n ${shell.toString()}'); if (await file.exists()) { await file.delete(); } file.createSync(); file.writeAsStringSync(shell.toString()); try { var shellExe = Platform.isMacOS ? 'zsh' : 'bash'; processData.processValue = await Process.start(shellExe, [file.path], workingDirectory: workspace, runInShell: false); } on Exception catch (e) { print(e); } } else if (Platform.isWindows) { workspace = workspace ?? Directory.systemTemp.path; var file = File('$workspace/$tempFileName'); processData.file = file; print('Execute Shell: ${file.path} \n ${shell.toString()}'); if (await file.exists()) { await file.delete(); } file.createSync(); file.writeAsStringSync(shell.toString()); try { processData.processValue = await Process.start( 'cmd', ['/C', file.path], workingDirectory: workspace, runInShell: false); } on Exception catch (e) { print(e); } } else { print('This platform not support process executor.'); } c.complete(processData); await processData.waitForExit(); } on Exception catch (e) { print('General Exception in ProcessExecutor.start: $e'); } return c.future; } static Future run(StringBuffer shell, {String? workspace, bool printStdout = true}) async { var c = Completer(); workspace = workspace ?? Directory.systemTemp.path; var file = File('$workspace/$tempFileName'); print('---------------------------------------------------'); print('Shell Path: ${file.path} \nExecute Shell: ${shell.toString()}'); ProcessResult? processResult; if (Platform.isMacOS || Platform.isLinux) { file.createSync(); file.writeAsStringSync(shell.toString()); try { var shellExe = Platform.isMacOS ? 'zsh' : 'bash'; await Process.run(shellExe, [file.path], workingDirectory: workspace) .then((ProcessResult result) { processResult = result; }); if (printStdout) { print( // ignore: prefer_interpolation_to_compose_strings '[ProcessExecutor] Exit Code: ${processResult!.exitCode}\nResult: ${processResult!.stdout}\nError: ${processResult!.stderr}'); } } on Exception catch (e) { print(e); } finally { file.deleteSync(); } c.complete(processResult); } else if (Platform.isWindows) { file.createSync(); file.writeAsStringSync(shell.toString()); await Process.run('cmd', ['/C', file.path], workingDirectory: workspace) .then((ProcessResult result) { processResult = result; }); if (printStdout) { // print( // // ignore: prefer_interpolation_to_compose_strings // '[ProcessExecutor] Exit Code: ${processResult!.exitCode}\n' + // '==============================================================\n' + // 'Result: ${eucKr.decode()}\n' + // '==============================================================\n' + // 'Error: ${eucKr.decode(processResult!.stderr)}'); } file.deleteSync(); c.complete(processResult); } else { print('This platform not support process executor.'); c.complete(null); } return c.future; } static String get tempFileName { if (Platform.isMacOS || Platform.isLinux) { return 'temp_${DateTime.now().microsecondsSinceEpoch}.sh'; } else { return 'temp_${DateTime.now().microsecondsSinceEpoch}.bat'; } } static bool getSuccess(int exitCode) { if (Platform.isMacOS || Platform.isLinux) return exitCode == 0; if (Platform.isWindows) return exitCode == 1; return false; } } class NetStatData { late String type; late String from; late String to; late String state; late int pid; NetStatData(List categories) { var count = 0; var length = categories.length; for (var i = 0; i < length; ++i) { var category = categories[i].replaceAll(' ', ''); if (category.isNotEmpty) { switch (count) { case 0: type = category; break; case 1: from = category; break; case 2: to = category; break; case 3: state = category; break; case 4: var value = int.tryParse(category); value ??= -1; pid = value; break; } count++; } } } } bool _removedBat = false; Future removeTempBat() async { var c = Completer(); if (!_removedBat) { _removedBat = true; var reg = RegExp(r'(temp_bat_)([0-9_]+)(.bat)'); var list = Directory.current.listSync(); for (var item in list) { var match = reg.stringMatch(item.path); if (match != null) { await item.delete(); } } await Future.delayed(Duration(milliseconds: 500)); } c.complete(); return c.future; } Future runGetStdout(StringBuffer shell) async { var c = Completer(); var shellStr = shell.toString(); var batch = File('${Directory.systemTemp.path}/temp_${getRnadomPostfix()}.bat'); batch.writeAsStringSync(shellStr); var result = await ProcessExecutor.run(shell); if (result.exitCode == 0 || result.exitCode == 1) { c.complete(result.stdout); return c.future; } else { throw Exception(result.stderr); } } Future findUsePortPid(int port, [bool runSlient = false]) async { await removeTempBat(); var c = Completer(); var shell = StringBuffer('netstat -aon | find "$port"'); var stdout = ""; try { stdout = runSlient ? await ProcessSilentExecutor.runSlientBat(shell) : await runGetStdout(shell); } on Exception catch (e) {} if (stdout == "") { c.complete(null); } else { var netList = []; var returnPattern = RegExp('\n|\r'); var list = stdout.split(returnPattern); for (var item in list) { item = item.replaceAll(returnPattern, ''); if (item.isNotEmpty) { var categories = item.split(' '); if (categories.isNotEmpty) { netList.add(NetStatData(categories)); } } } NetStatData? data = null; if (netList.length > 1) { for (var net in netList) { if (net!.pid != -1) { data = net; break; } } } c.complete(data); } return c.future; } Future> findProcess(String searchWord, [bool runSlient = false]) async { await removeTempBat(); var c = Completer>(); var pList = []; if(Platform.isWindows) { var shell = StringBuffer('tasklist /fo "csv" | findstr $searchWord'); var stdout = runSlient ? await ProcessSilentExecutor.runSlientBat(shell) : await runGetStdout(shell); pList = parseProcessStd(stdout); } else { var shell = StringBuffer('ps -A | grep \'$searchWord\' | grep -v grep | awk \'{\$2=\$3=""; print \$0}\''); var result = await ProcessExecutor.run(shell, printStdout: false); if(result.exitCode == 0) { var procList = result.stdout.toString().split('\n'); for(var proc in procList) { if(proc.isNotEmpty) { var info = ProcessInfo(); var index = proc.indexOf(' '); info.pid = int.parse(proc.substring(0, index)); info.name = proc.substring(index).trimLeft(); pList.add(info); } } } } c.complete(pList); return c.future; } List parseProcessStd(String stdout) { var pList = []; var returnPattern = RegExp('\n|\r'); var list = stdout.split(returnPattern); for (var item in list) { item = item.replaceAll(returnPattern, ''); if (item.isNotEmpty) { var categories = item.split(','); if (categories.length > 4) { pList.add(ProcessInfo.category(categories)); } } } return pList; } Future findProcessByPid(int pid, [bool runSlient = false]) async { // await removeTempBat(); var c = Completer(); var shell = StringBuffer('tasklist /fi "pid eq ${pid.toString()}" /nh /fo "csv"'); var stdout = runSlient ? await ProcessSilentExecutor.runSlientBat(shell) : await runGetStdout(shell); var pList = parseProcessStd(stdout); if (pList.isNotEmpty) { c.complete(pList.first); } else { c.complete(null); } return c.future; } class ProcessSilentExecutor { static bool _initialized = false; static Future initialize() async { if (!_initialized) { _initialized = true; //remove before files var regBat = RegExp(r'(silent_bat_)([0-9_]+)(.bat)'); var regLog = RegExp(r'(silent_log_)([0-9_]+)(.txt)'); var list = Directory.current.listSync(); for (var item in list) { var matchBat = regBat.stringMatch(item.path); var matchLog = regLog.stringMatch(item.path); if (matchBat != null || matchLog != null) { item.deleteSync(); } } } } static Future runSlientBat(StringBuffer shell) async { initialize(); var c = Completer(); var shellStr = shell.toString(); print('Execute Shell: $shellStr'); var batch = File( '${Directory.systemTemp.path}/silent_bat_${getRnadomPostfix()}.bat'); batch.writeAsStringSync(shellStr); var result = await runSlient(batch.path); await batch.delete(); c.complete(result); return c.future; } static Future runSlient(String batchPath) async { initialize(); var c = Completer(); var current = Directory.current; String result; var log = File('${current.path}/silent_log_${getRnadomPostfix()}.txt'); await Process.run( '${current.path}/assets/bin/silentbatch.exe', [batchPath, log.path]) .then((resultData) { Process.killPid(resultData.pid); // print('"$batchPath" => exitCode: ${resultData.exitCode}'); }); try { result = await log.readAsString(); } on Exception catch (e) { print(e); result = ''; } await log.delete(); c.complete(result); return c.future; } } String getRnadomPostfix() { return '${DateTime.now().microsecondsSinceEpoch}_${Random().nextInt(100)}'; } File getTempBat({String? fixedName}) { fixedName ??= getRnadomPostfix(); File batch = File('${Directory.systemTemp.path}/temp_bat_$fixedName.bat'); return batch; } class ProcessInfoListener { late ProcessInfo? _process; bool isRunning = false; late Timer? _timer; late final int _checkInterval; late Function(ProcessInfoListener, bool)? _connectionListener; set _isRunning(bool value) { if (isRunning != value) { isRunning = value; changedConnection(); } } ProcessInfoListener(this._process, this._checkInterval) { checkLive(); } void checkLive() async { stopChecker(); _isRunning = true; _timer = Timer.periodic(Duration(seconds: _checkInterval), (time) async { var enable = false; if (_process != null) { var proc = await findProcessByPid(_process!.pid); if (proc != null) { print('process live'); enable = true; _isRunning = true; } } if (!enable) { _isRunning = false; stopChecker(); } }); } void stopChecker() { if (_timer != null) { _timer!.cancel(); _timer = null; } } void changedConnection() { if (_connectionListener != null) { _connectionListener!(this, isRunning); } } Function(ProcessInfoListener, bool)? get endListener { return _connectionListener; } void addEndListener(Function(ProcessInfoListener, bool)? listener) { _connectionListener = listener; } void removeConnectionListener() { _connectionListener = null; } void dispose() { stopChecker(); removeConnectionListener(); _isRunning = false; _process = null; } void terminate() { if (isRunning && _process != null) { Process.killPid(_process?.pid as int); } } } class ProcessInfo { late String name; late int pid; late String sessionName; late int session; late String memory; ProcessInfo(); ProcessInfo.category(List categories) { var count = 0; var length = categories.length; for (var i = 0; i < length; ++i) { var category = categories[i].replaceAll('"', ''); if (category.isNotEmpty) { switch (count) { case 0: name = category; break; case 1: var value = int.tryParse(category); value ??= -1; pid = value; break; case 2: sessionName = category; break; case 3: var value = int.tryParse(category); value ??= -1; session = value; break; case 4: memory = category; break; } count++; } } } }