Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
197 changes: 186 additions & 11 deletions src/main/java/com/nyxz/fabric/locationwebsocket/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The field name errorFilePath suggests it stores a path, but it's declared as File. Consider renaming to errorFile for consistency with how it's used, or change the type to Path if you want to emphasize it's a path.

Copilot uses AI. Check for mistakes.
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();
}
Comment on lines +65 to +67
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file truncation logic creates a new ByteChannel but doesn't write anything, which means the errorFile Writer (created on line 43) may be writing to a file that gets truncated afterwards. Move the truncation logic before creating the FileWriter, or use FileWriter with append=false to handle truncation during construction.

Copilot uses AI. Check for mistakes.
catch (IOException e){
LocationWebSocket.LOGGER.error("Failed to create error log file", e);
}
}

public static final Codec<Config> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Expand All @@ -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() {
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method name getErrorFile() suggests it returns a File object, but it actually returns a Writer. Consider renaming to getErrorWriter() or getErrorFileWriter() to better reflect what it returns.

Suggested change
public Writer getErrorFile() {
public Writer getErrorWriter() {

Copilot uses AI. Check for mistakes.
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
Expand All @@ -56,6 +111,7 @@ public static Config load(Path configPath) {
File file = configPath.toFile();

Config config = Config.DEFAULT;

if(file.exists()) {
try {
DataResult<Config> result = Config.CODEC.parse(
Expand All @@ -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;
}
Comment on lines 167 to 257
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logging methods logErrors, logInfo, and logDebug have significant code duplication. Consider extracting the common logic into a private helper method like log(String level, String message) to improve maintainability.

Copilot uses AI. Check for mistakes.
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -21,7 +22,7 @@ public class LocationWebSocket implements ModInitializer {

public static Config CONFIG;

public static List<ERRORS> errors = new ArrayList<com.nyxz.fabric.locationwebsocket.handler.ERRORS>();
public static List<ERRORS> errors = new ArrayList<>();


/**
Expand All @@ -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.");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
public enum ERRORS {
NO_PLAYERS_ONLINE,
WEBSOCKET_NOT_CONNECTED,
UNKNOWN_ERROR
UNKNOWN_ERROR,
ERROR_FILE_WRITE_FAILURE
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -32,13 +34,16 @@ 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
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());
}
Expand All @@ -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);
}
Expand Down
Loading