Refactored "file open events" to "app launch events", fixes #55

This commit is contained in:
Sebastian Stenzel
2019-02-21 00:02:12 +01:00
parent 8814372c68
commit debcab47e2
5 changed files with 84 additions and 61 deletions

View File

@@ -4,10 +4,10 @@ import dagger.Module;
import dagger.Provides;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.SettingsProvider;
import org.cryptomator.ui.model.AppLaunchEvent;
import javax.inject.Named;
import javax.inject.Singleton;
import java.nio.file.Path;
import java.util.Optional;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
@@ -23,8 +23,8 @@ class CryptomatorModule {
@Provides
@Singleton
@Named("fileOpenRequests")
BlockingQueue<Path> provideFileOpenRequests() {
@Named("launchEventQueue")
BlockingQueue<AppLaunchEvent> provideFileOpenRequests() {
return new ArrayBlockingQueue<>(10);
}

View File

@@ -13,8 +13,13 @@ import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.function.Function;
import java.util.stream.Stream;
import org.cryptomator.ui.model.AppLaunchEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -26,11 +31,11 @@ import javax.inject.Singleton;
class FileOpenRequestHandler {
private static final Logger LOG = LoggerFactory.getLogger(FileOpenRequestHandler.class);
private final BlockingQueue<Path> fileOpenRequests;
private final BlockingQueue<AppLaunchEvent> launchEventQueue;
@Inject
public FileOpenRequestHandler(@Named("fileOpenRequests") BlockingQueue<Path> fileOpenRequests) {
this.fileOpenRequests = fileOpenRequests;
public FileOpenRequestHandler(@Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue) {
this.launchEventQueue = launchEventQueue;
try {
Desktop.getDesktop().setOpenFileHandler(this::openFiles);
} catch (UnsupportedOperationException e) {
@@ -39,7 +44,9 @@ class FileOpenRequestHandler {
}
private void openFiles(final OpenFilesEvent evt) {
evt.getFiles().stream().map(File::toPath).forEach(fileOpenRequests::add);
Stream<Path> pathsToOpen = evt.getFiles().stream().map(File::toPath);
AppLaunchEvent launchEvent = new AppLaunchEvent(pathsToOpen);
tryToEnqueueFileOpenRequest(launchEvent);
}
public void handleLaunchArgs(String[] args) {
@@ -48,19 +55,22 @@ class FileOpenRequestHandler {
// visible for testing
void handleLaunchArgs(FileSystem fs, String[] args) {
for (String arg : args) {
Stream<Path> pathsToOpen = Arrays.stream(args).map(str -> {
try {
Path path = fs.getPath(arg);
tryToEnqueueFileOpenRequest(path);
return fs.getPath(str);
} catch (InvalidPathException e) {
LOG.trace("{} not a valid path", arg);
LOG.trace("Argument not a valid path: {}", str);
return null;
}
}
}).filter(Objects::nonNull);
AppLaunchEvent launchEvent = new AppLaunchEvent(pathsToOpen);
tryToEnqueueFileOpenRequest(launchEvent);
}
private void tryToEnqueueFileOpenRequest(Path path) {
if (!fileOpenRequests.offer(path)) {
LOG.warn("{} could not be enqueued for opening.", path);
private void tryToEnqueueFileOpenRequest(AppLaunchEvent launchEvent) {
if (!launchEventQueue.offer(launchEvent)) {
LOG.warn("Could not enqueue application launch event.", launchEvent);
}
}

View File

@@ -5,7 +5,13 @@
*******************************************************************************/
package org.cryptomator.launcher;
import org.cryptomator.ui.model.AppLaunchEvent;
import org.hamcrest.CoreMatchers;
import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
@@ -13,64 +19,55 @@ import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.spi.FileSystemProvider;
import java.nio.file.Paths;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class FileOpenRequestHandlerTest {
@Test
public void testOpenArgsWithCorrectPaths() throws IOException {
Path p1 = Mockito.mock(Path.class);
Path p2 = Mockito.mock(Path.class);
FileSystem fs = Mockito.mock(FileSystem.class);
FileSystemProvider provider = Mockito.mock(FileSystemProvider.class);
BasicFileAttributes attrs = Mockito.mock(BasicFileAttributes.class);
Mockito.when(p1.getFileSystem()).thenReturn(fs);
Mockito.when(p2.getFileSystem()).thenReturn(fs);
Mockito.when(fs.provider()).thenReturn(provider);
Mockito.when(fs.getPath(Mockito.anyString())).thenReturn(p1, p2);
Mockito.when(provider.readAttributes(Mockito.any(), Mockito.eq(BasicFileAttributes.class))).thenReturn(attrs);
private FileOpenRequestHandler inTest;
private BlockingQueue<AppLaunchEvent> queue;
BlockingQueue<Path> queue = new ArrayBlockingQueue<>(10);
FileOpenRequestHandler handler = new FileOpenRequestHandler(queue);
handler.handleLaunchArgs(fs, new String[] {"foo", "bar"});
Assertions.assertEquals(p1, queue.poll());
Assertions.assertEquals(p2, queue.poll());
@BeforeEach
public void setup() {
queue = new ArrayBlockingQueue<>(1);
inTest = new FileOpenRequestHandler(queue);
}
@Test
@DisplayName("./cryptomator.exe foo bar")
public void testOpenArgsWithCorrectPaths() throws IOException {
inTest.handleLaunchArgs(new String[]{"foo", "bar"});
AppLaunchEvent evt = queue.poll();
Assertions.assertNotNull(evt);
List<Path> paths = evt.getPathsToOpen().collect(Collectors.toList());
MatcherAssert.assertThat(paths, CoreMatchers.hasItems(Paths.get("foo"), Paths.get("bar")));
}
@Test
@DisplayName("./cryptomator.exe foo (with 'foo' being an invalid path)")
public void testOpenArgsWithIncorrectPaths() throws IOException {
FileSystem fs = Mockito.mock(FileSystem.class);
Mockito.when(fs.getPath(Mockito.anyString())).thenThrow(new InvalidPathException("foo", "foo is not a path"));
Mockito.when(fs.getPath("foo")).thenThrow(new InvalidPathException("foo", "foo is not a path"));
inTest.handleLaunchArgs(fs, new String[]{"foo"});
@SuppressWarnings("unchecked")
BlockingQueue<Path> queue = Mockito.mock(BlockingQueue.class);
FileOpenRequestHandler handler = new FileOpenRequestHandler(queue);
handler.handleLaunchArgs(fs, new String[] {"foo"});
Mockito.verifyNoMoreInteractions(queue);
AppLaunchEvent evt = queue.poll();
Assertions.assertNotNull(evt);
List<Path> paths = evt.getPathsToOpen().collect(Collectors.toList());
Assertions.assertTrue(paths.isEmpty());
}
@Test
@DisplayName("./cryptomator.exe foo (with full event queue)")
public void testOpenArgsWithFullQueue() throws IOException {
Path p = Mockito.mock(Path.class);
FileSystem fs = Mockito.mock(FileSystem.class);
FileSystemProvider provider = Mockito.mock(FileSystemProvider.class);
BasicFileAttributes attrs = Mockito.mock(BasicFileAttributes.class);
Mockito.when(p.getFileSystem()).thenReturn(fs);
Mockito.when(fs.provider()).thenReturn(provider);
Mockito.when(fs.getPath(Mockito.anyString())).thenReturn(p);
Mockito.when(provider.readAttributes(Mockito.eq(p), Mockito.eq(BasicFileAttributes.class))).thenReturn(attrs);
Mockito.when(attrs.isRegularFile()).thenReturn(true);
queue.add(new AppLaunchEvent(Stream.empty()));
Assumptions.assumeTrue(queue.remainingCapacity() == 0);
@SuppressWarnings("unchecked")
BlockingQueue<Path> queue = Mockito.mock(BlockingQueue.class);
Mockito.when(queue.offer(Mockito.any())).thenReturn(false);
FileOpenRequestHandler handler = new FileOpenRequestHandler(queue);
handler.handleLaunchArgs(fs, new String[] {"foo"});
inTest.handleLaunchArgs(new String[]{"foo"});
}
}

View File

@@ -50,6 +50,7 @@ import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.ui.ExitUtil;
import org.cryptomator.ui.controls.DirectoryListCell;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.model.AppLaunchEvent;
import org.cryptomator.ui.model.AutoUnlocker;
import org.cryptomator.ui.model.UpgradeStrategies;
import org.cryptomator.ui.model.UpgradeStrategy;
@@ -93,7 +94,7 @@ public class MainController implements ViewController {
private final ExitUtil exitUtil;
private final Localization localization;
private final ExecutorService executorService;
private final BlockingQueue<Path> fileOpenRequests;
private final BlockingQueue<AppLaunchEvent> launchEventQueue;
private final VaultFactory vaultFactoy;
private final ViewControllerLoader viewControllerLoader;
private final ObjectProperty<ViewController> activeController = new SimpleObjectProperty<>();
@@ -110,11 +111,11 @@ public class MainController implements ViewController {
private Subscription subs = Subscription.EMPTY;
@Inject
public MainController(@Named("mainWindow") Stage mainWindow, ExecutorService executorService, @Named("fileOpenRequests") BlockingQueue<Path> fileOpenRequests, ExitUtil exitUtil, Localization localization,
public MainController(@Named("mainWindow") Stage mainWindow, ExecutorService executorService, @Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue, ExitUtil exitUtil, Localization localization,
VaultFactory vaultFactoy, ViewControllerLoader viewControllerLoader, UpgradeStrategies upgradeStrategies, VaultList vaults, AutoUnlocker autoUnlocker) {
this.mainWindow = mainWindow;
this.executorService = executorService;
this.fileOpenRequests = fileOpenRequests;
this.launchEventQueue = launchEventQueue;
this.exitUtil = exitUtil;
this.localization = localization;
this.vaultFactoy = vaultFactoy;
@@ -249,12 +250,12 @@ public class MainController implements ViewController {
}
private void listenToFileOpenRequests(Stage stage) {
Tasks.create(fileOpenRequests::take).onSuccess(path -> {
addVault(path, true);
Tasks.create(launchEventQueue::take).onSuccess(event -> {
stage.setIconified(false);
stage.show();
stage.toFront();
stage.requestFocus();
event.getPathsToOpen().forEach(path -> addVault(path, true));
}).schedulePeriodically(executorService, Duration.ZERO, Duration.ZERO);
}

View File

@@ -0,0 +1,15 @@
package org.cryptomator.ui.model;
import java.nio.file.Path;
import java.util.stream.Stream;
public class AppLaunchEvent {
private final Stream<Path> pathsToOpen;
public AppLaunchEvent(Stream<Path> pathsToOpen) {this.pathsToOpen = pathsToOpen;}
public Stream<Path> getPathsToOpen() {
return pathsToOpen;
}
}