package au.pfip; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.LinkedHashMap; import java.util.Map; import java.util.Properties; /** * Settings file (Java XML properties). The first of these that exists is used: * /etc/timekeeper.inf, ~/timekeeper.inf, ~/.config/timekeeper/timekeeper.inf, * ./timekeeper.inf. Run as root and keep the file in /etc: a settings file * the monitored user can edit defeats the monitoring. * * Changes: missing keys fall back to defaults (a missing key used to throw a * NullPointerException or silently disable the program), the settings file is * closed after reading, and the defaults written into a new file no longer * contain real user names or a network name. */ public class settings { private static final Properties settings = new Properties(); private static final Map defaults = new LinkedHashMap<>(); private static String settingsFile; static { defaults.put("TimekeeperActive", "true"); defaults.put("LogFilePath", System.getProperty("user.home")); // Comma separated list of the accounts to watch, e.g. "alice,bob". Must be set. defaults.put("UsersToMonitor", ""); // Name of a network where enforcement is switched off. Empty means none. defaults.put("ExemptNetwork", ""); defaults.put("GamesURL", "https://overlord.pfip.au/e107_plugins/timekeeper/timekeeperClientServices/games.json"); defaults.put("PlaytimeURL", "https://overlord.pfip.au/e107_plugins/timekeeper/timekeeperClientServices/timer.php"); // How many checks in a row may fail to reach the server before the game is closed (1 = close at once). defaults.put("OfflineChecksBeforeKill", "5"); defaults.put("GamesRefreshMinutes", "60"); } public static boolean initialiseSettings() { try { String[] paths = {"/etc/timekeeper.inf", System.getProperty("user.home") + "/timekeeper.inf", System.getProperty("user.home") + "/.config/timekeeper/timekeeper.inf", System.getProperty("user.dir") + "/timekeeper.inf"}; for (String candidate : paths) { System.out.println("Checking for settings at " + candidate); Path path = Path.of(candidate); if (Files.exists(path)) { System.out.println("Found settings at " + path); settingsFile = String.valueOf(path); try (InputStream in = Files.newInputStream(path)) { settings.loadFromXML(in); } return true; } } Path path = Path.of(paths[2]); settingsFile = paths[2]; Files.createDirectories(path.getParent()); createSettingsFile(); System.out.println("Settings file created at " + settingsFile + ". Set UsersToMonitor in it."); return true; } catch (IOException e) { //noinspection CallToPrintStackTrace e.printStackTrace(); return false; } } private static void createSettingsFile() { for (Map.Entry entry : defaults.entrySet()) { addSetting(entry.getKey(), entry.getValue()); } saveSettingsFile(); } public static void saveSettingsFile() { try (OutputStream out = Files.newOutputStream(Path.of(settingsFile))) { settings.storeToXML(out, "Timekeeper configuration file."); } catch (IOException e) { throw new RuntimeException(e); } } public static void addSetting(String key, String val) { settings.setProperty(key, String.valueOf(val)); } /** * A setting, or its default when the file doesn't have it. Never null. */ public static String getSetting(String key) { String value = settings.getProperty(key); if (value == null) { value = defaults.get(key); } return value == null ? "" : value.trim(); } public static int getInt(String key, int fallback) { try { return Integer.parseInt(getSetting(key)); } catch (NumberFormatException e) { return fallback; } } /** * The folder the settings file lives in (also used for the games list cache). */ public static Path getSettingsDirectory() { Path parent = (settingsFile == null) ? null : Path.of(settingsFile).toAbsolutePath().getParent(); return parent != null ? parent : Path.of(System.getProperty("user.home")); } }