WatchService fires ENTRY_MODIFY sometimes twice and sometimes once
Asked Answered
C

2

9

I am using this WatchService example from Oracle:

import java.nio.file.*;
import static java.nio.file.StandardWatchEventKinds.*;
import static java.nio.file.LinkOption.*;
import java.nio.file.attribute.*;
import java.io.*;
import java.util.*;

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 register(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 registerAll(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
        {
            register(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) {
        System.out.format("Scanning %s ...\n", dir);
        registerAll(dir);
        System.out.println("Done.");
    } else {
        register(dir);
    }

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

/**
 * Process all events for keys queued to the watcher
 */
void processEvents() {
    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;
        }

        for (WatchEvent<?> event: key.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);

            // print out event
            System.out.format("%s: %s\n", event.kind().name(), 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)) {
                        registerAll(child);
                    }
                } catch (IOException x) {
                    // ignore to keep sample readbale
                }
            }
        }

        // 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).processEvents();
}
}

I am developing an app in Windows 7, and deployment environment is rhel 7.2. At first, in both OS'es, whenever I copied a file, it fired one ENTRY_CREATED and then two ENTRY_MODIFY. The first ENTRY_MODIFY was at the beginning of copying, and the second ENTRY_MODIFY was at the end of copying. So I was able to understand the copying process was over. However, It only fires one ENTRY_MODIFY in rhel 7.2 now. It still fires two ENTRY_MODIFY events in Windows 7 though.

I have found this in stackoverflow. That question asks why two ENTRY_MODIFY are fired. It is not exactly my question, but one of its answers disputes what I'm asking. Sadly, there is no solution to my question in that dispute though.

Because there is no ENTRY_MODIFY fired at the end but only in the beginning of copying, I can not understand when the copying is finished. What do you think might be the cause for this? Can it be fixed, how can I understand the copying is finished? I can't change rhel 7.2, but anything other than that I would gladly accept. Thanks in advance.

Caesaria answered 25/8, 2016 at 14:15 Comment(4)
Hello, please take a look at my answer here on one way to know when your file was copied. HTH.Pipistrelle
Thank you for your reply, I have read your comment many times. However, I don't get how acquiring the file lock will help me understand when the copying process is over. Could you please exlain in more detail?Caesaria
The file remains locked for RW access by other processes/threads as long as the copying process is busy. The code fragment in my answer tries to acquire a lock on that file (after you know about its existence by a ENTRY_CREATED event). The lock will be granted as soon as the file is no longer locked by the copying process. At that point the copying process is done.Pipistrelle
Hello again, in the processEvents method I check ENTRY_CREATE event then I add your code block into that condition. However, it fires java.io.FileNotFoundExcepiton : the process cannot access the file because it is being used by another process. Maybe I am using it wrong. Could you please add your code block in my example and see if it works. So that I can accept your answer.Caesaria
C
3

I just check if the file length is zero or not. Here an example of

for (WatchEvent<?> event : key.pollEvents()) {
    Path fileName = (Path) event.context();
    if("tomcat-users.xml".equals(fileName.toString())) {
        Path tomcatUsersXml = tomcatConf.resolve(fileName);
        if(tomcatUsersXml.toFile().length() > 0) {
            load(tomcatUsersXml);
        }
    } 
}
Clingstone answered 28/4, 2017 at 6:39 Comment(2)
glad to find a genius on stackoverflow - I had no luck finding anything I liked on the other thread but this is great if (path.toFile().length() > 0)Delbert
Does this also work if using BufferedWriter?Groggery
J
1

You can use a DelayQueue to deduplicate the events.

Jordans answered 24/12, 2019 at 8:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.