From 38cc577c7b398d67316d50d25f5f2eac13f6a4cf Mon Sep 17 00:00:00 2001 From: Svilen Valkanov Date: Wed, 3 Aug 2016 18:39:08 +0300 Subject: [PATCH] Various improvements in AbstractWatchService. * Fixes bug introduced by #1597 reverted with #1754, that led to duplicated directory names in FolderObserver * Added directory registration after initialization.This change allows all classes that extend AbstractWatchService to register directories to be watched, after the WatchService is initialized. This will be possible with the registerAllDirectoriesInternal() method.The implementaions can handle the DIRECTORY_CREATED event, in order to do that. * Added test cases for the registration of directories after service initialization * Test class is refactored * Improved javadoc of AbstractWatchService and AbstractQueueReader Signed-off-by: Svilen Valkanov --- .../service/AbstractWatchServiceTest.groovy | 258 ++++++++++++++---- .../service/AbstractWatchQueueReader.java | 123 +-------- .../core/service/AbstractWatchService.java | 114 +++++--- .../core/internal/folder/FolderObserver.java | 2 +- 4 files changed, 290 insertions(+), 207 deletions(-) diff --git a/bundles/core/org.eclipse.smarthome.core.test/src/test/groovy/org/eclipse/smarthome/core/service/AbstractWatchServiceTest.groovy b/bundles/core/org.eclipse.smarthome.core.test/src/test/groovy/org/eclipse/smarthome/core/service/AbstractWatchServiceTest.groovy index 86462d7e100..00de926274f 100644 --- a/bundles/core/org.eclipse.smarthome.core.test/src/test/groovy/org/eclipse/smarthome/core/service/AbstractWatchServiceTest.groovy +++ b/bundles/core/org.eclipse.smarthome.core.test/src/test/groovy/org/eclipse/smarthome/core/service/AbstractWatchServiceTest.groovy @@ -1,34 +1,26 @@ package org.eclipse.smarthome.core.service +import static java.nio.file.StandardWatchEventKinds.* import static org.hamcrest.CoreMatchers.* import static org.junit.Assert.* import static org.junit.matchers.JUnitMatchers.* -import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; -import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; -import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; - -import java.io.IOException; import java.nio.file.Path -import java.nio.file.WatchEvent; +import java.nio.file.WatchEvent import java.nio.file.WatchKey import java.nio.file.WatchService import java.nio.file.WatchEvent.Kind -import java.util.Map -import org.apache.commons.lang.SystemUtils; -import org.eclipse.smarthome.core.service.AbstractWatchServiceTest.FullEvent; +import org.apache.commons.lang.SystemUtils import org.eclipse.smarthome.test.OSGiTest import org.junit.After -import org.junit.AfterClass; -import org.junit.Before; +import org.junit.AfterClass import org.junit.BeforeClass -import org.junit.Ignore; -import org.junit.Test; +import org.junit.Test /** * Test for {@link AbstractWatchService}. - * + * * @author Dimitar Ivanov * */ @@ -116,7 +108,8 @@ class AbstractWatchServiceTest extends OSGiTest { isCreated = file.createNewFile() assertThat "The file '$file.absolutePath' was not created successfully", isCreated, is(true) - waitForAssert({assertThat "Exactly two watch events were expected within $NO_EVENT_TIMEOUT_IN_SECONDS seconds, but were: " + watchService.allFullEvents, watchService.allFullEvents.size(), is(2)},NO_EVENT_TIMEOUT_IN_SECONDS*1000,1000) + def expectedEvents = 2 + waitForAssert({assertThat "Exactly two watch events were expected within $NO_EVENT_TIMEOUT_IN_SECONDS seconds, but were: " + watchService.allFullEvents, watchService.allFullEvents.size(), is(expectedEvents)},NO_EVENT_TIMEOUT_IN_SECONDS*1000,1000) FullEvent innerFileEvent = watchService.allFullEvents[0] assertThat "The inner file '$innerfile.absolutePath' creation was not detected. All events detected: " + watchService.allFullEvents,innerFileEvent.eventKind,is(ENTRY_CREATE) @@ -128,7 +121,109 @@ class AbstractWatchServiceTest extends OSGiTest { } @Test - void 'directory changes are watched correctly'(){ + void 'subirs are registered and modifications are watched, folder is created after service activation'(){ + // Watch subdirectories and their modifications + // RelativeWatchService#processWatchEvent() provides implementation for watching folders after service activation + watchService = new RelativeWatchService(WATCHED_DIRECTORY,true,true) + + watchService.activate() + def subDirName = "subDir" + def fileName = "innerFile" + def innerFileName = subDirName + File.separatorChar + fileName + File innerFile = new File(WATCHED_DIRECTORY + File.separatorChar + innerFileName) + + // Make subdirectories + innerFile.getParentFile().mkdirs() + + assertDirectoryCraeteEventIsProcessed(subDirName) + + watchService.allFullEvents.clear(); + + boolean isCreated = innerFile.createNewFile() + assertThat "The file '$innerFile.absolutePath' was not created successfully", isCreated, is(true) + + assertAllEventsAreProcessed(subDirName,innerFile, innerFileName) + } + + @Test + void 'subdirs are not registered and modifications are watched, folder is created after service activation'(){ + // Do not watch subdirectories, but watch their modifications + // RelativeWatchService#processWatchEvent() provides implementation for watching folders after service activation + watchService = new RelativeWatchService(WATCHED_DIRECTORY,false,true) + watchService.activate() + + def subDirName = "subDir" + def fileName = "innerFile" + def innerFileName = subDirName + File.separatorChar + fileName + + File innerFile = new File(WATCHED_DIRECTORY + File.separatorChar + innerFileName) + + // Make subdirectories + innerFile.getParentFile().mkdirs() + + assertDirectoryCraeteEventIsProcessed(subDirName) + + watchService.allFullEvents.clear(); + + boolean isCreated = innerFile.createNewFile() + assertThat "The file '$innerFile.absolutePath' was not created successfully", isCreated, is(true) + + assertDirectoryModifyEventIsProcessed(subDirName) + } + + @Test + void 'subdirs are not registered and modifications are not watched, folder is created after service activation' () { + // Do not watch subdirectories and do not watch their modifications + // RelativeWatchService#processWatchEvent() provides implementation for watching folders after service activation + watchService = new RelativeWatchService(WATCHED_DIRECTORY,false,false) + watchService.activate() + + def subDirName = "subDir" + def fileName = "innerFile" + def innerFileName = subDirName + File.separatorChar + fileName + + File innerFile = new File(WATCHED_DIRECTORY + File.separatorChar + innerFileName) + + // Make subdirectories + innerFile.getParentFile().mkdirs() + + // Consequent creation and deletion in order to generate any watch events for the subdirectory + boolean isCreated = innerFile.createNewFile() + assertThat "The file '$innerFile.absolutePath' was not created successfully", isCreated, is(true) + + boolean isDeleted = innerFile.delete() + assertThat "Inner file is not deleted", isDeleted, is(true) + + assertNoEventsAreProcessed() + } + + @Test + void 'subdirs are registered, but dirs modifications are not watched, folder is created after service activation'() { + // Watch subdirectories, but not their modifications + // RelativeWatchService#processWatchEvent() provides implementation for watching folders after service activation + watchService = new RelativeWatchService(WATCHED_DIRECTORY,true,false) + + watchService.activate() + + def subDirName = "subDir" + def fileName = "innerFile" + def innerFileName = subDirName + File.separatorChar + fileName + + File innerFile = new File(WATCHED_DIRECTORY + File.separatorChar + innerFileName) + + // Make subdirectories + innerFile.getParentFile().mkdirs() + + //Create file in the subdirectory + boolean isCreated = innerFile.createNewFile() + assertThat "The file '$innerFile.absolutePath' was not created successfully", isCreated, is(true) + + //The subdirectory is created after the service initialization. As no modifications are watched the new subdir can not be registered + assertNoEventsAreProcessed() + } + + @Test + void 'subdirs are registered and modifications watched'(){ // Watch subdirectories and their modifications watchService = new RelativeWatchService(WATCHED_DIRECTORY,true,true) @@ -146,23 +241,11 @@ class AbstractWatchServiceTest extends OSGiTest { boolean isCreated = innerFile.createNewFile() assertThat "The file '$innerFile.absolutePath' was not created successfully", isCreated, is(true) - //This could vary across different platforms. For more information see "Platform dependencies" section in the WatchService documentation - def expectedEvents = SystemUtils.IS_OS_WINDOWS ? 2 : 1 - waitForAssert({assertThat "Exactly $expectedEvents watch events were expected within $NO_EVENT_TIMEOUT_IN_SECONDS seconds, but were: " + watchService.allFullEvents, watchService.allFullEvents.size(), is(expectedEvents)},NO_EVENT_TIMEOUT_IN_SECONDS*1000,1000) - - FullEvent fileEvent = watchService.allFullEvents[0] - assertThat "File '$innerFile.absolutePath' creation was not detected. All events detected: " + watchService.allFullEvents, fileEvent.eventKind, is(ENTRY_CREATE) - assertThat "File '$innerFile.absolutePath' name expected in the modified event. All events detected: " + watchService.allFullEvents, fileEvent.eventPath.toString(), is(innerFileName) - - if(SystemUtils.IS_OS_WINDOWS){ - FullEvent dirEvent = watchService.allFullEvents[1] - assertThat "Directory $subDirName modification was not detected. All events detected: " + watchService.allFullEvents, dirEvent.eventKind, is(ENTRY_MODIFY) - assertThat "Subdirectory was not found in the modified event", dirEvent.eventPath.toString(), is(subDirName) - } + assertAllEventsAreProcessed(subDirName,innerFile,innerFileName) } @Test - void 'subdirs are not registered and not watched'(){ + void 'subdirs are not registered and modifications are not watched'(){ // Do not watch the subdirectories of the root directory watchService = new RelativeWatchService(WATCHED_DIRECTORY,false,false) @@ -180,14 +263,33 @@ class AbstractWatchServiceTest extends OSGiTest { boolean isDeleted = innerFile.delete() assertThat "Inner file is not deleted", isDeleted, is(true) - // Wait for a possible event for the maximum timeout - sleep NO_EVENT_TIMEOUT_IN_SECONDS*1000 + assertNoEventsAreProcessed() + } - assertThat "No watch events are expected, but were: " + watchService.allFullEvents, watchService.allFullEvents.size(), is(0) + @Test + void 'subdirs are registered, but dirs modifications are not watched'() { + // Do watch the subdirectories of the root directory, but do not watch directory modifications + watchService = new RelativeWatchService(WATCHED_DIRECTORY,true,false) + + def innerFileName = "watchRequestSubDir"+ File.separatorChar + "watchRequestInnerFile" + File innerFile = new File(WATCHED_DIRECTORY + File.separatorChar + innerFileName) + // Make all the subdirectories before running the service + innerFile.getParentFile().mkdirs() + + watchService.activate() + + boolean isCreated = innerFile.createNewFile() + assertThat "The file '$innerFile.absolutePath' was not created successfully", isCreated, is(true) + + assertFileCreateEventIsProcessed(innerFile,innerFileName) + + watchService.allFullEvents.clear(); + + assertNoEventsAreProcessed() } @Test - void 'subdirs are not watched, but dirs modifications are watched'(){ + void 'subdirs are not registered, but dirs modifications are watched'(){ // Do not watch the subdirectories of the root directory, but watch directory modifications watchService = new RelativeWatchService(WATCHED_DIRECTORY,false,true) @@ -205,15 +307,7 @@ class AbstractWatchServiceTest extends OSGiTest { // Wait for possible watch event for the maximum timeout sleep NO_EVENT_TIMEOUT_IN_SECONDS*1000 - if(SystemUtils.IS_OS_WINDOWS){ - // This behavior could vary depending on the Platform (see the WatchService documentation) - assertThat "Exactly one event is expected, but were: " + watchService.allFullEvents, watchService.allFullEvents.size(), is(1) - FullEvent event = watchService.allFullEvents[0] - assertThat "Modification event for subdir '$subDirName' on file creation within it is expected", event.eventKind , is(ENTRY_MODIFY) - assertThat "The watch event does not contain the path of the modified directory", event.eventPath.toString() , is(subDirName) - }else { - assertThat "No events are expected if not Windows OS" + watchService.allFullEvents, watchService.allFullEvents.size(), is(0) - } + assertDirectoryModifyEventIsProcessed(subDirName) // Clear the asserted event watchService.allFullEvents.clear() @@ -221,10 +315,61 @@ class AbstractWatchServiceTest extends OSGiTest { boolean isDeleted = innerFile.delete() assertThat "Inner file '$innerFile.absolutePath' is not deleted", isDeleted, is(true) + assertNoEventsAreProcessed() + } + void assertNoEventsAreProcessed(){ // Wait for a possible event for the maximum timeout sleep NO_EVENT_TIMEOUT_IN_SECONDS*1000 - assertThat "No event is expected, but were: " + watchService.allFullEvents, watchService.allFullEvents.size(), is(0) + assertThat "No watch events are expected, but were: " + watchService.allFullEvents, watchService.allFullEvents.size(), is(0) + } + + void assertAllEventsAreProcessed(def subDirName,def innerFile, def innerFileName){ + //This could vary across different platforms. For more information see "Platform dependencies" section in the WatchService documentation + def expectedEvents = SystemUtils.IS_OS_WINDOWS ? 2 : 1 + waitForAssert({assertThat "Exactly $expectedEvents watch events were expected within $NO_EVENT_TIMEOUT_IN_SECONDS seconds, but were: " + watchService.allFullEvents, watchService.allFullEvents.size(), is(expectedEvents)},NO_EVENT_TIMEOUT_IN_SECONDS*1000,1000) + + FullEvent fileEvent = watchService.allFullEvents[0] + assertThat "File '$innerFile.absolutePath' creation was not detected. All events detected: " + watchService.allFullEvents, fileEvent.eventKind, is(ENTRY_CREATE) + assertThat "File '$innerFile.absolutePath' name expected in the modified event. All events detected: " + watchService.allFullEvents, fileEvent.eventPath.toString(), is(innerFileName) + + if(SystemUtils.IS_OS_WINDOWS){ + FullEvent dirEvent = watchService.allFullEvents[1] + assertThat "Directory $subDirName modification was not detected. All events detected: " + watchService.allFullEvents, dirEvent.eventKind, is(ENTRY_MODIFY) + assertThat "Subdirectory was not found in the modified event", dirEvent.eventPath.toString(), is(subDirName) + } + } + + void assertDirectoryCraeteEventIsProcessed(def subDirName) { + //Single event for directory creation is present + def expectedEvents = 1 + waitForAssert({assertThat "Exactly $expectedEvents watch events were expected within $NO_EVENT_TIMEOUT_IN_SECONDS seconds, but were: " + watchService.allFullEvents, watchService.allFullEvents.size(), is(expectedEvents)},NO_EVENT_TIMEOUT_IN_SECONDS*1000,1000) + FullEvent event = watchService.allFullEvents[0] + assertThat "Directory $subDirName craetion was not detected. All events detected: " + watchService.allFullEvents, event.eventKind, is(ENTRY_CREATE) + assertThat "Subdirectory was not found in the creation event", event.eventPath.toString(), is(subDirName) + } + + void assertFileCreateEventIsProcessed(def innerFile, def innerFileName) { + //Single event for file creation is present + def expectedEvents = 1 + waitForAssert({assertThat "Exactly $expectedEvents watch events were expected within $NO_EVENT_TIMEOUT_IN_SECONDS seconds, but were: " + watchService.allFullEvents, watchService.allFullEvents.size(), is(expectedEvents)},NO_EVENT_TIMEOUT_IN_SECONDS*1000,1000) + FullEvent fileEvent = watchService.allFullEvents[0] + assertThat "File '$innerFile.absolutePath' creation was not detected. All events detected: " + watchService.allFullEvents, fileEvent.eventKind, is(ENTRY_CREATE) + assertThat "File '$innerFile.absolutePath' name expected in the modified event. All events detected: " + watchService.allFullEvents, fileEvent.eventPath.toString(), is(innerFileName) + } + + void assertDirectoryModifyEventIsProcessed(def subDirName) { + //Create file is not detected, only the modification event is detected + def expectedEvents = SystemUtils.IS_OS_WINDOWS ? 1 : 0 + waitForAssert({assertThat "Exactly $expectedEvents watch events were expected within $NO_EVENT_TIMEOUT_IN_SECONDS seconds, but were: " + watchService.allFullEvents, watchService.allFullEvents.size(), is(expectedEvents)},NO_EVENT_TIMEOUT_IN_SECONDS*1000,1000) + + if(SystemUtils.IS_OS_WINDOWS){ + FullEvent fileEvent = watchService.allFullEvents[0] + assertThat "Directory $subDirName modification was not detected. All events detected: " + watchService.allFullEvents, fileEvent.eventKind, is(ENTRY_MODIFY) + assertThat "Subdirectory was not found in the modified event", fileEvent.eventPath.toString(), is(subDirName) + } else { + assertThat "No events are expected if not Windows OS" + watchService.allFullEvents, watchService.allFullEvents.size(), is(0) + } } void assertByRelativePath(String fileName) { @@ -254,8 +399,8 @@ class AbstractWatchServiceTest extends OSGiTest { void fullEventAssertionsByKind(String fileName, kind, osSpecific){ waitForAssert({ assertThat "At least one watch event is expected within $NO_EVENT_TIMEOUT_IN_SECONDS seconds.", - watchService.allFullEvents.size() >= 1, - is(true)},NO_EVENT_TIMEOUT_IN_SECONDS*1000,1000); + watchService.allFullEvents.size() >= 1, + is(true)},NO_EVENT_TIMEOUT_IN_SECONDS*1000,1000); if(osSpecific && kind.equals(ENTRY_DELETE)){ // There is possibility that one more modify event is triggered on some OS @@ -311,16 +456,23 @@ class AbstractWatchServiceTest extends OSGiTest { @Override - protected AbstractWatchQueueReader buildWatchQueueReader(WatchService watchService, Path toWatch,Map registeredKeys) { - def queueReader = new AbstractWatchQueueReader(watchService, toWatch,registeredKeys) { - @Override - protected void processWatchEvent(WatchEvent event, Kind kind, Path path) { - FullEvent fullEvent = new FullEvent(event,kind,path) - allFullEvents << fullEvent + protected AbstractWatchQueueReader buildWatchQueueReader(WatchService watchServiceImpl, Path toWatch,Map registeredKeys) { + def queueReader = new AbstractWatchQueueReader(watchServiceImpl, toWatch,registeredKeys) { + @Override + protected void processWatchEvent(WatchEvent event, Kind kind, Path path) { + FullEvent fullEvent = new FullEvent(event,kind,path) + if (kind.equals(ENTRY_CREATE)) { + Path relativePath = baseWatchedDir.resolve(path); + if (relativePath.toFile().isDirectory() && watchSubDirectories()) { + registerAllDirectoriesInternal(relativePath, registeredKeys); } - }; + } + allFullEvents << fullEvent + } + }; queueReader.setWatchingDirectoryChanges(watchDirectoryChanges) return queueReader + } @Override diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/service/AbstractWatchQueueReader.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/service/AbstractWatchQueueReader.java index ff970cbcded..e7857e5be54 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/service/AbstractWatchQueueReader.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/service/AbstractWatchQueueReader.java @@ -9,125 +9,6 @@ import static java.nio.file.StandardWatchEventKinds.OVERFLOW; -import java.nio.file.ClosedWatchServiceException; -import java.nio.file.Path; -import java.nio.file.WatchEvent; -import java.nio.file.WatchKey; -import java.nio.file.WatchService; -import java.text.MessageFormat; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Base class for watch queue readers - * - * @author Fabio Marini - * - */ -public abstract class AbstractWatchQueueReader implements Runnable { - - /** - * Default logger for ESH Watch Services - */ - protected final Logger logger = LoggerFactory.getLogger(AbstractWatchQueueReader.class); - - protected WatchService watchService; - - protected Path dir; - - /** - * Perform a simple cast of given event to WatchEvent - * - * @param event - * the event to cast - * @return the casted event - */ - @SuppressWarnings("unchecked") - static WatchEvent cast(WatchEvent event) { - return (WatchEvent) event; - } - - /** - * Build the object with the given parameters - * - * @param watchService - * the watch service - * @param dir - */ - public AbstractWatchQueueReader(WatchService watchService, Path dir) { - this.watchService = watchService; - this.dir = dir; - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.smarthome.core.service.IWatchService#activate() - */ - @Override - public void run() { - try { - for (;;) { - WatchKey key = null; - try { - key = watchService.take(); - } catch (InterruptedException exc) { - logger.warn(MessageFormat.format("Catched InterruptedException : {0}", exc.getLocalizedMessage())); - - return; - } - - for (WatchEvent event : key.pollEvents()) { - WatchEvent.Kind kind = event.kind(); - - if (kind == OVERFLOW) { - logger.warn(MessageFormat.format("Found event with overflow kind : {0}", event)); - - continue; - } - - // Context for directory entry event is the file name of - // entry - WatchEvent ev = cast(event); - Path path = ev.context(); - - processWatchEvent(event, kind, path); - } - - key.reset(); - } - } catch (ClosedWatchServiceException ecx) { - logger.debug("ClosedWatchServiceException catched! {}. \n{} Stopping ", ecx.getLocalizedMessage(), Thread - .currentThread().getName()); - - return; - } - } - - /** - * Processes the given watch event - * - * @param event - * the watch event to perform - * @param kind - * the event's kind - * @param name - * the path of event - */ - protected abstract void processWatchEvent(WatchEvent event, WatchEvent.Kind kind, Path path); -======= -/** - * Copyright (c) 2014-2015 openHAB UG (haftungsbeschraenkt) and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - */ -package org.eclipse.smarthome.core.service; - -import static java.nio.file.StandardWatchEventKinds.OVERFLOW; - import java.nio.file.ClosedWatchServiceException; import java.nio.file.Path; import java.nio.file.WatchEvent; @@ -289,7 +170,8 @@ private Path resolveToRelativePath(WatchKey key, WatchEvent event) { /** * Processes the given watch event. Note that the kind and the number of the events for the watched directory is a * platform dependent (see the "Platform dependencies" sections of {@link WatchService}). - * + *

+ * If {@link #isWatchingDirectoryChanges()} is true, all events will be send to this method, otherwise directory events will be skipped * @param event the watch event to be handled * @param kind the event's kind * @param path the path of the event (relative to the {@link #baseWatchedDir} @@ -318,5 +200,4 @@ public boolean isWatchingDirectoryChanges() { public final void setWatchingDirectoryChanges(boolean watchDirectoryChanges) { this.watchingDirectoryChanges = watchDirectoryChanges; } - } \ No newline at end of file diff --git a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/service/AbstractWatchService.java b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/service/AbstractWatchService.java index 7b0dcd6e396..14f98f47289 100644 --- a/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/service/AbstractWatchService.java +++ b/bundles/core/org.eclipse.smarthome.core/src/main/java/org/eclipse/smarthome/core/service/AbstractWatchService.java @@ -14,8 +14,11 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; +import java.nio.file.WatchKey; import java.nio.file.WatchService; import java.nio.file.attribute.BasicFileAttributes; +import java.util.HashMap; +import java.util.Map; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; @@ -28,6 +31,7 @@ * >java docs for more details * * @author Fabio Marini + * @author Dimitar Ivanov - added javadoc; introduced WatchKey to directory mapping for the queue reader * */ public abstract class AbstractWatchService { @@ -74,23 +78,17 @@ protected void initializeWatchService() { if (StringUtils.isNotBlank(pathToWatch)) { Path toWatch = Paths.get(pathToWatch); try { + final Map registeredWatchKeys = new HashMap<>(); if (watchSubDirectories()) { watchService = FileSystems.getDefault().newWatchService(); - Files.walkFileTree(toWatch, new SimpleFileVisitor() { - @Override - public FileVisitResult preVisitDirectory(Path subDir, BasicFileAttributes attrs) - throws IOException { - registerDirectory(subDir); - return FileVisitResult.CONTINUE; - } - }); + registerAllDirectoriesInternal(toWatch, registeredWatchKeys); } else { watchService = toWatch.getFileSystem().newWatchService(); - registerDirectory(toWatch); + registerDirectoryInternal(toWatch, registeredWatchKeys); } - AbstractWatchQueueReader reader = buildWatchQueueReader(watchService, toWatch); + AbstractWatchQueueReader reader = buildWatchQueueReader(watchService, toWatch, registeredWatchKeys); Thread qr = new Thread(reader, "Dir Watcher"); qr.start(); @@ -100,41 +98,93 @@ public FileVisitResult preVisitDirectory(Path subDir, BasicFileAttributes attrs) } } + /** + * This method walks a file tree and registers all directories to be watched + * by the watch service. + *
+ *
+ * It can be used from the implementations of {@link AbstractWatchService} + * to register directories, created after the service initialization.
+ * In order to do that make sure that + * {@link AbstractWatchQueueReader#isWatchingDirectoryChanges()} is + * true and process the create events in {@link AbstractWatchQueueReader#processWatchEvent()} + *
+ * This method should be called only if {@link #watchSubDirectories} is true + * + * @param toWatch the directory being watched by the watch service + * @param registeredWatchKeys a mapping between the registered directories and their {@link WatchKey registration + * keys}. + * @throws IOException + */ + final protected void registerAllDirectoriesInternal(Path toWatch, final Map registeredWatchKeys) + throws IOException { + Files.walkFileTree(toWatch, new SimpleFileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path subDir, BasicFileAttributes attrs) throws IOException { + registerDirectoryInternal(subDir, registeredWatchKeys); + return FileVisitResult.CONTINUE; + } + }); + } + + private void registerDirectoryInternal(Path directory, Map registredWatchKeys) throws IOException { + WatchKey registrationKey = registerDirectory(directory); + if (registrationKey != null) { + registredWatchKeys.put(registrationKey, directory); + } else { + logger.info("The directory '{}' was not registered in the watch service", directory); + } + } + + /** + * This method will close the {@link #watchService}. + */ protected void stopWatchService() { - if(watchService != null) { - try { - watchService.close(); - } catch (IOException e) { - logger.warn("Cannot deactivate folder watcher", e); - } - - watchService = null; - } + if (watchService != null) { + try { + watchService.close(); + } catch (IOException e) { + logger.warn("Cannot deactivate folder watcher", e); + } + + watchService = null; + } } /** - * - * @param watchService - * @param toWatch - * @return + * Build a queue reader to process the watch events, provided by the watch service for the given directory + * + * @param watchService the watch service, providing the watch events for the watched directory + * @param toWatch the directory being watched by the watch service + * @param registredWatchKeys a mapping between the registered directories and their {@link WatchKey registration + * keys}. + * @return the concrete queue reader */ - protected abstract AbstractWatchQueueReader buildWatchQueueReader(WatchService watchService, Path toWatch); + protected abstract AbstractWatchQueueReader buildWatchQueueReader(WatchService watchService, Path toWatch, + Map registredWatchKeys); /** - * - * @return + * @return the path to be watched as a {@link String}. The returned path should be applicable for creating a + * {@link Path} with the {@link Paths#get(String, String...)} method. */ protected abstract String getSourcePath(); /** - * - * @return + * Determines whether the subdirectories of the source path (determined by the {@link #getSourcePath()}) will be + * watched or not. + * + * @return true if the subdirectories will be watched and false if only the source path + * (determined by the {@link #getSourcePath()}) will be watched */ protected abstract boolean watchSubDirectories(); /** - * @param subDir - * @throws IOException + * Registers a directory to be watched by the watch service. The {@link WatchKey} of the registration should be + * provided. + * + * @param directory the directory, which will be registered in the watch service + * @return The {@link WatchKey} of the registration or null if no registration has been done. + * @throws IOException if an error occurs while processing the given path */ - protected abstract void registerDirectory(Path subDir) throws IOException; -} \ No newline at end of file + protected abstract WatchKey registerDirectory(Path directory) throws IOException; +} diff --git a/bundles/model/org.eclipse.smarthome.model.core/src/main/java/org/eclipse/smarthome/model/core/internal/folder/FolderObserver.java b/bundles/model/org.eclipse.smarthome.model.core/src/main/java/org/eclipse/smarthome/model/core/internal/folder/FolderObserver.java index 6fa80851300..db973dead72 100644 --- a/bundles/model/org.eclipse.smarthome.model.core/src/main/java/org/eclipse/smarthome/model/core/internal/folder/FolderObserver.java +++ b/bundles/model/org.eclipse.smarthome.model.core/src/main/java/org/eclipse/smarthome/model/core/internal/folder/FolderObserver.java @@ -142,7 +142,7 @@ public WatchQueueReader(WatchService watchService, Path dirToWatch, Map event, Kind kind, Path path) { - File toCheck = getFileByFileExtMap(folderFileExtMap, path.toString()); + File toCheck = getFileByFileExtMap(folderFileExtMap, path.getFileName().toString()); if (toCheck != null) { checkFile(modelRepo, toCheck, kind); }