You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2018/04/18 14:55:04 UTC
[sling-ide-tooling] 04/04: SLING-7587 - Create a CLI-only tool to
sync content
This is an automated email from the ASF dual-hosted git repository.
rombert pushed a commit to branch feature/SLING-7587
in repository https://gitbox.apache.org/repos/asf/sling-ide-tooling.git
commit c5c6da4e429c990ca6281e2217e81a596bfa1a93
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Tue Apr 17 15:09:03 2018 +0300
SLING-7587 - Create a CLI-only tool to sync content
Add a CLI reactor which includes a CLI bundle and a CLI distribution (WIP)
based on the feature model.
---
cli/cli/bnd.bnd | 1 +
cli/cli/pom.xml | 107 +++++++++++
.../org/apache/sling/ide/cli/impl/ContentSync.java | 128 +++++++++++++
.../org/apache/sling/ide/cli/impl/DirWatcher.java | 202 +++++++++++++++++++++
.../sling/ide/cli/impl/Slf4jLoggerFactory.java | 84 +++++++++
.../apache/sling/ide/cli/impl/DirWatcherTest.java | 177 ++++++++++++++++++
cli/dist/.gitignore | 3 +
cli/dist/assemble-app.sh | 5 +
cli/dist/features/clisync.json | 28 +++
cli/dist/pom.xml | 76 ++++++++
cli/dist/run-app.sh | 5 +
cli/pom.xml | 39 ++++
12 files changed, 855 insertions(+)
diff --git a/cli/cli/bnd.bnd b/cli/cli/bnd.bnd
new file mode 100644
index 0000000..1e09af3
--- /dev/null
+++ b/cli/cli/bnd.bnd
@@ -0,0 +1 @@
+-exportcontents: ${packages;VERSIONED}
\ No newline at end of file
diff --git a/cli/cli/pom.xml b/cli/cli/pom.xml
new file mode 100644
index 0000000..f6b83c0
--- /dev/null
+++ b/cli/cli/pom.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor
+ license agreements. See the NOTICE file distributed with this work for additional
+ information regarding copyright ownership. The ASF licenses this file to
+ you under the Apache License, Version 2.0 (the "License"); you may not use
+ this file except in compliance with the License. You may obtain a copy of
+ the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
+ by applicable law or agreed to in writing, software distributed under the
+ License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+ OF ANY KIND, either express or implied. See the License for the specific
+ language governing permissions and limitations under the License. -->
+<project
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+ xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>sling</artifactId>
+ <version>33</version>
+ </parent>
+
+ <artifactId>org.apache.sling.ide.cli</artifactId>
+ <name>Apache Sling IDE Tools CLI</name>
+ <version>1.2.3-SNAPSHOT</version>
+
+ <scm>
+ <connection>scm:git:https://gitbox.apache.org/repos/asf/sling-ide-tooling.git</connection>
+ <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-ide-tooling.git</developerConnection>
+ <url>https://gitbox.apache.org/repos/asf?p=sling-ide-tooling.git</url>
+ </scm>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>biz.aQute.bnd</groupId>
+ <artifactId>bnd-maven-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.cmpn</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.service.event</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling.ide</groupId>
+ <artifactId>org.apache.sling.ide.api</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling.ide</groupId>
+ <artifactId>org.apache.sling.ide.sync-fs</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-library</artifactId>
+ <version>1.3</version>
+ <scope>test</scope>
+ </dependency>
+
+ <!-- Note that OSGi annotations are OK since they are not retained at compile time -->
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.annotation.versioning</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.service.component.annotations</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.service.metatype.annotations</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+ <properties>
+ <sling.java.version>8</sling.java.version>
+ </properties>
+ <groupId>org.apache.sling.ide</groupId>
+</project>
diff --git a/cli/cli/src/main/java/org/apache/sling/ide/cli/impl/ContentSync.java b/cli/cli/src/main/java/org/apache/sling/ide/cli/impl/ContentSync.java
new file mode 100644
index 0000000..9924ced
--- /dev/null
+++ b/cli/cli/src/main/java/org/apache/sling/ide/cli/impl/ContentSync.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.ide.cli.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.StandardWatchEventKinds;
+
+import org.apache.sling.ide.cli.impl.DirWatcher.Event;
+import org.apache.sling.ide.content.sync.fs.FSResources;
+import org.apache.sling.ide.filter.FilterLocator;
+import org.apache.sling.ide.log.Logger;
+import org.apache.sling.ide.sync.content.SyncCommandFactory;
+import org.apache.sling.ide.sync.content.WorkspacePath;
+import org.apache.sling.ide.sync.content.WorkspacePaths;
+import org.apache.sling.ide.sync.content.WorkspaceProject;
+import org.apache.sling.ide.transport.Command;
+import org.apache.sling.ide.transport.Repository;
+import org.apache.sling.ide.transport.RepositoryFactory;
+import org.apache.sling.ide.transport.RepositoryInfo;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+@Component(immediate=true)
+public class ContentSync {
+
+ @Reference
+ private Logger logger;
+
+ @Reference
+ private RepositoryFactory repoFactory;
+
+ @Reference
+ private SyncCommandFactory commandFactory;
+
+ @Reference
+ private FilterLocator filterLocator;
+
+ private DirWatcher watcher;
+
+ private Thread watcherThread;
+
+
+ protected void activate() throws Exception {
+
+ File projectDir = new File("/home/robert/Documents/workspace/content003");
+
+ WorkspaceProject prj = FSResources.create(projectDir, projectDir, filterLocator);
+
+ logger.trace("Working on project {0} at {1}", prj.getName(), prj.getOSPath());
+
+ Repository repo = repoFactory.connectRepository(new RepositoryInfo("admin", "admin", "http://localhost:8080"));
+
+ repo.newListChildrenNodeCommand("/").execute();
+
+ logger.trace("Connected to {0} ", repo.getRepositoryInfo());
+
+ Path syncDirPath = prj.getSyncDirectory().getOSPath();
+
+ watcher = new DirWatcher(syncDirPath);
+
+ logger.trace("Watching syncDir {0}", syncDirPath);
+
+ watcherThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ while ( ! Thread.currentThread().isInterrupted() ) {
+
+ Event event = watcher.poll();
+
+ Path path = event.getPath();
+
+ WorkspacePath resourceRelativePath = WorkspacePaths.fromOsPath(path);
+ logger.trace("Change detected in workspace path {0}", resourceRelativePath);
+ if ( event.getKind() == StandardWatchEventKinds.ENTRY_CREATE ||
+ event.getKind() == StandardWatchEventKinds.ENTRY_MODIFY ) {
+ try {
+ Command<?> cmd = commandFactory.
+ newCommandForAddedOrUpdatedResource(repo, prj.getSyncDirectory().getFile(resourceRelativePath));
+ if ( cmd != null )
+ cmd.execute();
+ } catch (IOException e) {
+ logger.warn("Sync failed for path " + resourceRelativePath , e);
+ }
+ }
+
+ if ( event.getKind() == StandardWatchEventKinds.ENTRY_DELETE ) {
+ try {
+ Command<?> cmd = commandFactory.newCommandForRemovedResource(repo, prj.getSyncDirectory().getFile(resourceRelativePath));
+ if ( cmd != null )
+ cmd.execute();
+ } catch (IOException e) {
+ logger.warn("Sync failed for path " + resourceRelativePath , e);
+ }
+ }
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ });
+
+ watcherThread.start();
+ }
+
+ protected void deactivate() throws Exception {
+
+ if ( watcher != null ) {
+ watcherThread.interrupt();
+ }
+ }
+}
diff --git a/cli/cli/src/main/java/org/apache/sling/ide/cli/impl/DirWatcher.java b/cli/cli/src/main/java/org/apache/sling/ide/cli/impl/DirWatcher.java
new file mode 100644
index 0000000..8eb9795
--- /dev/null
+++ b/cli/cli/src/main/java/org/apache/sling/ide/cli/impl/DirWatcher.java
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.ide.cli.impl;
+
+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.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardWatchEventKinds;
+import java.nio.file.WatchEvent;
+import java.nio.file.WatchEvent.Kind;
+import java.nio.file.WatchKey;
+import java.nio.file.WatchService;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Watches a whole directory tree for changes
+ *
+ * <p>This class works on top of the standard {@link WatchService} API by generating
+ * events for all changes below a given directory.</p>
+ */
+public class DirWatcher implements AutoCloseable {
+
+ private final Path root;
+ private final WatchService ws;
+ private final DualMap watched = new DualMap();
+ private final Thread poller;
+ private final BlockingQueue<DirWatcher.Event> queue = new LinkedBlockingQueue<>();
+
+ public DirWatcher(Path path) throws IOException {
+ this.root = path;
+ ws = path.getFileSystem().newWatchService();
+
+ poller = new Thread(() -> {
+ while ( !Thread.currentThread().isInterrupted() ) {
+ try {
+ queue.addAll(pollInternal());
+ } catch ( InterruptedException e) {
+ Thread.currentThread().interrupt();
+ break;
+ }
+ }
+ }, getClass().getSimpleName() +"-Poller");
+
+ Stream.concat(
+ Stream.of(root),
+ Files.walk(root).filter(p -> p.toFile().isDirectory())
+ ).forEach( this::register);
+
+ poller.start();
+ }
+
+ public void close() throws IOException {
+ if ( poller != null )
+ poller.interrupt();
+ if ( ws != null)
+ ws.close();
+ }
+
+ /**
+ * Takes a single event from the queue, blocking if none are available
+ *
+ * @return the event
+ * @throws InterruptedException interrupted
+ */
+ public Event poll() throws InterruptedException {
+ return queue.take();
+ }
+
+ // visible for testing
+ int queueSize() {
+ return queue.size();
+ }
+
+ private void register(Path path) {
+ try {
+ WatchKey key = path.register(ws, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
+ watched.put(key, path);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void unregister(Path path) {
+ WatchKey key = watched.remove(path);
+ if ( key != null )
+ key.cancel();
+
+ }
+
+ private List<DirWatcher.Event> pollInternal() throws InterruptedException {
+ final WatchKey key = ws.take();
+
+ List<DirWatcher.Event> result = key.pollEvents().stream()
+ .filter( e -> e.context() instanceof Path )
+ .map( Event::new )
+ .map( e -> updateTracked(e, key) )
+ .map( e -> adjust(e, key) )
+ .collect( Collectors.toList() );
+
+ key.reset();
+
+ return result;
+ }
+
+ private DirWatcher.Event adjust(Event e, WatchKey key) {
+ Path keyPath = watched.get(key);
+ e.path = root.relativize(keyPath.resolve(e.path));
+ return e;
+ }
+
+ private DirWatcher.Event updateTracked(DirWatcher.Event evt, WatchKey key) {
+ if ( evt.getKind() == StandardWatchEventKinds.ENTRY_CREATE ) {
+ Path fullPath = watched.get(key).resolve(evt.getPath());
+ if ( fullPath.toFile().isDirectory())
+ register(fullPath);
+ } else if ( evt.getKind() == StandardWatchEventKinds.ENTRY_DELETE ) {
+ Path fullPath = watched.get(key).resolve(evt.getPath());
+ // we can't check if the path pointed to a directory since it is already deleted
+ unregister(fullPath);
+ }
+
+ return evt;
+ }
+
+ public static class Event {
+
+ public Event(WatchEvent<?> wrapper) {
+ kind = wrapper.kind();
+ path = (Path) wrapper.context();
+ }
+
+ private Kind<?> kind;
+ private Path path;
+
+ public Kind<?> getKind() {
+ return kind;
+ }
+
+ public Path getPath() {
+ return path;
+ }
+ }
+
+ static class DualMap {
+
+ private final Map<WatchKey, Path> forward = new HashMap<>();
+ private final Map<Path, WatchKey> reverse = new HashMap<>();
+ private final Object sync = new Object();
+
+ public void put(WatchKey key, Path path) {
+ synchronized (sync) {
+ forward.put(key, path);
+ reverse.put(path, key);
+ }
+ }
+
+ public Path get(WatchKey key) {
+ synchronized (sync) {
+ return forward.get(key);
+ }
+ }
+
+ public WatchKey get(Path path) {
+ synchronized (sync) {
+ return reverse.get(path);
+ }
+ }
+
+ public WatchKey remove(Path path) {
+ synchronized (sync) {
+ WatchKey key = reverse.get(path);
+ if ( key != null )
+ forward.remove(key);
+ return key;
+ }
+ }
+ }
+}
diff --git a/cli/cli/src/main/java/org/apache/sling/ide/cli/impl/Slf4jLoggerFactory.java b/cli/cli/src/main/java/org/apache/sling/ide/cli/impl/Slf4jLoggerFactory.java
new file mode 100644
index 0000000..288d142
--- /dev/null
+++ b/cli/cli/src/main/java/org/apache/sling/ide/cli/impl/Slf4jLoggerFactory.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.ide.cli.impl;
+
+
+import org.apache.sling.ide.log.Logger;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ServiceScope;
+import org.slf4j.LoggerFactory;
+
+@Component(service = Logger.class, scope = ServiceScope.BUNDLE)
+public class Slf4jLoggerFactory implements Logger {
+
+ private static final long PERF_IGNORE_THRESHOLD = 50;
+
+ private final org.slf4j.Logger wrapped = LoggerFactory.getLogger(Slf4jLoggerFactory.class);
+
+ private String marker;
+
+ protected void activate(ComponentContext ctx) {
+ marker = "[" + ctx.getUsingBundle().getSymbolicName() + "] ";
+ wrapped.info(marker + "Logger initialized");
+ }
+
+ @Override
+ public void warn(String message, Throwable cause) {
+ wrapped.warn( marker + message, cause);
+ }
+
+ @Override
+ public void warn(String message) {
+ wrapped.warn(marker + message);
+ }
+
+ @Override
+ public void trace(String message, Throwable error) {
+ wrapped.info(marker + message, error);
+ }
+
+ @Override
+ public void trace(String message, Object... arguments) {
+
+ // this is probably a horribly slow implementation, but it does not matter
+ for (int i = 0; i < arguments.length; i++) {
+ message = message.replace("{" + i + "}", String.valueOf(arguments[i]));
+ }
+
+ wrapped.info(marker + message);
+ }
+
+ @Override
+ public void error(String message, Throwable cause) {
+ wrapped.error(marker + message, cause);
+ }
+
+ @Override
+ public void error(String message) {
+ wrapped.error(marker + message);
+ }
+
+ @Override
+ public void tracePerformance(String message, long duration, Object... arguments) {
+ if (duration < PERF_IGNORE_THRESHOLD) {
+ return;
+ }
+ trace(message + " took " + duration + " ms", arguments);
+ }
+
+}
diff --git a/cli/cli/src/test/java/org/apache/sling/ide/cli/impl/DirWatcherTest.java b/cli/cli/src/test/java/org/apache/sling/ide/cli/impl/DirWatcherTest.java
new file mode 100644
index 0000000..4c6b173
--- /dev/null
+++ b/cli/cli/src/test/java/org/apache/sling/ide/cli/impl/DirWatcherTest.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.ide.cli.impl;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+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.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class DirWatcherTest {
+
+ @Rule
+ public TemporaryFolder folder = new TemporaryFolder();
+
+ @Test(timeout = 3000)
+ public void addedFileInRoot() throws IOException, InterruptedException {
+
+ File watchRoot = folder.newFolder();
+
+ try ( DirWatcher w = new DirWatcher(watchRoot.toPath()) ) {
+
+ final File created = new File(watchRoot, "README");
+ created.createNewFile();
+
+ DirWatcher.Event event = w.poll();
+
+ assertThat("event.kind", event.getKind(), equalTo(ENTRY_CREATE));
+ assertThat("event.path", event.getPath(), equalTo(Paths.get(created.getName())));
+
+ assertThat("queue.size", w.queueSize(), equalTo(0));
+ }
+ }
+
+ @Test(timeout = 3000)
+ public void addedFileInSubdir() throws IOException, InterruptedException {
+
+ File watchRoot = folder.newFolder();
+ File subDir = new File(watchRoot, "subDir");
+ subDir.mkdir();
+
+ try ( DirWatcher w = new DirWatcher(watchRoot.toPath()) ) {
+
+ File created = new File(subDir, "README");
+ created.createNewFile();
+
+ DirWatcher.Event event = w.poll();
+
+ assertThat("event.kind", event.getKind(), equalTo(ENTRY_CREATE));
+ assertThat("event.path", event.getPath(), equalTo(Paths.get(subDir.getName(), created.getName())));
+
+ assertThat("queue.size", w.queueSize(), equalTo(0));
+ }
+
+ }
+
+ @Test(timeout = 3000)
+ public void addedFileInNewSubdir() throws IOException, InterruptedException {
+
+ File watchRoot = folder.newFolder();
+
+ try ( DirWatcher w = new DirWatcher(watchRoot.toPath()) ) {
+
+ File subDir = new File(watchRoot, "subDir");
+ subDir.mkdir();
+
+ DirWatcher.Event event = w.poll();
+
+ assertThat("event.kind", event.getKind(), equalTo(ENTRY_CREATE));
+ assertThat("event.path", event.getPath(), equalTo(Paths.get(subDir.getName())));
+
+ File created = new File(subDir, "README");
+ created.createNewFile();
+
+ event = w.poll();
+ assertThat("event.kind", event.getKind(), equalTo(ENTRY_CREATE));
+ assertThat("event.path", event.getPath(), equalTo(Paths.get(subDir.getName(), created.getName())));
+
+ assertThat("queue.size", w.queueSize(), equalTo(0));
+ }
+ }
+
+ @Test(timeout = 3000)
+ public void deletedFile() throws IOException, InterruptedException {
+
+ File watchRoot = folder.newFolder();
+ File subDir = new File(watchRoot, "subDir");
+ subDir.mkdir();
+
+ File created = new File(subDir, "README");
+ created.createNewFile();
+
+ try ( DirWatcher w = new DirWatcher(watchRoot.toPath()) ) {
+
+ created.delete();
+
+ DirWatcher.Event event = w.poll();
+
+ assertThat("event.kind", event.getKind(), equalTo(ENTRY_DELETE));
+ assertThat("event.path", event.getPath(), equalTo(Paths.get(subDir.getName(), created.getName())));
+
+ assertThat("queue.size", w.queueSize(), equalTo(0));
+ }
+ }
+
+ @Test(timeout = 300000)
+ public void deleteDir() throws IOException, InterruptedException {
+
+ File watchRoot = folder.newFolder();
+ File subDir = new File(watchRoot, "subDir");
+ subDir.mkdir();
+
+
+ try ( DirWatcher w = new DirWatcher(watchRoot.toPath()) ) {
+
+ Files.delete(subDir.toPath());
+
+ DirWatcher.Event event = w.poll();
+
+ assertThat("event.kind", event.getKind(), equalTo(ENTRY_DELETE));
+ assertThat("event.path", event.getPath(), equalTo(Paths.get(subDir.getName())));
+
+ assertThat("queue.size", w.queueSize(), equalTo(0));
+ }
+ }
+
+ @Test(timeout = 3000)
+ public void modifyFile() throws IOException, InterruptedException {
+
+ File watchRoot = folder.newFolder();
+ final File created = new File(watchRoot, "README");
+ created.createNewFile();
+
+ try ( DirWatcher w = new DirWatcher(watchRoot.toPath()) ) {
+
+ Files.write(created.toPath(), "hello, world".getBytes(UTF_8));
+
+ DirWatcher.Event event = w.poll();
+
+ assertThat("event.kind", event.getKind(), equalTo(ENTRY_MODIFY));
+ assertThat("event.path", event.getPath(), equalTo(Paths.get(created.getName())));
+
+ Files.write(created.toPath(), "hello, again".getBytes(UTF_8));
+
+ event = w.poll();
+
+ assertThat("event.kind", event.getKind(), equalTo(ENTRY_MODIFY));
+ assertThat("event.path", event.getPath(), equalTo(Paths.get(created.getName())));
+
+ assertThat("queue.size", w.queueSize(), equalTo(0));
+ }
+ }
+}
diff --git a/cli/dist/.gitignore b/cli/dist/.gitignore
new file mode 100644
index 0000000..952774f
--- /dev/null
+++ b/cli/dist/.gitignore
@@ -0,0 +1,3 @@
+/felix-cache/
+/launcher/
+/sling.json
diff --git a/cli/dist/assemble-app.sh b/cli/dist/assemble-app.sh
new file mode 100755
index 0000000..e869306
--- /dev/null
+++ b/cli/dist/assemble-app.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+rm -rf felix-cache launcher
+
+java -cp ../../../whiteboard/featuremodel/feature-applicationbuilder/target/org.apache.sling.feature.applicationbuilder-0.0.1-SNAPSHOT.jar:${HOME}/.m2/repository/org/apache/felix/org.apache.felix.framework/5.6.8/org.apache.felix.framework-5.6.8.jar org.apache.sling.feature.applicationbuilder.impl.Main -d features/ -u file://${HOME}/.m2/repository -o sling.json
diff --git a/cli/dist/features/clisync.json b/cli/dist/features/clisync.json
new file mode 100644
index 0000000..0fb71fb
--- /dev/null
+++ b/cli/dist/features/clisync.json
@@ -0,0 +1,28 @@
+{
+ "id": "org.apache.sling.ide/org.apache.sling.ide.cli-dist/1.0.0",
+ "bundles": [
+ "org.apache.felix/org.apache.felix.eventadmin/1.4.10",
+ "org.slf4j/slf4j-api/1.7.25",
+ "org.slf4j/slf4j-simple/1.7.25",
+ "org.slf4j/jcl-over-slf4j/1.7.25",
+ "org.apache.felix/org.apache.felix.scr/2.0.12",
+ "org.apache.sling.ide/org.apache.sling.ide.api/1.2.3-SNAPSHOT",
+ "org.apache.sling.ide/org.apache.sling.ide.impl-vlt/1.2.3-SNAPSHOT",
+ "org.apache.sling.ide/org.apache.sling.ide.sync-fs/1.2.3-SNAPSHOT",
+ "org.apache.sling.ide/org.apache.sling.ide.cli/1.2.3-SNAPSHOT",
+ "org.apache.sling.ide/org.apache.sling.ide.vlt-wrapper/1.2.3-SNAPSHOT",
+ "javax.servlet/javax.servlet-api/3.1.0",
+ "commons-collections/commons-collections/3.2.2",
+ "org.apache.sling/org.apache.sling.fragment.xml/1.0.2",
+ "org.apache.geronimo.bundles/commons-httpclient/3.1_2",
+ "commons-codec/commons-codec/1.11",
+ "com.google.code.gson/gson/2.2.4",
+ "commons-io/commons-io/2.6",
+ "org.apache.felix/org.apache.felix.gogo.command/1.0.2",
+ "org.apache.felix/org.apache.felix.gogo.runtime/1.0.6",
+ "org.apache.felix/org.apache.felix.gogo.jline/1.0.0",
+ "org.apache.felix/org.apache.felix.gogo.shell/1.0.0",
+ "org.jline/jline/3.0.1",
+ "org.apache.geronimo.specs/geronimo-atinject_1.0_spec/1.0"
+ ]
+}
\ No newline at end of file
diff --git a/cli/dist/pom.xml b/cli/dist/pom.xml
new file mode 100644
index 0000000..e99bdc4
--- /dev/null
+++ b/cli/dist/pom.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor
+ license agreements. See the NOTICE file distributed with this work for additional
+ information regarding copyright ownership. The ASF licenses this file to
+ you under the Apache License, Version 2.0 (the "License"); you may not use
+ this file except in compliance with the License. You may obtain a copy of
+ the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
+ by applicable law or agreed to in writing, software distributed under the
+ License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+ OF ANY KIND, either express or implied. See the License for the specific
+ language governing permissions and limitations under the License. -->
+<project
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+ xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>sling</artifactId>
+ <version>33</version>
+ </parent>
+
+ <artifactId>org.apache.sling.ide.cli-dist</artifactId>
+ <name>Apache Sling IDE Tools CLI distribution module</name>
+ <packaging>osgiapp</packaging>
+
+ <scm>
+ <connection>scm:git:https://gitbox.apache.org/repos/asf/sling-ide-tooling.git</connection>
+ <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-ide-tooling.git</developerConnection>
+ <url>https://gitbox.apache.org/repos/asf?p=sling-ide-tooling.git</url>
+ </scm>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>osgifeature-maven-plugin</artifactId>
+ <version>0.01.7-SNAPSHOT</version>
+ <extensions>true</extensions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.service.event</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- Note that OSGi annotations are OK since they are not retained at compile time -->
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.annotation.versioning</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.service.component.annotations</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.service.metatype.annotations</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+ <properties>
+ <sling.java.version>8</sling.java.version>
+ </properties>
+</project>
diff --git a/cli/dist/run-app.sh b/cli/dist/run-app.sh
new file mode 100755
index 0000000..4571e12
--- /dev/null
+++ b/cli/dist/run-app.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+rm -rf felix-cache launcher
+
+java -jar ../../../whiteboard/featuremodel/feature-launcher/target/org.apache.sling.feature.launcher-0.0.1-SNAPSHOT.jar -a sling.json
diff --git a/cli/pom.xml b/cli/pom.xml
new file mode 100644
index 0000000..fd40a59
--- /dev/null
+++ b/cli/pom.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor
+ license agreements. See the NOTICE file distributed with this work for additional
+ information regarding copyright ownership. The ASF licenses this file to
+ you under the Apache License, Version 2.0 (the "License"); you may not use
+ this file except in compliance with the License. You may obtain a copy of
+ the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
+ by applicable law or agreed to in writing, software distributed under the
+ License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+ OF ANY KIND, either express or implied. See the License for the specific
+ language governing permissions and limitations under the License. -->
+<project
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+ xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache</groupId>
+ <artifactId>apache</artifactId>
+ <version>14</version>
+ </parent>
+ <groupId>org.apache.sling.ide</groupId>
+ <artifactId>sling-ide-tooling-cli</artifactId>
+ <version>1.2.3-SNAPSHOT</version>
+ <packaging>pom</packaging>
+ <name>Apache Sling IDE Tools - CLI</name>
+ <url>http://sling.apache.org</url>
+
+ <scm>
+ <connection>scm:git:https://gitbox.apache.org/repos/asf/sling-ide-tooling.git</connection>
+ <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-ide-tooling.git</developerConnection>
+ <url>https://gitbox.apache.org/repos/asf?p=sling-ide-tooling.git</url>
+ </scm>
+
+ <modules>
+ <module>cli</module>
+ <module>dist</module>
+ </modules>
+</project>
--
To stop receiving notification emails like this one, please contact
rombert@apache.org.