Automatically load particles that are added to or updated in the particles folder
This commit is contained in:
parent
02c4f818a0
commit
ab86d77069
|
|
@ -7,16 +7,23 @@ import com.alttd.config.ParticleConfig;
|
|||
import com.alttd.database.Database;
|
||||
import com.alttd.listeners.*;
|
||||
import com.alttd.objects.APartType;
|
||||
import com.alttd.storage.AutoReload;
|
||||
import com.alttd.util.Logger;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class AltitudeParticles extends JavaPlugin {
|
||||
|
||||
@Getter
|
||||
public static AltitudeParticles instance;
|
||||
|
||||
private static AutoReload autoReload = null;
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
instance = this;
|
||||
|
|
@ -46,9 +53,44 @@ public class AltitudeParticles extends JavaPlugin {
|
|||
}
|
||||
|
||||
public void reload() {
|
||||
Logger.info("Reloading AltitudeParticles...");
|
||||
Config.reload();
|
||||
Logger.info("1");
|
||||
DatabaseConfig.reload();
|
||||
Logger.info("2");
|
||||
ParticleConfig.reload();
|
||||
Logger.info("3");
|
||||
Path path = Path.of(Config.AUTO_RELOAD_PATH);
|
||||
Logger.info("4");
|
||||
File file = path.toFile();
|
||||
Logger.info("5");
|
||||
if (file.exists() && file.isDirectory()) {
|
||||
Logger.info("6");
|
||||
try {
|
||||
Logger.info("7");
|
||||
if (autoReload != null) {
|
||||
Logger.info("8-bad");
|
||||
autoReload.stop();
|
||||
Logger.info("9-bad");
|
||||
}
|
||||
Logger.info("8");
|
||||
autoReload = new AutoReload(path);
|
||||
Logger.info("9");
|
||||
autoReload.startWatching();
|
||||
Logger.info("10");
|
||||
} catch (IOException e) {
|
||||
Logger.info("error 1");
|
||||
Logger.severe("Failed to start AutoReload at path %", Config.AUTO_RELOAD_PATH);
|
||||
Logger.info("error 2");
|
||||
Logger.error("Failed to start AutoReload", e);
|
||||
Logger.info("error 3");
|
||||
}
|
||||
Logger.info("11");
|
||||
} else {
|
||||
Logger.info("6-bad");
|
||||
Logger.severe("Failed to start AutoReload at path %", Config.AUTO_RELOAD_PATH);
|
||||
}
|
||||
Logger.info("12");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,4 +98,9 @@ public final class Config extends AbstractConfig {
|
|||
CLICK_BLOCK_COOL_DOWN = config.getInt("cool_down.click-block", CLICK_BLOCK_COOL_DOWN);
|
||||
TELEPORT_ARRIVE_COOL_DOWN = config.getInt("cool_down.teleport-arrive", TELEPORT_ARRIVE_COOL_DOWN);
|
||||
}
|
||||
|
||||
public static String AUTO_RELOAD_PATH = "/mnt/configs/AltitudeParticles/particles";
|
||||
private static void loadAutoReload() {
|
||||
AUTO_RELOAD_PATH = config.getString("auto-reload.path", AUTO_RELOAD_PATH);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,26 +8,32 @@ import com.alttd.objects.ParticleSet;
|
|||
import com.alttd.storage.ParticleStorage;
|
||||
import com.alttd.util.Logger;
|
||||
import com.destroystokyo.paper.ParticleBuilder;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HexFormat;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.*;
|
||||
|
||||
public class ParticleConfig {
|
||||
|
||||
private static final int MAX_DEPTH = 1;
|
||||
private static final File particlesDir = new File(File.separator + "mnt" + File.separator + "configs"
|
||||
+ File.separator + "AltitudeParticles" + File.separator + "particles");
|
||||
private static ParticleConfig instance = null;
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
static {
|
||||
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
objectMapper.disable(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE);
|
||||
}
|
||||
|
||||
private static ParticleConfig getInstance() {
|
||||
if (instance == null)
|
||||
|
|
@ -37,25 +43,87 @@ public class ParticleConfig {
|
|||
|
||||
/**
|
||||
* Finds all files in particles directory that are valid .json files
|
||||
* Only searches one level deep into subdirectories
|
||||
*
|
||||
* @return all files found
|
||||
*/
|
||||
private List<File> getJsonFiles() {
|
||||
List<File> files = new ArrayList<>();
|
||||
|
||||
// Ensure particles directory exists
|
||||
if (!ensureParticlesDirectoryExists()) {
|
||||
return files;
|
||||
}
|
||||
|
||||
try {
|
||||
Files.walkFileTree(particlesDir.toPath(), getJsonFileVistor(files));
|
||||
} catch (IOException e) {
|
||||
Logger.warning("Error while traversing directory: " + e.getMessage());
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
private FileVisitor<? super @NotNull Path> getJsonFileVistor(List<File> files) {
|
||||
return new SimpleFileVisitor<>() {
|
||||
private int depth = 0;
|
||||
|
||||
@Override
|
||||
public @NotNull FileVisitResult preVisitDirectory(@NotNull Path dir, @NotNull BasicFileAttributes attrs) {
|
||||
if (depth > ParticleConfig.MAX_DEPTH) {
|
||||
return FileVisitResult.SKIP_SUBTREE;
|
||||
}
|
||||
depth++;
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FileVisitResult visitFile(@NotNull Path file, @NotNull BasicFileAttributes attrs) {
|
||||
File physicalFile = file.toFile();
|
||||
if (isValidJsonFile(physicalFile)) {
|
||||
files.add(physicalFile);
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FileVisitResult postVisitDirectory(@NotNull Path dir, IOException exc) {
|
||||
depth--;
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the particles directory exists and is a directory
|
||||
*
|
||||
* @return true if directory exists or was created successfully, false otherwise
|
||||
*/
|
||||
private boolean ensureParticlesDirectoryExists() {
|
||||
if (!particlesDir.exists()) {
|
||||
if (!particlesDir.mkdir())
|
||||
if (!particlesDir.mkdirs()) {
|
||||
Logger.warning("Unable to create particles directory");
|
||||
return files;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!particlesDir.isDirectory()) {
|
||||
Logger.warning("Particles directory doesn't exist (it's a file??)");
|
||||
return files;
|
||||
Logger.warning("Particles path exists but is not a directory: " + particlesDir.getAbsolutePath());
|
||||
return false;
|
||||
}
|
||||
File[] validFiles = particlesDir.listFiles(file -> file.isFile() && file.canRead() && file.getName().endsWith(".json"));
|
||||
if (validFiles == null)
|
||||
return files;
|
||||
files.addAll(List.of(validFiles));
|
||||
return files;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a file is a valid JSON file
|
||||
*
|
||||
* @param file the file to check
|
||||
* @return true if the file is a valid JSON file
|
||||
*/
|
||||
private boolean isValidJsonFile(File file) {
|
||||
return file.isFile() && file.canRead() && file.getName().endsWith(".json");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -149,9 +217,15 @@ public class ParticleConfig {
|
|||
|
||||
public static void reload() {
|
||||
ParticleStorage.clear();
|
||||
ParticleConfig instance = getInstance();
|
||||
instance = getInstance();
|
||||
|
||||
for (File file : instance.getJsonFiles()) {
|
||||
loadParticleFromFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
public static void loadParticleFromFile(File file) {
|
||||
instance = getInstance();
|
||||
try {
|
||||
ParticleData particleData = objectMapper.readValue(file, ParticleData.class);
|
||||
|
||||
|
|
@ -164,5 +238,4 @@ public class ParticleConfig {
|
|||
Logger.error("Error processing particle file " + file.getName(), exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,14 @@ import java.util.List;
|
|||
@Setter
|
||||
@Getter
|
||||
public class ParticleData {
|
||||
// TODO add optional property for a list of users that can use the particle
|
||||
// If that list is present the particle should be loaded as a dev particle
|
||||
// Dev particles should disable all others while in use and all be grouped together
|
||||
// (since the dev should know what each particle is and does)
|
||||
// Seeing dev particles should require a permission
|
||||
@JsonProperty("user_list")
|
||||
private List<String> userList;
|
||||
|
||||
@JsonProperty("particle_name")
|
||||
private String particleName;
|
||||
|
||||
|
|
|
|||
171
src/main/java/com/alttd/storage/AutoReload.java
Normal file
171
src/main/java/com/alttd/storage/AutoReload.java
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
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<WatchKey, Path> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,18 +2,26 @@ package com.alttd.storage;
|
|||
|
||||
import com.alttd.objects.APartType;
|
||||
import com.alttd.objects.ParticleSet;
|
||||
import com.alttd.util.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ParticleStorage {
|
||||
private static final HashMap<APartType, List<ParticleSet>> particles = new HashMap<>();
|
||||
|
||||
public static void addParticleSet(APartType aPartType, ParticleSet particleSet) {
|
||||
List<ParticleSet> particleSets = particles.getOrDefault(aPartType, new ArrayList<>());
|
||||
if (particleSets.contains(particleSet))
|
||||
Optional<ParticleSet> existingParticleSet = particleSets.stream()
|
||||
.filter(p -> p.getParticleId().equalsIgnoreCase(particleSet.getParticleId()))
|
||||
.findAny();
|
||||
if (existingParticleSet.isPresent()) {
|
||||
Logger.warning("Overwriting particle set %", particleSet.getParticleId());
|
||||
particleSets.remove(existingParticleSet.get());
|
||||
return;
|
||||
}
|
||||
particleSets.add(particleSet);
|
||||
particles.put(aPartType, particleSets);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user