All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.takari.watcher.DirectoryWatcherJdk Maven / Gradle / Ivy

The newest version!
/**
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.takari.watcher;

import static io.takari.watcher.PathUtils.cast;
import static io.takari.watcher.PathUtils.createHashCodeMap;
import static io.takari.watcher.PathUtils.createKeyRootsMap;
import static io.takari.watcher.PathUtils.hash;
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import static java.nio.file.StandardWatchEventKinds.OVERFLOW;

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Map;

import com.google.common.hash.HashCode;

class DirectoryWatcherJdk {

  private final WatchService watcher;
  private final DirectoryChangeListener listener;
  private final Path directory;
  private final Map pathHashes;
  private final Map keyRoots;

  public DirectoryWatcherJdk(Path directory, DirectoryChangeListener listener) throws IOException {
    this.directory = directory;
    this.watcher = FileSystems.getDefault().newWatchService();
    this.listener = listener;
    this.pathHashes = createHashCodeMap(directory);
    this.keyRoots = createKeyRootsMap();
    registerAll(directory);
  }

  private void register(Path directory) throws IOException {
    keyRoots.put(directory.register(watcher, new WatchEvent.Kind[] {ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY}), directory);
  }

  private void registerAll(final Path start) throws IOException {
    // register directory and sub-directories
    Files.walkFileTree(start, new SimpleFileVisitor() {
      @Override
      public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
        throws IOException {
        register(dir);
        return FileVisitResult.CONTINUE;
      }
    });
  }

  public void processEventsJdk() throws IOException {
    for (;;) {
      if (listener.stopWatching()) {
        return;
      }
      // wait for key to be signalled
      WatchKey key;
      try {
        key = watcher.take();
      } catch (InterruptedException x) {
        return;
      }
      for (WatchEvent event : key.pollEvents()) {
        WatchEvent.Kind kind = event.kind();
        if (kind == OVERFLOW) {
          continue;
        }
        // Context for directory entry event is the file name of entry
        WatchEvent ev = cast(event);
        Path name = ev.context();
        Path child = keyRoots.containsKey(key) ? keyRoots.get(key).resolve(name) : directory.resolve(name);
        // if directory is created, and watching recursively, then register it and its sub-directories
        if (kind == ENTRY_CREATE) {
          if (Files.isDirectory(child, NOFOLLOW_LINKS)) {
            registerAll(child);
          } else {
            pathHashes.put(child, hash(child));
            listener.onCreate(child);
          }
        } else if (kind == ENTRY_MODIFY) {
          HashCode existingHash = pathHashes.get(child);
          HashCode newHash = hash(child);
          // newHash can be null when using File#delete() on windows - it generates MODIFY and DELETE in succession
          // in this case the MODIFY event can be safely ignored
          if (existingHash != null && newHash != null && !existingHash.equals(newHash)) {
            pathHashes.put(child, newHash);
            listener.onModify(child);
          }
        } else if (kind == ENTRY_DELETE) {
          pathHashes.remove(child);
          listener.onDelete(child);
        }
      }
      boolean valid = key.reset();
      if (!valid) {
        break;
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy