Sunday, 18 September 2016

Watching a directory for changes in Java

If your application is closely working with files and directories, you may come to a situation, where you need to monitor a directory (or) file for any changes like file updates, deletes, new sub directory creations etc.,

Watch Service API
Java NIO package provides Watch service api to monitor a directory for changes. All you need to do is register your directory with watch service to monitor changes. One great flexibility of watch service is, while registering a directory to watch service, you can specify the events that you are interested in like file creation, file deletion, or file modification.

Following step-by-step procedure explains, how to watch a directory using watch service.

Step 1: Get WatchService instance by using following statement 'FileSystems.getDefault().newWatchService()'.

WatchService watcher = FileSystems.getDefault().newWatchService();

WatchService is an interface, it watch all the registered objects for any changes. For example, you can monitor a directory for all the operations like create, update, delete and rename operations happened on the directory.

Step 2: Register the directory to the WatchService instance to monitor.

Path dir = Paths.get("/Users/harikrishna_gurram");
WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);

‘register’ method return 'WatchKey', it represents a token representing the registration of a Watchable (Here it is a directory) object with a WatchService.

This key remains valid, until
          a. It is cancelled, explicitly, by invoking its cancel method.
          b. Cancelled implicitly, because the object is no longer accessible,
          c. By closing the watch service using WatchService close method.

A watch key has a state, at the time of creation key is in ready state. When an event is detected then the key is signalled and queued so that it can be retrieved by invoking the watch service's poll() or take methods. Key remains in this state until its reset method is invoked to return the key to the ready state.

What happen if my key is in signalled state and some other event (like creation, deletion (or) updation on directory) on this key?
Events detected while the key is in the signalled state are queued but do not cause the key to be re-queued for retrieval from the watch service. Events are retrieved by invoking the key's {@link #pollEvents pollEvents} method. This method retrieves and removes all events accumulated for the object. When initially created, a watch key has no pending events.

When can I call reset method on watch key?
Make sure ‘reset’ method is only invoked after the events for the object have been processed.

Following table summarizes the methods of WatchKey interface.

Method
Description
boolean isValid()
Return true if the watch key is valid, else false
List<WatchEvent<?>> pollEvents()
Retrieves and removes all pending events for this watch key
boolean reset()
Reset this watch key. If the watch key is in ready state (or) already cancelled, calling this method has no effect.
void cancel()
Cancels the registration with the watch service.
Watchable watchable()
Returns the object for which this watch key was created. This method return the object, even after calling the cancel method.

Step 3: First two steps register the directory to watcher service. It is time for us to retrieve the signaled events. WatchService interface provides take method, which retrieves and removes next watch key, waiting if none are yet present.

WatchKey key = watcher.take();

Step 4: Once you got the signaled WatchKey, you can able to get all the events associated with this watch key by calling ‘pollEvents’ method. pollEvents method retrieves and removes all pending events for this watch key, returning a List of the events that were retrieved.

List<WatchEvent<?>>  pollEvents = key.pollEvents();


Following is the complete working application.

import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import static java.nio.file.StandardWatchEventKinds.OVERFLOW;

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class WatchDir {

 private final WatchService watcher;
 private final Map<WatchKey, Path> keys;
 private final boolean recursive;
 private boolean trace = false;

 @SuppressWarnings("unchecked")
 static <T> WatchEvent<T> cast(WatchEvent<?> event) {
  return (WatchEvent<T>) event;
 }

 /**
  * Register the given directory with the WatchService
  */
 private void registerDirectory(Path dir) throws IOException {
  WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
  if (trace) {
   Path prev = keys.get(key);
   if (prev == null) {
    System.out.format("register: %s\n", dir);
   } else {
    if (!dir.equals(prev)) {
     System.out.format("update: %s -> %s\n", prev, dir);
    }
   }
  }
  keys.put(key, dir);
 }

 /**
  * Register the given directory, and all its sub-directories, with the
  * WatchService.
  */
 private void registerDirectoryTree(final Path start) throws IOException {
  // register directory and sub-directories
  Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
   @Override
   public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
    registerDirectory(dir);
    return FileVisitResult.CONTINUE;
   }
  });
 }

 /**
  * Creates a WatchService and registers the given directory
  */
 WatchDir(Path dir, boolean recursive) throws IOException {
  this.watcher = FileSystems.getDefault().newWatchService();
  this.keys = new HashMap<WatchKey, Path>();
  this.recursive = recursive;

  if (recursive) {
   registerDirectoryTree(dir);
  } else {
   registerDirectory(dir);
  }

  // enable trace after initial registration
  this.trace = true;
 }

 /**
  * Process all events for keys queued to the watcher
  */
 void startService() {
  for (;;) {

   // wait for key to be signalled
   WatchKey key;
   try {
    key = watcher.take();
   } catch (InterruptedException x) {
    return;
   }

   Path dir = keys.get(key);
   if (dir == null) {
    System.err.println("WatchKey not recognized!!");
    continue;
   }

   List<WatchEvent<?>> pollEvents = key.pollEvents();

   for (WatchEvent<?> event : pollEvents) {
    WatchEvent.Kind kind = event.kind();

    // TBD - provide example of how OVERFLOW event is handled
    if (kind == OVERFLOW) {
     continue;
    }

    // Context for directory entry event is the file name of entry
    WatchEvent<Path> ev = cast(event);
    Path name = ev.context();
    Path child = dir.resolve(name);

    System.out.println("event.kind().name() = " + event.kind().name());
    System.out.println("child : " + child);

    // if directory is created, and watching recursively, then
    // register it and its sub-directories
    if (recursive && (kind == ENTRY_CREATE)) {
     try {
      if (Files.isDirectory(child, NOFOLLOW_LINKS)) {
       registerDirectoryTree(child);
      }
     } catch (IOException x) {
      // ignore to keep sample readbale
     }
    } else if (kind == ENTRY_CREATE) {
     // Created Event handling
    } else if (kind == ENTRY_DELETE) {
     // Write code for entry delete
    } else if (kind == ENTRY_MODIFY) {
     // Write code for entry modify
    } else if (kind == OVERFLOW) {
     // Write code for overflow condition
    } else {
     // Do nothing
    }
   }

   // reset key and remove from set if directory no longer accessible
   boolean valid = key.reset();
   if (!valid) {
    keys.remove(key);

    // all directories are inaccessible
    if (keys.isEmpty()) {
     break;
    }
   }
  }
 }

 static void usage() {
  System.err.println("usage: java WatchDir [-r] dir");
  System.exit(-1);
 }

 public static void main(String[] args) throws IOException {
  // parse arguments

  if (args.length == 0 || args.length > 2) { 
   usage(); 
  }

  boolean recursive = false;
  int dirArg = 0;
  if (args[0].equals("-r")) {
   if (args.length < 2) {
    usage();
   }
   recursive = true;
   dirArg++;
  }

  // register directory and process its events
  Path dir = Paths.get(args[dirArg]);
  new WatchDir(dir, recursive).startService();
 }
}

Sample Output
C:>java WatchDir -r C:\\Users\\Documents\\testing
event.kind().name() = ENTRY_DELETE
child : C:\Users\Documents\testing\1\2\a.txt
event.kind().name() = ENTRY_MODIFY
child : C:\Users\Documents\testing\1\2




Previous                                                 Next                                                 Home

No comments:

Post a Comment