Watching a Directory for Changes in Java
Asked Answered
K

6

52

I want to watch a directory for file changes. And I used WatchService in java.nio. I can successfully listen for file created event. But I can't listen for file modify event. I checked official java tutorial, but still struggling.

Here is the source code.

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

import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.Files;
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;

public class MainWatch {

    public static void watchDirectoryPath(Path path) {
        // Sanity check - Check if path is a folder
        try {
            Boolean isFolder = (Boolean) Files.getAttribute(path,
                    "basic:isDirectory", NOFOLLOW_LINKS);
            if (!isFolder) {
                throw new IllegalArgumentException("Path: " + path
                        + " is not a folder");
            }
        } catch (IOException ioe) {
            // Folder does not exists
            ioe.printStackTrace();
        }

        System.out.println("Watching path: " + path);

        // We obtain the file system of the Path
        FileSystem fs = path.getFileSystem();

        // We create the new WatchService using the new try() block
        try (WatchService service = fs.newWatchService()) {

            // We register the path to the service
            // We watch for creation events
            path.register(service, ENTRY_CREATE);
            path.register(service, ENTRY_MODIFY);
            path.register(service, ENTRY_DELETE);

            // Start the infinite polling loop
            WatchKey key = null;
            while (true) {
                key = service.take();

                // Dequeueing events
                Kind<?> kind = null;
                for (WatchEvent<?> watchEvent : key.pollEvents()) {
                    // Get the type of the event
                    kind = watchEvent.kind();
                    if (OVERFLOW == kind) {
                        continue; // loop
                    } else if (ENTRY_CREATE == kind) {
                        // A new Path was created
                        Path newPath = ((WatchEvent<Path>) watchEvent)
                                .context();
                        // Output
                        System.out.println("New path created: " + newPath);
                    } else if (ENTRY_MODIFY == kind) {
                        // modified
                        Path newPath = ((WatchEvent<Path>) watchEvent)
                                .context();
                        // Output
                        System.out.println("New path modified: " + newPath);
                    }
                }

                if (!key.reset()) {
                    break; // loop
                }
            }

        } catch (IOException ioe) {
            ioe.printStackTrace();
        } catch (InterruptedException ie) {
            ie.printStackTrace();
        }

    }

    public static void main(String[] args) throws IOException,
            InterruptedException {
        // Folder we are going to watch
        // Path folder =
        // Paths.get(System.getProperty("C:\\Users\\Isuru\\Downloads"));
        File dir = new File("C:\\Users\\Isuru\\Downloads");
        watchDirectoryPath(dir.toPath());
    }
    }
Kassi answered 4/5, 2014 at 4:13 Comment(3)
What OS and version of Java?Chadburn
Windows 8 and Java 1.7Kassi
Have you tried the "stock" WatchDir.java ? It does work on Linux at least.Triplicity
R
39

Actually you have incorrectly subscribed to events. Only last listener has been registered with ENTRY_DELETE events type.

To register for all kind of events at once you should use:

 path.register(service, ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE); 
Retool answered 22/5, 2014 at 8:59 Comment(0)
M
27

Warning! Shameless self promotion!

I have created a wrapper around Java 1.7's WatchService that allows registering a directory and any number of glob patterns. This class will take care of the filtering and only emit events you are interested in.

DirectoryWatchService watchService = new SimpleDirectoryWatchService(); // May throw
watchService.register( // May throw
        new DirectoryWatchService.OnFileChangeListener() {
            @Override
            public void onFileCreate(String filePath) {
                // File created
            }

            @Override
            public void onFileModify(String filePath) {
                // File modified
            }

            @Override
            public void onFileDelete(String filePath) {
                // File deleted
            }
        },
        <directory>, // Directory to watch
        <file-glob-pattern-1>, // E.g. "*.log"
        <file-glob-pattern-2>, // E.g. "input-?.txt"
        ... // As many patterns as you like
);

watchService.start();

Complete code is in this repo.

Mortmain answered 26/6, 2015 at 12:15 Comment(10)
Upvote for the complete solution. One small recommendation though, note that the example only works on Java 8. Dont get me wrong, I LOVE lambdas, but Java 7 is much more common these days, so I had to retrofit it to Java 7 when i used it. Ill post the 'fixed' version soon for those that cant use Java 8 yetYeseniayeshiva
Hey, thanks for putting in the effort. The company I work for already switched to Java 8 since Java 7 support cycle has ended. That is why I did not have a compelling reason to support Java 7. I will update the Gist title to reflect this.Mortmain
I've just downloaded the gist zip and tried it using java 1.7. It can't find import java.util.stream and some default methods. @nterry Did you publish the java 7 version anywhere? Tnks.Backblocks
I'm having an issue with this, where I download a file into the directory, but I need it to do the action AFTER the download is complete. Is there any way to do that?Terzas
@thouliha I don't see a safe way to do that using a watcher. Ideally, the process that is downloading the file should somehow broadcast an event that says "download complete".Mortmain
@Mortmain Thanks for this. I found a way around my problem by basically using a timer to only do the action after a certain time after the file's been created(the download size is small, so this fixes my issue). I was downloading a file with a browser, not java, so I don't have any alerts that could've been thrown.Terzas
@thouliha I found a good approach here - https://mcmap.net/q/354249/-check-if-file-is-already-open. Basically, try to rename the file and if you succeed, the download is complete. I am hoping the browser will keep a lock on the file while it is being downloaded.Mortmain
The code linked in this answer is gone. Can the link be updated or did you delete it, @Mortmain ?Cartwright
@Cartwright Oh crap! That was an organizational Github account and now it is deleted after I left the company. I will update the link.Mortmain
Absolutely no shame. It works like a charm! Thanks @MortmainPeterson
E
3

I made some classes for this.

public interface FileAvailableListener {
    public void fileAvailable(File file) throws IOException;
}

and

public class FileChange {

private long lastModified;
private long size;
private long lastCheck;

public FileChange(File file) {
    this.lastModified=file.lastModified();
    this.size=file.length();
    this.lastCheck = System.currentTimeMillis();
}

public long getLastModified() {
    return lastModified;
}
public long getSize() {
    return size;
}
public long getLastCheck() {
    return lastCheck;
}

public boolean isStable(FileChange other,long stableTime) {
    boolean b1 = (getLastModified()==other.getLastModified());
    boolean b2 = (getSize()==other.getSize());
    boolean b3 = ((other.getLastCheck()-getLastCheck())>stableTime);
    return b1 && b2 && b3;
}
}

and

public class DirectoryWatcher {

private Timer timer;
private List<DirectoryMonitorTask> tasks = new ArrayList<DirectoryMonitorTask>();

public DirectoryWatcher() throws URISyntaxException, IOException, InterruptedException {
    super();
    timer = new Timer(true);        
}
public void addDirectoryMonitoringTask(DirectoryMonitorTask task,long period) {
    tasks.add(task);
    timer.scheduleAtFixedRate(task, 5000, period);      
}
public List<DirectoryMonitorTask> getTasks() {
    return Collections.unmodifiableList(tasks);
}
public Timer getTimer() {
    return timer;
}
}

and

class DirectoryMonitorTask extends TimerTask {

public final static String DIRECTORY_NAME_ARCHIVE="archive";
public final static String DIRECTORY_NAME_ERROR="error";
public final static String LOCK_FILE_EXTENSION=".lock";
public final static String ERROR_FILE_EXTENSION=".error";   
public final static String FILE_DATE_FORMAT="yyyyMMddHHmmssSSS";

private String name;
private FileAvailableListener listener;
private Path directory;
private File directoryArchive;
private File directoryError;
private long stableTime;
private FileFilter filter;
private WatchService watchService;
private SimpleDateFormat dateFormatter = new SimpleDateFormat(FILE_DATE_FORMAT);
private Hashtable<File,FileChange> fileMonitor = new Hashtable<File,FileChange>();

public DirectoryMonitorTask(String name,FileAvailableListener listener,Path directory,long stableTime,FileFilter filter) throws IOException {
    super();
    this.name=name;
    this.listener=listener;
    this.directory=directory;
    this.stableTime=stableTime;
    if (stableTime<1) {
        stableTime=1000;
    }
    this.filter=filter;
    validateNotNull("Name",name);
    validateNotNull("Listener",listener);
    validateNotNull("Directory",directory);
    validate(directory);
    directoryArchive = new File(directory.toFile(),DIRECTORY_NAME_ARCHIVE);
    directoryError = new File(directory.toFile(),DIRECTORY_NAME_ERROR);
    directoryArchive.mkdir();
    directoryError.mkdir();
    //
    log("Constructed for "+getDirectory().toFile().getAbsolutePath());

    initialize();
    //
    watchService = FileSystems.getDefault().newWatchService();
    directory.register(watchService,StandardWatchEventKinds.ENTRY_CREATE,StandardWatchEventKinds.ENTRY_DELETE,StandardWatchEventKinds.ENTRY_MODIFY);
    log("Started");
}

private void initialize() {
    File[] files = getDirectory().toFile().listFiles();
    for (File file : files) {
        if (isLockFile(file)) {
            file.delete();
        } else if (acceptFile(file)) {
            fileMonitor.put(file,new FileChange(file));
            log("Init file added -"+file.getName());
        }
    }
}
public SimpleDateFormat getDateFormatter() {
    return dateFormatter;
}
public Path getDirectory() {
    return directory;
}
public FileAvailableListener getListener() {
    return listener;
}
public String getName() {
    return name;
}
public WatchService getWatchService() {
    return watchService;
}
public long getStableTime() {
    return stableTime;
}
public File getDirectoryArchive() {
    return directoryArchive;
}
public File getDirectoryError() {
    return directoryError;
}
public FileFilter getFilter() {
    return filter;
}   
public Iterator<File> getMonitoredFiles() {
    return fileMonitor.keySet().iterator();
}

@Override
public void run() {
    WatchKey key;
    try {
        key = getWatchService().take();
        // Poll all the events queued for the key
        for (WatchEvent<?> event : key.pollEvents()) {                                      
            @SuppressWarnings("unchecked")
            Path filePath = ((WatchEvent<Path>) event).context();
            File file = filePath.toFile();
            if ((!isLockFile(file)) && (acceptFile(file))) {
                switch (event.kind().name()) {
                    case "ENTRY_CREATE":
                        //                          
                        fileMonitor.put(file,new FileChange(file));
                        log("File created ["+file.getName()+"]");
                        break;
                        //
                    case "ENTRY_MODIFY":
                        //                          
                        fileMonitor.put(file,new FileChange(file));
                        log("File modified ["+file.getName()+"]");
                        break;  
                        //
                    case "ENTRY_DELETE":
                        //
                        log("File deleted ["+file.getName()+"]");
                        createLockFile(file).delete();
                        fileMonitor.remove(file);                           
                        break;
                        //
                }
            }
        }
        // reset is invoked to put the key back to ready state
        key.reset();
    } catch (InterruptedException e) {              
        e.printStackTrace();
    }

    Iterator<File> it = fileMonitor.keySet().iterator();

    while (it.hasNext()) {
        File file = it.next();  
        FileChange fileChange = fileMonitor.get(file);
        FileChange fileChangeCurrent = new FileChange(file);

        if (fileChange.isStable(fileChangeCurrent, getStableTime())) {
            log("File is stable ["+file.getName()+"]");
            String filename = getDateFormatter().format(new Date())+"_"+file.getName();
            File lockFile = createLockFile(file);
            if (!lockFile.exists()) {
                log("File do not has lock file ["+file.getName()+"]");
                try {
                    Files.createFile(lockFile.toPath());
                    log("Processing file ["+file.getName()+"]");
                    getListener().fileAvailable(file);                      
                    file.renameTo(new File(getDirectoryArchive(),filename));
                    log("Moved to archive file ["+file.getName()+"]");
                } catch (IOException e) {                       
                    file.renameTo(new File(getDirectoryError(),filename));
                    createErrorFile(file,e);
                    log("Moved to error file ["+file.getName()+"]");
                } finally {
                    lockFile.delete();

                }
            } else {                    
                log("File do has lock file ["+file.getName()+"]");
                fileMonitor.remove(file);
            }               
        } else {                
            log("File is unstable ["+file.getName()+"]");
            fileMonitor.put(file,fileChangeCurrent);
        }
    }       
}

public boolean acceptFile(File file) {
    if (getFilter()!=null) {
        return getFilter().accept(file);
    } else {
        return true;
    }       
}

public boolean isLockFile(File file) {
    int pos = file.getName().lastIndexOf('.');
    String extension="";
    if (pos!=-1) {
        extension = file.getName().substring(pos).trim().toLowerCase();
    }   
    return(extension.equalsIgnoreCase(LOCK_FILE_EXTENSION));
}

private File createLockFile(File file) {
    return new File(file.getParentFile(),file.getName()+LOCK_FILE_EXTENSION);
}

private void createErrorFile(File file,IOException exception) {
    File errorFile = new File(file.getParentFile(),file.getName()+ERROR_FILE_EXTENSION);

    StringWriter sw = null;
    PrintWriter pw = null;
    FileWriter fileWriter = null;
    try {
        //          
        fileWriter = new FileWriter(errorFile);
        if (exception!=null) {
            sw = new StringWriter();
            pw = new PrintWriter(sw);
            exception.printStackTrace(pw);      
            fileWriter.write(sw.toString());
        } else {
            fileWriter.write("Exception is null.");
        }
        //      
        fileWriter.flush();
        //
    } catch (IOException e) {
    } finally {
        if (sw!=null) {
            try {
                sw.close();
            } catch (IOException e1) {              
            }
        }
        if (pw!=null) {
            pw.close();
        }
        if (fileWriter!=null) {
            try {
                fileWriter.close();
            } catch (IOException e) {                   
            }
        }
    }
}

private void validateNotNull(String name,Object obj) {
    if (obj==null) {
        throw new NullPointerException(name+" is null.");
    }           
}       
private void validate(Path directory) throws IOException {          
    File file = directory.toFile();
    if (!file.exists()) {
        throw new IOException("Directory ["+file.getAbsolutePath()+"] do not exists.");
    } else if (!file.isDirectory()) {
        throw new IOException("Directory ["+file.getAbsolutePath()+"] is not a directory.");
    } else if (!file.canRead()) {               
        throw new IOException("Can not read from directory ["+file.getAbsolutePath()+"].");
    } else if (!file.canWrite()) {
        throw new IOException("Can not write to directory ["+file.getAbsolutePath()+"] .");
    }       
}

private void log(String msg) {
    //TODO
    System.out.println("Task ["+getName()+"] "+msg);
}
}
Entrance answered 18/4, 2016 at 14:45 Comment(0)
G
1
package p1;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.List;

public class WatchForFile {

    public void WatchMyFolder(String path )
    {
        File dir = new File(path);
        Path myDir= dir.toPath();
          try 
          {
              Boolean isFolder = (Boolean) Files.getAttribute(myDir,"basic:isDirectory", NOFOLLOW_LINKS);
              if (!isFolder)
              {
                  throw new IllegalArgumentException("Path: " + myDir + " is not a folder");
              }
          }
          catch (IOException ioe)
          {
              ioe.printStackTrace();
          }

          System.out.println("Watching path: " + myDir);

        try {
           WatchService watcher = myDir.getFileSystem().newWatchService();
           myDir.register(watcher, StandardWatchEventKinds.ENTRY_CREATE,StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);

           WatchKey watckKey = watcher.take();

           List<WatchEvent<?>> events = watckKey.pollEvents();

           for (WatchEvent event : events) {
                if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
                    System.out.println("Created: " + event.kind().toString());

                }
                if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE) {
                    System.out.println("Delete: " + event.context().toString());
                }
                if (event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) {
                    System.out.println("Modify: " + event.context().toString());
                }
            }

        }
        catch (Exception e) 
        {
            System.out.println("Error: " + e.toString());
        }
    }
}
Godlike answered 20/9, 2017 at 12:35 Comment(0)
T
1

Check this Code...

https://github.com/omkar9999/FileWatcherHandler

This project allows watching files for different file events like create, modify & delete and then act on these events in a generic way.

How to Use?
Create a Path object representing the directory to monitor for file events.

Path path = Paths.get("/home/omkar/test");

Implement the FileHandler interface to perform an action detected by file event registered.

public class FileHandlerTest implements FileHandler {

    private static final Logger LOGGER = Logger.getLogger(FileHandlerTest.class.getName());

    /*
     * This implemented method will delete the file
     * 
     * @see com.io.util.FileHandler#handle(java.io.File,
     * java.nio.file.WatchEvent.Kind)
     */
    public void handle(File file, Kind<?> fileEvent) {
        LOGGER.log(Level.INFO,"Handler is triggered for file {0}",file.getPath());
        if(fileEvent == StandardWatchEventKinds.ENTRY_CREATE) {
            try {
                boolean deleted = Files.deleteIfExists(Paths.get(file.getPath()));
                assertTrue(deleted);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

Create an instance of an Implemented FileHandler

FileHandlerTest fileHandlerTest = new FileHandlerTest();

Create an instance of a FileWatcher by passing path, an instance of an Implemented FileHandler, and types of file events that you want to monitor separated by commas.

FileWatcher fileWatcher = new FileWatcher(path, fileHandlerTest, StandardWatchEventKinds.ENTRY_CREATE);

Now Create and start a new Thread.

Thread watcherThread = new Thread(fileWatcher);
watcherThread.start();

This thread will start polling for your registered file events and will invoke your custom handle method once any of the registered events are detected.

Tattler answered 22/10, 2018 at 6:25 Comment(0)
A
1
public class FileWatcher implements Runnable {

    private static final Logger LOGGER =Logger.getLogger(FileWatcher.class.getName());

    private WatchService watcher;
    private FileHandler fileHandler;
    private List<Kind<?>> watchedEvents;
    private Path directoryWatched;

    /**
     * @param directory
     * @Path directory to watch files into
     * @param fileHandler
     * @FileHandler implemented instance to handle the file event
     * @param watchRecursive
     *            if directory is to be watched recursively
     * @param watchedEvents
     *            Set of file events watched
     * 
     * @throws IOException
     */
    public FileWatcher(Path directory, FileHandler fileHandler, boolean watchRecursive,
            WatchEvent.Kind<?>... watchedEvents) throws IOException {
        super();
        this.watcher = FileSystems.getDefault().newWatchService();
        this.fileHandler = fileHandler;
        this.directoryWatched = directory;
        this.watchedEvents = Arrays.asList(watchedEvents);
        if (watchRecursive) {
            // register all subfolders
            Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                    LOGGER.log(Level.INFO, "Registering {0} ", dir);
                    dir.register(watcher, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE,
                            StandardWatchEventKinds.ENTRY_MODIFY);
                    return FileVisitResult.CONTINUE;
                }
            });
        } else {
            directory.register(watcher, watchedEvents);
        }
    }

    @SuppressWarnings({ "unchecked" })
    public void run() {
        LOGGER.log(Level.INFO, "Starting FileWatcher for {0}", directoryWatched.toAbsolutePath());
        WatchKey key = null;
        while (true) {
            try {
                key = watcher.take();
                if (key != null) {
                    for (WatchEvent<?> event : key.pollEvents()) {
                        WatchEvent.Kind<?> kind = event.kind();

                        WatchEvent<Path> ev = (WatchEvent<Path>) event;
                        //directory in which file event is detected
                        Path directory = (Path) key.watchable(); 
                        Path fileName = ev.context();
                        if (watchedEvents.contains(kind)) {
                            LOGGER.log(Level.INFO, "Invoking handle on {0}", fileName.toAbsolutePath());
                            fileHandler.handle(directory.resolve(fileName).toFile(), kind);
                        }
                    }
                    key.reset();
                }
            } catch (InterruptedException ex) {
                LOGGER.log(Level.SEVERE, "Polling Thread was interrupted ", ex);
                Thread.currentThread().interrupt();
            }
        }
    }
}
Audiophile answered 24/6, 2019 at 22:0 Comment(1)
While all answers are appreciated very much, please consider adding some text to code-only answers as it gives details on how your solution should work. If people have similar problems, they can better understand the problem.Enamel

© 2022 - 2024 — McMap. All rights reserved.