gregory 1 nedēļu atpakaļ
revīzija
ad8cbc9fb3

+ 29 - 0
.gitignore

@@ -0,0 +1,29 @@
+### IntelliJ IDEA ###
+out/
+!**/src/main/**/out/
+!**/src/test/**/out/
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+bin/
+!**/src/main/**/bin/
+!**/src/test/**/bin/
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store

+ 3 - 0
.idea/.gitignore

@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml

+ 1 - 0
.idea/.name

@@ -0,0 +1 @@
+TimekeeperEnforcement

+ 33 - 0
.idea/artifacts/TimekeeperEnforcement_jar.xml

@@ -0,0 +1,33 @@
+<component name="ArtifactManager">
+  <artifact type="jar" build-on-make="true" name="TimekeeperEnforcement:jar">
+    <output-path>$PROJECT_DIR$/out/artifacts/TimekeeperEnforcement_jar</output-path>
+    <root id="archive" name="TimekeeperEnforcement.jar">
+      <element id="module-output" name="TimekeeperEnforcement" />
+      <element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter/5.8.1/junit-jupiter-5.8.1.jar" path-in-jar="/" />
+      <element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.8.1/junit-jupiter-api-5.8.1.jar" path-in-jar="/" />
+      <element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar" path-in-jar="/" />
+      <element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.8.1/junit-platform-commons-1.8.1.jar" path-in-jar="/" />
+      <element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar" path-in-jar="/" />
+      <element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-params/5.8.1/junit-jupiter-params-5.8.1.jar" path-in-jar="/" />
+      <element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-engine/5.8.1/junit-jupiter-engine-5.8.1.jar" path-in-jar="/" />
+      <element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.8.1/junit-platform-engine-1.8.1.jar" path-in-jar="/" />
+      <element id="extracted-dir" path="$MAVEN_REPOSITORY$/commons-io/commons-io/2.15.0/commons-io-2.15.0.jar" path-in-jar="/" />
+      <element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/github/hypfvieh/dbus-java/3.3.2/dbus-java-3.3.2.jar" path-in-jar="/" />
+      <element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/github/jnr/jnr-unixsocket/0.38.17/jnr-unixsocket-0.38.17.jar" path-in-jar="/" />
+      <element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/github/jnr/jnr-ffi/2.2.11/jnr-ffi-2.2.11.jar" path-in-jar="/" />
+      <element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.9/jffi-1.3.9.jar" path-in-jar="/" />
+      <element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.9/jffi-1.3.9-native.jar" path-in-jar="/" />
+      <element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.2/asm-9.2.jar" path-in-jar="/" />
+      <element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/ow2/asm/asm-commons/9.2/asm-commons-9.2.jar" path-in-jar="/" />
+      <element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/ow2/asm/asm-analysis/9.2/asm-analysis-9.2.jar" path-in-jar="/" />
+      <element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/ow2/asm/asm-tree/9.2/asm-tree-9.2.jar" path-in-jar="/" />
+      <element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/ow2/asm/asm-util/9.2/asm-util-9.2.jar" path-in-jar="/" />
+      <element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/github/jnr/jnr-a64asm/1.0.0/jnr-a64asm-1.0.0.jar" path-in-jar="/" />
+      <element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/github/jnr/jnr-x86asm/1.0.2/jnr-x86asm-1.0.2.jar" path-in-jar="/" />
+      <element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/github/jnr/jnr-constants/0.10.3/jnr-constants-0.10.3.jar" path-in-jar="/" />
+      <element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/github/jnr/jnr-enxio/0.32.13/jnr-enxio-0.32.13.jar" path-in-jar="/" />
+      <element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/github/jnr/jnr-posix/3.1.15/jnr-posix-3.1.15.jar" path-in-jar="/" />
+      <element id="extracted-dir" path="$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar" path-in-jar="/" />
+    </root>
+  </artifact>
+</component>

+ 10 - 0
.idea/libraries/commons_io.xml

@@ -0,0 +1,10 @@
+<component name="libraryTable">
+  <library name="commons.io" type="repository">
+    <properties maven-id="commons-io:commons-io:2.15.0" />
+    <CLASSES>
+      <root url="jar://$MAVEN_REPOSITORY$/commons-io/commons-io/2.15.0/commons-io-2.15.0.jar!/" />
+    </CLASSES>
+    <JAVADOC />
+    <SOURCES />
+  </library>
+</component>

+ 25 - 0
.idea/libraries/github_hypfvieh_dbus_java.xml

@@ -0,0 +1,25 @@
+<component name="libraryTable">
+  <library name="github.hypfvieh.dbus.java" type="repository">
+    <properties maven-id="com.github.hypfvieh:dbus-java:3.3.2" />
+    <CLASSES>
+      <root url="jar://$MAVEN_REPOSITORY$/com/github/hypfvieh/dbus-java/3.3.2/dbus-java-3.3.2.jar!/" />
+      <root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-unixsocket/0.38.17/jnr-unixsocket-0.38.17.jar!/" />
+      <root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-ffi/2.2.11/jnr-ffi-2.2.11.jar!/" />
+      <root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.9/jffi-1.3.9.jar!/" />
+      <root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jffi/1.3.9/jffi-1.3.9-native.jar!/" />
+      <root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm/9.2/asm-9.2.jar!/" />
+      <root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-commons/9.2/asm-commons-9.2.jar!/" />
+      <root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-analysis/9.2/asm-analysis-9.2.jar!/" />
+      <root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-tree/9.2/asm-tree-9.2.jar!/" />
+      <root url="jar://$MAVEN_REPOSITORY$/org/ow2/asm/asm-util/9.2/asm-util-9.2.jar!/" />
+      <root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-a64asm/1.0.0/jnr-a64asm-1.0.0.jar!/" />
+      <root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-x86asm/1.0.2/jnr-x86asm-1.0.2.jar!/" />
+      <root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-constants/0.10.3/jnr-constants-0.10.3.jar!/" />
+      <root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-enxio/0.32.13/jnr-enxio-0.32.13.jar!/" />
+      <root url="jar://$MAVEN_REPOSITORY$/com/github/jnr/jnr-posix/3.1.15/jnr-posix-3.1.15.jar!/" />
+      <root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar!/" />
+    </CLASSES>
+    <JAVADOC />
+    <SOURCES />
+  </library>
+</component>

+ 5 - 0
.idea/misc.xml

@@ -0,0 +1,5 @@
+<project version="4">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/out" />
+  </component>
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/TimekeeperEnforcement.iml" filepath="$PROJECT_DIR$/TimekeeperEnforcement.iml" />
+    </modules>
+  </component>
+</project>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

+ 45 - 0
TimekeeperEnforcement.iml

@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="commons.io" level="project" />
+    <orderEntry type="module-library" scope="TEST">
+      <library name="JUnit5.8.1">
+        <CLASSES>
+          <root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter/5.8.1/junit-jupiter-5.8.1.jar!/" />
+          <root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.8.1/junit-jupiter-api-5.8.1.jar!/" />
+          <root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
+          <root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.8.1/junit-platform-commons-1.8.1.jar!/" />
+          <root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar!/" />
+          <root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-params/5.8.1/junit-jupiter-params-5.8.1.jar!/" />
+          <root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-engine/5.8.1/junit-jupiter-engine-5.8.1.jar!/" />
+          <root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.8.1/junit-platform-engine-1.8.1.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library>
+        <CLASSES>
+          <root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter/5.8.1/junit-jupiter-5.8.1.jar!/" />
+          <root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.8.1/junit-jupiter-api-5.8.1.jar!/" />
+          <root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
+          <root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.8.1/junit-platform-commons-1.8.1.jar!/" />
+          <root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar!/" />
+          <root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-params/5.8.1/junit-jupiter-params-5.8.1.jar!/" />
+          <root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-engine/5.8.1/junit-jupiter-engine-5.8.1.jar!/" />
+          <root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.8.1/junit-platform-engine-1.8.1.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="library" name="github.hypfvieh.dbus.java" level="project" />
+  </component>
+</module>

+ 3 - 0
src/META-INF/MANIFEST.MF

@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: au.pfip.Main
+

+ 23 - 0
src/au/pfip/FileHandling.java

@@ -0,0 +1,23 @@
+package au.pfip;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static java.nio.file.StandardOpenOption.APPEND;
+import static java.nio.file.StandardOpenOption.CREATE;
+
+public class FileHandling {
+
+    private String logFilePath;
+
+    public void initialiseFileHandling(String path) throws IOException {
+        logFilePath = path;
+        Files.deleteIfExists(Path.of(logFilePath));
+    }
+
+    public void write(final String s) throws IOException {
+        Files.writeString(Path.of(logFilePath), s + System.lineSeparator(), CREATE, APPEND);
+    }
+
+}

+ 232 - 0
src/au/pfip/Main.java

@@ -0,0 +1,232 @@
+// Press Shift twice to open the Search Everywhere dialog and type `show whitespaces`,
+// then press Enter. You can now see whitespace characters in your code.
+
+package au.pfip;
+
+import java.util.Objects;
+
+/**
+ * The code below will print the words Hello World
+ * to the screen, and it is amazing
+ */
+public class Main {
+
+    private static final double version = 4.0;
+    public static final String GamesURL = "https://overlord.pfip.au/e107_plugins/timekeeper/timekeeperClientServices/games.json";
+    public static final String PlaytimeURL = "https://overlord.pfip.au/e107_plugins/timekeeper/timekeeperClientServices/timer.php";
+    private static String runningGame = "";
+    private static String activeUser = "";
+    private static int playTimeRemaining = 0;
+    private static Boolean shutdownFlag = false;
+    public static String[] GamesList;
+    private static String[] Users;
+
+    /**
+     * @param args Commandline arguments
+     */
+    public static void main(String[] args) {
+
+        // If we couldn't get/make settings just exit as no config available
+        if(!settings.initialiseSettings())
+        {
+            System.out.println("Settings engine failed to initialise, exiting now.");
+            burnTime(600);
+            System.exit(0);
+        }
+
+        // If we can't make log files just go on without them
+        if(!logging.initialiseLogging())
+        {
+            System.out.println("Logging engine failed to initialise, we can live without this.");
+        }
+
+        logging.information("Starting Timekeeper " + version);
+        boolean timekeeperActive = Boolean.parseBoolean(settings.getSetting("TimekeeperActive"));
+
+        if(!timekeeperActive)
+        {
+            logging.information("Timekeeper is set to not active, bailing out after a long delay.");
+            System.out.println("Timekeeper is set to not active, bailing out after a long delay.");
+            burnTime(600);
+            System.exit(0);
+        }
+
+        Users = settings.getSetting("UsersToMonitor").split(",");
+
+        if(Shell.check("/usr/bin/nmcli | /usr/bin/grep \"connected to \"",settings.getSetting("ExemptNetwork")))
+        {
+            logging.information("Network " + settings.getSetting("ExemptNetwork") + "detected.");
+            burnTime(600);
+            System.exit(0);
+        }
+
+        // Get list of games to be monitored.
+        communications.getGamesListFromServer();
+
+        // Inform users that the Timekeeper is starting up.
+        for (String user : Users) {
+            communications.msgPopup("Starting up version " + version + ".",user);
+        }
+
+        registerShutdownHook();
+        while(!shutdownFlag) {
+
+            startChecks();
+
+            checkPlayTime();
+
+            killGames();
+
+            burnTime(60);
+        }
+
+        // Inform users that the Timekeeper is shutting down.
+        for (String user : Users) {
+            communications.msgPopup("Shutting down.",user);
+        }
+
+        logging.information("Shutting down.");
+    }
+
+    /**
+     * Kill running games if there is no playtime left.
+     */
+    private static void killGames()
+    {
+        if(!Objects.equals(runningGame, "") && playTimeRemaining <= 0) {
+            System.out.println("Killing " + runningGame + " for user " + activeUser);
+            communications.msgPopup("Killing: " + runningGame + " as you are out of play time.",activeUser);
+            killGame(runningGame,activeUser);
+            //Shell.check("ps aux | grep " + user + " | grep " + game + " | grep -v \"grep\"", game)
+        }else if(!Objects.equals(runningGame, "") && playTimeRemaining == 30){
+            communications.msgPopup("Notice: You have 30 minutes remaining.",activeUser);
+        }else if(!Objects.equals(runningGame, "") && playTimeRemaining == 5){
+            communications.msgPopup("Notice: You have 5 minutes remaining.",activeUser);
+        }else if(!Objects.equals(runningGame, "") && playTimeRemaining == 4){
+            communications.msgPopup("Notice: You have 4 minutes remaining.",activeUser);
+        }else if(!Objects.equals(runningGame, "") && playTimeRemaining == 3){
+            communications.msgPopup("Notice: You have 3 minutes remaining.",activeUser);
+        }else if(!Objects.equals(runningGame, "") && playTimeRemaining == 2){
+            communications.msgPopup("Notice: You have 2 minutes remaining.",activeUser);
+        }else if(!Objects.equals(runningGame, "") && playTimeRemaining == 1){
+            communications.msgPopup("Notice: You have 1 minute remaining.",activeUser);
+        }else if(!Objects.equals(activeUser, "")){
+            System.out.println(activeUser + " still has playtime remaining (" + playTimeRemaining + " minutes).");
+        }
+    }
+
+    /**
+     * Get remaining playtime from the server
+     */
+    private static void checkPlayTime()
+    {
+        if(!Objects.equals(runningGame, "")) {
+            System.out.println(runningGame);
+            playTimeRemaining = communications.getPlaytimeFromServer(activeUser, runningGame);
+            System.out.println(playTimeRemaining);
+        }else{
+            System.out.println("No games running.");
+        }
+    }
+
+    private static void startChecks()
+    {
+        for (String user : Users) {
+            System.out.println("Checking running games for " + user);
+            checkRunningGames(user);
+        }
+    }
+
+    private static void checkRunningGames(String user)
+    {
+        for (String game : GamesList) {
+                               // ps aux | grep      gregory | grep    minecraft | grep -v grep
+            if (Shell.check("/usr/bin/ps aux | /usr/bin/grep " + user + " | /usr/bin/grep " + game + " | /usr/bin/grep -v grep", game)) {
+                runningGame = game;
+                activeUser = user;
+                //System.out.println("Dis true---------");
+                System.out.println(game + " detected running.");
+                return;
+            } else {
+                //System.out.println(game + " not running.");
+                runningGame = "";
+                activeUser = "";
+            }
+        }
+    }
+
+    /**
+     * Burn 60 seconds while watching for shutdown signal.
+     */
+    private static void burnTime(int time)
+    {
+        System.out.println("burning 60s.");
+        int timer = time;
+        while(!shutdownFlag && timer!=0) {
+            timer--;
+            try {
+                //System.out.println("burning " + timer + "s.");
+                //noinspection BusyWait
+                Thread.sleep(1000);
+            } catch (InterruptedException e) {
+                logging.error("RuntimeException(e) ~ " + e);
+            }
+        }
+    }
+
+
+
+
+    private static void registerShutdownHook() {
+        final Thread mainThread = Thread.currentThread();
+        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+            try {
+                System.out.println("Shutting down.");
+                logging.information("Sigterm received.");
+                Main.setShutdownProcess();
+                mainThread.join();
+            } catch (InterruptedException ex) {
+                logging.error("RuntimeException(e) ~ " + ex);
+            }
+
+        }));
+    }
+
+    static public void setShutdownProcess() {
+        shutdownFlag = true;
+    }
+
+
+
+    public static void killGame(String game, String user)
+    {
+
+        for (int i = 0; i <= 10; i++) {
+            if (Shell.check("/usr/bin/ps aux | /usr/bin/grep " + user + " | /usr/bin/grep " + game + " | /usr/bin/grep -v grep", game)) {
+                try{
+                    if(i >= 8) {
+                        Shell.runCmd("/usr/bin/pkill -9 -f " + game);
+                        logging.information("/usr/bin/pkill -9 -f " + game);
+                        logging.information("Killing " + game + " forcefully.");
+                    }else{
+                        Shell.runCmd("/usr/bin/pkill -15 -f " + game);
+                        logging.information("/usr/bin/pkill -15 -f " + game);
+                        logging.information("Killing " + game + " gracefully.");
+                    }
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                    logging.error("InterruptedException(e) ~ " + e);
+                }
+            } else {
+                //System.out.println(game + " not running.");
+                runningGame = "";
+                activeUser = "";
+            }
+        }
+    }
+
+
+}
+
+// check for users logged in now.
+//ps -eo user,uid | awk 'NR>1 && $2 >= 1000 && ++seen[$2]==1{print $1}'

+ 72 - 0
src/au/pfip/Shell.java

@@ -0,0 +1,72 @@
+package au.pfip;
+
+import java.io.BufferedReader;
+        import java.io.File;
+        import java.io.InputStreamReader;
+        import java.util.ArrayList;
+
+public class Shell {
+
+    /** Returns null if it failed for some reason.
+     */
+    private static ArrayList<String> command(final String cmdline) {
+        try {
+            Process process =
+                    new ProcessBuilder("/usr/bin/bash", "-c", cmdline)
+                            .redirectErrorStream(true)
+                            .directory(new File("."))
+                            .start();
+
+            ArrayList<String> output = new ArrayList<>();
+            BufferedReader br = new BufferedReader(
+                    new InputStreamReader(process.getInputStream()));
+            String line;
+            while ( (line = br.readLine()) != null )
+                output.add(line);
+
+            //There should really be a timeout here.
+            if (0 != process.waitFor())
+                return null;
+
+            return output;
+
+        } catch (Exception e) {
+            logging.error("RuntimeException(e) ~ " + e);
+            return null;
+        }
+    }
+
+    public static boolean check(String cmd, String needle)
+    {
+        String cmdOutput = test(cmd);
+        return cmdOutput.contains(needle);
+    }
+
+    public static void runCmd(String cmd)
+    {
+        test(cmd);
+    }
+
+// --Commented out by Inspection START (4/2/25, 10:02 pm):
+//    public static boolean checkMulti(String cmd, String needle)
+//    {
+//        String cmdOutput = test(cmd);
+//        return cmdOutput.contains(needle);
+//    }
+// --Commented out by Inspection STOP (4/2/25, 10:02 pm)
+
+    private static String test(String cmdline) {
+        ArrayList<String> output = command(cmdline);
+        StringBuilder reply = new StringBuilder();
+        if (null == output) {
+            return "";
+        }
+        else {
+            for (String line : output) {
+                reply.append(line);
+            }
+        }
+        return reply.toString();
+    }
+
+}

+ 117 - 0
src/au/pfip/communications.java

@@ -0,0 +1,117 @@
+package au.pfip;
+
+import org.apache.commons.io.IOUtils;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.List;
+
+public class communications {
+
+    public static void getGamesListFromServer()
+    {
+        String RawGamesList;
+        StringBuilder logGames = new StringBuilder();
+        try{
+            RawGamesList = getURLData(Main.GamesURL);
+        } catch (IOException e){
+            logging.information("Exception while trying to get game list");
+            RawGamesList = "";
+        }
+        Main.GamesList = parseUniqueValues(RawGamesList).toArray(new String[0]); // set array of games here
+        for (int i = 0; i <= (Main.GamesList.length-1); i++) {
+            logGames.append(Main.GamesList[i]).append(",");
+        }
+        logging.information("Games to monitor: " + logGames);
+    }
+
+    public static int getPlaytimeFromServer(String user,String game)
+    {
+        StringBuilder urlQuery = new StringBuilder();
+        String RawPlaytime;
+        int Playtime;
+
+        urlQuery.append(Main.PlaytimeURL)
+                .append("?user=")
+                .append(user)
+                .append("&game=")
+                .append(game);
+
+        try{
+            logging.information("Using query " + urlQuery.toString() + " to get playtime from server.");
+            RawPlaytime = getURLData(urlQuery.toString()).trim();
+            boolean isNumeric = RawPlaytime.codePoints().allMatch( Character::isDigit );
+            if(isNumeric){
+                Playtime = Integer.parseInt(RawPlaytime.trim());
+            }else{
+                Playtime =0;
+                msgPopup("Notice: " + RawPlaytime + ".",user);
+            }
+        } catch (NumberFormatException | IOException e){
+            logging.information("Exception while trying to get playtime");
+            Playtime = 0;
+        }
+        return Playtime;
+    }
+
+    private static String getURLData(String GamesURL)  throws IOException
+    {
+        URL url = new URL(GamesURL);
+        URLConnection con = url.openConnection();
+        InputStream in = con.getInputStream();
+        String encoding = con.getContentEncoding();  // ** WRONG: should use "con.getContentType()" instead, but it returns something like "text/html; charset=UTF-8" so this value must be parsed to extract the actual encoding
+        encoding = encoding == null ? "UTF-8" : encoding;
+        return IOUtils.toString(in, encoding);
+    }
+
+    public static String getOnlyJsonValues(String json) {
+        json = json.replaceAll("\\{", "");
+        json = json.replaceAll("}", "");
+        json = json.replaceAll("\\[", "");
+        json = json.replaceAll("]", "");
+        json = json.replaceAll("\".*?\":", "");
+        json = json.replaceAll("\"", "");
+        //json = json.replaceAll("\n", "");
+        return json;
+    }
+
+    public static List<String> parseUniqueValues(String json) {
+        List<String> values = new ArrayList<>();
+        String onlyValues = getOnlyJsonValues(json);
+
+        try {
+            StringReader stringReader = new StringReader(onlyValues);
+            BufferedReader bufReader = new BufferedReader(stringReader);
+            String line;
+            while ((line = bufReader.readLine()) != null) {
+                if (line.endsWith(",")) {
+                    line = line.substring(0, line.length() - 1);
+                }
+                if (line.startsWith("\"") && line.endsWith("\"")) {
+                    line = line.substring(1, line.length() - 1);
+                }
+
+
+                line = line.trim();
+                if (!line.isEmpty()) {
+                    values.add(line);
+                }
+
+            }
+        } catch (IOException e) {
+            //LOG.warn("Unable to read lines in String:" + onlyValues);
+        }
+
+        return values;
+    }
+
+    public static void msgPopup(String message,String user) {
+        Shell.runCmd("sudo -u " + user + " XDG_RUNTIME_DIR=/run/user/$(id -u " + user + ") notify-send -t 10000 --app-name \"TimekeeperEnforcer\" \"" + message + "\"");
+    }
+
+}

+ 68 - 0
src/au/pfip/logging.java

@@ -0,0 +1,68 @@
+package au.pfip;
+
+import java.time.LocalDate;
+import java.time.LocalTime;
+
+public class logging {
+
+    private static Boolean Initialised = false;
+    private static final FileHandling logFile = new FileHandling();
+
+    public static boolean initialiseLogging()
+    {
+        try
+        {
+            String newLogFile = settings.getSetting("LogFilePath") + "/timekeeper" + LocalDate.now().getDayOfMonth() + ".log";
+            logFile.initialiseFileHandling(newLogFile);
+
+        } catch (Exception e)
+        {
+            //noinspection CallToPrintStackTrace
+            e.printStackTrace();
+            return false;
+        }
+        Initialised = true;
+        return true;
+    }
+
+    public static void information(String message)
+    {
+        if(!Initialised)
+        {
+            stdOut(message);
+        }else{
+            try
+            {
+                logFile.write(LocalDate.now() + " " + LocalTime.now() + "~info~" + message);
+            } catch (Exception e)
+            {
+                //noinspection CallToPrintStackTrace
+                e.printStackTrace();
+            }
+        }
+    }
+
+    public static void error(String message)
+    {
+        if(!Initialised)
+        {
+            stdOut(message);
+        }else{
+            try
+            {
+                logFile.write(LocalDate.now() + "~Error~" + message);
+            } catch (Exception e)
+            {
+                //noinspection CallToPrintStackTrace
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private static void stdOut(String message)
+    {
+        System.out.print(LocalDate.now() + "~");
+        System.out.print("An error occurred before logging was setup.~");
+        System.out.println(message);
+    }
+}

+ 71 - 0
src/au/pfip/settings.java

@@ -0,0 +1,71 @@
+package au.pfip;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Properties;
+
+public class settings {
+
+    private static final Properties settings = new Properties();
+    private static String settingsFile;
+
+    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"};
+            Path path;
+
+            for (int i = 0; i <= (paths.length-1); i++) {
+                System.out.println("Checking for settings at " + paths[i]);
+                path = Path.of(paths[i]);
+                if(Files.exists(path))
+                {
+                    System.out.println("Found settings at " + path);
+                    settingsFile = String.valueOf(path);
+                    settings.loadFromXML(Files.newInputStream(path));
+                    return true;
+                }
+            }
+            path = Path.of(paths[2]);
+            settingsFile = paths[2];
+            Files.createDirectories(path.getParent());
+            createSettingsFile(String.valueOf(path.getParent()));
+            System.out.println("Settings file created at " + settingsFile);
+            return true;
+        } catch (IOException e) {
+            //noinspection CallToPrintStackTrace
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    private static void createSettingsFile(String logPath)
+    {
+        addSetting("TimekeeperActive", String.valueOf(true));
+        addSetting("LogFilePath", System.getProperty("user.home"));
+        addSetting("UsersToMonitor", "elliot,silas,gregory");
+        addSetting("ExemptNetwork", "RMS_BYOD");
+        saveSettingsFile();
+    }
+
+    public static void saveSettingsFile()
+    {
+        try {
+            settings.storeToXML(Files.newOutputStream(Path.of(settingsFile)), "Timekeeper configuration file.");
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static void addSetting(String key,String val)
+    {
+        settings.setProperty(key, String.valueOf(val));
+    }
+
+    public static String getSetting(String key)
+    {
+        return settings.getProperty(key);
+    }
+
+}