Skip to content

Commit

Permalink
Auto-load config files when the backing file changes
Browse files Browse the repository at this point in the history
  • Loading branch information
Sollace committed Jul 23, 2024
1 parent 76ab98a commit 17691ba
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 2 deletions.
113 changes: 113 additions & 0 deletions src/main/java/com/minelittlepony/common/util/io/PathMonitor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package com.minelittlepony.common.util.io;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchEvent.Kind;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;

public class PathMonitor implements AutoCloseable {
private static final Logger LOGGER = LogManager.getLogger();
private static final Executor EXECUTOR = CompletableFuture.delayedExecutor(1, TimeUnit.SECONDS);

private final Object locker = new Object();

private final Consumer<Event> callback;

@Nullable
private WatchKey key;
@Nullable
private Path watchedPath;

private boolean paused;

private final Runnable pollTask = () -> {
tick();
EXECUTOR.execute(this.pollTask);
};

public PathMonitor(Consumer<Event> callback) {
this.callback = callback;
pollTask.run();
}

public void set(Path newFile) {
synchronized (locker) {
try {
close();
watchedPath = newFile;
key = newFile.getParent().register(newFile.getParent().getFileSystem().newWatchService(),
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_CREATE
);
} catch (IOException e) {
close();
LOGGER.error(e);
}
}
}

public void wrap(Runnable action) {
synchronized (locker) {
try {
paused = true;
action.run();
} finally {
pollEvents();
paused = false;
}
}
}

public List<WatchEvent<?>> pollEvents() {
synchronized (locker) {
if (key == null || !key.isValid()) {
return List.of();
}

return key.pollEvents();
}
}

public void tick() {
synchronized (locker) {
for (WatchEvent<?> ev : pollEvents()) {
if (!paused && ev.context() instanceof Path p && (watchedPath != null && watchedPath.endsWith(p))) {
Kind<?> kind = ev.kind();

if (StandardWatchEventKinds.ENTRY_DELETE.equals(kind)) {
callback.accept(Event.DELETE);
} else if (StandardWatchEventKinds.ENTRY_MODIFY.equals(kind) || StandardWatchEventKinds.ENTRY_CREATE.equals(kind)) {
callback.accept(Event.MODIFY);
}
}
}
}
}

@Override
public void close() {
if (key != null) {
pollEvents();
key.cancel();
key = null;
}
watchedPath = null;
}

public enum Event {
MODIFY,
DELETE
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* @author Sollace
*
*/
@Deprecated
public class ClippingSpace {

/**
Expand Down
30 changes: 28 additions & 2 deletions src/main/java/com/minelittlepony/common/util/settings/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

import java.nio.file.Path;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Stream;

import com.minelittlepony.common.util.io.PathMonitor;

/**
* A configuration container that lets you programmatically index values by a key.
*/
Expand All @@ -18,9 +21,32 @@ public abstract class Config implements Iterable<Grouping> {
private final Adapter adapter;
private final Path path;

private final List<Consumer<Config>> listeners = new ArrayList<>();

@SuppressWarnings("unchecked")
private final PathMonitor monitor = new PathMonitor(event -> {
switch (event) {
case MODIFY:
load();
break;
case DELETE:
categories.forEach((name, category) -> {
category.entries().forEach(setting -> {
((Setting<Object>)setting).set(setting.getDefault());
});
});
}
listeners.forEach(listener -> listener.accept(this));
});

protected Config(Adapter adapter, Path path) {
this.adapter = adapter;
this.path = path;
monitor.set(path);
}

public void onChangedExternally(Consumer<Config> listener) {
listeners.add(listener);
}

/**
Expand Down Expand Up @@ -91,11 +117,11 @@ public Iterator<Grouping> iterator() {
* Commits any unsaved changes for this config.
*/
public void save() {
adapter.save(this, path);
monitor.wrap(() -> adapter.save(this, path));
}

public void load() {
adapter.load(this, path);
monitor.wrap(() -> adapter.load(this, path));
}

public interface Adapter {
Expand Down

0 comments on commit 17691ba

Please sign in to comment.