oto/lib/cli/commands/scheduler/scheduler_manager.dart
toki e865f7dc8b Internalize dart_framework: remove external git dependency
- Copy all used dart_framework files into lib/framework/
- Replace all package:dart_framework/ imports with package:oto_cli/framework/
- Add yaml, archive, ftpconnect as explicit direct dependencies
- Remove dart_framework git dependency from pubspec.yaml

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 11:20:27 +09:00

376 lines
11 KiB
Dart

// ignore_for_file: depend_on_referenced_packages
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'package:oto_cli/framework/platform/isolate_manager.dart';
import 'package:oto_cli/framework/platform/process.dart';
import 'package:oto_cli/framework/utils/os_startup.dart';
import 'package:oto_cli/framework/utils/string_util.dart';
import 'package:oto_cli/framework/utils/system_util.dart';
import 'package:oto_cli/cli/commands/scheduler/data/scheduler_data.dart';
import 'package:oto_cli/cli/commands/scheduler/scheduler_isolate.dart';
import 'package:oto_cli/cli/commands/scheduler/scheduler_linux.dart';
import 'package:oto_cli/cli/commands/scheduler/scheduler_osx.dart';
import 'package:oto_cli/cli/commands/scheduler/scheduler_windows.dart';
import 'package:oto_cli/oto/application.dart';
import 'package:oto_cli/oto/data/command_data.dart';
import 'package:path/path.dart' as path;
class SchedulerManager {
static String get _schedulerPath {
return '$dataPath/scheduler';
}
static String get _logsPath {
return '$_schedulerPath/logs';
}
ProcessInfo? _currentScheduler;
final Map<String, IsolateHandler> _map = {};
final logFile = File(
'$_logsPath/[$pid]_scheduler_${getDate()}_${getTime().replaceAll(':', '')}.txt');
String get exePath {
return throw UnimplementedError('Unimplemented exePath');
}
String get _settingsPath {
return '$_schedulerPath/settings.json';
}
SchedulerListData get _localData {
var file = File(_settingsPath);
if (!file.existsSync()) {
_localData = SchedulerListData();
}
return SchedulerListData.fromJson(jsonDecode(file.readAsStringSync()));
}
set _localData(SchedulerListData data) {
var file = File(_settingsPath);
file.writeAsStringSync(JsonEncoder.withIndent(' ').convert(data.toJson()),
mode: FileMode.write, flush: true);
}
String get _startupID {
return isDebug ? '$appIdendifier.debug' : appIdendifier;
}
HashSet<String> get _schedulers {
HashSet<String> hash = HashSet();
var dir = Directory(_schedulerPath);
if (dir.existsSync()) {
var fileList = dir.listSync();
for (var item in fileList) {
if (item is File) {
if (path.extension(item.path).toLowerCase().contains('.yaml')) {
hash.add(path.basenameWithoutExtension(item.path));
}
}
}
}
return hash;
}
SchedulerManager();
factory SchedulerManager.create() {
if (Platform.isMacOS) {
return SchedulerOsx();
} else if (Platform.isWindows) {
return SchedulerWindows();
} else if (Platform.isLinux) {
return SchedulerLinux();
} else {
throw UnsupportedError('This platform not supported.');
}
}
Future regist(String filePath) async {
var file = File(filePath);
if (!file.existsSync()) {
return throw Exception("The file '$filePath' does not exist.");
}
Map<String, dynamic>? map;
DataBuild? build;
try {
map = Application.getMapFromYamlA(await file.readAsString());
build = DataBuild.fromJson(map!);
} on Exception catch (e, stacktace) {
print(stacktace);
return throw Exception(
"The file '$filePath' is not in yaml format or is an unsupported build format.");
}
if (build.scheduler == null) {
return throw Exception('Scheduler entry does not exist within yaml.');
}
var dirLogs = Directory(_logsPath);
var alias = build.scheduler!.alias;
var hash = _schedulers;
if (hash.contains(alias)) {
return throw Exception(
'A scheduler with the same alias is registered. Please do it with a different alias.');
}
await _checkStartup();
await _checkProcess();
await _startProcess();
if (!dirLogs.existsSync()) {
dirLogs.createSync(recursive: true);
}
var newPath = '$_schedulerPath/$alias.yaml';
await file.copy(newPath);
print('');
print(':::: Scheduler registered.');
print('');
print('[alias]'.padRight(8) + alias);
print('[path]'.padRight(8) + newPath);
print('');
//update settings
var local = _localData;
var schedulerData = SchedulerData();
schedulerData.alias = alias;
local.map[alias] = schedulerData;
_localData = local;
return simpleFuture;
}
Future unregist(String alias) async {
if (_schedulers.contains(alias)) {
var file = File('$_schedulerPath/$alias.yaml');
file.deleteSync();
} else {
return throw Exception("There is no scheduler called '$alias'.");
}
var local = _localData;
if (local.map.containsKey(alias)) {
local.map.remove(alias);
_localData = local;
}
await _checkTerminate();
print('');
print(":::: Scheduler '$alias's registration has been removed.");
}
Future showList() async {
print('');
var scheduleList = _schedulers;
if (scheduleList.isEmpty) {
print(':::: There are no registered schedulers.');
print('');
} else {
print(':::: List of currently registered schedulers.');
print('');
print('${' [Enable]'.padRight(20)}${'[Alias]'.padRight(20)}[Path]');
var local = _localData;
for (var item in _schedulers) {
var enable = true;
if (local.map.containsKey(item)) {
enable = local.map[item]!.enable;
}
print(
'${' - $enable'.padRight(20)}${item.padRight(20)}$_schedulerPath/$item.yaml');
}
print('');
}
await _checkProcess();
return simpleFuture;
}
Future enableScheduler(String alias, bool enable) async {
var local = _localData;
if (_schedulers.contains(alias)) {
var data = SchedulerData();
data.alias = alias;
data.enable = enable;
local.map[alias] = data;
_localData = local;
var activate = enable ? 'activated' : 'deactivate';
print('');
print("Scheduler '$alias' has been $activate.");
print('');
await _checkStartup();
await _checkProcess();
await _startProcess();
exit(0);
} else {
return throw Exception("There is no scheduler called '$alias'.");
}
}
Future terminate() async {
await _checkProcess();
if (_currentScheduler != null) {
Process.killPid(_currentScheduler!.pid);
print('Scheduler operation stopped.');
}
return simpleFuture;
}
Future _checkTerminate() async {
if (_schedulers.isEmpty) {
await _unregistStartup();
await terminate();
}
return simpleFuture;
}
Future _startProcess() async {
// Start directly if Launchd did not auto-launch the process
if (_currentScheduler == null) {
if (Platform.isWindows) {
final program = Platform.resolvedExecutable;
var arguments = <String>[];
if (isDebug) arguments.add(scriptPath);
arguments.addAll(['scheduler', '-s']);
await Process.start(program, arguments,
mode: ProcessStartMode.detached);
} else {
var script = isDebug ? ' $scriptPath' : '';
var shell = StringBuffer();
shell.writeln('nohup $executablePath$script scheduler -s &');
// shell.writeln('disown');
print(shell.toString());
ProcessExecutor.run(shell,
workspace: Directory.systemTemp.path, printStdout: false);
await _checkProcess();
}
}
return simpleFuture;
}
Future _checkProcess() async {
var oto = Platform.isWindows ? '$appName.exe' : appName;
//process check
var processes = await findProcess(Platform.isWindows
? (isDebug ? 'dart.exe' : '$appName.exe')
: 'scheduler -s');
for (var process in processes) {
var processName = process.name;
if (isDebug) {
if (processName.contains('main.dart scheduler -s')) {
_currentScheduler = process;
}
} else {
if (processName.contains('$oto scheduler -s')) {
_currentScheduler = process;
}
}
if (_currentScheduler != null) {
print('Scheduler is running on pid [${_currentScheduler!.pid}].');
break;
}
}
return simpleFuture;
}
Future _checkStartup() async {
if (!await OSStartup.isRegistedStartup(_startupID)) {
List<String> param = [];
var exe = Platform.resolvedExecutable;
if (isDebug) {
param.add(scriptPath);
} else {
exe = exePath;
}
param.add('scheduler');
param.add('-s');
await OSStartup.registStartup(exe, _startupID,
arguments: param, logErrorFilePath: '$_schedulerPath/launch_err.log');
}
return simpleFuture;
}
Future _unregistStartup() async {
if (await OSStartup.isRegistedStartup(_startupID)) {
await OSStartup.unregistStartup(_startupID);
}
return simpleFuture;
}
//======================== other process ========================//
Future start() async {
_log('Scheduler is running on pid [$pid].');
while (true) {
// Check every second for registered scheduler files
await Future.delayed(Duration(seconds: 1));
var settings = _localData.map;
for (var alias in _schedulers) {
var enable =
settings.containsKey(alias) ? settings[alias]!.enable : true;
if (!_map.containsKey(alias) && enable) {
_log('Schduler found: $alias');
var handler = IsolateManager.create(IsolateScheduler(
{'alias': alias, 'root': _schedulerPath, 'logs': _logsPath}));
handler.addListener('terminated', _onTerminated);
handler.addListener('log', _onLog);
_map[alias] = handler;
}
if (_map.containsKey(alias) && !enable) {
_removeHandler(alias);
}
}
}
}
void _removeHandler(String alias) {
var handler = _map.remove(alias);
handler!.exit();
}
void _onTerminated(Object? data) async {
var map = data as Map;
var alias = map['alias'] as String;
var type = map['type'] as String;
var message = map['message'] as String;
_removeHandler(alias);
switch (type) {
case 'error':
_log('[Error] $message');
break;
}
var local = _localData;
var list = _schedulers;
if (list.isEmpty) {
_log('Scheduler operation stopped.');
if (local.map.containsKey(alias)) {
local.map.remove(alias);
}
} else {
list.contains(alias);
SchedulerData data = SchedulerData();
if (local.map.containsKey(alias)) {
data = local.map[alias]!;
}
data.alias = alias;
data.enable = false;
local.map[alias] = data;
}
_localData = local;
await _checkTerminate();
}
void _onLog(Object? data) {
_log(data as String);
}
void _log(String message) {
logFile.writeAsStringSync('[${getDates()}] $message\n',
mode: FileMode.append, flush: true);
}
}