247 lines
10 KiB
Java
247 lines
10 KiB
Java
package name.nathanmcrae.numbersstation;
|
||
|
||
import com.leakyabstractions.result.api.Result;
|
||
import com.leakyabstractions.result.core.Results;
|
||
import java.io.IOException;
|
||
import java.io.OutputStreamWriter;
|
||
import java.io.Writer;
|
||
import java.lang.management.ManagementFactory;
|
||
import java.lang.ProcessHandle;
|
||
import java.nio.charset.StandardCharsets;
|
||
import java.nio.file.Files;
|
||
import java.nio.file.Path;
|
||
import java.nio.file.Paths;
|
||
import java.time.LocalDateTime;
|
||
import java.time.ZoneId;
|
||
import java.util.concurrent.TimeUnit;
|
||
import java.util.logging.Level;
|
||
import java.util.logging.Logger;
|
||
import java.util.regex.Matcher;
|
||
import java.util.regex.Pattern;
|
||
import javafx.util.Pair;
|
||
|
||
public class LinuxScheduler {
|
||
private static final Logger logger = Logger.getLogger(Main.class.getName());
|
||
|
||
public static Result<Boolean, String> registerSchedule(StationSettings settings) {
|
||
try {
|
||
String taskName = "numbers-station-main_" + settings.getName();
|
||
|
||
Process listProcess = new ProcessBuilder("crontab", "-l").start();
|
||
if (!listProcess.waitFor(5, TimeUnit.SECONDS)) {
|
||
String message = "Failed to query " + taskName + " task: process timed out";
|
||
logger.log(Level.SEVERE, message);
|
||
return Results.failure(message);
|
||
}
|
||
|
||
Pair<String, String> output = WindowsScheduler.captureProcessOutput(listProcess);
|
||
if(listProcess.exitValue() != 0) {
|
||
String message = "Failed to get user id. Exit code: " + listProcess.exitValue() + ". stdout: " + output.getKey() + "\n\tstderr: " + output.getValue();
|
||
logger.log(Level.SEVERE, message);
|
||
return Results.failure(message);
|
||
}
|
||
|
||
String currentCrontab = output.getKey();
|
||
|
||
String cronEntry = "# " + taskName + "\n" + cronExpression(settings) + "\n\n";
|
||
|
||
Pattern pattern = Pattern.compile("# " + taskName + "\\n[^\\n]+\\n\\n");
|
||
|
||
Matcher matcher = pattern.matcher(currentCrontab);
|
||
|
||
StringBuilder sb = new StringBuilder();
|
||
|
||
if (matcher.find()) {
|
||
matcher.appendReplacement(sb, cronEntry);
|
||
|
||
boolean foundMultiple = false;
|
||
while (matcher.find()) {
|
||
foundMultiple = true;
|
||
matcher.appendReplacement(sb, "");
|
||
}
|
||
|
||
if (foundMultiple) {
|
||
logger.log(Level.WARNING, "Found multiple instances of '" + taskName + "' prior to replacement");
|
||
}
|
||
|
||
matcher.appendTail(sb);
|
||
} else {
|
||
sb.append(currentCrontab);
|
||
sb.append(cronEntry);
|
||
}
|
||
|
||
String newCrontab = sb.toString();
|
||
|
||
Process addProcess = new ProcessBuilder("crontab", "-").start();
|
||
Writer w = new OutputStreamWriter(addProcess.getOutputStream(), "UTF-8");
|
||
System.out.println(newCrontab);
|
||
w.write(newCrontab);
|
||
w.flush();
|
||
w.close();
|
||
if (!addProcess.waitFor(5, TimeUnit.SECONDS)) {
|
||
String message = "Failed to register " + taskName + " task: process timed out";
|
||
logger.log(Level.SEVERE, message);
|
||
return Results.failure(message);
|
||
}
|
||
|
||
if(addProcess.exitValue() != 0) {
|
||
Pair<String, String> addOutput = WindowsScheduler.captureProcessOutput(addProcess);
|
||
String message = "Failed to get user id. Exit code: " + addProcess.exitValue() + ". stdout: " + addOutput.getKey() + ". stderr: " + addOutput.getValue();
|
||
logger.log(Level.SEVERE, message);
|
||
return Results.failure(message);
|
||
}
|
||
} catch (CronExpressionException | IOException | InterruptedException e) {
|
||
String message = "Exception while registering schedule";
|
||
logger.log(Level.SEVERE, message, e);
|
||
return Results.failure(message);
|
||
}
|
||
|
||
return Results.success(true);
|
||
}
|
||
|
||
public static Result<Boolean, String> scheduleExists(StationSettings settings) {
|
||
try {
|
||
String taskName = "numbers-station-main_" + settings.getName();
|
||
|
||
Process listProcess = new ProcessBuilder("crontab", "-l").start();
|
||
if (!listProcess.waitFor(5, TimeUnit.SECONDS)) {
|
||
String message = "Failed to query " + taskName + " task: process timed out";
|
||
logger.log(Level.SEVERE, message);
|
||
return Results.failure(message);
|
||
}
|
||
|
||
Pair<String, String> output = WindowsScheduler.captureProcessOutput(listProcess);
|
||
if(listProcess.exitValue() != 0) {
|
||
String message = "Failed to get user id. Exit code: " + listProcess.exitValue() + ". stdout: " + output.getKey() + "\n\tstderr: " + output.getValue();
|
||
logger.log(Level.SEVERE, message);
|
||
return Results.failure(message);
|
||
}
|
||
|
||
String currentCrontab = output.getKey();
|
||
|
||
String cronEntry = "# " + taskName + "\n" + cronExpression(settings) + "\n\n";
|
||
|
||
Pattern pattern = Pattern.compile("# " + taskName + "\\n[^\\n]+\\n\\n");
|
||
|
||
Matcher matcher = pattern.matcher(currentCrontab);
|
||
|
||
if (matcher.find()) {
|
||
return Results.success(true);
|
||
} else {
|
||
return Results.success(true);
|
||
}
|
||
} catch (CronExpressionException | IOException | InterruptedException e) {
|
||
String message = "Exception while checking for schedule";
|
||
logger.log(Level.SEVERE, message, e);
|
||
return Results.failure(message);
|
||
}
|
||
}
|
||
|
||
public static Result<Boolean, String> removeSchedule(StationSettings settings) {
|
||
try {
|
||
String taskName = "numbers-station-main_" + settings.getName();
|
||
|
||
Process listProcess = new ProcessBuilder("crontab", "-l").start();
|
||
if (!listProcess.waitFor(5, TimeUnit.SECONDS)) {
|
||
String message = "Failed to query " + taskName + " task: process timed out";
|
||
logger.log(Level.SEVERE, message);
|
||
return Results.failure(message);
|
||
}
|
||
|
||
Pair<String, String> output = WindowsScheduler.captureProcessOutput(listProcess);
|
||
if(listProcess.exitValue() != 0) {
|
||
String message = "Failed to get user id. Exit code: " + listProcess.exitValue() + ". stdout: " + output.getKey() + "\n\tstderr: " + output.getValue();
|
||
logger.log(Level.SEVERE, message);
|
||
return Results.failure(message);
|
||
}
|
||
|
||
String currentCrontab = output.getKey();
|
||
|
||
String cronEntry = "# " + taskName + "\n" + cronExpression(settings) + "\n\n";
|
||
|
||
Pattern pattern = Pattern.compile("# " + taskName + "\\n[^\\n]+\\n\\n");
|
||
|
||
Matcher matcher = pattern.matcher(currentCrontab);
|
||
|
||
StringBuilder sb = new StringBuilder();
|
||
|
||
while (matcher.find()) {
|
||
matcher.appendReplacement(sb, "");
|
||
}
|
||
|
||
String newCrontab = sb.toString();
|
||
|
||
Process addProcess = new ProcessBuilder("crontab", "-").start();
|
||
Writer w = new OutputStreamWriter(addProcess.getOutputStream(), "UTF-8");
|
||
System.out.println(newCrontab);
|
||
w.write(newCrontab);
|
||
w.flush();
|
||
w.close();
|
||
if (!addProcess.waitFor(5, TimeUnit.SECONDS)) {
|
||
String message = "Failed to register " + taskName + " task: process timed out";
|
||
logger.log(Level.SEVERE, message);
|
||
return Results.failure(message);
|
||
}
|
||
|
||
if(addProcess.exitValue() != 0) {
|
||
Pair<String, String> addOutput = WindowsScheduler.captureProcessOutput(addProcess);
|
||
String message = "Failed to get user id. Exit code: " + addProcess.exitValue() + ". stdout: " + addOutput.getKey() + ". stderr: " + addOutput.getValue();
|
||
logger.log(Level.SEVERE, message);
|
||
return Results.failure(message);
|
||
}
|
||
} catch (CronExpressionException | IOException | InterruptedException e) {
|
||
String message = "Exception while removing schedule";
|
||
logger.log(Level.SEVERE, message, e);
|
||
return Results.failure(message);
|
||
}
|
||
|
||
return Results.success(true);
|
||
}
|
||
|
||
/**
|
||
* * * * * * {command to execute}
|
||
* | | | | |
|
||
* | | | | day of the week (0–6) (Sunday to Saturday;
|
||
* | | | month (1–12) 7 is also Sunday on some systems)
|
||
* | | day of the month (1–31)
|
||
* | hour (0–23)
|
||
* minute (0–59)
|
||
*/
|
||
public static String cronExpression(StationSettings settings) throws CronExpressionException {
|
||
LocalDateTime scheduleDateTime = settings.getScheduleStart().withZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime();
|
||
|
||
String minute = Integer.toString(scheduleDateTime.getMinute());
|
||
String hour = Integer.toString(scheduleDateTime.getHour());
|
||
String dayOfMonth = Integer.toString(scheduleDateTime.getDayOfMonth());
|
||
String dayOfWeek = Integer.toString(scheduleDateTime.getDayOfMonth());
|
||
|
||
String scheduleString = "";
|
||
switch(settings.getMessagePeriod()) {
|
||
case DAILY:
|
||
scheduleString = minute + " " + hour + " * * *";
|
||
break;
|
||
case WEEKLY:
|
||
scheduleString = minute + " " + hour + " * * " + dayOfWeek;
|
||
break;
|
||
case MONTHLY:
|
||
scheduleString = minute + " " + hour + " " + dayOfMonth + " * *";
|
||
break;
|
||
default:
|
||
throw new CronExpressionException("Message period not implemented: '" + settings.getMessagePeriod() + "'");
|
||
}
|
||
|
||
String processName = ManagementFactory.getRuntimeMXBean().getName();
|
||
long pid = Long.parseLong(processName.split("@")[0]);
|
||
ProcessHandle currentProcess = ProcessHandle.of(pid).orElseThrow();
|
||
Path executablePath = currentProcess.info().command().map(Paths::get).orElseThrow();
|
||
logger.info("Executable Path: " + executablePath);
|
||
return scheduleString + " " + executablePath.toString() + " --station \"" + settings.getName() + "\"";
|
||
}
|
||
|
||
public static class CronExpressionException extends Exception {
|
||
public CronExpressionException(String message) {
|
||
super(message);
|
||
}
|
||
}
|
||
}
|