/******************************************************************************* * Copyright (c) 2007, 2008 aQute and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * aQute - initial implementation and ideas * IBM Corporation - initial adaptation to Equinox provisioning use *******************************************************************************/ package org.python.pydev.core.path_watch; import java.io.File; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.python.pydev.core.log.Log; import org.python.pydev.shared_core.callbacks.ListenerList; import org.python.pydev.shared_core.io.FileUtils; class DirectoryWatcher { private class WatcherThread extends Thread { private final long pollFrequency; private boolean done = false; private WatcherThread(long frequency) { super("Directory Watcher"); //$NON-NLS-1$ this.pollFrequency = frequency; } @Override public void run() { do { try { poll(); synchronized (this) { wait(pollFrequency); } } catch (InterruptedException e) { // ignore } catch (Throwable e) { Log.log(e); done = true; } } while (!done); } private synchronized void done() { done = true; notify(); } } /** * 10 seconds */ private static final long DEFAULT_POLL_FREQUENCY = 10000; private final File[] directories; private ListenerList<DirectoryChangeListener> listeners = new ListenerList<DirectoryChangeListener>( DirectoryChangeListener.class); private Set<File> scannedFiles = new HashSet<File>(); private Set<File> removals; private WatcherThread watcher; private boolean watchSubdirs; DirectoryWatcher(File directory, boolean watchSubtree) { if (directory == null) { throw new IllegalArgumentException("Null folder"); } this.directories = new File[] { directory }; this.watchSubdirs = watchSubtree; } synchronized void addListener(DirectoryChangeListener listener) { listeners.add(listener); } synchronized void removeListener(DirectoryChangeListener listener) { listeners.remove(listener); } void start() { start(DEFAULT_POLL_FREQUENCY); } private synchronized void poll() { startPoll(); scanDirectories(); stopPoll(); } private synchronized void start(final long pollFrequency) { if (watcher != null) { throw new IllegalStateException("Thread already started"); } watcher = new WatcherThread(pollFrequency); watcher.start(); } synchronized void stop() { if (watcher == null) { throw new IllegalStateException("Unable to stop (thread not started)"); } watcher.done(); watcher = null; } synchronized void dispose() { if (watcher != null) { stop(); } if (listeners != null) { for (DirectoryChangeListener listener : listeners.getListeners()) { removeListener(listener); } } } private void startPoll() { removals = scannedFiles; scannedFiles = new HashSet<File>(); for (DirectoryChangeListener l : listeners.getListeners()) { l.startPoll(); } } private void scanDirectories() { for (int index = 0; index < directories.length; index++) { File directory = directories[index]; scanDirectoryRecursively(directory); } } private void scanDirectoryRecursively(File directory) { if (directory == null) { return; } File list[] = directory.listFiles(); if (list == null) { return; } for (int i = 0; i < list.length; i++) { File file = list[i]; // remember that we saw the file and remove it from this list of files to be // removed at the end. Then notify all the listeners as needed. scannedFiles.add(file); removals.remove(file); for (DirectoryChangeListener listener : listeners.getListeners()) { if (isInterested(listener, file)) { processFile(file, listener); } } if (watchSubdirs && file.isDirectory()) { scanDirectoryRecursively(file); } } } private void stopPoll() { notifyRemovals(); removals = scannedFiles; for (DirectoryChangeListener l : listeners.getListeners()) { l.stopPoll(); } } private boolean isInterested(DirectoryChangeListener listener, File file) { return listener.isInterested(file); } /** * Notify the listeners of the files that have been deleted or marked for deletion. */ private void notifyRemovals() { Set<File> removed = removals; for (DirectoryChangeListener listener : listeners.getListeners()) { for (Iterator<File> j = removed.iterator(); j.hasNext();) { File file = j.next(); if (isInterested(listener, file)) { listener.removed(file); } } } } private void processFile(File file, DirectoryChangeListener listener) { try { Long oldTimestamp = listener.getSeenFile(file); if (oldTimestamp == null) { // The file is new listener.added(file); } else { // The file is not new but may have changed long lastModified = FileUtils.lastModified(file); if (oldTimestamp.longValue() != lastModified) { listener.changed(file); } } } catch (Exception e) { Log.log(e); } } }