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}