Get registering and running task scheduler task working
This commit is contained in:
parent
4defb7d3a0
commit
e5877c8a34
@ -7,6 +7,7 @@ $modules = $(
|
||||
"com.fasterxml.jackson.core",
|
||||
"com.fasterxml.jackson.dataformat.xml",
|
||||
"com.fasterxml.jackson.datatype.jsr310",
|
||||
"result",
|
||||
"javafx.controls",
|
||||
"javafx.fxml",
|
||||
"org.apache.commons.cli"
|
||||
|
@ -1,5 +1,7 @@
|
||||
package name.nathanmcrae.numbersstation;
|
||||
|
||||
import com.leakyabstractions.result.api.Result;
|
||||
import com.leakyabstractions.result.core.Results;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@ -21,7 +23,7 @@ public class Main extends Application {
|
||||
private static final String VERSION = "0.0.1";
|
||||
private static Path statePath = null;
|
||||
|
||||
public Path getStatePath() {
|
||||
public static Path getStatePath() {
|
||||
if (statePath == null) {
|
||||
String stateHome = System.getenv("XDG_STATE_HOME");
|
||||
if (stateHome == null || stateHome.isEmpty() || !Paths.get(stateHome).isAbsolute()) {
|
||||
@ -37,7 +39,6 @@ public class Main extends Application {
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) throws Exception {
|
||||
setupLogger();
|
||||
Parent root = FXMLLoader.load(getClass().getResource("MainView.fxml"));
|
||||
primaryStage.setTitle("Numbers Station");
|
||||
primaryStage.setScene(new Scene(root));
|
||||
@ -45,7 +46,7 @@ public class Main extends Application {
|
||||
logger.info("Application started");
|
||||
}
|
||||
|
||||
private void setupLogger() {
|
||||
private static void setupLogger() {
|
||||
try {
|
||||
Path logFile = getStatePath().resolve("main.log");
|
||||
|
||||
@ -61,7 +62,7 @@ public class Main extends Application {
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseArguments(String[] args) {
|
||||
private static String parseArguments(String[] args) {
|
||||
Options options = new Options();
|
||||
|
||||
Option help = new Option("h", "help", false, "Show help");
|
||||
@ -99,10 +100,12 @@ public class Main extends Application {
|
||||
formatter.printHelp("numbers-station", options);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
return stationName;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
parseArguments(args);
|
||||
String stationName = parseArguments(args);
|
||||
|
||||
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
|
||||
logger.log(Level.SEVERE, "Unhandled exception caught", throwable);
|
||||
@ -113,6 +116,41 @@ public class Main extends Application {
|
||||
logger.log(Level.SEVERE, "Unhandled exception in JavaFX application thread", throwable);
|
||||
});
|
||||
|
||||
setupLogger();
|
||||
|
||||
if (stationName != null) {
|
||||
// TODO: errors in runStation should trigger a notification
|
||||
runStation(stationName);
|
||||
} else {
|
||||
launch(args);
|
||||
}
|
||||
}
|
||||
|
||||
public static void runStation(String stationName) {
|
||||
if (stationName == null || stationName == "") {
|
||||
logger.log(Level.SEVERE, "Station name must be provided and not empty");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
Result<MainSettings, Exception> result = MainSettings.load();
|
||||
if (!result.hasSuccess()) {
|
||||
logger.log(Level.SEVERE, "Unable to load settings");
|
||||
System.exit(1);
|
||||
}
|
||||
MainSettings settings = result.getSuccess().get();
|
||||
|
||||
StationSettings loadedStation = settings.getStations().stream()
|
||||
.filter(station -> station.getName().equals(stationName))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (loadedStation == null) {
|
||||
logger.log(Level.SEVERE, "Unable to find station " + stationName);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
logger.info("Loaded station " + stationName);
|
||||
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
package name.nathanmcrae.numbersstation;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.leakyabstractions.result.api.Result;
|
||||
import com.leakyabstractions.result.core.Results;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
@ -118,46 +116,24 @@ public class MainController implements Initializable {
|
||||
}
|
||||
}
|
||||
|
||||
private void loadSettings() throws IOException {
|
||||
XmlMapper xmlMapper = new XmlMapper();
|
||||
xmlMapper.registerModule(new JavaTimeModule());
|
||||
String userHome = System.getProperty("user.home");
|
||||
|
||||
Path filePath = MainSettings.getSettingsFilePath();
|
||||
Path directoryPath = filePath.getParent();
|
||||
try {
|
||||
if (!Files.exists(directoryPath)) {
|
||||
Files.createDirectories(directoryPath);
|
||||
}
|
||||
|
||||
if (!Files.exists(filePath)) {
|
||||
settings = new MainSettings();
|
||||
|
||||
xmlMapper.writeValue(new File(filePath.toString()), settings);
|
||||
} else {
|
||||
settings = xmlMapper.readValue(new File(filePath.toString()), MainSettings.class);
|
||||
for (StationSettings station : settings.getStations()) {
|
||||
if (station.getDigitsPerGroup() == 0) {
|
||||
station.setDigitsPerGroup(4);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.SEVERE, "Failed to load settings from " + filePath.toString(), e);
|
||||
System.out.println("File contents: " + Files.readString(filePath));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
stationNameField.textProperty().bindBidirectional(selectedStationName);
|
||||
|
||||
try {
|
||||
loadSettings();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
Result<MainSettings, Exception> result = MainSettings.load();
|
||||
if (!result.hasSuccess()) {
|
||||
// TODO: on failure, prompt user to re-initialize settings
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setTitle("Settings load error");
|
||||
alert.setHeaderText(null);
|
||||
alert.setContentText("Unable to load settings file");
|
||||
alert.showAndWait();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
settings = result.getSuccess().get();
|
||||
|
||||
selectedStationName.set(settings.getSelectedStationName());
|
||||
|
||||
if (selectedStationName.get() == null || selectedStationName.get() == "") {
|
||||
|
@ -5,6 +5,8 @@ import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.leakyabstractions.result.api.Result;
|
||||
import com.leakyabstractions.result.core.Results;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@ -12,6 +14,7 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class MainSettings {
|
||||
private static final Logger logger = Logger.getLogger(Main.class.getName());
|
||||
@ -71,6 +74,40 @@ public class MainSettings {
|
||||
this.selectedStationName = selectedStationName;
|
||||
}
|
||||
|
||||
public static Result<MainSettings, Exception> load() {
|
||||
XmlMapper xmlMapper = new XmlMapper();
|
||||
xmlMapper.registerModule(new JavaTimeModule());
|
||||
String userHome = System.getProperty("user.home");
|
||||
|
||||
MainSettings settings;
|
||||
|
||||
Path filePath = MainSettings.getSettingsFilePath();
|
||||
Path directoryPath = filePath.getParent();
|
||||
try {
|
||||
if (!Files.exists(directoryPath)) {
|
||||
Files.createDirectories(directoryPath);
|
||||
}
|
||||
|
||||
if (!Files.exists(filePath)) {
|
||||
settings = new MainSettings();
|
||||
|
||||
xmlMapper.writeValue(new File(filePath.toString()), settings);
|
||||
} else {
|
||||
settings = xmlMapper.readValue(new File(filePath.toString()), MainSettings.class);
|
||||
for (StationSettings station : settings.getStations()) {
|
||||
if (station.getDigitsPerGroup() == 0) {
|
||||
station.setDigitsPerGroup(4);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.SEVERE, "Failed to load settings from " + filePath.toString(), e);
|
||||
return Results.failure(e);
|
||||
}
|
||||
|
||||
return Results.success(settings);
|
||||
}
|
||||
|
||||
public void save() {
|
||||
XmlMapper xmlMapper = new XmlMapper();
|
||||
xmlMapper.registerModule(new JavaTimeModule());
|
||||
|
@ -397,9 +397,18 @@ public class MainSettingsController {
|
||||
|
||||
@FXML
|
||||
private void handleTestConnectionButtonPress() {
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void handleTestRegisterScheduleButtonPress() {
|
||||
WindowsScheduler.registerSchedule(settings);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void handleTestRunScheduleButtonPress() {
|
||||
WindowsScheduler.runSchedule(settings);
|
||||
}
|
||||
|
||||
public StringProperty stationAddressProperty() {
|
||||
return stationAddress;
|
||||
}
|
||||
|
@ -133,25 +133,27 @@
|
||||
<AnchorPane prefHeight="152.0" prefWidth="578.0">
|
||||
<children>
|
||||
<CheckBox fx:id="manageScheduleExternallyCheckBox" layoutX="395.0" mnemonicParsing="false" text="Manage schedule externally" AnchorPane.rightAnchor="14.5" />
|
||||
<RadioButton fx:id="dailyRadioButton" layoutX="14.0" layoutY="8.0" mnemonicParsing="false" text="Daily" disable="${manageScheduleExternallyCheckBox.selected}">
|
||||
<RadioButton fx:id="dailyRadioButton" disable="${manageScheduleExternallyCheckBox.selected}" layoutX="14.0" layoutY="8.0" mnemonicParsing="false" text="Daily">
|
||||
<toggleGroup>
|
||||
<ToggleGroup fx:id="messagePeriodGroup" />
|
||||
</toggleGroup>
|
||||
</RadioButton>
|
||||
<RadioButton fx:id="weeklyRadioButton" layoutX="14.0" layoutY="38.0" mnemonicParsing="false" text="Weekly" disable="${manageScheduleExternallyCheckBox.selected}">
|
||||
<RadioButton fx:id="weeklyRadioButton" disable="${manageScheduleExternallyCheckBox.selected}" layoutX="14.0" layoutY="38.0" mnemonicParsing="false" text="Weekly">
|
||||
<toggleGroup>
|
||||
<fx:reference source="messagePeriodGroup" />
|
||||
</toggleGroup>
|
||||
</RadioButton>
|
||||
<RadioButton fx:id="monthlyRadioButton" layoutX="14.0" layoutY="68.0" mnemonicParsing="false" text="Monthly" disable="${manageScheduleExternallyCheckBox.selected}">
|
||||
<RadioButton fx:id="monthlyRadioButton" disable="${manageScheduleExternallyCheckBox.selected}" layoutX="14.0" layoutY="68.0" mnemonicParsing="false" text="Monthly">
|
||||
<toggleGroup>
|
||||
<fx:reference source="messagePeriodGroup" />
|
||||
</toggleGroup>
|
||||
</RadioButton>
|
||||
<DatePicker fx:id="scheduleStartDatePicker" layoutX="115.0" layoutY="34.0" disable="${manageScheduleExternallyCheckBox.selected}" />
|
||||
<DatePicker fx:id="scheduleStartDatePicker" disable="${manageScheduleExternallyCheckBox.selected}" layoutX="115.0" layoutY="34.0" />
|
||||
<Label layoutX="115.0" layoutY="8.0" text="Starting from:" />
|
||||
<TextField fx:id="scheduleStartTimeField" layoutX="115.0" layoutY="64.0" text="23:24:49" disable="${manageScheduleExternallyCheckBox.selected}" />
|
||||
<TextField fx:id="scheduleStartTimeField" disable="${manageScheduleExternallyCheckBox.selected}" layoutX="115.0" layoutY="64.0" text="23:24:49" />
|
||||
<Label layoutX="48.0" layoutY="102.0" text="TODO: Jitter" />
|
||||
<Button layoutX="377.0" layoutY="73.0" mnemonicParsing="false" onMousePressed="#handleTestRegisterScheduleButtonPress" text="Test register scheduled task" />
|
||||
<Button layoutX="377.0" layoutY="107.0" mnemonicParsing="false" onMousePressed="#handleTestRunScheduleButtonPress" text="Test running scheduled task" />
|
||||
|
||||
</children>
|
||||
</AnchorPane>
|
||||
|
@ -1,5 +1,7 @@
|
||||
package name.nathanmcrae.numbersstation;
|
||||
|
||||
import com.leakyabstractions.result.api.Result;
|
||||
import com.leakyabstractions.result.core.Results;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
@ -7,8 +9,10 @@ import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javafx.util.Pair;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
@ -24,7 +28,7 @@ import org.w3c.dom.Element;
|
||||
public class WindowsScheduler {
|
||||
private static final Logger logger = Logger.getLogger(Main.class.getName());
|
||||
|
||||
public static Document generateScheduleDocument(String authorName, String userId) throws ParserConfigurationException {
|
||||
public static Document generateScheduleDocument(String authorName, String userId, StationSettings station) throws ParserConfigurationException {
|
||||
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
|
||||
|
||||
@ -173,33 +177,82 @@ public class WindowsScheduler {
|
||||
actions.appendChild(exec);
|
||||
|
||||
Element command = doc.createElement("Command");
|
||||
command.appendChild(doc.createTextNode("notepad.exe"));
|
||||
// TODO: need to figure out the real invocation
|
||||
command.appendChild(doc.createTextNode("powershell"));
|
||||
exec.appendChild(command);
|
||||
|
||||
Element arguments = doc.createElement("Arguments");
|
||||
arguments.appendChild(doc.createTextNode("-Version 5 -NoProfile -File P:/personal_root/projects/number-station/src/main/java/run.ps1 --station \"" + station.getName() + "\""));
|
||||
exec.appendChild(arguments);
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
public static String getUserId() throws IOException, InterruptedException {
|
||||
public static Result<String, String> getUserId() throws IOException, InterruptedException {
|
||||
Process process = new ProcessBuilder("whoami", "/user").start();
|
||||
process.waitFor();
|
||||
|
||||
if(!process.waitFor(5, TimeUnit.SECONDS)) {
|
||||
String message = "Failed to get user id: process timed out";
|
||||
logger.log(Level.SEVERE, message);
|
||||
return Results.failure(message);
|
||||
}
|
||||
|
||||
if(process.exitValue() != 0) {
|
||||
Pair<String, String> output = captureProcessOutput(process);
|
||||
String message = "Failed to get user id. Exit code: " + process.exitValue() + ". stdout: " + output.getKey() + "\n\tstderr: " + output.getValue();
|
||||
logger.log(Level.SEVERE, message);
|
||||
return Results.failure(message);
|
||||
}
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.contains("S-")) {
|
||||
return line.split("\\s+")[1];
|
||||
return Results.success(line.split("\\s+")[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void registerSchedule(StationSettings settings) {
|
||||
return Results.failure("Failed to get user id: no user id found");
|
||||
}
|
||||
|
||||
public static Result<Boolean, String> registerSchedule(StationSettings settings) {
|
||||
Path tempFile = null;
|
||||
try {
|
||||
String taskName = "numbers-station-main_" + settings.getName();
|
||||
|
||||
Document doc = generateScheduleDocument(System.getProperty("user.name"), getUserId());
|
||||
Process queryTaskProcess = new ProcessBuilder("schtasks.exe", "/query", "/tn", taskName).start();
|
||||
if (!queryTaskProcess.waitFor(5, TimeUnit.SECONDS)) {
|
||||
String message = "Failed to query " + taskName + " task: process timed out";
|
||||
logger.log(Level.SEVERE, message);
|
||||
return Results.failure(message);
|
||||
}
|
||||
|
||||
// Remove previous instance of task if it exists
|
||||
if (queryTaskProcess.exitValue() == 0) {
|
||||
Process removeTaskProcess = new ProcessBuilder("schtasks.exe", "/delete", "/f", "/tn", taskName).start();
|
||||
if (!removeTaskProcess.waitFor(5, TimeUnit.SECONDS)) {
|
||||
String message = "Failed to remove " + taskName + " task: process timed out";
|
||||
logger.log(Level.SEVERE, message);
|
||||
return Results.failure(message);
|
||||
}
|
||||
|
||||
if (removeTaskProcess.exitValue() == 0) {
|
||||
logger.info(taskName + " task removed successfully.");
|
||||
} else {
|
||||
Pair<String, String> output = captureProcessOutput(removeTaskProcess);
|
||||
String message = "Failed to remove " + taskName + " task. Exit code: " + removeTaskProcess.exitValue() + ". stdout: " + output.getKey() + "\n\tstderr: " + output.getValue();
|
||||
logger.log(Level.SEVERE, message);
|
||||
return Results.failure(message);
|
||||
}
|
||||
}
|
||||
|
||||
Result<String, String> userIdResult = getUserId();
|
||||
if (!userIdResult.hasSuccess()) {
|
||||
return Results.failure(userIdResult.getFailure().get());
|
||||
}
|
||||
String userId = userIdResult.getSuccess().get();
|
||||
Document doc = generateScheduleDocument(System.getProperty("user.name"), userId, settings);
|
||||
|
||||
TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
||||
Transformer transformer = transformerFactory.newTransformer();
|
||||
@ -213,18 +266,30 @@ public class WindowsScheduler {
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(tempFile)) {
|
||||
writer.write("<?xml version=\"1.0\"?>\n");
|
||||
transformer.transform(source, new StreamResult(writer));
|
||||
} catch (IOException | TransformerException e) {
|
||||
String message = "Failed to write schedule to temporary file";
|
||||
logger.log(Level.SEVERE, message, e);
|
||||
return Results.failure(message);
|
||||
}
|
||||
|
||||
Process process = new ProcessBuilder("schtasks.exe", "/create", "/tn", taskName, "/xml", tempFile.toString()).start();
|
||||
int exitCode = process.waitFor();
|
||||
if (!process.waitFor(5, TimeUnit.SECONDS)) {
|
||||
String message = "Failed to register " + taskName + " task: process timed out";
|
||||
logger.log(Level.SEVERE, message);
|
||||
return Results.failure(message);
|
||||
}
|
||||
|
||||
if (exitCode == 0) {
|
||||
if (process.exitValue() == 0) {
|
||||
logger.info(taskName + " task registered successfully.");
|
||||
} else {
|
||||
logger.info("Failed to register " + taskName + " task. Exit code: " + exitCode);
|
||||
String message = "Failed to register " + taskName + " task. Exit code: " + process.exitValue();
|
||||
logger.log(Level.SEVERE, message);
|
||||
return Results.failure(message);
|
||||
}
|
||||
} catch (ParserConfigurationException | TransformerException | IOException | InterruptedException e) {
|
||||
logger.log(Level.SEVERE, "Exception while registering schedule", e);
|
||||
String message = "Exception while registering schedule";
|
||||
logger.log(Level.SEVERE, message, e);
|
||||
return Results.failure(message);
|
||||
} finally {
|
||||
if (tempFile != null) {
|
||||
try {
|
||||
@ -234,5 +299,38 @@ public class WindowsScheduler {
|
||||
}
|
||||
}
|
||||
}
|
||||
return Results.success(true);
|
||||
}
|
||||
|
||||
public static Result<Boolean, String> runSchedule(StationSettings settings) {
|
||||
String taskName = "numbers-station-main_" + settings.getName();
|
||||
|
||||
try {
|
||||
Process taskProcess = new ProcessBuilder("schtasks.exe", "/run", "/tn", taskName).start();
|
||||
} catch (IOException e) {
|
||||
String message = "Failed to run " + taskName + " task";
|
||||
logger.log(Level.SEVERE, message, e);
|
||||
return Results.failure(message);
|
||||
}
|
||||
|
||||
return Results.success(true);
|
||||
}
|
||||
|
||||
public static Pair<String, String> captureProcessOutput(Process process) throws IOException {
|
||||
StringBuilder output = new StringBuilder();
|
||||
StringBuilder error = new StringBuilder();
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
output.append(line);
|
||||
}
|
||||
while ((line = errorReader.readLine()) != null) {
|
||||
error.append(line);
|
||||
}
|
||||
}
|
||||
|
||||
return new Pair<>(output.toString(), error.toString());
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ $modules = $(
|
||||
"com.fasterxml.jackson.core",
|
||||
"com.fasterxml.jackson.dataformat.xml",
|
||||
"com.fasterxml.jackson.datatype.jsr310",
|
||||
"result",
|
||||
"javafx.controls",
|
||||
"javafx.fxml",
|
||||
"org.apache.commons.cli"
|
||||
|
Loading…
x
Reference in New Issue
Block a user