001/*
002 * Syncany, www.syncany.org
003 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com>
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU General Public License as published by
007 * the Free Software Foundation, either version 3 of the License, or
008 * (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018package org.syncany.operations.watch;
019
020import static name.pachler.nio.file.StandardWatchEventKind.ENTRY_CREATE;
021import static name.pachler.nio.file.StandardWatchEventKind.ENTRY_DELETE;
022import static name.pachler.nio.file.StandardWatchEventKind.ENTRY_MODIFY;
023import static name.pachler.nio.file.StandardWatchEventKind.OVERFLOW;
024import static name.pachler.nio.file.ext.ExtendedWatchEventModifier.FILE_TREE;
025
026import java.io.IOException;
027import java.nio.file.Path;
028import java.nio.file.Paths;
029import java.util.List;
030
031import name.pachler.nio.file.FileSystems;
032import name.pachler.nio.file.WatchEvent;
033import name.pachler.nio.file.WatchEvent.Kind;
034import name.pachler.nio.file.WatchKey;
035import name.pachler.nio.file.WatchService;
036
037/**
038 * The Windows recursive file watcher monitors a folder (and its sub-folders)
039 * by registering a <em>recursive</em> watch on the root folder. This class is used
040 * only on Windows and uses jpathwatch-based {@link WatchService}.
041 *
042 * <p>Via the jpathwatch library, the class uses the Windows-native recursive folder-
043 * watching capabilities of Windows and thereby does not need to register watches on
044 * all sub-folders. Instead, only one watch on the root folder is needed.
045 *
046 * <p>When a file event occurs, a timer is started to wait for the file operations
047 * to settle. It is reset whenever a new event occurs. When the timer times out,
048 * an event is thrown through the {@link WatchListener}.
049 *
050 * @author Philipp C. Heckel (philipp.heckel@gmail.com)
051 */
052public class WindowsRecursiveWatcher extends RecursiveWatcher {
053        private WatchService watchService;
054        private WatchKey rootWatchKey;
055
056        public WindowsRecursiveWatcher(Path root, List<Path> ignorePaths, int settleDelay, WatchListener listener) {
057                super(root, ignorePaths, settleDelay, listener);
058
059                this.watchService = null;
060                this.rootWatchKey = null;
061        }
062
063        @Override
064        public void beforeStart() throws Exception {
065                name.pachler.nio.file.Path extLibRootDir = name.pachler.nio.file.Paths.get(root.toString());
066
067                watchService = FileSystems.getDefault().newWatchService();
068                rootWatchKey = extLibRootDir.register(watchService,
069                                new Kind[] { ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY, OVERFLOW }, FILE_TREE);
070        }
071
072        @Override
073        protected void beforePollEventLoop() {
074                // Nothing must happen before the event loop.
075        }
076
077        @Override
078        public boolean pollEvents() throws InterruptedException {
079                WatchKey watchKey = watchService.take();
080
081                List<WatchEvent<?>> watchEvents = watchKey.pollEvents();
082                boolean hasRelevantEvents = false;
083
084                // Filter ignored events
085                for (WatchEvent<?> watchEvent : watchEvents) {
086                        if (watchEvent.kind() == ENTRY_CREATE || watchEvent.kind() == ENTRY_MODIFY || watchEvent.kind() == ENTRY_DELETE) {
087                                boolean ignoreEvent = false;
088
089                                name.pachler.nio.file.Path extLibFilePath = (name.pachler.nio.file.Path) watchEvent.context();
090                                Path filePath = toAbsoluteNormalizedPath(extLibFilePath.toString());                            
091                                
092                                for (Path ignorePath : ignorePaths) {
093                                        if (filePath.startsWith(ignorePath.toAbsolutePath().normalize())) {
094                                                ignoreEvent = true;
095                                                break;
096                                        }
097                                }
098
099                                if (!ignoreEvent) {
100                                        hasRelevantEvents = true;
101                                        break;
102                                }
103                        }
104                }
105
106                watchKey.reset();
107                return hasRelevantEvents;
108        }
109
110        private Path toAbsoluteNormalizedPath(String potentiallyRelativePathStr) {
111                Path filePath = Paths.get(potentiallyRelativePathStr);
112                
113                if (!filePath.isAbsolute()) {
114                        return Paths.get(root.toString(), filePath.toString()).normalize();
115                }
116                else {
117                        return filePath.normalize();
118                }
119        }
120
121        @Override
122        protected void watchEventsOccurred() {
123                // This watcher monitors recursively. No need to add more watches.
124        }
125
126        @Override
127        public synchronized void afterStop() throws IOException {
128                rootWatchKey.cancel();
129                watchService.close();
130        }
131}