diff --git a/gradle.properties b/gradle.properties index e0e6e62..80d9683 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ loader_version=0.18.1 loom_version=1.14-SNAPSHOT # Mod Properties -mod_version=1.0.0 +mod_version=1.1.0 maven_group=com.nyxz.fabric.locationwebsocket archives_base_name=locationwebsocket diff --git a/src/main/java/com/nyxz/fabric/locationwebsocket/Config.java b/src/main/java/com/nyxz/fabric/locationwebsocket/Config.java index 783a9ff..a6968f4 100644 --- a/src/main/java/com/nyxz/fabric/locationwebsocket/Config.java +++ b/src/main/java/com/nyxz/fabric/locationwebsocket/Config.java @@ -5,26 +5,69 @@ import com.mojang.serialization.DataResult; import com.mojang.serialization.JsonOps; import com.mojang.serialization.codecs.RecordCodecBuilder; +import com.nyxz.fabric.locationwebsocket.handler.ERRORS; import net.minecraft.util.StrictJsonParser; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import static net.fabricmc.fabric.impl.resource.loader.ModResourcePackUtil.GSON; public class Config { - public static int WEBSOCKET_PORT = 8080; - public static String WEBSOCKET_URL = "127.0.0.1"; + public static int webSocketPort = 8080; + public static String webSocketUrl = "127.0.0.1"; + + public static boolean enableMod = true; + + + public static File errorFilePath; + public static Writer errorFile; + /** + * Lock object for synchronizing access to errorFile. + * All code that writes to errorFile must synchronize on this lock. + */ + private static final Object errorFileLock = new Object(); + + /** + * Thread-safe helper for writing to the error file. + * Usage: Config.writeErrorLine("message"); + */ + public static void writeErrorLine(String line) throws IOException { + if (errorFile != null) { + synchronized (errorFileLock) { + errorFile.write(line); + errorFile.write(System.lineSeparator()); + errorFile.flush(); + } + } + } /** * Constructor for the Config file - * @param WEBSOCKET_PORT The port for the WebSocket server - * @param WEBSOCKET_URL The URL for the WebSocket server + * @param webSocketPort The port for the WebSocket server + * @param webSocketUrl The URL for the WebSocket server */ - public Config(int WEBSOCKET_PORT, String WEBSOCKET_URL) { - Config.WEBSOCKET_PORT = WEBSOCKET_PORT; - Config.WEBSOCKET_URL = WEBSOCKET_URL; + public Config(int webSocketPort, String webSocketUrl, boolean enableMod) { + Config.enableMod = enableMod; + Config.webSocketPort = webSocketPort; + Config.webSocketUrl = webSocketUrl; + } + + public Config(int webSocketPort, String webSocketUrl, boolean enableMod, File errorFile) throws IOException { + Config.enableMod = enableMod; + Config.webSocketPort = webSocketPort; + Config.webSocketUrl = webSocketUrl; + Config.errorFile = new FileWriter(errorFile); + Config.errorFilePath = errorFile; + + try{ + Files.newByteChannel(errorFilePath.toPath(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING).close(); + } + catch (IOException e){ + LocationWebSocket.LOGGER.error("Failed to create error log file", e); + } } public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( @@ -33,19 +76,31 @@ public Config(int WEBSOCKET_PORT, String WEBSOCKET_URL) { .forGetter(Config::getWebsocketPort), Codec.STRING .fieldOf("websocketURL") - .forGetter(Config::getWebsocketUrl) + .forGetter(Config::getWebsocketUrl), + Codec.BOOL + .fieldOf("enableMod") + .forGetter(Config::getEnabled) ).apply(instance, Config::new)); + public int getWebsocketPort() { - return WEBSOCKET_PORT; + return webSocketPort; } public String getWebsocketUrl() { - return WEBSOCKET_URL; + return webSocketUrl; + } + + public boolean getEnabled() { + return enableMod; + } + + public Writer getErrorFile() { + return errorFile; } // - private static final Config DEFAULT = new Config(8080, "localhost"); + private static final Config DEFAULT = new Config(8080, "localhost", true); /** * Load configuration from file, or create default if not present @@ -56,6 +111,7 @@ public static Config load(Path configPath) { File file = configPath.toFile(); Config config = Config.DEFAULT; + if(file.exists()) { try { DataResult result = Config.CODEC.parse( @@ -78,6 +134,125 @@ public static Config load(Path configPath) { LocationWebSocket.LOGGER.info("Config at {} is not writable or readable. Using default config.", configPath); } + if (!config.getEnabled()) { + LocationWebSocket.LOGGER.info("Mod is disabled."); + return config; + } + + try{ + File errorFile = new File(configPath.getParent().toFile(), "error.log"); + config = new Config( + config.getWebsocketPort(), + config.getWebsocketUrl(), + config.getEnabled(), + errorFile + ); + } catch (IOException e) { + config = new Config( + config.getWebsocketPort(), + config.getWebsocketUrl(), + config.getEnabled() + ); + LocationWebSocket.LOGGER.error("Failed to create error log file", e); + } + return config; } + + /** + * Log errors to the error file + * @param message The error message + * @return True if the error was logged, false otherwise + */ + public static boolean logErrors(String message) { + if (LocationWebSocket.CONFIG.getErrorFile() != null) { + try { + LocationWebSocket.CONFIG.getErrorFile().write("[ERROR]: " + message + "\n"); + LocationWebSocket.CONFIG.getErrorFile().flush(); + LocationWebSocket.errors.remove(ERRORS.ERROR_FILE_WRITE_FAILURE); + return true; + } catch (IOException e) { + if (LocationWebSocket.errors.contains(ERRORS.ERROR_FILE_WRITE_FAILURE)) { + return false; + } + LocationWebSocket.errors.add(ERRORS.ERROR_FILE_WRITE_FAILURE); + LocationWebSocket.LOGGER.error("Failed to write to error log file", e); + return false; + } + } + return false; + } + + /** + * Log errors to the error file with throwable + * @param message The error message + * @param throwable The throwable + * @return True if the error was logged, false otherwise + */ + public static boolean logErrors(String message, Throwable throwable) { + if (LocationWebSocket.CONFIG.getErrorFile() != null) { + try { + LocationWebSocket.CONFIG.getErrorFile().write("[ERROR]: " + message + " " + throwable.toString() + "\n"); + LocationWebSocket.CONFIG.getErrorFile().flush(); + LocationWebSocket.errors.remove(ERRORS.ERROR_FILE_WRITE_FAILURE); + return true; + } catch (IOException e) { + if (LocationWebSocket.errors.contains(ERRORS.ERROR_FILE_WRITE_FAILURE)) { + return false; + } + LocationWebSocket.errors.add(ERRORS.ERROR_FILE_WRITE_FAILURE); + LocationWebSocket.LOGGER.error("Failed to write to error log file", e); + return false; + } + } + return false; + } + + /** + * Log info messages to the error file + * @param message The info message + * @return True if the info was logged, false otherwise + */ + public static boolean logInfo(String message) { + if (LocationWebSocket.CONFIG.getErrorFile() != null) { + try{ + LocationWebSocket.CONFIG.getErrorFile().write("[INFO]: " + message + "\n"); + LocationWebSocket.CONFIG.getErrorFile().flush(); + LocationWebSocket.errors.remove(ERRORS.ERROR_FILE_WRITE_FAILURE); + return true; + } catch (IOException e) { + if (LocationWebSocket.errors.contains(ERRORS.ERROR_FILE_WRITE_FAILURE)){ + return false; + } + LocationWebSocket.errors.add(ERRORS.ERROR_FILE_WRITE_FAILURE); + LocationWebSocket.LOGGER.error("Failed to write to error log file", e); + return false; + } + } + return false; + } + + /** + * Log debug messages to the error file + * @param message The debug message + * @return True if the debug was logged, false otherwise + */ + public static boolean logDebug(String message) { + if (LocationWebSocket.CONFIG.getErrorFile() != null) { + try { + LocationWebSocket.CONFIG.getErrorFile().write("[DEBUG]: " + message + "\n"); + LocationWebSocket.CONFIG.getErrorFile().flush(); + LocationWebSocket.errors.remove(ERRORS.ERROR_FILE_WRITE_FAILURE); + return true; + } catch (IOException e) { + if (LocationWebSocket.errors.contains(ERRORS.ERROR_FILE_WRITE_FAILURE)) { + return false; + } + LocationWebSocket.errors.add(ERRORS.ERROR_FILE_WRITE_FAILURE); + LocationWebSocket.LOGGER.error("Failed to write to error log file", e); + return false; + } + } + return false; + } } diff --git a/src/main/java/com/nyxz/fabric/locationwebsocket/LocationWebSocket.java b/src/main/java/com/nyxz/fabric/locationwebsocket/LocationWebSocket.java index f515966..83d264f 100644 --- a/src/main/java/com/nyxz/fabric/locationwebsocket/LocationWebSocket.java +++ b/src/main/java/com/nyxz/fabric/locationwebsocket/LocationWebSocket.java @@ -7,6 +7,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; @@ -21,7 +22,7 @@ public class LocationWebSocket implements ModInitializer { public static Config CONFIG; - public static List errors = new ArrayList(); + public static List errors = new ArrayList<>(); /** @@ -36,10 +37,12 @@ public void onInitialize() { try { - Path configPath = FabricLoader.getInstance().getConfigDir().resolve("LocationWebsocket_settings.json"); + Path configDir = FabricLoader.getInstance().getConfigDir().resolve("LocationWebSocket"); + Files.createDirectories(configDir); + Path configPath = configDir.resolve("config.json"); LocationWebSocket.CONFIG = Config.load(configPath); } catch (Exception e) { - LOGGER.error("Failed to load config: " + e.getMessage()); + LOGGER.error("Failed to load config: {}", e.getMessage()); LOGGER.error("Mod will not function correctly (or at all) without a valid config."); } diff --git a/src/main/java/com/nyxz/fabric/locationwebsocket/handler/ERRORS.java b/src/main/java/com/nyxz/fabric/locationwebsocket/handler/ERRORS.java index 9277251..21cbefc 100644 --- a/src/main/java/com/nyxz/fabric/locationwebsocket/handler/ERRORS.java +++ b/src/main/java/com/nyxz/fabric/locationwebsocket/handler/ERRORS.java @@ -3,5 +3,6 @@ public enum ERRORS { NO_PLAYERS_ONLINE, WEBSOCKET_NOT_CONNECTED, - UNKNOWN_ERROR + UNKNOWN_ERROR, + ERROR_FILE_WRITE_FAILURE } diff --git a/src/main/java/com/nyxz/fabric/locationwebsocket/handler/WebSocket.java b/src/main/java/com/nyxz/fabric/locationwebsocket/handler/WebSocket.java index 62e8d34..4539ead 100644 --- a/src/main/java/com/nyxz/fabric/locationwebsocket/handler/WebSocket.java +++ b/src/main/java/com/nyxz/fabric/locationwebsocket/handler/WebSocket.java @@ -14,13 +14,15 @@ public WebSocket(URI serverUri) { } public WebSocket(){ - super(URI.create("ws://" + Config.WEBSOCKET_URL + ":" + Config.WEBSOCKET_PORT)); + super(URI.create("ws://" + Config.webSocketUrl + ":" + Config.webSocketPort)); } @Override public void onOpen(ServerHandshake handshakedata) { // LocationWebSocket.LOGGER.info("WebSocket connection opened."); //No need for a logger here, creates a lot of spam. (only debug purpose) + +// Config.logInfo("WebSocket connection opened."); } @Override @@ -32,6 +34,8 @@ public void onMessage(String message) { public void onClose(int code, String reason, boolean remote) { // LocationWebSocket.LOGGER.info("WebSocket connection closed: {}", reason); //No need for a logger here, creates a lot of spam. (only debug purpose) + + //Config.logInfo("WebSocket connection closed: " + reason); } @Override @@ -39,6 +43,7 @@ public void onError(Exception e) { if (LocationWebSocket.errors.contains(ERRORS.WEBSOCKET_NOT_CONNECTED)){ return; } + Config.logErrors("WebSocket error: " + e.getMessage()); LocationWebSocket.errors.add(ERRORS.WEBSOCKET_NOT_CONNECTED); LocationWebSocket.LOGGER.error("WebSocket error. {}", e.toString()); } @@ -49,10 +54,15 @@ public void sendMessage(String message) { connectBlocking(); } send(message); + if (LocationWebSocket.errors.contains(ERRORS.WEBSOCKET_NOT_CONNECTED)){ + Config.logDebug("WebSocket debug: Connection re-established."); + LocationWebSocket.errors.remove(ERRORS.WEBSOCKET_NOT_CONNECTED); + } } catch (Exception e) { if (LocationWebSocket.errors.contains(ERRORS.WEBSOCKET_NOT_CONNECTED)){ return; } + Config.logErrors("WebSocket error: " + e.getMessage()); LocationWebSocket.errors.add(ERRORS.WEBSOCKET_NOT_CONNECTED); LocationWebSocket.LOGGER.error("Failed to send WebSocket message.", e); } diff --git a/src/main/java/com/nyxz/fabric/locationwebsocket/mixin/TickEventsMixin.java b/src/main/java/com/nyxz/fabric/locationwebsocket/mixin/TickEventsMixin.java index 73b6187..46117ac 100644 --- a/src/main/java/com/nyxz/fabric/locationwebsocket/mixin/TickEventsMixin.java +++ b/src/main/java/com/nyxz/fabric/locationwebsocket/mixin/TickEventsMixin.java @@ -1,8 +1,8 @@ package com.nyxz.fabric.locationwebsocket.mixin; +import com.nyxz.fabric.locationwebsocket.Config; import com.nyxz.fabric.locationwebsocket.LocationWebSocket; import com.nyxz.fabric.locationwebsocket.handler.ERRORS; -import com.nyxz.fabric.locationwebsocket.handler.ERRORS; import com.nyxz.fabric.locationwebsocket.handler.Location; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.minecraft.server.MinecraftServer; @@ -20,26 +20,33 @@ public class TickEventsMixin { */ @Inject(at = @At("HEAD"), method = "loadLevel") private void init(CallbackInfo info) { - try { - LocationWebSocket.LOGGER.info("World initialized on server and ready to send tick events."); - Location.server = (MinecraftServer) (Object) this; - } - catch (Exception e) { - LocationWebSocket.LOGGER.error("Error during world initialization: " + e.getMessage()); - } - ServerTickEvents.START_WORLD_TICK.register(client -> { + if (LocationWebSocket.CONFIG != null && LocationWebSocket.CONFIG.getEnabled()) { try { - Thread thread = new Thread(new Location()); - thread.start(); + Config.logInfo("World initialized on server and ready to send tick events."); + LocationWebSocket.LOGGER.info("World initialized on server and ready to send tick events."); + Location.server = (MinecraftServer) (Object) this; + } catch (Exception e) { + Config.logErrors("Error during world initialization: " + e.getMessage()); + LocationWebSocket.LOGGER.error("Error during world initialization: " + e.getMessage()); } - catch (Exception e) { - if (LocationWebSocket.errors.contains(ERRORS.UNKNOWN_ERROR)) { - return; + ServerTickEvents.START_WORLD_TICK.register(client -> { + try { + Thread thread = new Thread(new Location()); + thread.start(); + if (LocationWebSocket.errors.contains(ERRORS.UNKNOWN_ERROR)){ + Config.logDebug("WebSocket debug: Connection re-established."); + LocationWebSocket.errors.remove(ERRORS.UNKNOWN_ERROR); + } + } catch (Exception e) { + Config.logErrors("Error during tick event: " + e.getMessage()); + if (LocationWebSocket.errors.contains(ERRORS.UNKNOWN_ERROR)) { + return; + } + LocationWebSocket.errors.add(ERRORS.UNKNOWN_ERROR); + LocationWebSocket.LOGGER.error("Error during tick event: " + e.getMessage()); } - LocationWebSocket.errors.add(ERRORS.UNKNOWN_ERROR); - LocationWebSocket.LOGGER.error("Error during tick event: " + e.getMessage()); - } - }); + }); + } } } \ No newline at end of file