diff --git a/src/main/java/name/nathanmcrae/numbersstation/LinuxScheduler.java b/src/main/java/name/nathanmcrae/numbersstation/LinuxScheduler.java new file mode 100644 index 0000000..971e030 --- /dev/null +++ b/src/main/java/name/nathanmcrae/numbersstation/LinuxScheduler.java @@ -0,0 +1,152 @@ +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.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +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 registerSchedule(StationSettings settings) { + try { + String taskName = "numbers-station-main_" + settings.getName(); + + // TODO: assume it's on the PATH + Process listProcess = new ProcessBuilder("/usr/bin/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 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, ""); + } + + matcher.appendTail(sb); + } else { + sb.append(currentCrontab); + sb.append(cronEntry); + } + + String newCrontab = sb.toString(); + + // TODO: assume it's on the PATH + Process addProcess = new ProcessBuilder("/usr/bin/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 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); + } + + // Path cronDPath = Paths.get("/etc/cron.d"); + // if (!Files.exists(cronDPath)) { + // String message = "/etc/cron.d does not exist, cannot create cron entry. Select 'Manage schedule externally' and set up scheduling as desired."; + // logger.log(Level.SEVERE, message); + // return Results.failure(message); + // } + + // Path cronPath = cronDPath.resolve(settings.safeName()); + + // try { + // Files.write(cronPath, cronEntry(settings).getBytes(StandardCharsets.UTF_8)); + // } catch (Exception e) { + // String message = "Failed to write cron file at '" + cronPath.toString() + "'"; + // 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 { + String minute = Integer.toString(settings.getScheduleStartTime().getMinute()); + String hour = Integer.toString(settings.getScheduleStartTime().getHour()); + String dayOfMonth = Integer.toString(settings.getScheduleStartDate().getDayOfMonth()); + String dayOfWeek = Integer.toString(settings.getScheduleStartDate().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() + "'"); + } + + // TODO: figure out actual invocation + return scheduleString + " /home/nathanmcrae/personal_root/projects/numbers-station/run.sh --station \"" + settings.getName() + "\""; + } + + public static class CronExpressionException extends Exception { + public CronExpressionException(String message) { + super(message); + } + } +} diff --git a/src/main/java/name/nathanmcrae/numbersstation/StationSettingsController.java b/src/main/java/name/nathanmcrae/numbersstation/StationSettingsController.java index 991411b..a44c3e6 100644 --- a/src/main/java/name/nathanmcrae/numbersstation/StationSettingsController.java +++ b/src/main/java/name/nathanmcrae/numbersstation/StationSettingsController.java @@ -454,7 +454,7 @@ public class StationSettingsController { if (osName.contains("win")) { WindowsScheduler.registerSchedule(settings); } else if (osName.contains("nix") || osName.contains("nux") || osName.contains("aix")) { - logger.log(Level.SEVERE, "Unsupported OS " + osName); + LinuxScheduler.registerSchedule(settings); } else { logger.log(Level.SEVERE, "Unsupported OS " + osName); // TODO: Alert