dart-app-core/lib/platform/process.dart
2025-01-05 06:50:21 +09:00

696 lines
19 KiB
Dart

// 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<List<int>, String> _decoder;
// ignore: no_leading_underscores_for_local_identifiers
ProcessData(this._enableStdout, this._enableStderr,
{LogHandler? logHandler, Converter<List<int>, 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<String> get stdoutArr {
return getBufferToArray(stdout);
}
List<String> get stderrArr {
return getBufferToArray(stderr);
}
List<String> getBufferToArray(StringBuffer buffer) {
var list = <String>[];
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 _powershellGranted = false;
static Future getPowershellGrant() async {
if (!_powershellGranted) {
_powershellGranted = true;
var result = await Process.run(
'powershell.exe',
[
'-Command',
'Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned'
],
runInShell: true,
);
if (result.stderr.toString().trim().isNotEmpty) {
print('[Powershell Grant] Error: ${result.stderr}');
}
}
return simpleFuture;
}
static Future<ProcessData> startExe(String exe, List<String> 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 ?? ('Execute Shell: $arg', LogType.verbose);
}
try {
processData.process =
await Process.start(exe, args, workingDirectory: workspace);
} on Exception catch (e) {
logHandler ?? (e, LogType.error);
}
} else {
logHandler ?? ('This platform not support process executor.');
}
return dataFutrue(processData);
}
static Future<ProcessData> start(StringBuffer shell,
{Converter<List<int>, String>? decoder,
String? workspace,
bool? printStdout,
bool? printStderr,
LogHandler? logHandler}) async {
var c = Completer<ProcessData>();
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 ??
(
'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 ?? (e, LogType.error);
}
} else if (Platform.isWindows) {
workspace = workspace ?? Directory.systemTemp.path;
var file = File('$workspace/$tempFileName');
processData.file = file;
if (printStdout_) {
logHandler ??
(
'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 getPowershellGrant();
try {
processData.process = await Process.start(
'powershell.exe', ['-NoProfile', '-File', file.absolute.path]);
} on Exception catch (e) {
logHandler ?? (e, LogType.error);
}
} else {
logHandler ??
('This platform not support process executor.', LogType.error);
}
c.complete(processData);
await processData.waitForExit();
} on Exception catch (e) {
logHandler ??
('General Exception in ProcessExecutor.start: $e', LogType.error);
}
return c.future;
}
static Future<ProcessResult> run(StringBuffer shell,
{String? workspace,
bool printStdout = true,
bool printStderr = true,
Converter<List<int>, String>? decoder,
LogHandler? logHandler}) async {
var c = Completer<ProcessResult>();
workspace = workspace ?? Directory.systemTemp.path;
var file = File('$workspace/$tempFileName');
if (printStdout) {
logHandler ??
(
'---------------------------------------------------',
LogType.verbose
);
logHandler ??
(
'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 ?? (e, LogType.error);
} finally {
file.deleteSync();
}
c.complete(processResult);
} else if (Platform.isWindows) {
file.createSync();
file.writeAsStringSync(shell.toString());
print(file.absolute.path);
await getPowershellGrant();
processResult = await Process.run(
'powershell.exe', // PowerShell 실행
['-NoProfile', '-File', file.absolute.path],
runInShell: true);
file.deleteSync();
c.complete(processResult);
} else {
logHandler ??
('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 ??
(
// 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 ??
(
// 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<String> 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<String> runGetStdout(StringBuffer shell) async {
var c = Completer<String>();
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<NetStatData?> findUsePortPid(int port) async {
await removeTempBat();
var c = Completer<NetStatData?>();
var shell = StringBuffer('netstat -aon | find "$port"');
var stdout = "";
try {
stdout = await runGetStdout(shell);
} on Exception {}
if (stdout == "") {
c.complete(null);
} else {
var netList = <NetStatData?>[];
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<List<ProcessInfo>> findProcess(String searchWord) async {
await removeTempBat();
var c = Completer<List<ProcessInfo>>();
var pList = <ProcessInfo>[];
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<ProcessInfo> parseProcessStd(String stdout) {
var pList = <ProcessInfo>[];
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<ProcessInfo?> findProcessByPid(int pid) async {
// await removeTempBat();
var c = Completer<ProcessInfo?>();
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<String> 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;
}
count++;
}
}
}
}