diff --git a/src/dataStructures/MonitoredFile.java b/src/dataStructures/MonitoredFile.java new file mode 100644 index 0000000..ae53ca3 --- /dev/null +++ b/src/dataStructures/MonitoredFile.java @@ -0,0 +1,37 @@ +package dataStructures; + +import java.nio.file.Path; + +public class MonitoredFile implements Comparable +{ + public final Path path; + public final Long lastModified; + + public MonitoredFile(Path path, Long lastModified) + { + this.path = path; + this.lastModified = lastModified; + } + + // Override equals operator - used for .contains(...) on list items + @Override + public boolean equals(Object other) + { + if(this.compareTo(other) == 0) + return true; + + return false; + } + + @Override + // Comparison + public int compareTo(Object other) + { + int strCmp = path.getFileName().toString().compareTo(((MonitoredFile)other).path.getFileName().toString()); + + if(strCmp == 0) + return lastModified.compareTo(((MonitoredFile)other).lastModified); + + return strCmp; + } +} diff --git a/src/utils/DirectoryMonitor.java b/src/utils/DirectoryMonitor.java new file mode 100644 index 0000000..59a80e8 --- /dev/null +++ b/src/utils/DirectoryMonitor.java @@ -0,0 +1,160 @@ +package utils; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import dataStructures.MonitoredFile; + +// NOTE: Consider shifting internal behavior to https://docs.oracle.com/javase/tutorial/essential/io/notification.html +// for external stability and support +public class DirectoryMonitor +{ + // Logging + private void Log(String s) { GlobalLog.Log(LogFilter.Util, s); } + private void Warn(String s) { GlobalLog.Warn(LogFilter.Util, s); } + private void Error(String s) { GlobalLog.Error(LogFilter.Util, s); } + + // Variables + private String directory; + private ArrayList last; + + // Constructs a new file monitor and logs initialization + public DirectoryMonitor(String directory) + { + Log("Reading directory files for montioring in: " + directory.toString()); + + this.directory = directory; + this.last = Scrape(); + + Print(last); + } + + // Check when a file was last modified by path + private Long LastModified(Path path) + { + File file = new File(path.toString()); + + if(file.exists()) + { + return file.lastModified(); + } + else + { + Error("File looking for doesn't exist for directory monitor at path: " + path); + return null; + } + } + + // Scrape the target directory for all files and store them as custom objects in the list + private ArrayList Scrape() + { + ArrayList files = new ArrayList(); + + try + { + try (Stream paths = Files.walk(Paths.get(directory))) + { + paths.filter(Files::isRegularFile).forEach((path)-> + { + files.add(new MonitoredFile(path, LastModified(path))); + }); + } + } + catch(Exception e) + { + Error(e.getMessage()); + } + + return files; + } + + // Update function - the first callback is called if a new item is added to the directory, + // second is called if an item is updated, and third is called if an item is removed. + @SuppressWarnings("unchecked") + public void Update(Consumer onAdd, Consumer onUpdate, Consumer onDelete) + { + // Unparsed + ArrayList active = Scrape(); + ArrayList curr = (ArrayList)active.clone(); + ArrayList prev = (ArrayList)last.clone(); + + // Parsed + ArrayList added = new ArrayList(); + ArrayList updated = new ArrayList(); + ArrayList deleted = new ArrayList(); + ArrayList existing = new ArrayList(); + + // Iterate over everything + for(int i = 0; i < active.size(); ++i) + { + // If we contain the previous, remove it. + if(prev.contains(active.get(i))) + { + MonitoredFile item = active.get(i); + + prev.remove(item); + curr.remove(item); + existing.add(item); + } + + // Check all other cases + for(int j = 0; j < prev.size(); ++j) + { + MonitoredFile l1 = active.get(i); + MonitoredFile l2 = prev.get(j); + + // Check if item is updated. + if(l1.path.toString().equals(l2.path.toString())) + { + updated.add(l1); + prev.remove(l2); + curr.remove(l1); + onUpdate.accept(l2); + } + } + } + + // Anything that wasn't removed from prev has been removed. + for(int j = 0; j < prev.size(); ++j) + { + MonitoredFile item = prev.get(j); + + deleted.add(item); + onDelete.accept(item); + } + + prev.clear(); + + // Add all the remaining things in the current directory as added items. + for(int i = 0; i < curr.size(); ++i) + { + MonitoredFile item = curr.get(i); + + added.add(item); + onAdd.accept(item); + } + + // Rebuild local state + last.clear(); + last.addAll(existing); + last.addAll(added); + last.addAll(updated); + Collections.sort(last); + + if(last.size() == 0) + Warn("There's nothing at all in a FileMonitor's target folder! Folder: " + directory); + } + + // Prints out an arraylist of items from a directory by path + private void Print(ArrayList toPrint) + { + for(int i = 0; i < toPrint.size(); ++i) + Log(toPrint.get(i).path.toString()); + } +}