Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor WatchService #3004

Merged
merged 8 commits into from
Feb 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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