Monitor folders for changes

This is a simple bit of code to monitor a directory and send a signal when something changes within that folder.

The purpose of this project is to be able to monitor one, or more, directories so that the application is notified anytime a new file is added to the directories under observation.

The code is based around the None-blocking Input Output 2 (NIO.2) Java API.  This API was introduced as part of J2SE 1.7 and part of its intention was to provide a more usable set of tools for accessing the native file system.

So, what we are going to do is as follows.  Create a class to handle all the monitoring activities.  We will use a Map to hold the directories we want to monitor and what event we are looking to monitor. The options we have for monitoring are: –

  • Created
  • Modified
  • Deleted

If we want to monitor a directory for files created and modified then there would 2 entries added to the Map, one for create, and the other for delete.

In addition to the Map for holding directories, the class will also have a Watch Service. The watch service is passed items to monitor.  The Watch Service is then called to obtain information about any events under observation.

The constructor for the class creates an empty Map and obtains the Watch Service by calling the FileSystems class to obtain the default FileSystem object and from that FileSystem object a Watch Service.

To set the directory and the type of monitor there are a public method registerWithOptions which takes the directory to monitor and the type of event to monitor.  The directory registers the Watch Service and the type of event and returned from this call is a Watch Key.  The key along with the directory it applies to is added to Map.

Once directories for monitoring are set, to start the monitoring process there is a public method called startMonitoring.  This, in turn, sets a flag called running and then calls a protected method processEvents. The flag ensures the loop within processEvents method continues to loop.  A public method called stopMonitoring changes the flag to false and this stops the loop within processEvents and causes the method to terminate.

That’s all there is to the Monitor Directory class.  The only thing remaining is to build a simple test program to exercise the class.

To exercise the Monitor Directory there needs to be a directory on the local machine that is accessible.  Create  a new class, for simplicity sake, I place this new class in the same project and package as the Monitor Directory class.  Create a main method in the class as the program entry point. Next, create a new MonitorDir object, call the registerWithOptions method passing it the directory you have decided to monitor and the event you want to monitor for.  If you want to monitor for more than one event, then make calls for each event.  Once the directories are registered, call the startMonitor method.  Run the program from the IDE and open the console to see the output.

Move to your file browser of choice, navigate to the directory selected for monitoring and take action to trigger the event selected.  So, if the event we are monitoring for is the create event, by copying a file and pasting it into the directory under observation you should see a message displayed in the console.  Example message: “ENTRY_CREATE: C:\jetty\build.fxbuild”

So, that’s it we have created some code to enable us to monitor a directory, or two, for various events.  We then exercised this code by creating a simple class to create an object and register a directory for an event.

See below fo rthe source code.

Monitor Directory class

/**
 * 
 */
package softwarepulse.utils.io;

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.nio.file.WatchEvent.Kind;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.HashMap;
import java.util.Map;
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;



/**
 * @author jmcneil
 * (c) copyright Software Pulse 2020
 *
 */
public class MonitorDir {

    private WatchService watcher;
    private Map<WatchKey,Path> keys;
    private boolean running;

    /**
     * Passed the directory path to monitor
     * @param dir
     * @throws IOException
     */
    public MonitorDir() throws IOException {
        this.watcher = 
        		FileSystems.getDefault().newWatchService();
        this.keys = new HashMap<WatchKey,Path>();
        running = false;
	}

    
    /**
     * Registers the Directory and the invoker specified event 
     * to watch for
     * @param dir
     * @param option
     * @throws IOException
     */
    public void registerWithOption(Path dir, Kind<Path> option)
    		throws IOException {
		WatchKey key = dir.register(watcher, option );
		keys.put(key, dir);
    }
    
    protected void processEvents() {
    	
    	// Keep looping round
        while(running) {

            WatchKey watchKey;

            try {
                // wait for event to be signalled
                watchKey = watcher.take();
            } catch (InterruptedException x) {
            	// interrupted by another thread
                return;
            }

            Path dir = keys.get(watchKey);
            // Check it is for our watch in case the Watch
            // Service is used elsewhere in the app
            if (dir == null) {
                System.err.println
                ("WatchKey not recognized!!");
                continue;
            }

            // Get list of all pending events and remove for
            // this key
            for (WatchEvent<?> event: watchKey.pollEvents()) {
                Kind<?> kind = event.kind();

                // Overflow events indicate lost or discarded
                // events
                if (kind == OVERFLOW) {
                	// Ignore lost or discarded events
                    continue;
                }

                // For create, delete or modify events, context
                // is the relative path from watch directory
                Path name = (Path)event.context();
                // Create an absolute path
                Path child = dir.resolve(name);

                // print out event
                System.out.format("%s: %s\n",
                		event.kind().name(), child);

            }

            // reset key and remove from set if directory no
            // longer accessible reset the key and reactivate 
            // Watch Service 
            boolean valid = watchKey.reset();
            // If unable to reset the key
            if (!valid) {
                keys.remove(watchKey);

                // all directories are inaccessible
                if (keys.isEmpty()) {
                    break;
                }
            }
        }
    }
    
    
    public void startMonitoring() {
    	running = true;
    	processEvents();
    }

    
    public void stopMonitoring() {
    	running = false;
    }
}


Test Monitor class

import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * @author jmcneil
 * (c) copyright Software Pulse 2020
 *
 */
public class TestMonitorDir1 {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		Path dir = Paths.get("C:\\ePay");
		try {
			MonitorDir monitorDir = new MonitorDir();
			monitorDir.registerWithOption(dir, ENTRY_CREATE);
			monitorDir.startMonitoring();
		} catch (IOException e) {
			System.out.println("Unable to launch application.");
			e.printStackTrace();
		}
	}

}

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.