org.seedstack.maven.watcher.DirectoryWatcher Maven / Gradle / Ivy
The newest version!
/*
* Copyright © 2013-2021, The SeedStack authors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.seedstack.maven.watcher;
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.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
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.security.MessageDigest;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import org.apache.maven.plugin.logging.Log;
public class DirectoryWatcher implements Runnable {
private final Map digests;
private final Log log;
private final FileChangeListener listener;
private final WatchService watcher;
private final Map keys;
private final WatchEvent.Modifier modifier;
private boolean trace = false;
private boolean stop;
public DirectoryWatcher(Log log, FileChangeListener listener) throws IOException {
this.digests = new HashMap<>();
this.log = log;
this.listener = listener;
this.modifier = determineModifier();
this.watcher = FileSystems.getDefault().newWatchService();
this.keys = new HashMap<>();
}
@SuppressWarnings("unchecked")
private WatchEvent.Modifier determineModifier() {
try {
return (WatchEvent.Modifier) Enum.valueOf(
(Class) Class.forName("com.sun.nio.file.SensitivityWatchEventModifier"),
"HIGH");
} catch (ClassNotFoundException e) {
return null;
}
}
public void watchRecursively(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 {
watch(dir);
return FileVisitResult.CONTINUE;
}
});
}
public void watch(Path dir) throws IOException {
WatchKey key;
if (modifier != null) {
key = dir.register(watcher, new WatchEvent.Kind[]{ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY}, modifier);
} else {
key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
}
if (trace) {
Path prev = keys.get(key);
if (prev == null) {
log.debug("Watching new directory: " + dir);
} else {
if (!dir.equals(prev)) {
log.debug("Directory updated: " + dir);
}
}
}
keys.put(key, dir);
}
public void run() {
this.trace = true;
while (!stop) {
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException x) {
return;
}
try {
// Let the watcher aggregate events for 500 ms
Thread.sleep(500);
} catch (InterruptedException e) {
stop = true;
continue;
}
Path dir = keys.get(key);
if (dir == null) {
continue;
}
HashSet fileEvents = new HashSet<>();
for (WatchEvent> event : key.pollEvents()) {
WatchEvent.Kind kind = event.kind();
if (kind == OVERFLOW) {
continue;
}
WatchEvent ev = cast(event);
Path name = ev.context();
Path path = dir.resolve(name);
File file = path.toFile();
if (!file.isDirectory()) {
if (event.kind() == ENTRY_CREATE) {
log.debug("New file: " + path);
if (isReallyCreatedOrModified(file)) {
fileEvents.add(new FileEvent(FileEvent.Kind.CREATE, file));
}
} else if (event.kind() == ENTRY_MODIFY) {
log.debug("File modified: " + path);
if (isReallyCreatedOrModified(file)) {
fileEvents.add(new FileEvent(FileEvent.Kind.MODIFY, file));
}
} else if (event.kind() == ENTRY_DELETE) {
log.debug("File deleted: " + path);
if (isReallyDeleted(file)) {
fileEvents.add(new FileEvent(FileEvent.Kind.DELETE, file));
}
}
}
if (kind == ENTRY_CREATE) {
try {
if (Files.isDirectory(path, NOFOLLOW_LINKS)) {
watchRecursively(path);
}
} catch (IOException e) {
log.error("Unable to watch " + path, e);
}
}
}
if (!fileEvents.isEmpty()) {
listener.onChange(fileEvents);
}
boolean valid = key.reset();
if (!valid) {
keys.remove(key);
if (keys.isEmpty()) {
break;
}
}
}
}
private boolean isReallyCreatedOrModified(File file) {
if (file.exists()) {
try {
byte[] knownDigest = digests.get(file);
byte[] newDigest = computeSha1(file);
digests.put(file, newDigest);
return !Arrays.equals(newDigest, knownDigest);
} catch (Exception e) {
log.warn("Unable to compute digest of file " + file.getAbsolutePath());
return true;
}
} else {
digests.remove(file);
return false;
}
}
private boolean isReallyDeleted(File file) {
if (!file.exists()) {
digests.remove(file);
return true;
} else {
return false;
}
}
public void stop() {
stop = true;
}
private byte[] computeSha1(File file) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
try (InputStream fis = new FileInputStream(file)) {
int n = 0;
byte[] buffer = new byte[16384];
while (n != -1) {
n = fis.read(buffer);
if (n > 0) {
digest.update(buffer, 0, n);
}
}
}
return digest.digest();
}
@SuppressWarnings("unchecked")
private static WatchEvent cast(WatchEvent> event) {
return (WatchEvent) event;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy