package dgm.modules.fsmon; import com.google.common.collect.MapDifference; import com.google.common.collect.Maps; import com.google.common.hash.HashCode; import com.google.common.hash.HashFunction; import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; import org.apache.commons.io.FileUtils; import org.apache.commons.io.filefilter.WildcardFileFilter; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; /** * Monitor the configuration directory for changes in the index configurations. */ public class PollingFilesystemMonitor implements Runnable { protected final FilesystemMonitor monitor; protected final File directory; protected final int interval; protected final Thread poller = new Thread(this); public PollingFilesystemMonitor(String directory, int interval, FilesystemMonitor monitor) { this.directory = new File(directory); this.interval = interval; this.monitor = monitor; if(interval < 100) throw new RuntimeException("Don't monitor faster than once every 100ms"); } public final void start() { if(poller.isAlive()) return; poller.start(); } @Override public final void run() { Map<String, HashCode> state = null; while(true) { try { Thread.sleep(interval); } catch (InterruptedException e) { break; } // compute the state of each index final Map<String, HashCode> newState = new HashMap<String, HashCode>(); for(File d : nullSafeFileList(directory.listFiles())) { // skip non directories if(!d.isDirectory()) continue; // skip empty directories if(nullSafeFileList(d.listFiles()).length == 0) continue; // each subdirectory encodes an index, so compute it's state hash newState.put(d.getName(), subdirState(d)); } // first scan, assign state and ignore if(state == null) { state = newState; continue; } // find changed indices final MapDifference<String, HashCode> diff = Maps.difference(state, newState); // update state state = newState; // nothing has changed, so sleep again if(diff.areEqual()) continue; // directory contents have changed for(Map.Entry<String,MapDifference.ValueDifference<HashCode>> index : diff.entriesDiffering().entrySet()) monitor.directoryChanged(index.getKey()); // directory added for(Map.Entry<String, HashCode> index : diff.entriesOnlyOnRight().entrySet()) monitor.directoryChanged(index.getKey()); // directory removed for(Map.Entry<String, HashCode> index : diff.entriesOnlyOnLeft().entrySet()) monitor.directoryChanged(index.getKey()); } } File[] nullSafeFileList(File[] input) { return input != null ? input : new File[]{}; } /** * The hash is computed based on the modification times, file size and file names of all files recursively. * The order in which the files are listed doesn't matter. * <p/> * If the returned hash changed between calls, this means that something in the subdirectory has changed. * * @return a hashcode representing the subdirectory state. */ protected static HashCode subdirState(File directory) { // MD5 hash some attributes of each file final HashFunction hf = Hashing.md5(); final ArrayList<HashCode> codes = new ArrayList<HashCode>(1000); // non recursively load all configuration files final WildcardFileFilter filter = new WildcardFileFilter(new String[]{"*.conf.js", "*.json"}); final Iterator<File> fi = FileUtils.iterateFiles(directory, filter, null); while(fi.hasNext()) { final File file = fi.next(); // add some attributes to the hasher final Hasher hasher = hf.newHasher(); hasher.putString(file.getAbsolutePath()); hasher.putLong(file.lastModified()); hasher.putLong(file.length()); // we collect all hash codes... codes.add(hasher.hash()); } // when we have no files, return some fixed hash if(codes.size() == 0) { final Hasher hasher = hf.newHasher(); hasher.putLong(0); return hasher.hash(); } // ... because we don't care about order in which files are listed return Hashing.combineUnordered(codes); } }