/************************************************************************** OmegaT - Computer Assisted Translation (CAT) tool with fuzzy matching, translation memory, keyword search, glossaries, and translation leveraging into updated projects. Copyright (C) 2009-2014 Alex Buloichik Home page: http://www.omegat.org/ Support center: http://groups.yahoo.com/group/OmegaT/ This file is part of OmegaT. OmegaT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OmegaT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. **************************************************************************/ package org.omegat.util; import java.io.File; import java.io.FileFilter; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.logging.Logger; /** * Class for monitor directory content changes. It just looks directory every x seconds and run callback if * some files changed. * * @author Alex Buloichik <alex73mail@gmail.com> * @author Briac Pilpre */ public class DirectoryMonitor extends Thread { /** Local logger. */ private static final Logger LOGGER = Logger.getLogger(DirectoryMonitor.class.getName()); private boolean stopped = false; protected final File dir; protected final Callback callback; protected final DirectoryCallback directoryCallback; private final Map<String, FileInfo> existFiles = new TreeMap<String, FileInfo>(); protected static final long LOOKUP_PERIOD = 1000; /** * Create monitor. * * @param dir * directory to monitoring */ public DirectoryMonitor(final File dir, final Callback callback) { if (dir == null) { throw new IllegalArgumentException("Dir cannot be null."); } this.dir = dir; this.callback = callback; this.directoryCallback = null; } public DirectoryMonitor(final File dir, final Callback callback, final DirectoryCallback directoryCallback) { if (dir == null) { throw new IllegalArgumentException("Dir cannot be null."); } this.dir = dir; this.callback = callback; // Can't call this(dir, callback) because fields are final. this.directoryCallback = directoryCallback; } public File getDir() { return dir; } /** * Stop directory monitoring. */ public void fin() { stopped = true; } @Override public void run() { setName(this.getClass().getSimpleName()); setPriority(MIN_PRIORITY); while (!stopped) { checkChanges(); try { Thread.sleep(LOOKUP_PERIOD); } catch (InterruptedException ex) { stopped = true; } } } public synchronized Set<File> getExistFiles() { Set<File> result = new TreeSet<File>(); for (String fn : existFiles.keySet()) { result.add(new File(fn)); } return result; } /** * Process changes in directory. This method can be called before thread start for load all files from * directory immediately. */ public synchronized void checkChanges() { boolean directoryChanged = false; // find deleted or changed files for (String fn : new ArrayList<String>(existFiles.keySet())) { if (stopped) { return; } File f = new File(fn); if (!f.exists()) { // file removed LOGGER.finer("File '" + f + "' removed"); existFiles.remove(fn); callback.fileChanged(f); directoryChanged = true; } else { FileInfo fi = new FileInfo(f); if (!fi.equals(existFiles.get(fn))) { // file changed LOGGER.finer("File '" + f + "' changed"); existFiles.put(fn, fi); callback.fileChanged(f); directoryChanged = true; } } } // find new files List<File> foundFiles = FileUtil.findFiles(dir, new FileFilter() { public boolean accept(File pathname) { return true; } }); for (File f : foundFiles) { if (stopped) { return; } String fn = f.getPath(); if (!existFiles.keySet().contains(fn)) { // file added LOGGER.finer("File '" + f + "' added"); existFiles.put(fn, new FileInfo(f)); callback.fileChanged(f); directoryChanged = true; } } if (directoryCallback != null && directoryChanged) { directoryCallback.directoryChanged(dir); } } /** * Information about exist file. */ protected static class FileInfo { public long lastModified, length; public FileInfo(final File file) { lastModified = file.lastModified(); length = file.length(); } @Override public boolean equals(Object obj) { if (obj == null || !(obj instanceof FileInfo)) { return false; } FileInfo o = (FileInfo) obj; return lastModified == o.lastModified && length == o.length; } @Override public int hashCode() { return Objects.hash(lastModified, length); } } /** * Callback for monitoring. */ public interface Callback { /** * Called on any file changes - created, modified, deleted. */ void fileChanged(File file); } public interface DirectoryCallback { /** * Called once for every directory where a file was changed - created, modified, deleted. */ void directoryChanged(File file); } }