dart-app-core/lib/platform/process.dart
leedongmyung[desktop] c1fcb69ea4 test
2023-11-25 12:48:14 +09:00

537 lines
14 KiB
Dart

import 'dart:async';
import 'dart:io';
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'package:dart_framework/charset/euc_kr.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;
// 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<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;
}
set processValue(Process value) {
process = value;
stdout = StringBuffer();
stderr = StringBuffer();
process.stdout.transform(utf8.decoder).listen((event) {
stdout.writeln(event);
if (_enableStdout) {
print(event);
}
});
process.stderr.transform(utf8.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();
}
}
class ProcessExecutor {
static Future<ProcessData> startExe(String exe, List<String> args,
{String? workspace, bool? printStdout, bool? printStderr}) async {
var c = Completer<ProcessData>();
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<ProcessData> start(StringBuffer shell,
{String? workspace, bool? printStdout, bool? printStderr}) async {
var c = Completer<ProcessData>();
try {
var processData = ProcessData(printStdout ?? true, printStderr ?? true);
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 {
processData.processValue = await Process.start('zsh', [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);
} on Exception catch (e) {
print('General Exception in ProcessExecutor.start: $e');
}
return c.future;
}
static Future<ProcessResult> run(StringBuffer shell,
{String? workspace, bool printStdout = true}) async {
var c = Completer<ProcessResult>();
ProcessResult? processResult;
if (Platform.isMacOS || Platform.isLinux) {
workspace = workspace ?? Directory.systemTemp.path;
var file = File('$workspace/$tempFileName');
print('Execute Shell: ${file.path} \n ${shell.toString()}');
file.createSync();
file.writeAsStringSync(shell.toString());
try {
await Process.run('zsh', [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) {
workspace = workspace ?? Directory.systemTemp.path;
var file = File('$workspace/$tempFileName');
print('Execute Bat: ${file.path} \n ${shell.toString()}');
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(processResult!.stdout)}\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<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<List<NetStatData>> findNetStat(String searchWord) async {
await removeTempBat();
var c = Completer<List<NetStatData>>();
var shell = StringBuffer('netstat -ano | findstr $searchWord');
var stdout = await ProcessSilentExecutor.run(shell);
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));
}
}
}
c.complete(netList);
return c.future;
}
Future<List<ProcessInfo>> findProcess(String searchWord) async {
await removeTempBat();
var c = Completer<List<ProcessInfo>>();
var shell = StringBuffer('tasklist /fo "csv" | findstr $searchWord');
var stdout = await ProcessSilentExecutor.run(shell);
var pList = parseProcessStd(stdout);
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) {
var categories = item.split(',');
if (categories.length > 4) {
pList.add(ProcessInfo(categories));
}
}
}
return pList;
}
Future<List<ProcessInfo>> findProcessByPid(int pid) async {
// await removeTempBat();
var c = Completer<List<ProcessInfo>>();
var batch = getTempBat(fixedName: pid.toString());
if (!batch.existsSync()) {
batch.writeAsStringSync(
'tasklist /fi "pid eq ${pid.toString()}" /nh /fo "csv"');
}
String stdout = await ProcessSilentExecutor.runBat(batch.path);
var pList = parseProcessStd(stdout);
c.complete(pList);
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<String> run(StringBuffer shell) async {
initialize();
var c = Completer<String>();
var batch =
File('${Directory.current.path}/silent_bat_${getRnadomPostfix()}.bat');
batch.writeAsStringSync(shell.toString());
var result = await runBat(batch.path);
await batch.delete();
c.complete(result);
return c.future;
}
static Future<String> runBat(String batchPath) async {
initialize();
var c = Completer<String>();
var current = Directory.current;
String result;
var log = File('${current.path}/silent_log_${getRnadomPostfix()}.txt');
await Process.run('${current.path}/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.current.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 list = await findProcessByPid(_process!.pid);
if (list.isNotEmpty) {
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(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;
case 2:
sessionName = category;
break;
case 3:
var value = int.tryParse(category);
value ??= -1;
session = value;
break;
case 4:
memory = category;
break;
}
count++;
}
}
}
}