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

org.grails.io.watch.MacOsWatchServiceDirectoryWatcher Maven / Gradle / Ivy

package org.grails.io.watch;

import java.nio.file.WatchEvent.Kind;
import io.methvin.watchservice.MacOSXListeningWatchService;
import io.methvin.watchservice.WatchablePath;
import static io.methvin.watcher.DirectoryChangeEvent.EventType.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Implementation of a {@link AbstractDirectoryWatcher} that uses {@link java.nio.WatchService}.
 * This implementation is used for Java 7 and later.
 * @author Eric Helgeson
 * @author David Estes
 * @since 3.2
 * @see DirectoryWatcher
 */
class MacOsWatchServiceDirectoryWatcher extends AbstractDirectoryWatcher {

	private static final Logger LOG = LoggerFactory.getLogger(MacOsWatchServiceDirectoryWatcher.class);
    private Map> watchKeyToExtensionsMap = new ConcurrentHashMap>();
    private Set individualWatchedFiles = new HashSet();

	private final WatchService watchService;

    @SuppressWarnings("unchecked")
    private static  WatchEvent cast(WatchEvent event) {
        return (WatchEvent)event;
    }

	public MacOsWatchServiceDirectoryWatcher(){
		try {
			watchService = new MacOSXListeningWatchService();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public void run() {
        while (active) {
			try {
                WatchKey watchKey;
                try {
                    watchKey = watchService.take();
                } catch (InterruptedException x) {
                    return;
                }
                for (WatchEvent watchEvent : watchKey.pollEvents()) {
                    WatchEvent.Kind kind = watchEvent.kind();
                    if (kind == StandardWatchEventKinds.OVERFLOW) {
                        // TODO how is this supposed to be handled? I think the best thing to do is ignore it, but I'm not positive
                        LOG.warn("WatchService Overflow occurred");
                        continue;
                    }
                    WatchEvent pathWatchEvent = cast(watchEvent);
                    Path child = pathWatchEvent.context();
                    File childFile = child.toFile();
                    if(individualWatchedFiles.contains(child) || individualWatchedFiles.contains(child.normalize())){
                        if(kind == StandardWatchEventKinds.ENTRY_CREATE){
                            fireOnNew(childFile);
                        }else if(kind == StandardWatchEventKinds.ENTRY_MODIFY){
                            fireOnChange(childFile);
                        }else if(kind == StandardWatchEventKinds.ENTRY_DELETE){
                            // do nothing... there's no way to communicate deletions
                        }
                    }else{
                        List fileExtensions = watchKeyToExtensionsMap.get(watchKey);
                        if(fileExtensions==null){
                            // this event didn't match a file in individualWatchedFiles so it's a not an individual file we're interested in
                            // this event also didn't match a directory that we're interested in (if it did, fileExtentions wouldn't be null)
                            // so it must be event for a file we're not interested in. An example of how this can happen is:
                            // there's a directory with files in it like this:
                            // /images/a.png
                            // /images/b.png
                            // by using the addWatchFile method, /images/a.png is watched.
                            // Now, /images/b.png is changed. Because java.nio.file.WatchService watches directories, it gets a WatchEvent
                            // for /images/b.png. But we aren't interested in that.
                            LOG.debug("WatchService received an event for a file/directory that it's not interested in.");
                        }else{
                            if(kind==StandardWatchEventKinds.ENTRY_CREATE){
                                // new directory created, so watch its contents
                                addWatchDirectory(child,fileExtensions);
                                if(childFile.isDirectory() && childFile.exists()) {
                                    final File[] files = childFile.listFiles();
                                    if(files != null) {
                                        for (File newFile : files) {
                                            if(isValidFileToMonitor(newFile, fileExtensions)) {
                                                fireOnNew(newFile);
                                            }
                                        }
                                    }
                                }
                            }
                            if(isValidFileToMonitor(childFile,fileExtensions)){
                                if(kind == StandardWatchEventKinds.ENTRY_CREATE){
                                    fireOnNew(childFile);
                                }else if(kind == StandardWatchEventKinds.ENTRY_MODIFY){
                                    fireOnChange(childFile);
                                }else if(kind == StandardWatchEventKinds.ENTRY_DELETE){
                                    // do nothing... there's no way to communicate deletions
                                }
                            }
                        }
                    }
                }
                watchKey.reset();
			} catch (Exception e) {
                LOG.error(e.toString());
                // ignore
			}
        }
        try {
			watchService.close();
		} catch (IOException e) {
			LOG.debug("Exception while closing watchService", e);
		}
	}

	@Override
	public void addWatchFile(File fileToWatch) {
		if(!isValidFileToMonitor(fileToWatch, Arrays.asList("*"))) return;
		try {
            if(!fileToWatch.exists()) return;
			Path pathToWatch = fileToWatch.toPath().toAbsolutePath();
			individualWatchedFiles.add(pathToWatch);
            WatchablePath watchPath = new WatchablePath(pathToWatch);
			Kind[] events = new Kind[] { StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY };
			watchPath.register(watchService, events);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public void addWatchDirectory(File dir, final List fileExtensions) {
		Path dirPath = dir.toPath();
		addWatchDirectory(dirPath, fileExtensions);
	}

	private void addWatchDirectory(Path dir, final List fileExtensions) {
		if(!isValidDirectoryToMonitor(dir.toFile())){
			return;
		}
		try {
			//add the subdirectories too
			Files.walkFileTree(dir, new SimpleFileVisitor() {
	            @Override
	            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
	                throws IOException
	            {
	            	if(!isValidDirectoryToMonitor(dir.toFile())){
	            		return FileVisitResult.SKIP_SUBTREE;
	            	}
                    WatchablePath watchPath = new WatchablePath(dir);
	            	Kind[] events = new Kind[] { StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY };
					WatchKey watchKey = watchPath.register(watchService, events);
	    			final List originalFileExtensions = watchKeyToExtensionsMap.get(watchKey);
	    			if(originalFileExtensions==null){
	    				watchKeyToExtensionsMap.put(watchKey, fileExtensions);
	    			}else{
	    				final HashSet newFileExtensions = new HashSet(originalFileExtensions);
	    				newFileExtensions.addAll(fileExtensions);
	    				watchKeyToExtensionsMap.put(watchKey, Collections.unmodifiableList(new ArrayList(newFileExtensions)));
	    			}
	                return FileVisitResult.CONTINUE;
	            }
	        });
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy