io.stallion.fileSystem.FileSystemWatcherRunner.java Source code

Java tutorial

Introduction

Here is the source code for io.stallion.fileSystem.FileSystemWatcherRunner.java

Source

/*
 * Stallion Core: A Modern Web Framework
 *
 * Copyright (C) 2015 - 2016 Stallion Software LLC.
 *
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation, either version 2 of
 * the License, or (at your option) any later version. This program is distributed in the hope that
 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
 * License for more details. You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>.
 *
 *
 *
 */

package io.stallion.fileSystem;

import com.sun.nio.file.SensitivityWatchEventModifier;
import io.stallion.services.Log;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.DirectoryFileFilter;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.nio.file.*;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static io.stallion.utils.Literals.list;
import static io.stallion.utils.Literals.safeLoop;
import static java.nio.file.StandardWatchEventKinds.*;

/**
 * A side thread that watches the file system, and responds to file change
 * events, and calls the registered watch event handler.
 *
 *
 */
public class FileSystemWatcherRunner implements Runnable {
    private WatchService watcher;
    private Boolean shouldRun = true;
    private Map<String, IWatchEventHandler> watchedByPath = new HashMap<>();

    public FileSystemWatcherRunner() {
        this(false);
    }

    public FileSystemWatcherRunner(boolean isCodeWatcher) {

        try {
            watcher = FileSystems.getDefault().newWatchService();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void run() {
        try {
            doRun();
        } catch (Exception exc) {
            System.err.print(exc);
        }
        Log.info("FileSystemWatcher run method is complete.");
    }

    public void registerWatcher(IWatchEventHandler handler) {

        Log.fine("Watch folder {0} handler={1}", handler.getWatchedFolder(), handler.getClass().getSimpleName());
        registerWatcherForFolder(handler, handler.getWatchedFolder());
        if (handler.getWatchTree()) {
            List<File> directories = list(new File(handler.getWatchedFolder()));
            for (int x : safeLoop(100000)) {
                if (directories.size() == 0) {
                    break;
                }
                File directory = directories.remove(0);
                //Collection<File> subdirectories = FileUtils.listFiles(
                //        directory,
                //        DirectoryFileFilter.DIRECTORY,
                //        DirectoryFileFilter.DIRECTORY
                //);
                File[] subdirectories = directory.listFiles((FileFilter) DirectoryFileFilter.INSTANCE);
                if (subdirectories != null && subdirectories.length > 0) {
                    for (File dir : subdirectories) {
                        Log.finer("Register recursive watcher: " + dir.getAbsolutePath());
                        directories.add(dir);
                        registerWatcherForFolder(handler, dir.getAbsolutePath());
                    }
                }
            }
        }
        watchedByPath.put(handler.getWatchedFolder(), handler);
    }

    private void registerWatcherForFolder(IWatchEventHandler handler, String folder) {
        Path itemsDir = FileSystems.getDefault().getPath(folder);
        try {
            if (new File(itemsDir.toString()).isDirectory()) {
                itemsDir.register(watcher,
                        new WatchEvent.Kind[] { StandardWatchEventKinds.ENTRY_MODIFY,
                                StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE },
                        SensitivityWatchEventModifier.HIGH);
                Log.fine("Folder registered with watcher {0}", folder);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void doRun() {
        while (shouldRun) {
            Log.fine("Running the file system watcher.");
            WatchKey key;
            try {
                key = watcher.take();
            } catch (InterruptedException x) {
                Log.warn("Interuppted the watcher!!!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Log.info("Exit watcher run method.");
                    return;
                }
                continue;
            }
            Log.fine("Watch event key taken. Runner instance is {0}", this.hashCode());

            for (WatchEvent<?> event : key.pollEvents()) {

                WatchEvent.Kind<?> kind = event.kind();
                Log.fine("Event is " + kind);
                // This key is registered only
                // for ENTRY_CREATE events,
                // but an OVERFLOW event can
                // occur regardless if events
                // are lost or discarded.
                if (kind == OVERFLOW) {
                    continue;
                }

                // The filename is the
                // context of the event.
                WatchEvent<Path> ev = (WatchEvent<Path>) event;
                Path filename = ev.context();

                // Ignore emacs autosave files
                if (filename.toString().contains(".#")) {
                    continue;
                }
                Log.finer("Changed file is {0}", filename);
                Path directory = (Path) key.watchable();
                Log.finer("Changed directory is {0}", directory);
                Path fullPath = directory.resolve(filename);
                Log.fine("Changed path is {0}", fullPath);
                Boolean handlerFound = false;
                for (IWatchEventHandler handler : watchedByPath.values()) {
                    Log.finer("Checking matching handler {0} {1}", handler.getInternalHandlerLabel(),
                            handler.getWatchedFolder());
                    // Ignore private files
                    if (filename.getFileName().startsWith(".")) {
                        continue;
                    }
                    if ((handler.getWatchedFolder().equals(directory.toAbsolutePath().toString())
                            || (handler.getWatchTree() && directory.startsWith(handler.getWatchedFolder())))
                            && (StringUtils.isEmpty(handler.getExtension())
                                    || fullPath.toString().endsWith(handler.getExtension()))) {
                        String relativePath = filename.getFileName().toString();
                        Log.info("Handling {0} with watcher {1} for folder {2}", filename,
                                handler.getClass().getName(), handler.getWatchedFolder());
                        try {
                            handler.handle(relativePath, fullPath.toString(), kind, event);
                            handlerFound = true;
                        } catch (Exception e) {
                            Log.exception(e, "Exception processing path={0} handler={1}", relativePath,
                                    handler.getClass().getName());
                        }
                    }
                }
                if (!handlerFound) {
                    Log.info("No handler found for {0}", fullPath);
                }
            }
            // Reset the key -- this step is critical if you want to
            // receive further watch events.  If the key is no longer valid,
            // the directory is inaccessible so exit the loop.
            boolean valid = key.reset();
            if (!valid) {
                Log.warn("Key invalid! Exit watch.");
                break;
            }
        }
    }

    public void shutdown() {
        setShouldRun(false);
        try {
            if (watcher != null) {
                watcher.close();
            }
            watcher = null;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        watchedByPath = new HashMap<>();
    }

    public Boolean getShouldRun() {
        return shouldRun;
    }

    public void setShouldRun(Boolean shouldRun) {
        this.shouldRun = shouldRun;
    }

}