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

org.rapidoid.io.watch.WatcherThread Maven / Gradle / Ivy

package org.rapidoid.io.watch;

/*
 * #%L
 * rapidoid-watch
 * %%
 * Copyright (C) 2014 - 2015 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%
 */

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.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.Paths;
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 java.util.concurrent.CancellationException;

import org.rapidoid.activity.AbstractLoopThread;
import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;
import org.rapidoid.log.Log;
import org.rapidoid.u.U;

@Authors("Nikolche Mihajlovski")
@Since("4.1.0")
public class WatcherThread extends AbstractLoopThread {

	private final Map keys = U.map();

	private final FilesystemChangeListener onChange;
	private final String path;
	private final File file;
	private final boolean recursive;

	private volatile WatchService watcher;

	public WatcherThread(FilesystemChangeListener change, String path, boolean recursive) {
		this.onChange = change;
		this.path = path;
		this.recursive = recursive;
		this.file = new File(path);

		setName("watcher");
	}

	private void startWatching() {
		Path dir = Paths.get(path);
		if (recursive) {
			startWatchingTree(dir);
		} else {
			startWatching(dir);
		}
	}

	private void startWatching(Path dir) {
		Log.info("Watching directory for changes", "dir", path, "recursive", recursive);

		try {
			if (watcher == null) {
				watcher = FileSystems.getDefault().newWatchService();
			}

			WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
			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 {
					startWatching(dir);
					return FileVisitResult.CONTINUE;
				}
			});
		} catch (Exception e) {
			Log.warn("Couldn't register a watch for the directory tree", "dir", root);
		}
	}

	@Override
	protected void loop() {
		if (watcher == null) {
			if (file.exists()) {
				startWatching();
			} else {
				U.sleep(1000);
				return;
			}
		}

		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 U.notExpected();
			}
		}

		boolean isKeyValid = key.reset();
		if (!isKeyValid) {
			keys.remove(key);

			if (keys.isEmpty()) {
				if (!file.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);
				}
				watcher = null;
			}
		}
	}

	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