Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into 3.x
Browse files Browse the repository at this point in the history
Conflicts:
	bundles/runtime/org.eclipse.fx.core/src/org/eclipse/fx/core/internal/FileSystemServiceImpl.java
  • Loading branch information
tomsontom committed Dec 15, 2017
2 parents bf0f602 + c2efc8d commit bf42e96
Show file tree
Hide file tree
Showing 2 changed files with 218 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
/*******************************************************************************
* Copyright (c) 2015 BestSolution.at 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
*
* Contributors:
* Tom Schindl<[email protected]> - initial API and implementation
*******************************************************************************/
package org.eclipse.fx.core.internal;

import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.ClosedWatchServiceException;
import java.nio.file.FileSystem;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
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.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.function.BiConsumer;

import org.eclipse.fx.core.FilesystemService;
import org.eclipse.fx.core.Subscription;
import org.eclipse.fx.core.URI;
import org.eclipse.fx.core.log.LoggerCreator;
import org.osgi.service.component.annotations.Component;

/**
* Implementation of a file system
*
* @since 1.2
*/
@Component
public class FileSystemServiceImpl implements FilesystemService {
private CheckThread thread;

@Override
public boolean applies(URI path) {
return path.toString().startsWith("file:"); //$NON-NLS-1$
}

@SuppressWarnings("all")
@Override
public Subscription observePath(URI uri, BiConsumer<Kind, URI> consumer) {
try {
return observePath(Paths.get(new java.net.URI(uri.toString())),
(k, p) -> consumer.accept(k, URI.create(p.toUri().toString())));
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}

@Override
public Subscription observePath(Path path, BiConsumer<Kind, Path> consumer) {
if (this.thread == null || !this.thread.isAlive()) {
this.thread = new CheckThread(path.getFileSystem());
this.thread.start();
}
try {
return this.thread.registerURI(path, consumer);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

static class CheckThread extends Thread {
public final Map<WatchKey, List<PathSubscription>> subscriptions = new HashMap<>();
private final WatchService watcher;
private final Executor dispatcher = Executors.newCachedThreadPool();
private final Thread shutdownCleanup;

public CheckThread(FileSystem fileSystem) {
setDaemon(true);
try {
this.watcher = fileSystem.newWatchService();
} catch (IOException e) {
throw new IllegalStateException();
}
this.shutdownCleanup = new Thread(() -> {
try {
this.watcher.close();
} catch (Exception e) {
// do not care about
}
});
Runtime.getRuntime().addShutdownHook(this.shutdownCleanup);
}

@Override
protected void finalize() throws Throwable {
super.finalize();
}

public Subscription registerURI(Path path, BiConsumer<Kind, Path> consumer) throws IOException {
WatchKey register = path.register(this.watcher, StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);

PathSubscription s = new PathSubscription(this, path, register, consumer);
synchronized (this.subscriptions) {
List<PathSubscription> list = this.subscriptions.get(register);
if (list == null) {
list = new CopyOnWriteArrayList<>();
this.subscriptions.put(register, list);
}
list.add(s);
}

return s;
}

@SuppressWarnings("null")
@Override
public void run() {
while (true) {
WatchKey key;
try {
key = this.watcher.take();
} catch (Exception x) {
if( x instanceof ClosedWatchServiceException ) {
// nothing to be done
} else {
LoggerCreator.createLogger(getClass()).warning("File watcher failed. Watching ended", x); //$NON-NLS-1$
}

return;
}

for (WatchEvent<?> event : key.pollEvents()) {
@SuppressWarnings("unchecked")
WatchEvent<Path> e = (WatchEvent<Path>) event;
WatchEvent.Kind<?> kind = e.kind();
if (kind == StandardWatchEventKinds.OVERFLOW) {
continue;
}
Path context = e.context();
synchronized (this.subscriptions) {
List<PathSubscription> pathSubscriptionList = this.subscriptions.get(key);

if (pathSubscriptionList != null) {
for (PathSubscription pathSubscription : pathSubscriptionList) {
PathSubscription fp = pathSubscription;
this.dispatcher.execute((() -> fp.consumer.accept(toKind(kind),
fp.path.resolve(context))));
}
}
}
}

key.reset();
}
}

synchronized void removeSubscription(PathSubscription subscription) {
synchronized (this.subscriptions) {
List<PathSubscription> list = this.subscriptions.get(subscription.register);

if( list != null ) {
list.remove(subscription);
if( list.isEmpty() ) {
this.subscriptions.remove(subscription.register);
}
}

if (this.subscriptions.isEmpty()) {
try {
Runtime.getRuntime().removeShutdownHook(this.shutdownCleanup);
this.watcher.close();
} catch (IOException e) {
// ignore
}
}
}
}

private static Kind toKind(WatchEvent.Kind<?> kind) {
if (kind == StandardWatchEventKinds.ENTRY_CREATE) {
return Kind.CREATE;
} else if (kind == StandardWatchEventKinds.ENTRY_DELETE) {
return Kind.DELETE;
} else {
return Kind.MODIFY;
}
}
}

static class PathSubscription implements Subscription {
public final Path path;
public final WatchKey register;
public final CheckThread thread;
public final BiConsumer<Kind, Path> consumer;

public PathSubscription(CheckThread thread, Path path, WatchKey register, BiConsumer<Kind, Path> consumer) {
this.thread = thread;
this.path = path;
this.register = register;
this.consumer = consumer;
}

@Override
public void dispose() {
this.register.cancel();
this.thread.removeSubscription(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.ClosedWatchServiceException;
import java.nio.file.FileSystems;
import java.nio.file.FileSystem;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
Expand Down Expand Up @@ -62,7 +62,7 @@ public Subscription observePath(URI uri, BiConsumer<Kind, URI> consumer) {
@Override
public Subscription observePath(Path path, BiConsumer<Kind, Path> consumer) {
if (this.thread == null || !this.thread.isAlive()) {
this.thread = new CheckThread();
this.thread = new CheckThread(path.getFileSystem());
this.thread.start();
}
try {
Expand All @@ -78,10 +78,10 @@ static class CheckThread extends Thread {
private final Executor dispatcher = Executors.newCachedThreadPool();
private final Thread shutdownCleanup;

public CheckThread() {
public CheckThread(FileSystem fileSystem) {
setDaemon(true);
try {
this.watcher = FileSystems.getDefault().newWatchService();
this.watcher = fileSystem.newWatchService();
} catch (IOException e) {
throw new IllegalStateException();
}
Expand Down

0 comments on commit bf42e96

Please sign in to comment.