DirectoryPoller.java

/*
 * DirectoryPoller
 *
 * $Id$
 * $HeadURL$
 */
package gov.usgs.util;

import java.io.File;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.LinkedList;
import java.util.Timer;
import java.util.TimerTask;

/**
 * Monitor a directory for files, notifying FileListenerInterfaces.
 *
 * Implementers of the FileListenerInterface should process files before
 * returning, because these files may move or disappear.
 */
public class DirectoryPoller {

	/** Timer schedules polling frequency. */
	private Timer timer;

	/** Directory to watch. */
	private final File pollDirectory;

	/** Directory to store files in. */
	private final File storageDirectory;

	/** Notification of files. */
	private List<FileListenerInterface> listeners = new LinkedList<FileListenerInterface>();

	/**
	 * Create a DirectoryPoller.
	 *
	 * @param pollDirectory
	 *            directory that is polled for new files.
	 * @param storageDirectory
	 *            directory where polled files are moved. When null, polled
	 *            files are deleted after calling listeners.
	 */
	public DirectoryPoller(final File pollDirectory, final File storageDirectory) {
		if (!pollDirectory.exists()) {
			pollDirectory.mkdirs();
		}
		this.pollDirectory = pollDirectory;

		if (storageDirectory != null && !storageDirectory.exists()) {
			storageDirectory.mkdirs();
		}
		this.storageDirectory = storageDirectory;
	}

	/** @return pollDirectory file */
	public File getPollDirectory() {
		return this.pollDirectory;
	}

	/** @return storageDirectory file */
	public File getStorageDirectory() {
		return this.storageDirectory;
	}

	/** @param listener FileListenerInterface to add */
	public void addFileListener(final FileListenerInterface listener) {
		listeners.add(listener);
	}

	/** @param listener FileListenerInterface to remove */
	public void removeFileListener(final FileListenerInterface listener) {
		listeners.remove(listener);
	}

	/**
	 * Start polling in a background thread.
	 *
	 * Any previously scheduled polling is stopped before starting at this
	 * frequency. This schedules using fixed-delay (time between complete polls)
	 * as opposed to fixed-rate (how often to start polling).
	 *
	 * @param frequencyInMilliseconds
	 *            how often to poll.
	 */
	public void start(final long frequencyInMilliseconds) {
		if (timer != null) {
			// already started
			stop();
		}

		timer = new Timer();
		timer.schedule(new PollTask(), 0L, frequencyInMilliseconds);
	}

	/**
	 * Stop any currently scheduled polling.
	 */
	public void stop() {
		if (timer != null) {
			timer.cancel();
			timer = null;
		}
	}

	/**
	 * The Polling Task. Notifies all listeners then either deletes or moves the
	 * file to storage.
	 *
	 * @author jmfee
	 *
	 */
	protected class PollTask extends TimerTask {
		public void run() {
			// get files from poll directory
			File[] files = pollDirectory.listFiles();
			for (File file : files) {
				// send file to listeners
				notifyListeners(file);
				// move file to storage
				moveToStorage(file);
			}
		}
	}

	/**
	 * Notify all listeners that files exist and need to be processed.
	 *
	 * @param file that needs to be processed
	 */
	public void notifyListeners(final File file) {
		Iterator<FileListenerInterface> iter = new LinkedList<FileListenerInterface>(
				listeners).iterator();
		while (iter.hasNext()) {
			try {
				iter.next().onFile(file);
			} catch (Exception e) {
				// keep notifying other listeners
			}
		}
	}

	/**
	 * Move a file from polldir to storage directory. Attempts to move file into
	 * storage directory. The file is not moved if no storage directory was
	 * specified, or if the file no longer exists.
	 *
	 * @param file
	 *            file to move.
	 */
	private void moveToStorage(final File file) {
		if (storageDirectory == null) {
			// nowhere to move, just delete
			file.delete();
			return;
		}

		if (!file.exists()) {
			// was already removed, done
			return;
		}

		// build a filename that doesn't exist
		String fileName = file.getName();
		File storageFile = new File(storageDirectory, fileName);
		if (storageFile.exists()) {
			fileName = new Date().getTime() + "_" + fileName;
			storageFile = new File(storageDirectory, fileName);
		}
		// rename file
		file.renameTo(storageFile);
	}

}