package com.alttd.storage; import com.alttd.config.ParticleConfig; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; import java.util.HashMap; import java.util.Map; @Slf4j public class AutoReload { private final WatchService watchService; private final Map keys; private final Path rootDirectory; private volatile boolean running = true; public AutoReload(Path directory) throws IOException { this.watchService = FileSystems.getDefault().newWatchService(); this.keys = new HashMap<>(); this.rootDirectory = directory; register(directory); registerAll(directory); } private void registerAll(Path start) throws IOException { Files.walkFileTree(start, new SimpleFileVisitor<>() { @Override public @NotNull FileVisitResult preVisitDirectory(@NotNull Path path, @NotNull BasicFileAttributes attrs) throws IOException { if (path.toFile().isDirectory()) { register(path); } return FileVisitResult.CONTINUE; } }); } private void register(@NotNull Path dir) throws IOException { WatchKey key = dir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY); keys.put(key, dir); } public void startWatching() { log.info("Starting watch thread."); Thread watchThread = new Thread(() -> { log.info("Watch thread started."); while (running) { log.info("Watch thread loop start"); WatchKey key; try { key = watchService.take(); log.info("Watch thread loop key {}", key.toString()); } catch (InterruptedException e) { log.error("Interrupted while waiting for key", e); return; } if (!running) { log.info("Exiting watch thread."); return; } Path dir = keys.get(key); if (dir == null) { log.warn("Detected unknown key: {}. Ignoring.", key.toString()); continue; } detectChanges(key, dir); if (!key.reset()) { keys.remove(key); if (keys.isEmpty()) { log.info("No longer watching any directories. Exiting."); break; } } } }); watchThread.start(); } private void detectChanges(@NotNull WatchKey key, Path dir) { for (WatchEvent event : key.pollEvents()) { WatchEvent.Kind kind = event.kind(); if (kind == StandardWatchEventKinds.OVERFLOW) { log.warn("Detected overflow event. Ignoring."); continue; } Path child = resolveEventPath(event, dir); boolean isDirectory = Files.isDirectory(child); if (shouldIgnoreDirectoryEvent(isDirectory, dir)) { continue; } if (kind == StandardWatchEventKinds.ENTRY_CREATE && isDirectory) { handleNewDirectoryCreation(child); continue; } if (isDirectory) { continue; } handleFileEvent(kind, child); } } private @NotNull Path resolveEventPath(@NotNull WatchEvent event, Path dir) { Object context = event.context(); if (!(context instanceof Path path)) { throw new IllegalArgumentException("Expected event context to be a Path, but got: " + context); } return dir.resolve(path); } private boolean shouldIgnoreDirectoryEvent(boolean isDirectory, Path dir) { if (isDirectory && !dir.equals(rootDirectory)) { log.warn("Detected directory {} outside of root directory. Ignoring.", dir); return true; } return false; } private void handleNewDirectoryCreation(Path child) { try { log.info("Registering new directory: {}", child); registerAll(child); } catch (IOException e) { log.error("Failed to register directory: {}", child); } } private void handleFileEvent(WatchEvent.Kind kind, Path child) { if (kind == StandardWatchEventKinds.ENTRY_MODIFY) { log.debug("Detected file modification: {}", child); reloadFile(child); } else if (kind == StandardWatchEventKinds.ENTRY_DELETE) { log.debug("Detected file deletion: {}", child); handleFileDeletion(); } else if (kind == StandardWatchEventKinds.ENTRY_CREATE) { log.debug("Detected file creation: {}", child); reloadFile(child); } else { log.warn("Unknown event kind: {}", kind); } } private void reloadFile(Path child) { ParticleConfig.loadParticleFromFile(child.toFile()); } private void handleFileDeletion() { log.info("Detected file deletion. Reloading all particles."); ParticleConfig.reload(); } public void stop() { running = false; } }