org.rapidoid.io.watch.WatcherThread Maven / Gradle / Ivy
package org.rapidoid.io.watch;
import org.rapidoid.activity.AbstractLoopThread;
import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;
import org.rapidoid.collection.Coll;
import org.rapidoid.commons.Err;
import org.rapidoid.log.Log;
import org.rapidoid.u.U;
import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.atomic.AtomicInteger;
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import static java.nio.file.StandardWatchEventKinds.*;
/*
* #%L
* rapidoid-watch
* %%
* Copyright (C) 2014 - 2016 Nikolche Mihajlovski and contributors
* %%
* 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.
* #L%
*/
@Authors("Nikolche Mihajlovski")
@Since("4.1.0")
public class WatcherThread extends AbstractLoopThread {
private static final AtomicInteger idGen = new AtomicInteger();
private final Map keys = U.map();
private final FilesystemChangeListener onChange;
private final boolean recursive;
private final WatchService watcher;
private final List folders = Coll.synchronizedList();
private final Set watching = Coll.synchronizedSet();
public WatcherThread(FilesystemChangeListener change, Collection targetFolders, boolean recursive) {
this.onChange = change;
this.recursive = recursive;
for (String folder : targetFolders) {
this.folders.add(new File(folder).getAbsolutePath());
}
setName("watcher" + idGen.incrementAndGet());
try {
watcher = FileSystems.getDefault().newWatchService();
} catch (IOException e) {
throw U.rte("Couldn't create a file system watch service!", e);
}
}
private void init() {
for (String folder : folders) {
init(folder);
}
}
private void init(String folder) {
if (!watching.contains(folder) && new File(folder).exists()) {
Log.debug("Watching folder for changes", "folder", folder, "recursive", recursive);
Path dir = Paths.get(folder);
if (recursive) {
startWatchingTree(dir);
} else {
init(dir);
}
watching.add(folder);
}
}
private void init(Path dir) {
Log.debug("Registering directory watch", "dir", dir);
try {
WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
U.notNull(key, "watch key");
keys.put(key, dir);
} catch (IOException e) {
Log.error("Couldn't register to watch for changes on: " + dir, e);
}
}
private void startWatchingTree(final Path root) {
try {
Dir.traverse(root, new SimpleFileVisitor() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
init(dir);
return FileVisitResult.CONTINUE;
}
});
} catch (Exception e) {
Log.warn("Couldn't register a watch for the directory tree", "dir", root);
}
}
@Override
protected void loop() {
init();
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException x) {
throw new CancellationException();
}
Path dir = keys.get(key);
if (dir == null) {
return;
}
for (WatchEvent> event : key.pollEvents()) {
WatchEvent.Kind> kind = event.kind();
if (ENTRY_CREATE.equals(kind)) {
Path child = getChild(dir, event);
if (recursive && Files.isDirectory(child, NOFOLLOW_LINKS)) {
startWatchingTree(child);
}
onChange.created(fullNameOf(child));
} else if (ENTRY_MODIFY.equals(kind)) {
Path child = getChild(dir, event);
onChange.modified(fullNameOf(child));
} else if (ENTRY_DELETE.equals(kind)) {
Path child = getChild(dir, event);
onChange.deleted(fullNameOf(child));
} else if (OVERFLOW.equals(kind)) {
Log.warn("Received OVERFLOW event from the Watch service!");
} else {
throw Err.notExpected();
}
}
boolean isKeyValid = key.reset();
if (!isKeyValid) {
keys.remove(key);
if (keys.isEmpty()) {
if (!dir.toFile().exists()) {
Log.warn("Cannot watch directory because it doesn't exist anymore", "dir", dir);
} else {
Log.error("Cannot watch directory due to unknown reason!", "dir", dir);
}
}
watching.remove(dir.toFile().getAbsolutePath());
}
}
private Path getChild(Path dir, WatchEvent> event) {
WatchEvent ev = U.cast(event);
Path path = ev.context();
return dir.resolve(path);
}
private String fullNameOf(Path child) {
return child.toAbsolutePath().toString();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy