I am attempting to create a config handler that will automatically parse the updated config or remove the config if deleted. The watcher itself is listed below. The issue I am having is with the files
HashMap. In my constructor I check for already existing configs and add it to the hash map. This section is fine. However, when the timer executes and calls the run()
method the statement: Long storedModified = files.get(f);
is null causing every file to be an 'add' action.
public abstract class DirWatcher extends TimerTask {
private final File folder;
public HashMap<File, Long> files = new HashMap<>(); //Keeps track of modified time
public DirWatcher(String path) {
this.folder = new File(path);
System.out.println("Watching files on path: " path);
//gets initial files
File[] startingFiles = this.folder.listFiles(file -> file.getName().endsWith(".json"));
if(startingFiles == null || startingFiles.length < 1) return;
for (File file : startingFiles) {
System.out.println("Starting: File is " file.getName());
files.put(file, file.lastModified());
}
System.out.println(files); //Has values
}
public final void run() {
System.out.println(files); // = {} but should have those values from constructor
HashSet<File> checkedFiles = new HashSet<>(); //This is to check for deleted files.
for(File f : getConfigFiles()) { //Check the dir because there could be new files
Long storedModified = files.get(f); //see if we currently track it.
checkedFiles.add(f);
if(storedModified == null) { //if not, it is a new file.
files.put(f, f.lastModified());
onUpdate(f, "add");
}
else if(storedModified != f.lastModified()) { //It is an update to the file
files.put(f, f.lastModified()); //add it to tracker
onUpdate(f, "modified");
}
}
//Now check for deleted files. We can remove it from files hashmap and stop tracking.
Set<File> ref = files.keySet();
ref.removeAll(checkedFiles);
for (File deletedFile : ref) {
files.remove(deletedFile); //remove from tracking.
onUpdate(deletedFile, "delete");
}
}
public File[] getConfigFiles() {
return folder.listFiles(file -> file.getName().endsWith(".json"));
}
}
Implementation: Note that files
here actually prints the config files it has found so far.
public ConfigHandler(Instance instance) { //Instance just gets me the folder.
this.configDir = instance.getDataFolder();
this.path = this.configDir.getAbsolutePath();
System.out.println(this.configDir);
TimerTask configWatch = new DirWatcher(this.path) {
@Override
protected void onUpdate(File file, String action) {
System.out.println("[FILE WATCHER] Found updated file with action:" action ". File is " file.getName());
System.out.println(files);
}
};
Timer timer = new Timer();
timer.schedule(configWatch, new Date(), 5000);
}
I have been logging values at various points but I still don't understand why this hashmap is empty when my run() method is called.
EDIT: moved checking for deleted files outside of the for loop & add getConfigFiles method.
CodePudding user response:
It maybe a multi-threading question.
The class Timer
has a field called thread
, and the code is private final TimerThread thread = new TimerThread(queue);
If you are using the class java.util.Timer
.
In your code, timer.schedule(configWatch, new Date(), 5000);
means do first call immediately.
This is a synchronization problem, you can try this: private HashMap<File, Long> files = new ConcurrentHashMap<>();
ConcurrentHashMap
is a thread-safe Map.
This is another way to watch file changes: Watching a Directory for Changes in Java
It's also need another thread to do this job.
And, you can design a command to allow user reload config file manually.
Update
After I review your log, I found this:
[21:24:12 INFO]: [STDOUT] [RUN]: {C:\Users\user\Desktop\1.19 paper\plugins\HeadHunting\Untitled-1.json=1670799307531} in me.sam.Config.ConfigHandler$1@3f7c8e5b
[21:24:17 INFO]: [STDOUT] [RUN]: {} in me.sam.Config.ConfigHandler$1@3f7c8e5b
In first run, the files
field has value, in second run, it's empty.
I think, the problem is here.
//Now check for deleted files. We can remove it from files hashmap and stop tracking.
Set<File> ref = files.keySet();
ref.removeAll(checkedFiles);
for (File deletedFile : ref) {
files.remove(deletedFile); //remove from tracking.
onUpdate(deletedFile, "delete");
}
Here is some code from jdk, java.util.HashMap
public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
ks = new KeySet();
keySet = ks;
}
return ks;
}
// and the KeySet.remove method code
public final boolean remove(Object key) {
return removeNode(hash(key), key, null, false, true) != null;
}
I think, maybe you should do like this.
Set<File> ref = new HashSet<>(files.keySet());
ref.removeAll(checkedFiles);
// ...