독립 control plane 구성을 위해 기존 Dart CLI/runtime을 runner 앱 경계로 옮기고, 후속 client/core/root 작업을 같은 마일스톤 task group에 연결한다.
159 lines
4.7 KiB
Dart
159 lines
4.7 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:cron/cron.dart';
|
|
import 'package:dart_framework/data/isolate_data.dart';
|
|
import 'package:dart_framework/platform/isolate_manager.dart';
|
|
import 'package:dart_framework/utils/string_util.dart';
|
|
import 'package:dart_framework/utils/system_util.dart';
|
|
import 'package:file_hasher/file_hasher.dart';
|
|
import 'package:oto/cli/commands/command_exe.dart';
|
|
import 'package:oto/cli/commands/scheduler/scheduler_interval.dart';
|
|
import 'package:oto/oto/application.dart';
|
|
import 'package:oto/oto/data/command_data.dart';
|
|
|
|
class IsolateScheduler extends IsolateBase {
|
|
late String schedulerPath;
|
|
late String logsPath;
|
|
late String alias;
|
|
late File file;
|
|
late File logFile;
|
|
int fileHash = 0;
|
|
|
|
bool logEnable = false;
|
|
bool notProgress = true;
|
|
bool cronClosed = true;
|
|
int? interval;
|
|
String? cronExpress;
|
|
late DataBuild build;
|
|
SchedulerInterval? schedulerInterval;
|
|
CommandExe commandExe = CommandExe();
|
|
|
|
Cron? cron;
|
|
int nextStartTime = 0;
|
|
|
|
IsolateScheduler(Map data) {
|
|
alias = data['alias'];
|
|
schedulerPath = data['root'];
|
|
logsPath = data['logs'];
|
|
file = File('$schedulerPath/$alias.yaml');
|
|
}
|
|
|
|
@override
|
|
void onReady(Object? initData) async {
|
|
while (true) {
|
|
await Future.delayed(Duration(seconds: 1));
|
|
|
|
if (file.existsSync()) {
|
|
var updated = false;
|
|
var hash = await FileHasher.hash(file);
|
|
if (hash != fileHash) {
|
|
updated = true;
|
|
fileHash = hash;
|
|
// Reload file contents on every change
|
|
var map = Application.getMapFromYamlA(await file.readAsString());
|
|
|
|
//============ Validate yaml format ============//
|
|
try {
|
|
build = DataBuild.fromJson(map!);
|
|
} on Exception {
|
|
onError('Failed to parse yaml file.');
|
|
return;
|
|
}
|
|
if (build.scheduler == null) {
|
|
onError('Scheduler entry does not exist within yaml.');
|
|
return;
|
|
}
|
|
|
|
var scheduler = build.scheduler!;
|
|
if (scheduler.cron == null && scheduler.interval == null) {
|
|
onError(
|
|
'A scheduler script must have either a cron entry or an interval entry.');
|
|
return;
|
|
}
|
|
|
|
if (scheduler.cron != null) {
|
|
try {
|
|
Schedule.parse(scheduler.cron!);
|
|
cronExpress = scheduler.cron!;
|
|
} on Error {
|
|
onError(
|
|
"Cron express '${build.scheduler!.cron}' is not a suitable format.");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (scheduler.interval != null) {
|
|
interval = scheduler.interval!;
|
|
}
|
|
|
|
logEnable =
|
|
scheduler.enableLog == null ? false : scheduler.enableLog!;
|
|
|
|
schedulerInterval?.activate = false;
|
|
}
|
|
|
|
//============ execute cron/interval ============//
|
|
if (cronExpress != null) {
|
|
//Use cron
|
|
if (cronClosed) {
|
|
cronClosed = false;
|
|
cron = Cron();
|
|
cron!.schedule(Schedule.parse(cronExpress!), () async {
|
|
if (notProgress) {
|
|
await execute(build);
|
|
cronClosed = true;
|
|
await cron!.close();
|
|
}
|
|
});
|
|
}
|
|
} else {
|
|
//Use interval
|
|
if (updated) {
|
|
schedulerInterval = SchedulerInterval(interval!, build, execute);
|
|
}
|
|
}
|
|
} else {
|
|
// Exit if the file no longer exists
|
|
onError("The file '${file.path}' does not exist.");
|
|
if (cron != null) await cron?.close();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
Future execute(DataBuild data) async {
|
|
send(IsoMessege('log', data: "Scheduler '$alias' has started."));
|
|
notProgress = false;
|
|
logFile = File(
|
|
'$logsPath/[$pid]_${alias}_${getDate()}_${getTime().replaceAll(':', '')}.txt');
|
|
if (data.scheduler!.enableLog ?? false) {
|
|
log('Start process on pid [$pid]');
|
|
}
|
|
await commandExe.executeScheduler(data, logEnable, logFile.path);
|
|
notProgress = true;
|
|
send(IsoMessege('log', data: "Scheduler '$alias' is done."));
|
|
return simpleFuture;
|
|
}
|
|
|
|
void log(String message) {
|
|
logFile.writeAsStringSync('[${getDates()}] $message\n',
|
|
mode: FileMode.append, flush: true);
|
|
}
|
|
|
|
void logError(Exception e, StackTrace stacktace) {
|
|
log('[${e.toString()}]\n${stacktace.toString()}');
|
|
}
|
|
|
|
void onErrorStacktrace(Exception e, StackTrace stacktrace) {
|
|
onError('$e\n$stacktrace');
|
|
}
|
|
|
|
void onError(String message) {
|
|
sendTerminated('error', message);
|
|
}
|
|
|
|
void sendTerminated(String type, String message) {
|
|
send(IsoMessege('terminated',
|
|
data: {'type': type, 'message': message, 'alias': alias}));
|
|
}
|
|
}
|