690 lines
19 KiB
Dart
690 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:args/args.dart';
|
|
import 'package:dart_framework/cp949.dart';
|
|
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 = 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 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 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,
|
|
);
|
|
print('[Powershell Grant] ${result.stdout}');
|
|
print('[Powershell Grant] Error: ${result.stderr}');
|
|
}
|
|
return simpleFuture;
|
|
}
|
|
|
|
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());
|
|
try {
|
|
processData.process = await Process.start('cmd', ['/C', file.path],
|
|
workingDirectory: workspace, runInShell: false);
|
|
} 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());
|
|
|
|
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 (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++;
|
|
}
|
|
}
|
|
}
|
|
}
|