dart-app-core/lib/utils/os_startup.dart

307 lines
8.9 KiB
Dart

import 'dart:io';
import 'dart:async';
import 'package:path/path.dart' as path;
import 'package:dart_framework/core/process.dart';
class OSStartup {
static _Startup? _instance;
static _Startup? get _current {
if (_instance == null) {
if (Platform.isMacOS) {
_instance = _StartupMac();
} else if (Platform.isLinux) {
_instance = _StartupLinux();
} else {
_instance = _StartupWindows();
}
}
return _instance;
}
static Future<bool> isRegistedStartup(String label) async {
return _current!.isRegistedStartup(label);
}
//shell file must have '#!/bin/sh'
static Future<bool> registStartup(String executableFilePath, String label,
{String? logStandardFilePath, String? logErrorFilePath}) async {
return _current!.registStartup(executableFilePath, label,
logStandardFilePath: logStandardFilePath,
logErrorFilePath: logErrorFilePath);
}
static Future<bool> unregistStartup(String label) async {
return _current!.unregistStartup(label);
}
}
class _Startup {
Future<bool> isRegistedStartup(String label) async {
var c = Completer<bool>();
return c.future;
}
//shell file must have '#!/bin/sh'
Future<bool> registStartup(String executableFilePath, String label,
{String? logStandardFilePath, String? logErrorFilePath}) async {
var c = Completer<bool>();
return c.future;
}
Future<bool> unregistStartup(String label) async {
var c = Completer<bool>();
return c.future;
}
}
class _StartupMac extends _Startup {
final plistTemplate = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>KeepAlive</key>
<false/>
<key>Label</key>
<string>{LABEL}</string>
<key>Program</key>
<string>{FILE_PATH}</string>
<key>RunAtLoad</key>
<true/>{LOG_STANDARD}{LOG_ERROR}
</dict>
</plist>""";
final logStandardTemplate = """
<key>StandardOutPath</key>
<string>{LOG_STANDARD}</string>""";
final logErrorTemplate = """
<key>StandardErrorPath</key>
<string>{LOG_ERROR}</string>""";
@override
Future<bool> isRegistedStartup(String label) async {
var c = Completer<bool>();
var plistName = '$label.plist';
var userPath = Platform.environment['HOME'] as String;
c.complete(
await File('$userPath/Library/LaunchAgents/$plistName').exists());
return c.future;
}
//shell file must have '#!/bin/sh'
@override
Future<bool> registStartup(String executableFilePath, String label,
{String? logStandardFilePath, String? logErrorFilePath}) async {
var c = Completer<bool>();
final tagLabel = '{LABEL}';
final tagPath = '{FILE_PATH}';
var success = false;
ProcessResult? result;
var enable = true;
if (await isRegistedStartup(label)) {
enable = await unregistStartup(label);
}
if (enable) {
//create plist in LaunchAgents
final plistName = '$label.plist';
final userPath = Platform.environment['HOME'];
final tagLogStandard = '{LOG_STANDARD}';
final tagLogError = '{LOG_ERROR}';
var plistContent = plistTemplate
.replaceAll(tagLabel, label)
.replaceAll(tagPath, executableFilePath);
if (logStandardFilePath == null) {
plistContent = plistContent.replaceAll(tagLogStandard, '');
} else {
plistContent = plistContent.replaceAll(
tagLogStandard,
logStandardTemplate.replaceAll(
tagLogStandard, logStandardFilePath));
}
if (logErrorFilePath == null) {
plistContent = plistContent.replaceAll(tagLogError, '');
} else {
plistContent = plistContent.replaceAll(tagLogError,
logErrorTemplate.replaceAll(tagLogError, logErrorFilePath));
}
var plistFile = File('$userPath/Library/LaunchAgents/$plistName');
await plistFile.create();
await plistFile.writeAsString(plistContent);
var result = await ProcessExecutor.run(
StringBuffer('chmod +x "$executableFilePath"'));
success = result.exitCode == 0;
}
c.complete(success);
return c.future;
}
@override
Future<bool> unregistStartup(String label) async {
var c = Completer<bool>();
var success = false;
var plistName = '$label.plist';
var userPath = Platform.environment['HOME'];
var file = File('$userPath/Library/LaunchAgents/$plistName');
if (file.existsSync()) {
await file.delete();
success = true;
}
c.complete(success);
return c.future;
}
}
class _StartupLinux extends _Startup {
final serviceTemplate = """[Unit]
Description={LABEL}
#Documentation=http://toki-labs.com
[Service]
Type=simple
#User=deck
#Group=deck
#TimeoutStartSec=0
#Restart=on-failure
#RestartSec=30s
#ExecStartPre=
ExecStart={FILE_PATH}
#SyslogIdentifier=TestService
#ExecStop=
[Install]
WantedBy=default.target""";
@override
Future<bool> isRegistedStartup(String label) async {
var c = Completer<bool>();
var exist = false;
var userPath = Platform.environment['HOME'] as String;
exist = await File('$userPath/.config/systemd/user/$label').exists();
c.complete(exist);
return c.future;
}
//shell file must have '#!/bin/sh'
@override
Future<bool> registStartup(String executableFilePath, String label,
{String? logStandardFilePath, String? logErrorFilePath}) async {
var c = Completer<bool>();
final tagLabel = '{LABEL}';
final tagPath = '{FILE_PATH}';
ProcessResult? result;
var userPath = Platform.environment['HOME'] as String;
var systemPath = '$userPath/.config/systemd/user';
var serviceContent = serviceTemplate
.replaceAll(tagLabel, label)
.replaceAll(tagPath, executableFilePath);
var serviceFile = File('$systemPath/$label');
await serviceFile.create();
await serviceFile.writeAsString(serviceContent);
var shell = StringBuffer();
shell.write('cd $systemPath && ');
shell.write('systemctl --user enable $label && ');
shell.write('systemctl --user daemon-reload');
result = await ProcessExecutor.run(shell);
print('exitCode: ${result.exitCode}');
c.complete(result.exitCode == 0);
return c.future;
}
@override
Future<bool> unregistStartup(String label) async {
var c = Completer<bool>();
var success = false;
var userPath = Platform.environment['HOME'] as String;
var shell = StringBuffer();
var systemPath = '$userPath/.config/systemd/user';
shell.write('cd $systemPath && ');
shell.write('systemctl --user stop $label && ');
shell.write('systemctl --user disable $label && ');
shell.write('systemctl --user daemon-reload');
var result = await ProcessExecutor.run(shell);
success = result.exitCode == 0;
if (success) {
success = false;
var file = File('$systemPath/$label');
if (await file.exists()) {
await file.delete();
success = true;
}
}
c.complete(success);
return c.future;
}
}
class _StartupWindows extends _Startup {
final windowsStartupPath =
'\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\';
@override
Future<bool> isRegistedStartup(String label) async {
var c = Completer<bool>();
//For Windows 8, 10
var exist = false;
var userPath = Platform.environment['UserProfile'];
var startupDir = Directory('$userPath$windowsStartupPath');
var files = await startupDir.listSync();
for (var fileEntry in files) {
var fileName = path.basename(fileEntry.path);
if (fileName == label) {
exist = true;
break;
}
}
c.complete(exist);
return c.future;
}
//shell file must have '#!/bin/sh'
@override
Future<bool> registStartup(String executableFilePath, String label,
{bool? overwrite = true,
String? logStandardFilePath,
String? logErrorFilePath}) async {
var c = Completer<bool>();
var file = File(executableFilePath);
ProcessResult? result;
//For Windows 8, 10
var fileName = path.basename(file.path);
var extension = path.extension(fileName);
var symbolicPath = '%userprofile%$windowsStartupPath$label$extension';
result = await ProcessExecutor.run(
StringBuffer('mklink "$symbolicPath" "$executableFilePath"'));
c.complete(result.exitCode == 0);
return c.future;
}
@override
Future<bool> unregistStartup(String label) async {
var c = Completer<bool>();
var success = false;
//For Windows 8, 10
var userPath = Platform.environment['UserProfile'];
var startupDir = Directory('$userPath$windowsStartupPath');
var files = await startupDir.listSync();
for (var fileEntry in files) {
var fileName = path.basename(fileEntry.path);
if (fileName == label) {
await File(fileEntry.path).delete();
success = true;
break;
}
}
c.complete(success);
return c.future;
}
}