// ignore_for_file: avoid_init_to_null import 'dart:async'; import 'dart:io'; import 'dart:convert'; import 'dart:math'; import 'package:dart_framework/log/log.dart'; import 'package:dart_framework/utils/system_util.dart'; typedef LogHandler = Function(String, LogType); class ProcessData { int? exitCode; late Process _process; late File file; late StringBuffer stdout; late StringBuffer stderr; final bool _enableStdout; final bool _enableStderr; final Completer _completer = Completer(); LogHandler? _logHandler; bool _completeStdout = false; bool _completeStderr = false; bool _completeExitCode = false; late Converter, String> _decoder; // ignore: no_leading_underscores_for_local_identifiers ProcessData(this._enableStdout, this._enableStderr, {LogHandler? logHandler, Converter, String>? decoder}) { _logHandler = logHandler; _decoder = Platform.isWindows ? SystemEncoding().decoder : decoder ?? utf8.decoder; } bool get isSuccess { if (Platform.isWindows) return exitCode == 1; return exitCode == 0; } 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; } Process get process { return _process; } set process(Process value) { _process = value; stdout = StringBuffer(); stderr = StringBuffer(); _process.stdout.transform(_decoder).transform(LineSplitter()).listen( (event) { stdout.writeln(event); if (_enableStdout) { log(event, LogType.verbose); } }, onDone: () { _completeStdout = true; _checkComplete(); }); _process.stderr.transform(_decoder).transform(LineSplitter()).listen( (event) { stderr.writeln(event); if (_enableStderr) { log('[Error] $event', LogType.error); } }, onDone: () { _completeStderr = true; _checkComplete(); }); _checkExitCode(); } void log(String message, LogType logType) { if (_logHandler == null) { print(message); } else { _logHandler!(message, logType); } } void terminate() { _process.kill(); _deleteFile(); } void _deleteFile() async { if (file.existsSync()) { file.deleteSync(); } } void _checkExitCode() async { exitCode = await _process.exitCode; _completeExitCode = true; _checkComplete(); } void _checkComplete() { if (_completeExitCode && _completeStdout && _completeStderr) { _deleteFile(); _completer.complete(); } } Future waitForExit() async { return _completer.future; } } class ProcessExecutor { static bool _initedPowershell = false; static Future initPowershell() async { if (!_initedPowershell) { var result = await Process.run( 'powershell', [ '-Command', 'Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned' ], runInShell: true, ); _initedPowershell = true; if (result.stderr.toString().trim().isNotEmpty) { print('[Powershell Grant] Error: ${result.stderr}'); } } return simpleFuture; } static Future startExe(String exe, List args, {String? workspace, bool? printStdout, bool? printStderr, LogHandler? logHandler}) async { var arg = '$exe ${args.join(' ')}'; var printStdout_ = printStdout ?? true; var printStderr_ = printStderr ?? true; var processData = ProcessData(printStdout_, printStderr_, logHandler: logHandler); if (Platform.isMacOS || Platform.isWindows) { workspace = workspace ?? Directory.systemTemp.path; if (printStdout_) { logHandler?.call('Execute Shell: $arg', LogType.verbose); } try { processData.process = await Process.start(exe, args, workingDirectory: workspace); } on Exception catch (e) { logHandler?.call(e.toString(), LogType.error); } } else { logHandler ?? ('This platform not support process executor.'); } return dataFutrue(processData); } static Future start(StringBuffer shell, {Converter, String>? decoder, String? workspace, bool? printStdout, bool? printStderr, LogHandler? logHandler}) async { var c = Completer(); try { var printStdout_ = printStdout ?? true; var printStderr_ = printStderr ?? true; var processData = ProcessData(printStdout_, printStderr_, logHandler: logHandler, decoder: decoder); if (Platform.isMacOS || Platform.isLinux) { workspace = workspace ?? Directory.systemTemp.path; var file = File('$workspace/$tempFileName'); processData.file = file; if (printStdout_) { logHandler?.call ( 'Execute Shell: ${file.path} \n ${shell.toString()}', LogType.verbose ); } if (file.existsSync()) { file.deleteSync(); } file.createSync(); file.writeAsStringSync(shell.toString(), flush: true); try { var shellExe = Platform.isMacOS ? 'zsh' : 'bash'; processData.process = await Process.start(shellExe, [file.path], workingDirectory: workspace); } on Exception catch (e) { logHandler?.call(e.toString(), LogType.error); } } else if (Platform.isWindows) { workspace = workspace ?? Directory.systemTemp.path; var file = File('$workspace/$tempFileName'); processData.file = file; if (printStdout_) { logHandler?.call ( 'Execute Shell: ${file.path} \n ${shell.toString()}', LogType.verbose ); } if (await file.exists()) { await file.delete(); } file.createSync(); file.writeAsStringSync(shell.toString(), flush: true); await initPowershell(); try { processData.process = await Process.start( 'powershell', ['-NoProfile', '-File', file.absolute.path]); } on Exception catch (e) { logHandler?.call(e.toString(), LogType.error); } } else { logHandler?.call ('This platform not support process executor.', LogType.error); } c.complete(processData); await processData.waitForExit(); } on Exception catch (e) { logHandler?.call ('General Exception in ProcessExecutor.start: $e', LogType.error); } return c.future; } static Future run(StringBuffer shell, {String? workspace, bool printStdout = true, bool printStderr = true, Converter, String>? decoder, LogHandler? logHandler}) async { var c = Completer(); workspace = workspace ?? Directory.systemTemp.path; var file = File('$workspace/$tempFileName'); if (printStdout) { logHandler?.call ( '---------------------------------------------------', LogType.verbose ); logHandler?.call ( 'Shell Path: ${file.path} \nExecute Shell: ${shell.toString()}', LogType.verbose ); } ProcessResult? processResult; if (Platform.isMacOS || Platform.isLinux) { file.createSync(); file.writeAsStringSync(shell.toString()); try { var shellExe = Platform.isMacOS ? 'zsh' : 'bash'; processResult = await Process.run(shellExe, [file.path], workingDirectory: workspace); } on Exception catch (e) { logHandler?.call(e.toString(), LogType.error); } finally { file.deleteSync(); } c.complete(processResult); } else if (Platform.isWindows) { file.createSync(); file.writeAsStringSync(shell.toString()); print(file.absolute.path); await initPowershell(); processResult = await Process.run( 'powershell', // PowerShell 실행 ['-NoProfile', '-File', file.absolute.path], runInShell: true); file.deleteSync(); c.complete(processResult); } else { logHandler?.call ('This platform not support process executor.', LogType.error); c.complete(null); } if (processResult != null) { if (printStdout) { var stdout = processResult.stdout.toString(); if (decoder != null) { stdout = decoder.convert(stdout.codeUnits); } logHandler?.call ( // ignore: prefer_interpolation_to_compose_strings '[ProcessExecutor] Result: $stdout', LogType.verbose ); } if (printStderr) { var stderr = processResult.stderr.toString(); if (decoder != null) { stderr = decoder.convert(stderr.codeUnits); } logHandler?.call ( // ignore: prefer_interpolation_to_compose_strings '[ProcessExecutor] Exit Code: ${processResult.exitCode}\nError: $stderr', LogType.verbose ); } } return c.future; } static String get tempFileName { if (Platform.isMacOS || Platform.isLinux) { return 'temp_${DateTime.now().microsecondsSinceEpoch}.sh'; } else { return 'temp_${DateTime.now().microsecondsSinceEpoch}.ps1'; } } } 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) async { await removeTempBat(); var c = Completer(); var shell = StringBuffer('netstat -aon | find "$port"'); var stdout = ""; try { stdout = await runGetStdout(shell); } on Exception {} 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) async { await removeTempBat(); var c = Completer>(); var pList = []; if (Platform.isWindows) { var csvPath = File( '${Directory.systemTemp.absolute.path}\\temp_${Random().nextInt(100000)}.csv'); var shell = StringBuffer( 'Get-CimInstance -ClassName Win32_Process | Where-Object { \$_.Name.Contains("$searchWord") } | Select-Object CommandLine, ProcessId | Export-Csv -Path ${csvPath.absolute.path} -NoTypeInformation'); var result = await ProcessExecutor.run(shell); if (result.exitCode == 0 && csvPath.existsSync()) { pList = parseProcessStd(csvPath.readAsStringSync()); csvPath.deleteSync(); } } else if (Platform.isLinux) { var shell = StringBuffer('ps -ef | grep \'$searchWord\' | grep -v grep'); 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(' '); //deck 61218 920 0 08:24 ? 00:00:11 /home/deck/SDK/flutter/bin/cache/dart-sdk/bin/dart /home/deck/work/oto_cli/bin/main.dart scheduler -s proc = proc.substring(index, proc.length).trimLeft(); index = proc.indexOf(' '); info.pid = int.parse(proc.substring(0, index)); proc = proc.substring(index).trimLeft(); index = proc.indexOf(' '); info.ppid = int.parse(proc.substring(0, index)); proc = proc.substring(index).trimLeft(); index = proc.indexOf(' '); info.cpuUsage = int.parse(proc.substring(0, index)); proc = proc.substring(index).trimLeft(); index = proc.indexOf(' '); info.startTime = proc.substring(0, index); proc = proc.substring(index).trimLeft(); index = proc.indexOf(' '); info.sessionName = proc.substring(0, index); proc = proc.substring(index).trimLeft(); index = proc.indexOf(' '); info.duration = proc.substring(0, index); info.name = proc.substring(index).trimLeft(); pList.add(info); } } } } 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 && !item.contains('CommandLine')) { var categories = item.split(','); if (categories.length > 1) { pList.add(ProcessInfo.category(categories)); } } } return pList; } Future findProcessByPid(int pid) async { // await removeTempBat(); var c = Completer(); var shell = StringBuffer('tasklist /fi "pid eq ${pid.toString()}" /nh /fo "csv"'); var stdout = 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(); } } } } } 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 { String? startTime; String? duration; int? ppid; int? cpuUsage; 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('"', ''); switch (count) { case 0: name = category; break; case 1: var value = int.tryParse(category); value ??= -1; pid = value; break; } count++; } } }