refactor(Core): decompose constructCore() into focused init methods
build / build (push) Has been cancelled

- Extracted Core#constructCore into: initDirectories(), ensureDir(String),
  initOllamaUrl(), initLogWriter(), rotateLogFile(), initScheduler(),
  initShutdownHook(), closeLogWriter(), saveMessages(),
  buildMessagesArray(), writeMessagesTo(File, JSONArray)
- Overloaded constructors for Core
- Cleaned up some Javadocs

Fixed error(AddArrayMemory)
- Line 51 used the wrong value

Updated minor version for(::Core)

Dev note: Long overdue — that generic init block was ugly and is finally
laid to rest.
This commit is contained in:
2026-05-27 23:04:47 +02:00
parent 931c274cd3
commit 978cf8d7eb
3 changed files with 212 additions and 27 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ plugins {
}
group = 'me.zacharias.neurodock'
version = '1.5'
version = '1.6'
dependencies {
implementation project(":Plugin-API")
@@ -75,9 +75,15 @@ public class Core {
public static File CACHE_DIRECTORY;
static {
// This should not be enforced like this, instead this should read a data field, or wait for an init to be called....
// Unsure of how to properly do this at this time, however.
setDataDirectory("AI-Chat");
}
/**
* Set the data directory in appropriate locations depending on the host OS, falling back to $WORKING_DIR/data
* @param dataDirectory the data directory to use
*/
public static void setDataDirectory(String dataDirectory) {
String data;
@@ -120,11 +126,181 @@ public class Core {
}
}
/**
* Creates the base directories required by the application.
* <p>
* TODO: These paths should be resolved to OS-appropriate locations
* rather than being relative to the working directory.
*/
private void initDirectories() {
ensureDir("./logs/");
ensureDir("./pythonFiles/");
ensureDir("./messages/");
}
/**
* Ensures that a directory exists at the given path, creating it if it does not.
*
* @param path The path of the directory to create
*/
private static void ensureDir(String path) {
File dir = new File(path);
if (!dir.exists()) dir.mkdir();
}
/**
* Constructs the Ollama API {@link #url} from {@link #ollamaIP} and {@link #ollamaPort}.
*
* @throws RuntimeException If the resulting URL is malformed or the URI syntax is invalid
*/
private void initOllamaUrl() {
try {
url = new URI("http://" + ollamaIP + ":" + ollamaPort + "/api/chat").toURL();
} catch (MalformedURLException | URISyntaxException e) {
throw new RuntimeException("Failed to construct Ollama URL", e);
}
}
/**
* Initializes the {@link #logWriter}, rotating any pre-existing log file beforehand.
*
* @throws RuntimeException If the log file cannot be created or opened for writing
* @see #rotateLogFile()
*/
private void initLogWriter() {
try {
if (logFile.exists()) {
rotateLogFile();
}
logWriter = new BufferedWriter(new FileWriter(logFile));
} catch (IOException e) {
throw new RuntimeException("Failed to initialize log writer", e);
}
}
/**
* Rotates the existing log file by renaming it to a timestamped filename derived from
* its first line, then recreates {@link #logFile} as a fresh {@code latest.log}.
* <p>
* If the existing log file is empty, it is simply deleted and recreated.
*
* @throws IOException If the log file cannot be read, renamed, deleted, or recreated
*/
private void rotateLogFile() throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(logFile))) {
String line = br.readLine();
if (line != null) {
String date = line.substring(0, line.indexOf(">")).replaceAll("[/:]", "-");
logFile.renameTo(new File(logFile.getParentFile(), date + ".log"));
logFile = new File("./logs/latest.log");
} else {
System.out.println("Existing log file is empty, overwriting it!");
logFile.delete();
}
}
logFile.createNewFile();
}
/**
* Initializes the {@link #scheduler} and schedules periodic flushing of {@link #logWriter}
* to disk every 3 minutes.
*/
private void initScheduler() {
this.scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
try {
logWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}
}, 0, 3, TimeUnit.MINUTES);
}
/**
* Registers a JVM shutdown hook that shuts down the {@link #scheduler},
* flushes and closes the {@link #logWriter}, and persists the current
* session's messages to disk.
*
* @see #closeLogWriter()
* @see #saveMessages()
*/
private void initShutdownHook() {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
scheduler.shutdownNow();
closeLogWriter();
saveMessages();
}));
}
/**
* Flushes and closes the {@link #logWriter}.
* <p>
* Any {@link IOException} thrown during this process is suppressed, as the writer
* may already be closed by the time the shutdown hook runs.
*/
private void closeLogWriter() {
try {
logWriter.flush();
logWriter.close();
} catch (IOException ignore) {
System.out.println("Failed to flush log file, but that is not a problem.");
}
}
/**
* Persists the current session's messages from the {@link OllamaObject} to two locations:
* a timestamped archive file under {@code ./messages/}, and a rolling {@code messages.json}
* under {@link #DATA_DIR} for resuming the session later.
*
* @see #buildMessagesArray()
* @see #writeMessagesTo(File, JSONArray)
*/
private void saveMessages() {
JSONArray messages = buildMessagesArray();
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd_HH-mm-ss"));
writeMessagesTo(new File("./messages/" + timestamp + ".json"), messages);
writeMessagesTo(new File(DATA_DIR, "messages.json"), messages);
}
/**
* Collects all messages from the {@link OllamaObject} and serializes them into a {@link JSONArray}.
*
* @return A {@link JSONArray} containing all current messages
*/
private JSONArray buildMessagesArray() {
JSONArray messages = new JSONArray();
for (OllamaMessage message : ollamaObject.getMessages()) {
messages.put(new JSONObject(message.toString()));
}
return messages;
}
/**
* Writes a {@link JSONArray} of messages to the given file, overwriting it if it already exists.
*
* @param file The target file to write to
* @param messages The message content to write
* @throws RuntimeException If the file cannot be created or written to
*/
private void writeMessagesTo(File file, JSONArray messages) {
try {
if (file.exists()) file.delete();
file.createNewFile();
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
writer.write(messages.toString());
}
} catch (IOException e) {
throw new RuntimeException("Failed to write messages to " + file.getPath(), e);
}
}
/**
* This function is pending a better name, as this was introduced from being a generic block. This was ugly, and ideally things here should be refactored more.
*
* TODO: Fix a better name and refactor this method.
*/
@Deprecated(forRemoval = true)
private void constructCore(){
File dir = new File("./logs/");
if (!dir.exists()) {
@@ -226,34 +402,37 @@ public class Core {
}
/**
* Creates a new instance of Core with the provided PrintMessageHandler
* @param printMessageHandler The PrintMessageHandler to use as the default Output
* Creates a new instance of Core with the provided PrintMessageHandler,
* defaulting the Ollama backend to {@code localhost}.
*
* @param printMessageHandler The PrintMessageHandler to use as the default output
*/
public Core(@NotNull PrintAdvanceMessageHandler printMessageHandler) {
this.printMessageHandler = printMessageHandler;
ollamaIP = "localhost";
// TODO: This is a dirty way to not have a masisive init block, please check this methods comment.
constructCore();
this(printMessageHandler, "localhost");
}
/**
* Creates a new instance of Core with the provided PrintMessageHandler, with a specific IP used for Ollama
* @param printMessageHandler The PrintMessageHandler to use as the default Output
* @param ollamaIP The IP used for the Ollama backend
* Creates a new instance of Core with the provided PrintMessageHandler
* and a specific Ollama backend address.
*
* @param printMessageHandler The PrintMessageHandler to use as the default output
* @param ollamaIP The IP or hostname of the Ollama backend
*/
public Core(@NotNull PrintAdvanceMessageHandler printMessageHandler, @NotNull String ollamaIP)
{
this.printMessageHandler = printMessageHandler;
this.ollamaIP = ollamaIP;
// TODO: This is a dirty way to not have a masisive init block, please check this methods comment.
constructCore();
initDirectories();
initOllamaUrl();
initLogWriter();
initScheduler();
initShutdownHook();
}
/**
* Sets the {@link #ollamaObject} object to the provided argument,
* Also adds the memory base system. see {@link Core#setOllamaObjectNoMemory} if you don't want to add memory functions
* Also adds the memory base system. See {@link Core#setOllamaObjectNoMemory} if you don't want to add memory functions
* @param ollamaObject The OllamaObject to use
*/
public void setOllamaObject(OllamaObject ollamaObject) {
@@ -317,7 +496,6 @@ public class Core {
{
for(Pair<OllamaFunctionTool, String> tool : tools)
{
addTool(tool.getKey(), tool.getValue());
}
}
@@ -338,16 +516,24 @@ public class Core {
return ollamaObject;
}
/**
* Removes a tool with a given name
* @param name The tool to remove
*/
public void removeTool(String name) {
Pair<OllamaFunctionTool, String> funtionTool = funtionTools.stream().filter(tool -> tool.getKey().name().equalsIgnoreCase(name)).findFirst().orElse(null);
funtionTools.remove(funtionTool);
if(funtionTool.getKey() == null) {
// This should never happens... So if it does, Shit hit the fan
Exception e = new IllegalArgumentException("Function tool with name '"+name+"' does not exist");
e.printStackTrace();
funtionTools.stream()
.filter(tool -> tool.getKey().name().equalsIgnoreCase(name))
.findFirst()
.ifPresentOrElse(tool -> {
funtionTools.remove(tool);
ollamaObject.removeTool(tool.getKey());
}, () -> {
new IllegalArgumentException("Function tool with name '"+name+"' does not exist")
.printStackTrace();
System.exit(1);
}
ollamaObject.removeTool(funtionTool.getKey());
});
}
/**
@@ -461,8 +647,7 @@ public class Core {
if(functions.isEmpty()) {
OllamaToolError ollamaToolError = new OllamaToolError("Function '" + function.getString("name") + "' does not exist");
ollamaObject.addMessage(ollamaToolError);
printMessageHandler.printErrorMessage(ollamaToolError);
writeLog("Failed function call to "+function.getString("name"));
}
else {
@@ -48,7 +48,7 @@ public class AddArrayMemory extends OllamaFunctionTool {
if(!args[0].argument().equals("memory")) {
throw new OllamaToolErrorException(name(), "no memory provided");
}
this.memory.addMemory(name(), args[0].argument());
this.memory.addMemory(name(), (String) args[0].value());
}
return new OllamaToolResponse(name(), "Added arrayed memory");
}