Как следить за папкой и подпапками на наличие изменений

Я пытаюсь отслеживать изменения в определенной папке, а затем, если внутри нее происходит какое-либо добавление/редактирование/удаление, мне нужно получить тип изменения всех файлов в этой папке и ее подпапках. Я использую для этого WatchService, но он отслеживает только один путь и не обрабатывает подпапки.

Вот мой подход:

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

        // loop forever to watch directory
        while (true) {
            WatchKey watchKey;
            watchKey = watchService.take(); // This call is blocking until events are present

            // Create the list of path files
            ArrayList<String> filesLog = new ArrayList<String>();
            if(pathToWatch.toFile().exists()) {
                File fList[] = pathToWatch.toFile().listFiles();
                for (int i = 0; i < fList.length; i++) { 
                    filesLog.add(fList[i].getName());
                }
            }

            // Poll for file system events on the WatchKey
            for (final WatchEvent<?> event : watchKey.pollEvents()) {
                printEvent(event);
            }

            // Save the log
            saveLog(filesLog);

            if(!watchKey.reset()) {
                System.out.println("Path deleted");
                watchKey.cancel();
                watchService.close();
                break;
            }
        }

    } catch (InterruptedException ex) {
        System.out.println("Directory Watcher Thread interrupted");
        return;
    } catch (IOException ex) {
        ex.printStackTrace();  // Loggin framework
        return;
    }

Как я уже говорил, я получаю журнал только для файлов по выбранному пути, и я хочу просмотреть все файлы папок и подпапок, например:

Пример 1:

FileA (Created)
FileB
FileC
FolderA FileE
FolderA FolderB FileF

Пример 2:

FileA
FileB (Modified)
FileC
FolderA FileE
FolderA FolderB FileF

Есть ли лучшее решение?


person Yuri Heupa    schedule 09.09.2013    source источник
comment
Похоже, Oracle реализовал бы это следующим образом: docs.oracle. com/javase/tutorial/essential/io/examples/   -  person Erk    schedule 28.01.2020


Ответы (3)


WatchService наблюдает только за зарегистрированными вами Path. Он не проходит по этим путям рекурсивно.

Дан /Root как зарегистрированный путь

/Root
    /Folder1
    /Folder2
        /Folder3

Если есть изменение в Folder3, он его не уловит.

Вы можете зарегистрировать пути к каталогам рекурсивно самостоятельно с помощью

private void registerRecursive(final Path root) throws IOException {
    // register all subfolders
    Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
        @Override
        public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
            dir.register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
            return FileVisitResult.CONTINUE;
        }
    });
}

Теперь WatchService будет уведомлять обо всех изменениях во всех подпапках Path root, т.е. аргумент Path, который вы передаете.

person Sotirios Delimanolis    schedule 09.09.2013
comment
Насколько сильно снизится производительность, если мы реализуем службу наблюдения за папкой и всеми ее подпапками? Будет ли вместо этого дешевле подключаться к системному наблюдателю за каждым изменением файлов? - person Pacerier; 20.10.2014
comment
@Pacerier Я не знаю лежащей в основе реализации, но я действительно не думаю, что это имеет большое значение. О каком наблюдателе за системой вы говорите? - person Sotirios Delimanolis; 20.10.2014
comment
Прослушиватель, который пингуется каждый раз, когда обновляется файл на определенном диске. Если для каждой подпапки должен быть свой собственный прослушиватель, я думаю, что это серьезно снизит производительность системы, просто просмотр папки с 7 подпапками, каждая с 7 подпапками и т. д., до 7 уровней, что дает нам 7 ^ 7 = 823543 наблюдателей. - person Pacerier; 20.10.2014
comment
@Pacerier Подожди, нет. Не создавайте так много наблюдателей. Иметь один регистр Watcher для нескольких Path. - person Sotirios Delimanolis; 20.10.2014
comment
Я не про Яву. Да, мы можем зарегистрировать Java Watcher с 823543 Paths, но разве каждый регистр не использует внутренний наблюдатель за системой? - person Pacerier; 20.10.2014
comment
@Pacerier Насколько я знаю, ОС обрабатывает уведомления. Должен быть процесс файловой системы ОС, который делает это. Я не знаю подробностей. - person Sotirios Delimanolis; 20.10.2014
comment
Как посмотреть несуществующий SUB-каталог? Другими словами, если у меня есть папка, которую я просматриваю, и если я добавляю в нее папку, считается ли это изменением или созданием? - person MG Developer; 30.06.2015
comment
@java_developer Я должен попробовать, но я думаю, что будет ENTRY_CREATE для новой папки, добавленной к той, которую вы просматриваете, и ENTRY_UPDATE для даты последнего изменения папки, которую вы просматриваете. - person Sotirios Delimanolis; 30.06.2015
comment
@SotiriosDelimanolis Я пытаюсь сделать это сейчас (2018 г.) и обнаруживаю, что есть крайние случаи, когда события теряются. В частности, когда вы создаете несколько каталогов, то есть mkdir -p a/b/c (или в Windows mkdir a\b\c с включенными расширениями команд), код Java не видит создание второго и третьего подкаталогов, потому что они происходят слишком быстро. К тому времени, когда вы получите событие для первого каталога и сможете его зарегистрировать, другие подкаталоги уже существуют и не видны. - person Jim Garrison; 12.08.2018
comment
Становится еще хуже. При удалении дерева (как и в случае с rm -rf) в Windows 10 вы можете получить событие удаления для родительского каталога перед удалением для дочернего. Сумасшедший. Похоже, что API на самом деле не работает, по крайней мере, в Windows. - person Jim Garrison; 12.08.2018

Рекурсивная регистрация будет работать, как указал Сотириос. Это эффективно регистрирует каждый каталог/подкаталог, который существует в настоящее время.

В качестве альтернативы вы можете импортировать и использовать *com.sun.nio.file.ExtendedWatchEventModifier.FILE_TREE*, например:

dir.register(watcher, standardEventsArray, ExtendedWatchEventModifier.FILE_TREE);

Это будет отслеживать все поддерево на предмет изменений И учитывать добавленные каталоги и подкаталоги.

В противном случае вам придется отслеживать любые новые каталоги/подкаталоги, а также регистрировать их. Также может возникнуть проблема с удалением частей иерархии каталогов, поскольку у каждого зарегистрированного каталога есть дескриптор, наблюдающий за ним, поэтому (самые низкие) подкаталоги необходимо удалить в первую очередь при удалении частей структуры.

person mjash    schedule 02.10.2013
comment
Из документа: Обратите внимание, что этот модификатор доступен только на платформе Windows. Если указано на других платформах, Path.register() вызовет исключение UnsupportedOperationException. - person jamp; 15.10.2014
comment
Это не работает в Linux, как уже говорилось в предыдущем комментарии. - person Avamander; 08.10.2017

Я реализовал что-то подобное, используя потоки и лямбды Java 8.

Рекурсивное обнаружение папок реализовано как Потребитель @ФункциональныйИнтерфейс:

    final Map<WatchKey, Path> keys = new HashMap<>();

    Consumer<Path> register = p -> {
        if (!p.toFile().exists() || !p.toFile().isDirectory()) {
            throw new RuntimeException("folder " + p + " does not exist or is not a directory");
        }
        try {
            Files.walkFileTree(p, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                    LOG.info("registering " + dir + " in watcher service");
                    WatchKey watchKey = dir.register(watcher, new WatchEvent.Kind[]{ENTRY_CREATE}, SensitivityWatchEventModifier.HIGH);
                    keys.put(watchKey, dir);
                    return FileVisitResult.CONTINUE;
                }
            });
        } catch (IOException e) {
            throw new RuntimeException("Error registering path " + p);
        }
    };

Приведенный выше код вызывается каждый раз при создании новой папки для динамического добавления папок на более поздних этапах. Полное решение и более подробная информация здесь.

person Fabrizio Fortino    schedule 06.06.2016
comment
Если бы мне пришлось угадывать, я бы сказал, что отрицательный отзыв относится к этой старой версии: stackoverflow.com/revisions/37658255/1 , но кто проголосовал и почему не может быть определен никогда - person Flexo; 07.06.2016