Search code examples
androidfilefileobserver

How to know if removed File monitored by File Observer is a Directory or a File


I'm using FileObserver to monitor changes in folders.

Events are triggered as expected but I have problem making a distinction between Files and Directories in the events DELETE and MOVED_FROM, since after the event is triggered, calling both File.isFile() and File.isDirectory() is false (which make sense).

Is there an efficient way to make this check before file is removed? I do have a workaround by listing all files in effected folder, however it's inefficient.

Fileobserver code:

 mFileObserver = new FileObserver(DIRECTORY.getPath()) {
            @Override
            public void onEvent(int event, String path) {
                event &= FileObserver.ALL_EVENTS;
                switch (event) {
                    case (CREATE):
                    case (MOVED_TO):
                        Log.d(TAG, "Added to folder: " + DIRECTORY + " --> File name " + path);
                        addChild(path);
                        break;
                    case (DELETE):
                    case (MOVED_FROM):
                        Log.d(TAG, "Removed from folder " + DIRECTORY + " --> File name " + path);                            
                        removeChild(path);
                        break;
                    case (MOVE_SELF):
                    case (DELETE_SELF):
                        removeDirectory();
                        break;
                }
            }               
        };

EDIT:

This is how is File/Folder is evaluated in removeChild(String)

private void removeChild(String name) {

        mFileObserver.stopWatching();

        String filepath = this.getAbsolutePath() + separator + name;
        File file = new File(filepath);
        if (file.exists())
            Log.d(TAG, "Exists");
        else Log.d(TAG, " Does not Exists");

        if (file.isDirectory())
            Log.d(TAG, "is Directory");
        else Log.d(TAG, " is  NOT Directory");

        if (file.isFile())
            Log.d(TAG, "is File");
        else Log.d(TAG, " is  NOT File");
    }

And the relevant logcat output is:

04-03 12:37:20.714 5819-6352:  Removed from folder /storage/emulated/0/Pictures/GR --> File name ic_alarm_white_24dp.png
04-03 12:37:20.714 5819-6352:  Does not Exists
04-03 12:37:20.714 5819-6352:  is  NOT Directory
04-03 12:37:20.714 5819-6352:  is  NOT File

Solution

  • Is there an efficient way to make this check before file is removed?

    Unfortunately, not that I'm aware of. Which makes sense - filesystem events are things that have already happened.

    FileObserver uses inotify to get events. A good description of inotify functionality can be found at https://www.ibm.com/developerworks/library/l-inotify/:

    Monitor Linux file system events with inotify

    ...

    Before inotify there was dnotify. Unfortunately, dnotify had limitations that left users hoping for something better. Some of the advantages of inotify are:

    • Inotify uses a single file descriptor, while dnotify requires opening one file descriptor for each directory that you intend to watch for changes. This can be very costly when you are monitoring several directories at once, and you may even reach a per-process file descriptor limit.
    • The file descriptor used by inotify is obtained using a system call and does not have an associated device or file. With dnotify, the file descriptor pins the directory, preventing the backing device to be unmounted, a particular problem with removable media. With inotify, a watched file or directory on a file system that is unmounted generates an event, and the watch is automatically removed.
    • Inotify can watch files or directories. Dnotify monitors directories, and so programmers had to keep stat structures or an equivalent data structure reflecting the files in the directories being watched, then compare those with the current state after an event occurred in order to know what happened to the entry in the directory.
    • As noted above, inotify uses a file descriptor, allowing programmers to use standard select or poll functions to watch for events. This allows for efficient multiplexed I/O or integration with Glib's mainloop. In contrast, dnotify uses signals, which programmers often find more difficult or less than elegant. Signal-drive I.O notification was also added to inotify in kernel 2.6.25.

    The API for inotify

    ...

    Note that there are no mentions of "events about to happen", or anything like that.

    You don't need to keep a list of all files - you just need a list of directories - a simple Set<String> should do just fine. If the deleted String path is in the set, it was a directory.

    For a more robust approach, when you start your watch you can also put a FileObserver watch on all directories in the directory you're primarily watching (also add a watch to every directory created in your primary directory after you've created your watcher).

    Then if you get a DELETE_SELF from one of the child FileObserver objects, you'll know it was a directory. If the event doesn't have an associated child FileObserver object that got a DELETE_SELF event, it wasn't a directory.

    For an extremely large directory, this approach admittedly will have scalability problems...