Skip to content

Commit

Permalink
Refactor WatchService (#3004)
Browse files Browse the repository at this point in the history
Signed-off-by: Jan N. Klug <github@klug.nrw>
  • Loading branch information
J-N-K authored Feb 12, 2023
1 parent c349c01 commit 7f113c5
Show file tree
Hide file tree
Showing 58 changed files with 1,282 additions and 1,803 deletions.
19 changes: 19 additions & 0 deletions bom/compile/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,25 @@
<scope>compile</scope>
</dependency>

<!-- dirwatcher -->
<dependency>
<groupId>org.openhab.osgiify</groupId>
<artifactId>io.methvin.directory-watcher</artifactId>
<version>0.17.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.12.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>5.12.1</version>
<scope>compile</scope>
</dependency>
</dependencies>

</project>
20 changes: 20 additions & 0 deletions bom/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,26 @@
<version>3.27.0-GA</version>
<scope>compile</scope>
</dependency>

<!-- dirwatcher -->
<dependency>
<groupId>org.openhab.osgiify</groupId>
<artifactId>io.methvin.directory-watcher</artifactId>
<version>0.17.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.12.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>5.12.1</version>
<scope>compile</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
import org.openhab.core.automation.module.script.rulesupport.loader.AbstractScriptFileWatcher;
import org.openhab.core.service.ReadyService;
import org.openhab.core.service.StartLevelService;
import org.openhab.core.service.WatchService;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;

/**
Expand All @@ -37,20 +37,10 @@ public class DefaultScriptFileWatcher extends AbstractScriptFileWatcher {
private static final String FILE_DIRECTORY = "automation" + File.separator + "jsr223";

@Activate
public DefaultScriptFileWatcher(final @Reference ScriptEngineManager manager,
final @Reference ReadyService readyService, final @Reference StartLevelService startLevelService) {
super(manager, readyService, startLevelService, FILE_DIRECTORY);
}

@Activate
@Override
public void activate() {
super.activate();
}

@Deactivate
@Override
public void deactivate() {
super.deactivate();
public DefaultScriptFileWatcher(
final @Reference(target = WatchService.CONFIG_WATCHER_FILTER) WatchService watchService,
final @Reference ScriptEngineManager manager, final @Reference ReadyService readyService,
final @Reference StartLevelService startLevelService) {
super(watchService, manager, readyService, startLevelService, FILE_DIRECTORY, true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,21 @@
*/
package org.openhab.core.automation.module.script.rulesupport.loader;

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 static org.openhab.core.service.WatchService.Kind.CREATE;
import static org.openhab.core.service.WatchService.Kind.DELETE;
import static org.openhab.core.service.WatchService.Kind.MODIFY;

import java.io.File;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.automation.module.script.ScriptDependencyTracker;
import org.openhab.core.automation.module.script.rulesupport.internal.loader.BidiSetBag;
import org.openhab.core.service.AbstractWatchService;
import org.openhab.core.service.WatchService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -41,54 +39,34 @@
* @author Jan N. Klug - Refactored to OSGi service
*/
@NonNullByDefault
public abstract class AbstractScriptDependencyTracker implements ScriptDependencyTracker {
public abstract class AbstractScriptDependencyTracker
implements ScriptDependencyTracker, WatchService.WatchEventListener {
private final Logger logger = LoggerFactory.getLogger(AbstractScriptDependencyTracker.class);

protected final String libraryPath;
protected final Path libraryPath;

private final Set<ScriptDependencyTracker.Listener> dependencyChangeListeners = ConcurrentHashMap.newKeySet();

private final BidiSetBag<String, String> scriptToLibs = new BidiSetBag<>();
private @Nullable AbstractWatchService dependencyWatchService;
private final WatchService watchService;

public AbstractScriptDependencyTracker(final String libraryPath) {
this.libraryPath = libraryPath;
}
public AbstractScriptDependencyTracker(WatchService watchService, final String libraryPath) {
this.libraryPath = Path.of(libraryPath);
this.watchService = watchService;

public void activate() {
AbstractWatchService dependencyWatchService = createDependencyWatchService();
dependencyWatchService.activate();
this.dependencyWatchService = dependencyWatchService;
watchService.registerListener(this, this.libraryPath);
}

public void deactivate() {
AbstractWatchService dependencyWatchService = this.dependencyWatchService;
if (dependencyWatchService != null) {
dependencyWatchService.deactivate();
}
watchService.unregisterListener(this);
}

protected AbstractWatchService createDependencyWatchService() {
return new AbstractWatchService(libraryPath) {
@Override
protected boolean watchSubDirectories() {
return true;
}

@Override
protected WatchEvent.Kind<?> @Nullable [] getWatchEventKinds(Path path) {
return new WatchEvent.Kind<?>[] { ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY };
}

@Override
protected void processWatchEvent(WatchEvent<?> watchEvent, WatchEvent.Kind<?> kind, Path path) {
File file = path.toFile();
if (!file.isHidden() && (kind.equals(ENTRY_DELETE)
|| (file.canRead() && (kind.equals(ENTRY_CREATE) || kind.equals(ENTRY_MODIFY))))) {
dependencyChanged(file.getPath());
}
}
};
@Override
public void processWatchEvent(WatchService.Kind kind, Path path) {
File file = path.toFile();
if (!file.isHidden() && (kind == DELETE || (file.canRead() && (kind == CREATE || kind == MODIFY)))) {
dependencyChanged(file.getPath());
}
}

protected void dependencyChanged(String dependency) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@
*/
package org.openhab.core.automation.module.script.rulesupport.loader;

import static java.nio.file.StandardWatchEventKinds.*;
import static org.openhab.core.service.WatchService.Kind.CREATE;
import static org.openhab.core.service.WatchService.Kind.DELETE;
import static org.openhab.core.service.WatchService.Kind.MODIFY;

import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.nio.file.WatchEvent.Kind;
import java.util.Collection;
import java.util.List;
import java.util.Map;
Expand All @@ -48,11 +48,11 @@
import org.openhab.core.automation.module.script.ScriptEngineManager;
import org.openhab.core.automation.module.script.rulesupport.internal.loader.ScriptFileReference;
import org.openhab.core.common.NamedThreadFactory;
import org.openhab.core.service.AbstractWatchService;
import org.openhab.core.service.ReadyMarker;
import org.openhab.core.service.ReadyMarkerFilter;
import org.openhab.core.service.ReadyService;
import org.openhab.core.service.StartLevelService;
import org.openhab.core.service.WatchService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -67,7 +67,7 @@
* @author Jan N. Klug - Refactored dependency tracking to script engine factories
*/
@NonNullByDefault
public abstract class AbstractScriptFileWatcher extends AbstractWatchService implements ReadyService.ReadyTracker,
public abstract class AbstractScriptFileWatcher implements WatchService.WatchEventListener, ReadyService.ReadyTracker,
ScriptDependencyTracker.Listener, ScriptEngineManager.FactoryChangeListener, ScriptFileWatcher {

private static final Set<String> EXCLUDED_FILE_EXTENSIONS = Set.of("txt", "old", "example", "backup", "md", "swp",
Expand All @@ -82,20 +82,26 @@ public abstract class AbstractScriptFileWatcher extends AbstractWatchService imp

private final ScriptEngineManager manager;
private final ReadyService readyService;
private final WatchService watchService;
private final Path watchPath;
private final boolean watchSubDirectories;

protected ScheduledExecutorService scheduler;

private final Map<String, ScriptFileReference> scriptMap = new ConcurrentHashMap<>();
private final Map<String, Lock> scriptLockMap = new ConcurrentHashMap<>();
private final CompletableFuture<@Nullable Void> initialized = new CompletableFuture<>();

private volatile int currentStartLevel = 0;
private volatile int currentStartLevel;

public AbstractScriptFileWatcher(final ScriptEngineManager manager, final ReadyService readyService,
final StartLevelService startLevelService, final String fileDirectory) {
super(OpenHAB.getConfigFolder() + File.separator + fileDirectory);
public AbstractScriptFileWatcher(final WatchService watchService, final ScriptEngineManager manager,
final ReadyService readyService, final StartLevelService startLevelService, final String fileDirectory,
boolean watchSubDirectories) {
this.watchService = watchService;
this.manager = manager;
this.readyService = readyService;
this.watchSubDirectories = watchSubDirectories;
this.watchPath = Path.of(OpenHAB.getConfigFolder()).resolve(fileDirectory);

manager.addFactoryChangeListener(this);
readyService.registerTracker(this, new ReadyMarkerFilter().withType(StartLevelService.STARTLEVEL_MARKER_TYPE));
Expand All @@ -108,13 +114,6 @@ public AbstractScriptFileWatcher(final ScriptEngineManager manager, final ReadyS
}
}

@Override
public void activate() {
// TODO: needed to initialize underlying AbstractWatchService, should be removed when we switch to PR
// openhab-core#3004
super.activate();
}

/**
* Can be overridden by subclasses (e.g. for testing)
*
Expand All @@ -124,11 +123,23 @@ protected ScheduledExecutorService getScheduler() {
return Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("scriptwatcher"));
}

@Override
public void activate() {
if (!Files.exists(watchPath)) {
try {
Files.createDirectories(watchPath);
} catch (IOException e) {
logger.warn("Failed to create watched directory: {}", watchPath);
}
} else if (!Files.isDirectory(watchPath)) {
logger.warn("Trying to watch directory {}, however it is a file", watchPath);
}
watchService.registerListener(this, watchPath, watchSubDirectories);
}

public void deactivate() {
watchService.unregisterListener(this);
manager.removeFactoryChangeListener(this);
readyService.unregisterTracker(this);
super.deactivate();

CompletableFuture.allOf(
Set.copyOf(scriptMap.keySet()).stream().map(this::removeFile).toArray(CompletableFuture<?>[]::new))
Expand Down Expand Up @@ -200,22 +211,12 @@ private List<Path> listFiles(Path path, boolean includeSubDirectory) {
}

@Override
protected boolean watchSubDirectories() {
return true;
}

@Override
protected Kind<?> @Nullable [] getWatchEventKinds(@Nullable Path subDir) {
return new Kind<?>[] { ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY };
}

@Override
protected void processWatchEvent(@Nullable WatchEvent<?> event, @Nullable Kind<?> kind, @Nullable Path path) {
public void processWatchEvent(WatchService.Kind kind, Path path) {
File file = path.toFile();
if (!file.isHidden()) {
if (ENTRY_DELETE.equals(kind)) {
if (kind == DELETE) {
if (file.isDirectory()) {
if (watchSubDirectories()) {
if (watchSubDirectories) {
synchronized (this) {
String prefix = path.getParent().toString();
Set<String> toRemove = scriptMap.keySet().stream().filter(ref -> ref.startsWith(prefix))
Expand All @@ -228,8 +229,8 @@ protected void processWatchEvent(@Nullable WatchEvent<?> event, @Nullable Kind<?
}
}

if (file.canRead() && (ENTRY_CREATE.equals(kind) || ENTRY_MODIFY.equals(kind))) {
addFiles(listFiles(file.toPath(), watchSubDirectories()));
if (file.canRead() && (kind == CREATE || kind == MODIFY)) {
addFiles(listFiles(file.toPath(), watchSubDirectories));
}
}
}
Expand Down Expand Up @@ -341,17 +342,17 @@ private boolean createAndLoad(ScriptFileReference ref) {
}

private void initialImport() {
File directory = new File(pathToWatch);
File directory = watchPath.toFile();

if (!directory.exists()) {
if (!directory.mkdirs()) {
logger.warn("Failed to create watched directory: {}", pathToWatch);
logger.warn("Failed to create watched directory: {}", watchPath);
}
} else if (directory.isFile()) {
logger.warn("Trying to watch directory {}, however it is a file", pathToWatch);
logger.warn("Trying to watch directory {}, however it is a file", watchPath);
}

addFiles(listFiles(directory.toPath(), watchSubDirectories())).thenRun(() -> initialized.complete(null));
addFiles(listFiles(directory.toPath(), watchSubDirectories)).thenRun(() -> initialized.complete(null));
}

@Override
Expand Down
Loading

1 comment on commit 7f113c5

@openhab-bot
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commit has been mentioned on openHAB Community. There might be relevant details there:

https://community.openhab.org/t/openhab-4-0-snapshot-discussion/142322/198

Please sign in to comment.