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

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

There is a newer version: 5.5.5
Show newest version
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