1

I have a Watcher that updates my data structures when a change is heard. However, if the change is not instantaneous (i.e. if a large file is being copied from another file system, or a big part of the file is modified), the data-structure tries to update too early and throws an error.

How can I modify my code so that updateData() is called after only the last ENTRY_MODIFY is called, rather than after every single ENTRY_MODIFY.

private static boolean processWatcherEvents () {
    WatchKey key;
    try {
        key = watcher.poll( 10, TimeUnit.MILLISECONDS );
    } catch ( InterruptedException e ) {
        return false;
    }

    Path directory = keys.get( key );
    if ( directory == null ) {
        return false;
    }

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

        WatchEvent <Path> watchEvent = (WatchEvent<Path>)event;
        Path child = directory.resolve( watchEvent.context() );

        if ( eventKind == StandardWatchEventKinds.ENTRY_MODIFY ) {
            //TODO: Wait until modifications are "finished" before taking these actions. 
            if ( Files.isDirectory( child ) ) {
                updateData( child );
            }
        }

        boolean valid = key.reset();
        if ( !valid ) {
            keys.remove( key );
        }
    }

    return true;
}
Grumblesaurus
  • 3,021
  • 3
  • 31
  • 61
  • Have you tried assign child to a varialbe, and after the loop finish, execute updateDate? Or I am missing the idea here? – Beri May 21 '17 at 07:48
  • 1
    I think the answer I gave [here](http://stackoverflow.com/a/34718685/243373) may be what you need, the outlines at least. – TT. May 21 '17 at 09:02
  • Was the answer in the link I gave of any use? – TT. May 22 '17 at 12:10

3 Answers3

0

As @TT suggested, you can do it pretty easily with file locks.

When you get an event, use a blocking method lock() on read and write access. Hence the operation is blocking, the code automatically waits until the write operation is finished.

FileChannel channel = new RandomAccessFile(file, "rw").getChannel();
try (channel) {  // auto closable, uses channel.close() in finally block
  channel.lock();  // wait until file modifications are finished
  channel.read(...);  // now you can safely read the file
}

However, this won't work between different JVM processes, because they don't share the same lock.

mrek
  • 1,655
  • 1
  • 21
  • 34
-1

Is your problem can be solved by using timestamp. Create a map for storing the timestamp to the map.

Map<Path, Long> fileTimeStamps;

For process event check last modified timestamp.

 long oldFileModifiedTimeStamp = fileTimeStamps.get(filePath);
 long newFileModifiedTimeStamp = filePath.toFile().lastModified();
                if (newFileModifiedTimeStamp > oldFileModifiedTimeStamp)
                {
                    fileTimeStamps.remove(filePath);
                    onEventOccurred();
                    fileTimeStamps.put(filePath, filePath.toFile().lastModified());
                }
gati sahu
  • 2,576
  • 2
  • 10
  • 16
-1

I ended up writing a thread that keeps a list of things I want updated and delays actually updating them until 80 milliseconds have passed. Whenever an ENTRY_MODIFY event happens, it resets the counter. I think this is a good solution, but there may be better?

@SuppressWarnings({ "rawtypes", "unchecked" })
private static boolean processWatcherEvents () {
    WatchKey key;
    try {
        key = watcher.poll( 10, TimeUnit.MILLISECONDS );
    } catch ( InterruptedException e ) {
        return false;
    }

    Path directory = keys.get( key );
    if ( directory == null ) {
        return false;
    }

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

        WatchEvent <Path> watchEvent = (WatchEvent<Path>)event;
        Path child = directory.resolve( watchEvent.context() );

        if ( eventKind == StandardWatchEventKinds.ENTRY_CREATE ) {
            if ( Files.isDirectory( child ) ) {
                loadMe.add( child );
            } else {
                loadMe.add( child.getParent() );
            }

        } else if ( eventKind == StandardWatchEventKinds.ENTRY_DELETE ) {
            //Handled by removeMissingFiles(), can ignore. 

        } else if ( eventKind == StandardWatchEventKinds.ENTRY_MODIFY ) {
            System.out.println( "Modified: " + child.toString() ); //TODO: DD
            if ( Files.isDirectory( child ) ) {
                modifiedFileDelayedUpdater.addUpdateItem( child );
            } else {
                modifiedFileDelayedUpdater.addUpdateItem( child );
            }

        } else if ( eventKind == StandardWatchEventKinds.OVERFLOW ) {
            for ( Path path : musicSourcePaths ) {
                updateMe.add( path );
            }
        }

        boolean valid = key.reset();
        if ( !valid ) {
            keys.remove( key );
        }
    }

    return true;
}

...

class UpdaterThread extends Thread {
    public static final int DELAY_LENGTH_MS = 80; 
    public int counter = DELAY_LENGTH_MS;

    Vector <Path> updateItems = new Vector <Path> ();

    public void run() {
        while ( true ) {
            long sleepTime = 0;
            try { 
                long startSleepTime = System.currentTimeMillis();
                Thread.sleep ( 20 ); 
                sleepTime = System.currentTimeMillis() - startSleepTime;
            } catch ( InterruptedException e ) {} //TODO: Is this OK to do? Feels like a bad idea.  

            if ( counter > 0 ) {
                counter -= sleepTime;

            } else if ( updateItems.size() > 0 ) {
                Vector <Path> copyUpdateItems = new Vector<Path> ( updateItems );
                for ( Path path : copyUpdateItems ) {
                    Library.requestUpdate ( path );
                    updateItems.remove( path );

                }
            }
        }
    }

    public void addUpdateItem ( Path path ) {
        counter = DELAY_LENGTH_MS;
        if ( !updateItems.contains( path ) ) {
            updateItems.add ( path );
        }
    }
};
Grumblesaurus
  • 3,021
  • 3
  • 31
  • 61
  • This is not corect solution, because the write can be longer than that. You have to wait until the write operation is finished. You can achieve that using file locks, see my answer for more information. – mrek Jun 22 '21 at 12:07
  • It sends off a bunch of modified events as it is copying. We aren't waiting 80ms for it to finish. We wait 80ms after the last modified event. – Grumblesaurus Jun 24 '21 at 17:53