/**
* Licensed to Cloudera, Inc. under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Cloudera, Inc. licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.cloudera.util.dirwatcher;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.cloudera.util.Clock;
import com.google.common.base.Preconditions;
/**
* This class watches a specified directory for deletions, creations and
* "age off" events. It spawns a thread that periodically checks the directory.
*/
public class DirWatcher {
static final Logger LOG = LoggerFactory.getLogger(DirWatcher.class);
final private List<DirChangeHandler> list = Collections
.synchronizedList(new ArrayList<DirChangeHandler>());
private File dir;
private volatile boolean done = false;
private Set<File> previous = new HashSet<File>();
private long sleep_ms;
private Periodic thread;
private FileFilter filter;
/**
* checkperiod is the amount of time in milliseconds between directory polls.
*/
public DirWatcher(File dir, FileFilter filter, long checkPeriod) {
Preconditions.checkNotNull(dir);
Preconditions.checkArgument(dir.isDirectory(), dir + " is not a directory");
this.thread = null;
this.dir = dir;
this.sleep_ms = checkPeriod;
this.filter = filter;
}
/**
* Start the directory watching. This implementation uses a thread to poll
* periodically. If called multiple times, it will only start a single thread.
* This is not threadsafe
*/
public void start() {
if (thread != null) {
LOG.warn("Dir watcher already started!");
return;
}
this.thread = new Periodic();
this.thread.start();
LOG.info("Started dir watcher thread");
}
/**
* This attempts to stop the dir watching. With this thread-based
* implementation it, blocks until the thread is done. This is not thread
* safe.
*/
public void stop() {
if (thread == null) {
LOG.warn("DirWatcher already stopped");
return;
}
done = true;
try {
thread.join();
} catch (InterruptedException e) {
LOG.error(e.getMessage(), e);
} // waiting for thread to complete.
LOG.info("Stopped dir watcher thread");
thread = null;
}
/**
* This thread periodically checks a directory for updates
*/
class Periodic extends Thread {
Periodic() {
super("DirWatcher");
}
public void run() {
try {
while (!done) {
try {
check();
Clock.sleep(sleep_ms);
} catch (NumberFormatException nfe) {
LOG.warn("wtf ", nfe);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* This the core check method that updates information from the previous poll
* and fires events based on changes.
*/
public void check() {
File[] files = dir.listFiles();
if (files == null) {
LOG.warn("dir " + dir.getAbsolutePath() + " does not exist?!");
return;
}
Set<File> newfiles = new HashSet<File>(Arrays.asList(files));
// figure out what was created
Set<File> addedFiles = new HashSet<File>(newfiles);
addedFiles.removeAll(previous);
for (File f : addedFiles) {
if (filter.isSelected(f)) {
fireCreatedFile(f);
}
}
// figure out what was deleted
Set<File> removedFiles = new HashSet<File>(previous);
removedFiles.removeAll(newfiles);
for (File f : removedFiles) {
if (filter.isSelected(f)) {
fireDeletedFile(f);
}
}
previous = newfiles;
}
/**
* Add a handler callback object.
*/
public void addHandler(DirChangeHandler dch) {
list.add(dch);
}
/**
* Fire the 'created' callback on all handlers.
*/
public void fireCreatedFile(File f) {
// make copy so it is thread safe
DirChangeHandler[] hs = list.toArray(new DirChangeHandler[0]);
for (DirChangeHandler h : hs) {
h.fileCreated(f);
}
}
/**
* Fire the 'deleted' callback on all handlers.
*/
public void fireDeletedFile(File f) {
// make copy so it is thread safe
DirChangeHandler[] hs = list.toArray(new DirChangeHandler[0]);
for (DirChangeHandler h : hs) {
h.fileDeleted(f);
}
}
}