Search code examples
javafiledirectorylistenerwatchservice

Implementing renaming and deletion in java watchservice


I tried to implement simple renaming in java WatchService.

My assumption is: when files are renamed, three operations are performed

  • Deletion of file xxx
  • creation of file yyy
  • modification of file yyy

Below are my codes:

MyWatcher.java

import java.io.IOException;
    import java.nio.file.FileSystems;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    import java.nio.file.StandardWatchEventKinds;
    import java.nio.file.WatchEvent;
    import java.nio.file.WatchKey;
    import java.nio.file.WatchService;
    import java.util.ArrayList;
    import java.util.List;

    public class MyWatcher {

        @SuppressWarnings("rawtypes")
        public static void main(String[] strings) {

            Path myWatchPath = Paths.get("D:\\log4j");
            long preventDuplicateTime = 0;
            FileDelete onDelete = new FileDelete();//this object must be thread safe
            List<String> notifications = new ArrayList<String>();

            WatchService myPathWatchService = null;
            try {
                myPathWatchService = FileSystems.getDefault().newWatchService();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                myWatchPath.register(myPathWatchService,
                        StandardWatchEventKinds.ENTRY_CREATE,
                        StandardWatchEventKinds.ENTRY_DELETE,
                        StandardWatchEventKinds.ENTRY_MODIFY);
            } catch (IOException e) {
                e.printStackTrace();
            }
            boolean isKeyValid = true;
            while (isKeyValid) {
                WatchKey myPathWatchKey = null;
                try {
                    myPathWatchKey = myPathWatchService.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();// throw
                }
                    for (WatchEvent watchEvent : myPathWatchKey.pollEvents()) {
                        //WatchEvent.Kind kind = watchEvent.kind();
                        if (StandardWatchEventKinds.ENTRY_CREATE.equals(watchEvent
                                .kind())) {
                            String fileName = watchEvent.context().toString();
                            if(onDelete.status == -1)
                             System.out.println("File Created:" + fileName + " "
                                    + watchEvent.context());
                            else{
                                if(onDelete.status == 0){
                                    onDelete.createdTime = System.nanoTime();
                                if (onDelete.deletedTime / 10000000 == onDelete.createdTime / 10000000) {
                                    onDelete.createdFile = watchEvent.context().toString();
                                    onDelete.status++;
                                    notifications.add("File Created:" + fileName);
                                }else{
                                    for (String string : notifications) {
                                        System.out.println(string);
                                    }
                                    notifications = new ArrayList<String>();
                                    System.out.println("File Created:" + fileName + " "
                                            + watchEvent.context());
                                    onDelete = new FileDelete();  //Time duration not close (seems not renamed)
                                }
                                }else{
                                    //this should never come here!!
                                    onDelete = new FileDelete();
                                }
                            }
                        }
                        if (StandardWatchEventKinds.ENTRY_DELETE.equals(watchEvent
                                .kind())) {
                            String fileName = watchEvent.context().toString();
                            if(onDelete.status == -1){
                                onDelete = new FileDelete();
                                onDelete.status++;
                                onDelete.deletedFile = watchEvent.context().toString();
                                onDelete.deletedTime = System.nanoTime();
                                notifications.add("File deleted:" + fileName);
                            }
                            //System.out.println("File deleted:" + fileName);   // push to notfication to array for later use
                        }
                        if (StandardWatchEventKinds.ENTRY_MODIFY.equals(watchEvent
                                .kind())) {
                            long current = System.nanoTime();
                            String fileName = watchEvent.context().toString();
                            if(!(preventDuplicateTime/10000000 == current/10000000))
                                notifications.add("File modified:" + fileName);
                            preventDuplicateTime = (System.nanoTime());
                            onDelete.modifiedFile= fileName;
                            onDelete.modifiedTime =System.nanoTime();
                            if(onDelete.status != 1){
                                for (String messages : notifications) {
                                    System.out.println(messages);
                                }
                            onDelete= new FileDelete();
                            notifications = new ArrayList<String>();
                            }
                            else if(onDelete.createdFile.equals(onDelete.modifiedFile))
                                    if( onDelete.createdTime /10000000 == onDelete.modifiedTime/10000000){
                                        System.out.println("File renamed:" + fileName);
                                        onDelete = new FileDelete();
                                        notifications = new ArrayList<String>();
                             }
                        }
                    /*}*/

                }
                isKeyValid = myPathWatchKey.reset();
            }
        }
    }

FileRename.java

public class FileRename {
    int status =-1;
    String deletedFile = "";
    long deletedTime = 0 ;
    String createdFile = "";
    long createdTime =0 ;
    String modifiedFile = "";
    long modifiedTime = 0 ;
}

It's showing perfectly on renaming operations but the problem is I can't figure how to show for onDelete. Because each delete is pushed into notifications!! Or else help me implement renaming!!

*NOTE please don't suggest third party jars! (Since most of them, like JNotify, are OS dependent)


Solution

  • Please find below some explanation why an OS independent solution will not work. And why the event granularity of the Java WatchService is too weak for what you want to achieve.

    Based on the available events (CREATE, MODIFY, DELETE) you cannot determine which action happened.

    Take following example on a Linux machine

    create some test files

    touch /tmp/stackoverflow/foo /tmp/stackoverflow/foo2
    

    execute the following commands

    rm foo && cp foo2 bar && echo foo > bar
    

    This will create following events (monitored with WatchDir.java)

    ENTRY_DELETE ..:..:.. [.........]: /tmp/stackoverflow/foo
    ENTRY_CREATE 20:09:37 [rw.rw.rw.]: /tmp/stackoverflow/bar
    ENTRY_MODIFY 20:09:37 [rw.rw.rw.]: /tmp/stackoverflow/bar
    

    Following your assumption of the events order this would have been a rename action.

    Wheras mv bar foobar creates following events.

    ENTRY_DELETE ..:..:.. [.........]: /tmp/stackoverflow/bar
    ENTRY_CREATE 19:55:37 [rw.rw.rw.]: /tmp/stackoverflow/foobar
    

    Now the same for a Window machine

    create some test files

    rem.>c:/temp/stackoverflow/foo
    rem.>c:/tmp/stackoverflow/foo2
    

    execute the following commands

    del foo
    copy foo2 bar
    

    This will create following events

    ENTRY_DELETE ..:..:.. [.........]: c:\temp\stackoverflow\foo
    ENTRY_CREATE 19:59:10 [.........]: c:\temp\stackoverflow\bar
    ENTRY_MODIFY 19:59:10 [.........]: c:\temp\stackoverflow\bar
    

    Following your assumption of the events order this would have been a rename action.

    Wheras ren bar foobar creates in this case the same events order.

    ENTRY_DELETE ..:..:.. [.........]: c:\temp\stackoverflow\bar
    ENTRY_CREATE 20:02:41 [.........]: c:\temp\stackoverflow\foobar
    ENTRY_MODIFY 20:02:41 [.........]: c:\temp\stackoverflow\foobar
    

    Contrary with iwatch /tmp/stackoverflow/ on a Linux machine you can determine exactly what happen.

    Executing the commands rm foo && cp foo2 bar && echo foo > bar produces following inotify events.

    [31/Mär/2015 20:25:40] IN_DELETE /tmp/stackoverflow//foo
    [31/Mär/2015 20:25:40] * /tmp/stackoverflow//foo is deleted
    [31/Mär/2015 20:25:40] IN_CREATE /tmp/stackoverflow//bar
    [31/Mär/2015 20:25:40] IN_CLOSE_WRITE /tmp/stackoverflow//bar
    [31/Mär/2015 20:25:40] * /tmp/stackoverflow//bar is closed
    [31/Mär/2015 20:25:40] IN_CLOSE_WRITE /tmp/stackoverflow//bar
    [31/Mär/2015 20:25:40] * /tmp/stackoverflow//bar is closed
    

    whereas mv bar foobar creates following inotify events

    [31/Mär/2015 20:27:10] IN_MOVED_FROM /tmp/stackoverflow//bar
    [31/Mär/2015 20:27:10] IN_MOVED_TO /tmp/stackoverflow//foobar
    [31/Mär/2015 20:27:10] * /tmp/stackoverflow//bar is moved to /tmp/stackoverflow//foobar
    

    edit The related Java source code to confirm my explanation. There is no way (in plain Java) to find out if one file is renamed or one file is deleted and another one is created (at the same time).

    WindowsWatchService.java line 462

    // Translate file change action into watch event
    private WatchEvent.Kind<?> translateActionToEvent(int action)
    {
        switch (action) {
            case FILE_ACTION_MODIFIED :
                return StandardWatchEventKinds.ENTRY_MODIFY;
    
            case FILE_ACTION_ADDED :
            case FILE_ACTION_RENAMED_NEW_NAME :
                return StandardWatchEventKinds.ENTRY_CREATE;
    
            case FILE_ACTION_REMOVED :
            case FILE_ACTION_RENAMED_OLD_NAME :
                return StandardWatchEventKinds.ENTRY_DELETE;
    
            default :
                return null;  // action not recognized
        }
    }
    

    LinuxWatchService.java line 376

    /**
     * map inotify event to WatchEvent.Kind
     */
    private WatchEvent.Kind<?> maskToEventKind(int mask) {
        if ((mask & IN_MODIFY) > 0)
            return StandardWatchEventKinds.ENTRY_MODIFY;
        if ((mask & IN_ATTRIB) > 0)
            return StandardWatchEventKinds.ENTRY_MODIFY;
        if ((mask & IN_CREATE) > 0)
            return StandardWatchEventKinds.ENTRY_CREATE;
        if ((mask & IN_MOVED_TO) > 0)
            return StandardWatchEventKinds.ENTRY_CREATE;
        if ((mask & IN_DELETE) > 0)
            return StandardWatchEventKinds.ENTRY_DELETE;
        if ((mask & IN_MOVED_FROM) > 0)
            return StandardWatchEventKinds.ENTRY_DELETE;
        return null;
    }