diff --git a/main/commons-test/.gitignore b/main/commons-test/.gitignore
deleted file mode 100644
index b83d22266..000000000
--- a/main/commons-test/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/target/
diff --git a/main/commons-test/pom.xml b/main/commons-test/pom.xml
deleted file mode 100644
index 9f0055118..000000000
--- a/main/commons-test/pom.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
- 4.0.0
-
- org.cryptomator
- main
- 1.3.0-SNAPSHOT
-
- commons-test
- Cryptomator common test dependencies
- Shared utilities for tests
-
-
-
- org.cryptomator
- commons
-
-
-
- junit
- junit
-
-
- org.mockito
- mockito-core
-
-
- de.bechte.junit
- junit-hierarchicalcontextrunner
-
-
- org.hamcrest
- hamcrest-all
-
-
-
-
diff --git a/main/commons/pom.xml b/main/commons/pom.xml
index b3c5595ca..2ab70cf6e 100644
--- a/main/commons/pom.xml
+++ b/main/commons/pom.xml
@@ -13,7 +13,7 @@
1.3.0-SNAPSHOT
commons
- Cryptomator common
+ Cryptomator Commons
Shared utilities
@@ -26,6 +26,14 @@
org.apache.commons
commons-lang3
+
+ com.google.code.gson
+ gson
+
+
+ org.fxmisc.easybind
+ easybind
+
@@ -38,25 +46,10 @@
provided
-
+
- junit
- junit
- test
-
-
- org.mockito
- mockito-core
- test
-
-
- de.bechte.junit
- junit-hierarchicalcontextrunner
- test
-
-
- org.hamcrest
- hamcrest-all
+ org.slf4j
+ slf4j-simple
test
diff --git a/main/ui/src/main/java/org/cryptomator/ui/settings/Settings.java b/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java
similarity index 98%
rename from main/ui/src/main/java/org/cryptomator/ui/settings/Settings.java
rename to main/commons/src/main/java/org/cryptomator/common/settings/Settings.java
index 5a1edc66b..67fe4cb53 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/settings/Settings.java
+++ b/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java
@@ -6,7 +6,7 @@
* Contributors:
* Sebastian Stenzel - initial API and implementation
******************************************************************************/
-package org.cryptomator.ui.settings;
+package org.cryptomator.common.settings;
import java.util.function.Consumer;
diff --git a/main/ui/src/main/java/org/cryptomator/ui/settings/SettingsJsonAdapter.java b/main/commons/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java
similarity index 98%
rename from main/ui/src/main/java/org/cryptomator/ui/settings/SettingsJsonAdapter.java
rename to main/commons/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java
index 456f94c7e..ae3e301eb 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/settings/SettingsJsonAdapter.java
+++ b/main/commons/src/main/java/org/cryptomator/common/settings/SettingsJsonAdapter.java
@@ -3,7 +3,7 @@
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
-package org.cryptomator.ui.settings;
+package org.cryptomator.common.settings;
import java.io.IOException;
import java.util.ArrayList;
diff --git a/main/ui/src/main/java/org/cryptomator/ui/settings/SettingsProvider.java b/main/commons/src/main/java/org/cryptomator/common/settings/SettingsProvider.java
similarity index 99%
rename from main/ui/src/main/java/org/cryptomator/ui/settings/SettingsProvider.java
rename to main/commons/src/main/java/org/cryptomator/common/settings/SettingsProvider.java
index d074f7f68..b413a7e23 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/settings/SettingsProvider.java
+++ b/main/commons/src/main/java/org/cryptomator/common/settings/SettingsProvider.java
@@ -6,7 +6,7 @@
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
-package org.cryptomator.ui.settings;
+package org.cryptomator.common.settings;
import java.io.IOException;
import java.io.InputStream;
diff --git a/main/ui/src/main/java/org/cryptomator/ui/settings/VaultSettings.java b/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettings.java
similarity index 99%
rename from main/ui/src/main/java/org/cryptomator/ui/settings/VaultSettings.java
rename to main/commons/src/main/java/org/cryptomator/common/settings/VaultSettings.java
index 66b6419e2..915a4e487 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/settings/VaultSettings.java
+++ b/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettings.java
@@ -3,7 +3,7 @@
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
-package org.cryptomator.ui.settings;
+package org.cryptomator.common.settings;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
diff --git a/main/ui/src/main/java/org/cryptomator/ui/settings/VaultSettingsJsonAdapter.java b/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettingsJsonAdapter.java
similarity index 98%
rename from main/ui/src/main/java/org/cryptomator/ui/settings/VaultSettingsJsonAdapter.java
rename to main/commons/src/main/java/org/cryptomator/common/settings/VaultSettingsJsonAdapter.java
index a7c696ab8..7fd9bc407 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/settings/VaultSettingsJsonAdapter.java
+++ b/main/commons/src/main/java/org/cryptomator/common/settings/VaultSettingsJsonAdapter.java
@@ -3,7 +3,7 @@
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
-package org.cryptomator.ui.settings;
+package org.cryptomator.common.settings;
import java.io.IOException;
import java.nio.file.Paths;
diff --git a/main/ui/src/test/java/org/cryptomator/ui/settings/SettingsJsonAdapterTest.java b/main/commons/src/test/java/org/cryptomator/common/settings/SettingsJsonAdapterTest.java
similarity index 97%
rename from main/ui/src/test/java/org/cryptomator/ui/settings/SettingsJsonAdapterTest.java
rename to main/commons/src/test/java/org/cryptomator/common/settings/SettingsJsonAdapterTest.java
index 88aabd0a2..5ce31767e 100644
--- a/main/ui/src/test/java/org/cryptomator/ui/settings/SettingsJsonAdapterTest.java
+++ b/main/commons/src/test/java/org/cryptomator/common/settings/SettingsJsonAdapterTest.java
@@ -3,7 +3,7 @@
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
-package org.cryptomator.ui.settings;
+package org.cryptomator.common.settings;
import java.io.IOException;
diff --git a/main/ui/src/test/java/org/cryptomator/ui/settings/VaultSettingsJsonAdapterTest.java b/main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsJsonAdapterTest.java
similarity index 97%
rename from main/ui/src/test/java/org/cryptomator/ui/settings/VaultSettingsJsonAdapterTest.java
rename to main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsJsonAdapterTest.java
index ee38acf8e..0c2604e10 100644
--- a/main/ui/src/test/java/org/cryptomator/ui/settings/VaultSettingsJsonAdapterTest.java
+++ b/main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsJsonAdapterTest.java
@@ -3,7 +3,7 @@
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
-package org.cryptomator.ui.settings;
+package org.cryptomator.common.settings;
import java.io.IOException;
import java.io.StringReader;
diff --git a/main/ui/src/test/java/org/cryptomator/ui/settings/VaultSettingsTest.java b/main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsTest.java
similarity index 95%
rename from main/ui/src/test/java/org/cryptomator/ui/settings/VaultSettingsTest.java
rename to main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsTest.java
index 604e43ed8..b19412847 100644
--- a/main/ui/src/test/java/org/cryptomator/ui/settings/VaultSettingsTest.java
+++ b/main/commons/src/test/java/org/cryptomator/common/settings/VaultSettingsTest.java
@@ -6,7 +6,7 @@
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
-package org.cryptomator.ui.settings;
+package org.cryptomator.common.settings;
import static org.junit.Assert.assertEquals;
diff --git a/main/jacoco-report/pom.xml b/main/jacoco-report/pom.xml
index 17261c267..2079aed52 100644
--- a/main/jacoco-report/pom.xml
+++ b/main/jacoco-report/pom.xml
@@ -9,18 +9,7 @@
jacoco-report
Cryptomator Code Coverage Report
-
-
-
-
- org.cryptomator
- commons
-
-
- org.cryptomator
- commons-test
-
-
+ pom
diff --git a/main/keychain/pom.xml b/main/keychain/pom.xml
index 3976e2297..d5429fb69 100644
--- a/main/keychain/pom.xml
+++ b/main/keychain/pom.xml
@@ -37,10 +37,11 @@
provided
-
+
- org.cryptomator
- commons-test
+ org.slf4j
+ slf4j-simple
+ test
\ No newline at end of file
diff --git a/main/launcher/pom.xml b/main/launcher/pom.xml
new file mode 100644
index 000000000..25ff891e7
--- /dev/null
+++ b/main/launcher/pom.xml
@@ -0,0 +1,57 @@
+
+
+ 4.0.0
+
+ org.cryptomator
+ main
+ 1.3.0-SNAPSHOT
+
+ launcher
+ Cryptomator Launcher
+
+
+
+ org.cryptomator
+ commons
+
+
+ org.cryptomator
+ ui
+
+
+
+
+ com.google.guava
+ guava
+
+
+ org.apache.commons
+ commons-lang3
+
+
+
+
+ com.google.dagger
+ dagger
+
+
+ com.google.dagger
+ dagger-compiler
+ provided
+
+
+
+
+ org.apache.logging.log4j
+ log4j-core
+
+
+ org.apache.logging.log4j
+ log4j-slf4j-impl
+
+
+ org.apache.logging.log4j
+ log4j-jul
+
+
+
\ No newline at end of file
diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/ApplicationVersion.java b/main/launcher/src/main/java/org/cryptomator/launcher/ApplicationVersion.java
similarity index 89%
rename from main/ui/src/main/java/org/cryptomator/ui/util/ApplicationVersion.java
rename to main/launcher/src/main/java/org/cryptomator/launcher/ApplicationVersion.java
index 8b466b099..6a60c5e86 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/util/ApplicationVersion.java
+++ b/main/launcher/src/main/java/org/cryptomator/launcher/ApplicationVersion.java
@@ -3,12 +3,10 @@
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
-package org.cryptomator.ui.util;
+package org.cryptomator.launcher;
import java.util.Optional;
-import org.cryptomator.ui.Cryptomator;
-
public class ApplicationVersion {
public static String orElse(String other) {
diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/CleanShutdownPerformer.java b/main/launcher/src/main/java/org/cryptomator/launcher/CleanShutdownPerformer.java
new file mode 100644
index 000000000..0e932647e
--- /dev/null
+++ b/main/launcher/src/main/java/org/cryptomator/launcher/CleanShutdownPerformer.java
@@ -0,0 +1,35 @@
+package org.cryptomator.launcher;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class CleanShutdownPerformer extends Thread {
+
+ private static final Logger LOG = LoggerFactory.getLogger(CleanShutdownPerformer.class);
+ static final ConcurrentMap SHUTDOWN_TASKS = new ConcurrentHashMap<>();
+
+ @Override
+ public void run() {
+ LOG.debug("Running graceful shutdown tasks...");
+ SHUTDOWN_TASKS.keySet().forEach(r -> {
+ try {
+ r.run();
+ } catch (RuntimeException e) {
+ LOG.error("Exception while shutting down.", e);
+ }
+ });
+ SHUTDOWN_TASKS.clear();
+ LOG.info("Goodbye.");
+ }
+
+ static void scheduleShutdownTask(Runnable task) {
+ SHUTDOWN_TASKS.put(task, Boolean.TRUE);
+ }
+
+ static void registerShutdownHook() {
+ Runtime.getRuntime().addShutdownHook(new CleanShutdownPerformer());
+ }
+}
\ No newline at end of file
diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/Cryptomator.java b/main/launcher/src/main/java/org/cryptomator/launcher/Cryptomator.java
new file mode 100644
index 000000000..17b1096e5
--- /dev/null
+++ b/main/launcher/src/main/java/org/cryptomator/launcher/Cryptomator.java
@@ -0,0 +1,54 @@
+package org.cryptomator.launcher;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+
+import org.apache.commons.lang3.SystemUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javafx.application.Application;
+
+public class Cryptomator {
+
+ private static final Logger LOG = LoggerFactory.getLogger(Cryptomator.class);
+ static final BlockingQueue FILE_OPEN_REQUESTS = new ArrayBlockingQueue<>(10);
+
+ public static void main(String[] args) throws IOException {
+ LOG.info("Starting Cryptomator {} on {} {} ({})", ApplicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
+
+ FileOpenRequestHandler fileOpenRequestHandler = new FileOpenRequestHandler(FILE_OPEN_REQUESTS);
+ try (InterProcessCommunicator communicator = InterProcessCommunicator.start(new IpcProtocolImpl(fileOpenRequestHandler))) {
+ if (communicator.isServer()) {
+ fileOpenRequestHandler.handleLaunchArgs(args);
+ CleanShutdownPerformer.registerShutdownHook();
+ Application.launch(MainApplication.class, args);
+ } else {
+ communicator.handleLaunchArgs(args);
+ LOG.info("Found running application instance. Shutting down.");
+ }
+ }
+ System.exit(0); // end remaining non-daemon threads.
+ }
+
+ private static class IpcProtocolImpl implements InterProcessCommunicationProtocol {
+
+ private final FileOpenRequestHandler fileOpenRequestHandler;
+
+ // TODO: inject?
+ public IpcProtocolImpl(FileOpenRequestHandler fileOpenRequestHandler) {
+ this.fileOpenRequestHandler = fileOpenRequestHandler;
+ }
+
+ @Override
+ public void handleLaunchArgs(String[] args) {
+ LOG.info("Received launch args: {}", Arrays.stream(args).reduce((a, b) -> a + ", " + b).orElse(""));
+ fileOpenRequestHandler.handleLaunchArgs(args);
+ }
+
+ }
+
+}
diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java b/main/launcher/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java
new file mode 100644
index 000000000..fdaafe483
--- /dev/null
+++ b/main/launcher/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java
@@ -0,0 +1,102 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
+ * All rights reserved.
+ *
+ * This class is licensed under the LGPL 3.0 (https://www.gnu.org/licenses/lgpl-3.0.de.html).
+ *******************************************************************************/
+package org.cryptomator.launcher;
+
+import java.io.File;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+
+import org.apache.commons.lang3.SystemUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class FileOpenRequestHandler {
+
+ private static final Logger LOG = LoggerFactory.getLogger(FileOpenRequestHandler.class);
+ private final BlockingQueue fileOpenRequests;
+
+ public FileOpenRequestHandler(BlockingQueue fileOpenRequests) {
+ this.fileOpenRequests = fileOpenRequests;
+ if (SystemUtils.IS_OS_MAC_OSX) {
+ addOsxFileOpenHandler();
+ }
+ }
+
+ public void handleLaunchArgs(String[] args) {
+ handleLaunchArgs(FileSystems.getDefault(), args);
+ }
+
+ // visible for testing
+ void handleLaunchArgs(FileSystem fs, String[] args) {
+ for (String arg : args) {
+ try {
+ Path path = fs.getPath(arg);
+ tryToEnqueueFileOpenRequest(path);
+ } catch (InvalidPathException e) {
+ LOG.trace("{} not a valid path", arg);
+ }
+ }
+ }
+
+ private void tryToEnqueueFileOpenRequest(Path path) {
+ if (!fileOpenRequests.offer(path)) {
+ LOG.warn("{} could not be enqueued for opening.", path);
+ }
+ }
+
+ /**
+ * Event subscription code inspired by https://gitlab.com/axet/desktop/blob/master/java/src/main/java/com/github/axet/desktop/os/mac/AppleHandlers.java
+ */
+ private void addOsxFileOpenHandler() {
+ try {
+ final Class> applicationClass = Class.forName("com.apple.eawt.Application");
+ final Class> openFilesHandlerClass = Class.forName("com.apple.eawt.OpenFilesHandler");
+ final Method getApplication = applicationClass.getMethod("getApplication");
+ final Object application = getApplication.invoke(null);
+ final Method setOpenFileHandler = applicationClass.getMethod("setOpenFileHandler", openFilesHandlerClass);
+ final ClassLoader openFilesHandlerClassLoader = openFilesHandlerClass.getClassLoader();
+ final OpenFilesEventInvocationHandler openFilesHandler = new OpenFilesEventInvocationHandler();
+ final Object openFilesHandlerObject = Proxy.newProxyInstance(openFilesHandlerClassLoader, new Class>[] {openFilesHandlerClass}, openFilesHandler);
+ setOpenFileHandler.invoke(application, openFilesHandlerObject);
+ } catch (ReflectiveOperationException | RuntimeException e) {
+ // Since we're trying to call OS-specific code, we'll just have to hope for the best.
+ LOG.error("Exception adding OS X file open handler", e);
+ }
+ }
+
+ /**
+ * Handler class inspired by https://gitlab.com/axet/desktop/blob/master/java/src/main/java/com/github/axet/desktop/os/mac/AppleHandlers.java
+ */
+ private class OpenFilesEventInvocationHandler implements InvocationHandler {
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ if (method.getName().equals("openFiles")) {
+ final Class> openFilesEventClass = Class.forName("com.apple.eawt.AppEvent$OpenFilesEvent");
+ final Method getFiles = openFilesEventClass.getMethod("getFiles");
+ Object e = args[0];
+ try {
+ @SuppressWarnings("unchecked")
+ final List ff = (List) getFiles.invoke(e);
+ ff.stream().map(File::toPath).forEach(fileOpenRequests::add);
+ } catch (RuntimeException ee) {
+ throw ee;
+ } catch (Exception ee) {
+ throw new RuntimeException(ee);
+ }
+ }
+ return null;
+ }
+ }
+
+}
diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/InterProcessCommunicationProtocol.java b/main/launcher/src/main/java/org/cryptomator/launcher/InterProcessCommunicationProtocol.java
new file mode 100644
index 000000000..98e2eafb6
--- /dev/null
+++ b/main/launcher/src/main/java/org/cryptomator/launcher/InterProcessCommunicationProtocol.java
@@ -0,0 +1,5 @@
+package org.cryptomator.launcher;
+
+public interface InterProcessCommunicationProtocol {
+ void handleLaunchArgs(String[] args);
+}
\ No newline at end of file
diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/InterProcessCommunicator.java b/main/launcher/src/main/java/org/cryptomator/launcher/InterProcessCommunicator.java
new file mode 100644
index 000000000..a021067c0
--- /dev/null
+++ b/main/launcher/src/main/java/org/cryptomator/launcher/InterProcessCommunicator.java
@@ -0,0 +1,232 @@
+package org.cryptomator.launcher;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.rmi.ConnectException;
+import java.rmi.NoSuchObjectException;
+import java.rmi.NotBoundException;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+import java.rmi.server.RMIClientSocketFactory;
+import java.rmi.server.RMIServerSocketFactory;
+import java.rmi.server.RMISocketFactory;
+import java.rmi.server.UnicastRemoteObject;
+
+import org.apache.commons.lang3.SystemUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * First running application on a machine opens a server socket. Further processes will connect as clients.
+ */
+abstract class InterProcessCommunicator implements InterProcessCommunicationProtocol, Closeable {
+
+ private static final Logger LOG = LoggerFactory.getLogger(InterProcessCommunicator.class);
+ private static final String RMI_NAME = "Cryptomator";
+
+ public abstract boolean isServer();
+
+ /**
+ * @param endpoint The server-side communication endpoint.
+ * @return Either a client or a server communicator.
+ * @throws IOException In case of communication errors.
+ */
+ public static InterProcessCommunicator start(InterProcessCommunicationProtocol endpoint) throws IOException {
+ return start(getIpcPortPath(), endpoint);
+ }
+
+ // visible for testing
+ static InterProcessCommunicator start(Path portFilePath, InterProcessCommunicationProtocol endpoint) throws IOException {
+ // try to connect to existing server:
+ int port = readPort(portFilePath);
+ LOG.debug("Connecting to running process on TCP port {}...", port);
+ try {
+ ClientCommunicator client = new ClientCommunicator(port);
+ LOG.trace("Connected to running process.");
+ return client;
+ } catch (ConnectException | NotBoundException e) {
+ LOG.debug("Did not find running process.");
+ // continue
+ }
+
+ // spawn a new server:
+ LOG.trace("Spawning new server...");
+ ServerCommunicator server = new ServerCommunicator(endpoint);
+ writePort(portFilePath, server.getPort());
+ LOG.debug("Server listening on port {}.", server.getPort());
+ return server;
+ }
+
+ private static Path getIpcPortPath() {
+ final String settingsPathProperty = System.getProperty("cryptomator.ipcPortPath");
+ if (settingsPathProperty == null) {
+ LOG.warn("System property cryptomator.ipcPortPath not set.");
+ return Paths.get("ipcPort.tmp");
+ } else {
+ return Paths.get(replaceHomeDir(settingsPathProperty));
+ }
+ }
+
+ private static String replaceHomeDir(String path) {
+ if (path.startsWith("~/")) {
+ return SystemUtils.USER_HOME + path.substring(1);
+ } else {
+ return path;
+ }
+ }
+
+ public static class ClientCommunicator extends InterProcessCommunicator {
+
+ private final IpcProtocolRemote remote;
+
+ private ClientCommunicator(int port) throws ConnectException, NotBoundException, RemoteException {
+ if (port == 0) {
+ throw new ConnectException("Can not connect to port 0.");
+ }
+ Registry registry = LocateRegistry.getRegistry(port);
+ this.remote = (IpcProtocolRemote) registry.lookup(RMI_NAME);
+ }
+
+ @Override
+ public void handleLaunchArgs(String[] args) {
+ try {
+ remote.handleLaunchArgs(args);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean isServer() {
+ return false;
+ }
+
+ @Override
+ public void close() throws IOException {
+ // no-op
+ }
+
+ }
+
+ public static class ServerCommunicator extends InterProcessCommunicator {
+
+ private final ServerSocket socket;
+ private final Registry registry;
+ private final IpcProtocolRemoteImpl remote;
+
+ private ServerCommunicator(InterProcessCommunicationProtocol delegate) throws IOException {
+ this.socket = new ServerSocket(0, Byte.MAX_VALUE, InetAddress.getLocalHost());
+ RMIClientSocketFactory csf = RMISocketFactory.getDefaultSocketFactory();
+ SingletonServerSocketFactory ssf = new SingletonServerSocketFactory(socket);
+ this.registry = LocateRegistry.createRegistry(0, csf, ssf);
+ this.remote = new IpcProtocolRemoteImpl(delegate);
+ UnicastRemoteObject.exportObject(remote, 0);
+ registry.rebind(RMI_NAME, remote);
+ }
+
+ @Override
+ public void handleLaunchArgs(String[] args) {
+ throw new UnsupportedOperationException("Server doesn't invoke methods.");
+ }
+
+ @Override
+ public boolean isServer() {
+ return true;
+ }
+
+ private int getPort() {
+ return socket.getLocalPort();
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ registry.unbind(RMI_NAME);
+ UnicastRemoteObject.unexportObject(remote, true);
+ socket.close();
+ LOG.debug("Server shut down.");
+ } catch (NotBoundException | NoSuchObjectException e) {
+ // ignore
+ }
+ }
+
+ }
+
+ private static interface IpcProtocolRemote extends Remote {
+ void handleLaunchArgs(String[] args) throws RemoteException;
+ }
+
+ private static class IpcProtocolRemoteImpl implements IpcProtocolRemote {
+
+ private final InterProcessCommunicationProtocol delegate;
+
+ protected IpcProtocolRemoteImpl(InterProcessCommunicationProtocol delegate) throws RemoteException {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void handleLaunchArgs(String[] args) {
+ delegate.handleLaunchArgs(args);
+ }
+
+ }
+
+ /**
+ * Always returns the same pre-constructed server socket.
+ */
+ private static class SingletonServerSocketFactory implements RMIServerSocketFactory {
+
+ private final ServerSocket socket;
+
+ public SingletonServerSocketFactory(ServerSocket socket) {
+ this.socket = socket;
+ }
+
+ @Override
+ public synchronized ServerSocket createServerSocket(int port) throws IOException {
+ if (port != 0) {
+ throw new IllegalArgumentException("This factory doesn't support specific ports.");
+ }
+ return this.socket;
+ }
+
+ }
+
+ private static int readPort(Path path) throws IOException {
+ if (Files.notExists(path)) {
+ return 0;
+ }
+ ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
+ try (ReadableByteChannel ch = Files.newByteChannel(path, StandardOpenOption.READ)) {
+ if (ch.read(buf) == Integer.BYTES) {
+ buf.flip();
+ return buf.getInt();
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ private static void writePort(Path path, int port) throws IOException {
+ ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
+ buf.putInt(port);
+ buf.flip();
+ try (WritableByteChannel ch = Files.newByteChannel(path, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
+ if (ch.write(buf) != Integer.BYTES) {
+ throw new IOException("Did not write expected number of bytes.");
+ }
+ }
+ }
+
+}
diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/LauncherComponent.java b/main/launcher/src/main/java/org/cryptomator/launcher/LauncherComponent.java
new file mode 100644
index 000000000..06a50b024
--- /dev/null
+++ b/main/launcher/src/main/java/org/cryptomator/launcher/LauncherComponent.java
@@ -0,0 +1,18 @@
+package org.cryptomator.launcher;
+
+import javax.inject.Singleton;
+
+import org.cryptomator.logging.DebugMode;
+import org.cryptomator.ui.controllers.MainController;
+
+import dagger.Component;
+
+@Singleton
+@Component(modules = LauncherModule.class)
+interface LauncherComponent {
+
+ MainController mainController();
+
+ DebugMode debugMode();
+
+}
diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/LauncherModule.java b/main/launcher/src/main/java/org/cryptomator/launcher/LauncherModule.java
new file mode 100644
index 000000000..0c1aba68c
--- /dev/null
+++ b/main/launcher/src/main/java/org/cryptomator/launcher/LauncherModule.java
@@ -0,0 +1,63 @@
+package org.cryptomator.launcher;
+
+import java.nio.file.Path;
+import java.util.Optional;
+import java.util.concurrent.BlockingQueue;
+import java.util.function.Consumer;
+
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.cryptomator.ui.UiModule;
+
+import dagger.Module;
+import dagger.Provides;
+import javafx.application.Application;
+import javafx.stage.Stage;
+
+@Module(includes = {UiModule.class})
+class LauncherModule {
+
+ private final Application application;
+ private final Stage mainWindow;
+
+ public LauncherModule(Application application, Stage mainWindow) {
+ this.application = application;
+ this.mainWindow = mainWindow;
+ }
+
+ @Provides
+ @Singleton
+ Application provideApplication() {
+ return application;
+ }
+
+ @Provides
+ @Singleton
+ @Named("applicationVersion")
+ Optional provideApplicationVersion() {
+ return ApplicationVersion.get();
+ }
+
+ @Provides
+ @Singleton
+ @Named("mainWindow")
+ Stage provideMainWindow() {
+ return mainWindow;
+ }
+
+ @Provides
+ @Singleton
+ @Named("fileOpenRequests")
+ BlockingQueue provideFileOpenRequests() {
+ return Cryptomator.FILE_OPEN_REQUESTS;
+ }
+
+ @Provides
+ @Singleton
+ @Named("shutdownTaskScheduler")
+ Consumer provideShutdownTaskScheduler() {
+ return CleanShutdownPerformer::scheduleShutdownTask;
+ }
+
+}
diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/MainApplication.java b/main/launcher/src/main/java/org/cryptomator/launcher/MainApplication.java
new file mode 100644
index 000000000..5cdc4e6ae
--- /dev/null
+++ b/main/launcher/src/main/java/org/cryptomator/launcher/MainApplication.java
@@ -0,0 +1,58 @@
+package org.cryptomator.launcher;
+
+import org.cryptomator.ui.controllers.MainController;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javafx.application.Application;
+import javafx.application.Platform;
+import javafx.fxml.FXMLLoader;
+import javafx.stage.Stage;
+
+public class MainApplication extends Application {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MainApplication.class);
+ private Stage primaryStage;
+
+ @Override
+ public void start(Stage primaryStage) throws Exception {
+ LOG.info("JavaFX application started.");
+ this.primaryStage = primaryStage;
+ setupFXMLClassLoader();
+
+ LauncherModule launcherModule = new LauncherModule(this, primaryStage);
+ LauncherComponent launcherComponent = DaggerLauncherComponent.builder() //
+ .launcherModule(launcherModule) //
+ .build();
+
+ launcherComponent.debugMode().initialize();
+
+ MainController mainCtrl = launcherComponent.mainController();
+ mainCtrl.initStage(primaryStage);
+
+ primaryStage.show();
+ }
+
+ @Override
+ public void stop() throws Exception {
+ assert primaryStage != null;
+ primaryStage.hide();
+ LOG.info("JavaFX application stopped.");
+ }
+
+ // fix discussed in https://github.com/cryptomator/cryptomator/pull/29
+ private void setupFXMLClassLoader() {
+ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+ FXMLLoader.setDefaultClassLoader(contextClassLoader);
+ Platform.runLater(() -> {
+ /*
+ * This fixes a bug on OSX where the magic file open handler leads to no context class loader being set in the AppKit (event)
+ * thread if the application is not started opening a file.
+ */
+ if (Thread.currentThread().getContextClassLoader() == null) {
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+ }
+ });
+ }
+
+}
diff --git a/main/launcher/src/main/java/org/cryptomator/logging/ConfigurableFileAppender.java b/main/launcher/src/main/java/org/cryptomator/logging/ConfigurableFileAppender.java
new file mode 100644
index 000000000..a453b1adf
--- /dev/null
+++ b/main/launcher/src/main/java/org/cryptomator/logging/ConfigurableFileAppender.java
@@ -0,0 +1,132 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Sebastian Stenzel and others.
+ * This file is licensed under the terms of the MIT license.
+ * See the LICENSE.txt file for more info.
+ *
+ * Contributors:
+ * Sebastian Stenzel - initial API and implementation
+ *******************************************************************************/
+package org.cryptomator.logging;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.URISyntaxException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang3.SystemUtils;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Core;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender;
+import org.apache.logging.log4j.core.appender.FileManager;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
+import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
+import org.apache.logging.log4j.util.Strings;
+
+/**
+ * A preconfigured FileAppender only relying on a configurable system property, e.g. -Dcryptomator.logPath=/var/log/cryptomator.log.
+ * Other than the normal {@link org.apache.logging.log4j.core.appender.FileAppender} paths can be resolved relative to the users home directory.
+ */
+@Plugin(name = ConfigurableFileAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
+public class ConfigurableFileAppender extends AbstractOutputStreamAppender {
+
+ static final String PLUGIN_NAME = "ConfigurableFile";
+ private static final Pattern DRIVE_LETTER_WITH_PRECEEDING_SLASH = Pattern.compile("^/[A-Z]:", Pattern.CASE_INSENSITIVE);
+
+ private ConfigurableFileAppender(String name, Layout extends Serializable> layout, Filter filter, boolean ignoreExceptions, boolean immediateFlush, FileManager manager) {
+ super(name, layout, filter, ignoreExceptions, immediateFlush, manager);
+ LOGGER.info("Logging to " + manager.getFileName());
+ }
+
+ @PluginBuilderFactory
+ public static > B newBuilder() {
+ return new Builder().asBuilder();
+ }
+
+ /**
+ * Builds ConfigurableFileAppender instances.
+ *
+ * @param
+ * The type to build
+ */
+ public static class Builder> extends AbstractOutputStreamAppender.Builder //
+ implements org.apache.logging.log4j.core.util.Builder {
+
+ @Required(message = "No system property name containing the log file path provided.")
+ @PluginBuilderAttribute("pathPropertyName")
+ private String pathPropertyName;
+
+ @PluginBuilderAttribute
+ private boolean append = true;
+
+ @Override
+ public ConfigurableFileAppender build() {
+ final String pathProperty = System.getProperty(pathPropertyName);
+ if (Strings.isEmpty(pathProperty)) {
+ LOGGER.warn("No log file location provided in system property \"" + pathPropertyName + "\"");
+ return null;
+ }
+
+ final Path filePath = parsePath(pathProperty);
+ if (filePath == null) {
+ LOGGER.warn("Invalid path \"" + pathProperty + "\"");
+ return null;
+ }
+
+ if (!Files.exists(filePath.getParent())) {
+ try {
+ Files.createDirectories(filePath.getParent());
+ } catch (IOException e) {
+ LOGGER.error("Could not create parent directories for log file located at " + filePath.toString(), e);
+ return null;
+ }
+ }
+
+ FileManager manager = FileManager.getFileManager(filePath.toString(), append, false, isBufferedIo(), true, null, getOrCreateLayout(), getBufferSize(), getConfiguration());
+ return new ConfigurableFileAppender(getName(), getLayout(), getFilter(), isIgnoreExceptions(), isImmediateFlush(), manager);
+ }
+
+ public B withPathPropertyName(String pathPropertyName) {
+ this.pathPropertyName = pathPropertyName;
+ return asBuilder();
+ }
+
+ public B withAppend(boolean append) {
+ this.append = append;
+ return asBuilder();
+ }
+
+ }
+
+ private static Path parsePath(String path) {
+ if (path.startsWith("~/")) {
+ // home-dir-relative Path:
+ final Path userHome = FileSystems.getDefault().getPath(SystemUtils.USER_HOME);
+ return userHome.resolve(path.substring(2));
+ } else if (path.startsWith("/")) {
+ // absolute Path:
+ return FileSystems.getDefault().getPath(path);
+ } else {
+ // relative Path:
+ try {
+ String jarFileLocation = ConfigurableFileAppender.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath();
+ if (SystemUtils.IS_OS_WINDOWS && DRIVE_LETTER_WITH_PRECEEDING_SLASH.matcher(jarFileLocation).find()) {
+ // on windows we need to remove a preceeding slash from "/C:/foo/bar":
+ jarFileLocation = jarFileLocation.substring(1);
+ }
+ final Path workingDir = FileSystems.getDefault().getPath(jarFileLocation).getParent();
+ return workingDir.resolve(path);
+ } catch (URISyntaxException e) {
+ LOGGER.error("Unable to resolve working directory ", e);
+ return null;
+ }
+ }
+ }
+
+}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/DebugMode.java b/main/launcher/src/main/java/org/cryptomator/logging/DebugMode.java
similarity index 96%
rename from main/ui/src/main/java/org/cryptomator/ui/DebugMode.java
rename to main/launcher/src/main/java/org/cryptomator/logging/DebugMode.java
index 55d79327b..baa6c94f5 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/DebugMode.java
+++ b/main/launcher/src/main/java/org/cryptomator/logging/DebugMode.java
@@ -3,7 +3,7 @@
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
-package org.cryptomator.ui;
+package org.cryptomator.logging;
import static java.util.Arrays.asList;
import static org.apache.logging.log4j.LogManager.ROOT_LOGGER_NAME;
@@ -18,7 +18,7 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
-import org.cryptomator.ui.settings.Settings;
+import org.cryptomator.common.settings.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/main/ui/src/main/resources/log4j2.xml b/main/launcher/src/main/resources/log4j2.xml
similarity index 95%
rename from main/ui/src/main/resources/log4j2.xml
rename to main/launcher/src/main/resources/log4j2.xml
index 368769ee3..85afbab88 100644
--- a/main/ui/src/main/resources/log4j2.xml
+++ b/main/launcher/src/main/resources/log4j2.xml
@@ -7,7 +7,7 @@
Contributors:
Markus Kreusch - switched to log4j 2
-->
-
+
diff --git a/main/launcher/src/test/java/org/cryptomator/launcher/FileOpenRequestHandlerTest.java b/main/launcher/src/test/java/org/cryptomator/launcher/FileOpenRequestHandlerTest.java
new file mode 100644
index 000000000..ae43abc95
--- /dev/null
+++ b/main/launcher/src/test/java/org/cryptomator/launcher/FileOpenRequestHandlerTest.java
@@ -0,0 +1,72 @@
+package org.cryptomator.launcher;
+
+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.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+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);
+ Mockito.when(attrs.isRegularFile()).thenReturn(true);
+
+ BlockingQueue queue = new ArrayBlockingQueue<>(10);
+ FileOpenRequestHandler handler = new FileOpenRequestHandler(queue);
+ handler.handleLaunchArgs(fs, new String[] {"foo", "bar"});
+
+ Assert.assertEquals(p1, queue.poll());
+ Assert.assertEquals(p2, queue.poll());
+ }
+
+ @Test
+ 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"));
+
+ @SuppressWarnings("unchecked")
+ BlockingQueue queue = Mockito.mock(BlockingQueue.class);
+ FileOpenRequestHandler handler = new FileOpenRequestHandler(queue);
+ handler.handleLaunchArgs(fs, new String[] {"foo"});
+
+ Mockito.verifyNoMoreInteractions(queue);
+ }
+
+ @Test
+ 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);
+
+ @SuppressWarnings("unchecked")
+ BlockingQueue queue = Mockito.mock(BlockingQueue.class);
+ Mockito.when(queue.offer(Mockito.any())).thenReturn(false);
+ FileOpenRequestHandler handler = new FileOpenRequestHandler(queue);
+ handler.handleLaunchArgs(fs, new String[] {"foo"});
+ }
+
+}
diff --git a/main/launcher/src/test/java/org/cryptomator/launcher/InterProcessCommunicatorTest.java b/main/launcher/src/test/java/org/cryptomator/launcher/InterProcessCommunicatorTest.java
new file mode 100644
index 000000000..c258efb1b
--- /dev/null
+++ b/main/launcher/src/test/java/org/cryptomator/launcher/InterProcessCommunicatorTest.java
@@ -0,0 +1,82 @@
+package org.cryptomator.launcher;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.FileSystem;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class InterProcessCommunicatorTest {
+
+ Path portFilePath = Mockito.mock(Path.class);
+ FileSystem fs = Mockito.mock(FileSystem.class);
+ FileSystemProvider provider = Mockito.mock(FileSystemProvider.class);
+ SeekableByteChannel portFileChannel = Mockito.mock(SeekableByteChannel.class);
+ AtomicInteger port = new AtomicInteger(-1);
+
+ @Before
+ public void setup() throws IOException {
+ Mockito.when(portFilePath.getFileSystem()).thenReturn(fs);
+ Mockito.when(fs.provider()).thenReturn(provider);
+ Mockito.when(provider.newByteChannel(Mockito.eq(portFilePath), Mockito.any(), Mockito.any())).thenReturn(portFileChannel);
+ Mockito.when(portFileChannel.read(Mockito.any())).then(invocation -> {
+ ByteBuffer buf = invocation.getArgument(0);
+ buf.putInt(port.get());
+ return Integer.BYTES;
+ });
+ Mockito.when(portFileChannel.write(Mockito.any())).then(invocation -> {
+ ByteBuffer buf = invocation.getArgument(0);
+ port.set(buf.getInt());
+ return Integer.BYTES;
+ });
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testStartWithDummyPort1() throws IOException {
+ port.set(0);
+ InterProcessCommunicationProtocol protocol = Mockito.mock(InterProcessCommunicationProtocol.class);
+ try (InterProcessCommunicator result = InterProcessCommunicator.start(portFilePath, protocol)) {
+ Assert.assertTrue(result.isServer());
+ Mockito.verifyZeroInteractions(protocol);
+ result.handleLaunchArgs(new String[] {"foo"});
+ }
+ }
+
+ @Test
+ public void testStartWithDummyPort2() throws IOException {
+ Mockito.doThrow(new NoSuchFileException("port file")).when(provider).checkAccess(portFilePath);
+
+ InterProcessCommunicationProtocol protocol = Mockito.mock(InterProcessCommunicationProtocol.class);
+ try (InterProcessCommunicator result = InterProcessCommunicator.start(portFilePath, protocol)) {
+ Assert.assertTrue(result.isServer());
+ Mockito.verifyZeroInteractions(protocol);
+ }
+ }
+
+ @Test
+ public void testInterProcessCommunication() throws IOException, InterruptedException {
+ port.set(-1);
+ InterProcessCommunicationProtocol protocol = Mockito.mock(InterProcessCommunicationProtocol.class);
+ try (InterProcessCommunicator result1 = InterProcessCommunicator.start(portFilePath, protocol)) {
+ Assert.assertTrue(result1.isServer());
+ Mockito.verifyZeroInteractions(protocol);
+
+ try (InterProcessCommunicator result2 = InterProcessCommunicator.start(portFilePath, null)) {
+ Assert.assertFalse(result2.isServer());
+ Assert.assertNotSame(result1, result2);
+
+ result2.handleLaunchArgs(new String[] {"foo"});
+ Mockito.verify(protocol).handleLaunchArgs(new String[] {"foo"});
+ }
+ }
+ }
+
+}
diff --git a/main/launcher/src/test/resources/log4j2.xml b/main/launcher/src/test/resources/log4j2.xml
new file mode 100644
index 000000000..e0ea8ca2b
--- /dev/null
+++ b/main/launcher/src/test/resources/log4j2.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/main/pom.xml b/main/pom.xml
index ce4dd6020..661f652b1 100644
--- a/main/pom.xml
+++ b/main/pom.xml
@@ -31,7 +31,7 @@
1.2.0
0.4.0
1.0.0
- 2.1
+ 2.8.1
1.7.25
4.12
4.12.1
@@ -69,12 +69,6 @@
commons
${project.version}
-
- org.cryptomator
- commons-test
- ${project.version}
- test
-
org.cryptomator
keychain
@@ -85,7 +79,12 @@
ui
${project.version}
-
+
+ org.cryptomator
+ launcher
+ ${project.version}
+
+
org.cryptomator
@@ -114,6 +113,11 @@
slf4j-api
${slf4j.version}
+
+ org.slf4j
+ slf4j-simple
+ ${slf4j.version}
+
org.apache.logging.log4j
log4j-core
@@ -212,12 +216,6 @@
org.mockito
mockito-core
${mockito.version}
-
-
- hamcrest-core
- org.hamcrest
-
-
org.hamcrest
@@ -228,25 +226,37 @@
+
- org.apache.logging.log4j
- log4j-core
+ org.slf4j
+ slf4j-api
- org.apache.logging.log4j
- log4j-slf4j-impl
+ junit
+ junit
+ test
- org.apache.logging.log4j
- log4j-jul
+ org.hamcrest
+ hamcrest-all
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+ de.bechte.junit
+ junit-hierarchicalcontextrunner
+ test
commons
- commons-test
keychain
ui
+ launcher
diff --git a/main/uber-jar/pom.xml b/main/uber-jar/pom.xml
index 35b3e2273..a16e82cb2 100644
--- a/main/uber-jar/pom.xml
+++ b/main/uber-jar/pom.xml
@@ -1,12 +1,5 @@
-
+
4.0.0
@@ -15,43 +8,50 @@
1.3.0-SNAPSHOT
uber-jar
- pom
Single über jar with all dependencies
org.cryptomator
- ui
+ launcher
- maven-assembly-plugin
+ maven-shade-plugin
3.0.0
make-assembly
package
- single
+ shade
- Cryptomator-${project.parent.version}
-
- jar-with-dependencies
-
- false
-
-
- org.cryptomator.ui.Cryptomator
- ${project.version}
-
-
+ Cryptomator-${project.version}
+ false
+
+
+
+ org.cryptomator.launcher.Cryptomator
+ ${project.version}
+
+
+
+
+
+
+
+ com.github.edwgiz
+ maven-shade-plugin.log4j2-cachefile-transformer
+ ${log4j.version}
+
+
diff --git a/main/uber-jar/src/main/resources/log4j2.xml b/main/uber-jar/src/main/resources/log4j2.xml
new file mode 100644
index 000000000..9a9ee5032
--- /dev/null
+++ b/main/uber-jar/src/main/resources/log4j2.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/main/ui/pom.xml b/main/ui/pom.xml
index 09245175c..1e13bbf25 100644
--- a/main/ui/pom.xml
+++ b/main/ui/pom.xml
@@ -51,17 +51,15 @@
easybind
-
-
- com.google.code.gson
- gson
-
-
-
+
com.google.guava
guava
+
+ com.google.code.gson
+ gson
+
@@ -91,12 +89,6 @@
dagger-compiler
provided
-
-
-
- org.cryptomator
- commons-test
-
@@ -104,5 +96,19 @@
zxcvbn
1.2.2
+
+
+
+ org.apache.logging.log4j
+ log4j-core
+
+
+ org.apache.logging.log4j
+ log4j-slf4j-impl
+
+
+ org.apache.logging.log4j
+ log4j-jul
+
diff --git a/main/ui/src/main/java/org/cryptomator/ui/Cryptomator.java b/main/ui/src/main/java/org/cryptomator/ui/Cryptomator.java
deleted file mode 100644
index 90786f1f4..000000000
--- a/main/ui/src/main/java/org/cryptomator/ui/Cryptomator.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2014, 2016 cryptomator.org
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- * Tillmann Gaida - initial implementation
- * Sebastian Stenzel - refactoring
- ******************************************************************************/
-package org.cryptomator.ui;
-
-import java.io.File;
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.util.List;
-import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.function.Consumer;
-
-import org.apache.commons.lang3.SystemUtils;
-import org.cryptomator.ui.util.ApplicationVersion;
-import org.cryptomator.ui.util.SingleInstanceManager;
-import org.cryptomator.ui.util.SingleInstanceManager.RemoteInstance;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javafx.application.Application;
-
-public class Cryptomator {
-
- public static final CompletableFuture> OPEN_FILE_HANDLER = new CompletableFuture<>();
- private static final Logger LOG = LoggerFactory.getLogger(Cryptomator.class);
- private static final ConcurrentMap SHUTDOWN_TASKS = new ConcurrentHashMap<>();
-
- public static void main(String[] args) {
- LOG.info("Starting Cryptomator {} on {} {} ({})", ApplicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
-
- if (SystemUtils.IS_OS_MAC_OSX) {
- addOsxFileOpenHandler();
- }
-
- new CleanShutdownPerformer().registerShutdownHook();
-
- final Optional runningInstance = SingleInstanceManager.getRemoteInstance(MainApplication.APPLICATION_KEY);
- if (runningInstance.isPresent()) {
- sendArgsToRunningInstance(args, runningInstance);
- } else {
- Application.launch(MainApplication.class, args);
- }
- }
-
- private static void addOsxFileOpenHandler() {
- /*
- * On OSX we're in an awkward position. We need to register a handler in the main thread of this application. However, we can't
- * even pass objects to the application, so we're forced to use a static CompletableFuture for the handler, which actually opens
- * the file in the application.
- *
- * Code taken from https://github.com/axet/desktop/blob/master/src/main/java/com/github/axet/desktop/os/mac/AppleHandlers.java
- */
- try {
- final Class> applicationClass = Class.forName("com.apple.eawt.Application");
- final Class> openFilesHandlerClass = Class.forName("com.apple.eawt.OpenFilesHandler");
- final Method getApplication = applicationClass.getMethod("getApplication");
- final Object application = getApplication.invoke(null);
- final Method setOpenFileHandler = applicationClass.getMethod("setOpenFileHandler", openFilesHandlerClass);
-
- final ClassLoader openFilesHandlerClassLoader = openFilesHandlerClass.getClassLoader();
- final OpenFilesHandlerClassHandler openFilesHandlerHandler = new OpenFilesHandlerClassHandler();
- final Object openFilesHandlerObject = Proxy.newProxyInstance(openFilesHandlerClassLoader, new Class>[] {openFilesHandlerClass}, openFilesHandlerHandler);
-
- setOpenFileHandler.invoke(application, openFilesHandlerObject);
- } catch (ReflectiveOperationException | RuntimeException e) {
- // Since we're trying to call OS-specific code, we'll just have
- // to hope for the best.
- LOG.error("exception adding OSX file open handler", e);
- }
- }
-
- private static void sendArgsToRunningInstance(String[] args, final Optional remoteInstance) {
- try (RemoteInstance instance = remoteInstance.get()) {
- LOG.info("An instance of Cryptomator is already running at {}.", instance.getRemotePort());
- for (int i = 0; i < args.length; i++) {
- remoteInstance.get().sendMessage(args[i], 100);
- }
- } catch (Exception e) {
- LOG.error("Error forwarding arguments to remote instance", e);
- }
- }
-
- public static void addShutdownTask(Runnable r) {
- SHUTDOWN_TASKS.put(r, Boolean.TRUE);
- }
-
- public static void removeShutdownTask(Runnable r) {
- SHUTDOWN_TASKS.remove(r);
- }
-
- private static class CleanShutdownPerformer extends Thread {
- @Override
- public void run() {
- LOG.debug("Shutting down");
- SHUTDOWN_TASKS.keySet().forEach(r -> {
- try {
- r.run();
- } catch (RuntimeException e) {
- LOG.error("exception while shutting down", e);
- }
- });
- SHUTDOWN_TASKS.clear();
- }
-
- public void registerShutdownHook() {
- Runtime.getRuntime().addShutdownHook(this);
- }
- }
-
- private static void handleOpenFileRequest(File file) {
- try {
- OPEN_FILE_HANDLER.get().accept(file);
- } catch (Exception e) {
- LOG.error("exception handling file open event for file " + file.getAbsolutePath(), e);
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Handler class taken from https://github.com/axet/desktop/blob/master/src/main/java/com/github/axet/desktop/os/mac/AppleHandlers.java
- */
- private static class OpenFilesHandlerClassHandler implements InvocationHandler {
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- if (method.getName().equals("openFiles")) {
- final Class> openFilesEventClass = Class.forName("com.apple.eawt.AppEvent$OpenFilesEvent");
- final Method getFiles = openFilesEventClass.getMethod("getFiles");
- Object e = args[0];
- try {
- @SuppressWarnings("unchecked")
- final List ff = (List) getFiles.invoke(e);
- for (File f : ff) {
- handleOpenFileRequest(f);
- }
- } catch (RuntimeException ee) {
- throw ee;
- } catch (Exception ee) {
- throw new RuntimeException(ee);
- }
- }
- return null;
- }
- }
-}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/CryptomatorComponent.java b/main/ui/src/main/java/org/cryptomator/ui/CryptomatorComponent.java
deleted file mode 100644
index 830945bd3..000000000
--- a/main/ui/src/main/java/org/cryptomator/ui/CryptomatorComponent.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- * Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.ui;
-
-import java.util.concurrent.ExecutorService;
-
-import javax.inject.Singleton;
-
-import org.cryptomator.ui.controllers.MainController;
-import org.cryptomator.ui.model.VaultComponent;
-import org.cryptomator.ui.model.VaultModule;
-import org.cryptomator.ui.util.DeferredCloser;
-
-import dagger.Component;
-
-@Singleton
-@Component(modules = CryptomatorModule.class)
-public interface CryptomatorComponent {
-
- ExecutorService executorService();
-
- DeferredCloser deferredCloser();
-
- MainController mainController();
-
- ExitUtil exitUtil();
-
- DebugMode debugMode();
-
- VaultComponent newVaultComponent(VaultModule vaultModule);
-
-}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/ExitUtil.java b/main/ui/src/main/java/org/cryptomator/ui/ExitUtil.java
index 9126c8ff9..0aebd795f 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/ExitUtil.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/ExitUtil.java
@@ -33,11 +33,11 @@ import javax.script.ScriptException;
import javax.swing.SwingUtilities;
import org.apache.commons.lang3.SystemUtils;
+import org.cryptomator.common.settings.Settings;
import org.cryptomator.jni.JniException;
import org.cryptomator.jni.MacApplicationUiState;
import org.cryptomator.jni.MacFunctions;
import org.cryptomator.ui.settings.Localization;
-import org.cryptomator.ui.settings.Settings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -45,7 +45,7 @@ import javafx.application.Platform;
import javafx.stage.Stage;
@Singleton
-class ExitUtil {
+public class ExitUtil {
private static final Logger LOG = LoggerFactory.getLogger(ExitUtil.class);
@@ -62,7 +62,15 @@ class ExitUtil {
this.macFunctions = macFunctions;
}
- public void initExitHandler(Runnable exitCommand) {
+ public void initExitHandler() {
+ initExitHandler(ExitUtil::platformExitOnMainThread);
+ }
+
+ private static void platformExitOnMainThread() {
+ Platform.runLater(Platform::exit);
+ }
+
+ private void initExitHandler(Runnable exitCommand) {
if (SystemUtils.IS_OS_LINUX) {
initMinimizeExitHandler(exitCommand);
} else {
diff --git a/main/ui/src/main/java/org/cryptomator/ui/MainApplication.java b/main/ui/src/main/java/org/cryptomator/ui/MainApplication.java
deleted file mode 100644
index 80fd591c6..000000000
--- a/main/ui/src/main/java/org/cryptomator/ui/MainApplication.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2014, 2016 Sebastian Stenzel
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- * Sebastian Stenzel - initial API and implementation
- ******************************************************************************/
-package org.cryptomator.ui;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.util.concurrent.ExecutionException;
-
-import org.apache.commons.lang3.SystemUtils;
-import org.cryptomator.cryptolib.common.SecureRandomModule;
-import org.cryptomator.ui.controllers.MainController;
-import org.cryptomator.ui.util.ActiveWindowStyleSupport;
-import org.cryptomator.ui.util.DeferredCloser;
-import org.cryptomator.ui.util.SingleInstanceManager;
-import org.cryptomator.ui.util.SingleInstanceManager.LocalInstance;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javafx.application.Application;
-import javafx.application.Platform;
-import javafx.fxml.FXMLLoader;
-import javafx.scene.image.Image;
-import javafx.scene.text.Font;
-import javafx.stage.Stage;
-
-public class MainApplication extends Application {
-
- public static final String APPLICATION_KEY = "CryptomatorGUI";
- private static final Logger LOG = LoggerFactory.getLogger(MainApplication.class);
-
- private DeferredCloser closer;
-
- @Override
- public void start(Stage primaryStage) throws IOException {
- LOG.info("JavaFX application started");
-
- CryptomatorComponent comp = createCryptomatorComponent(primaryStage);
- MainController mainCtrl = comp.mainController();
- closer = comp.deferredCloser();
-
- comp.debugMode().initialize();
-
- setupFXMLClassLoader();
- setupStylesheets();
-
- initializeStage(primaryStage, mainCtrl);
- showWindow(primaryStage);
-
- registerExitHandler(comp);
-
- openFilesRequestedDuringStartup(primaryStage, mainCtrl);
- registerApplicationToProcessOpenFileRequests(primaryStage, comp, mainCtrl);
- }
-
- @Override
- public void stop() {
- try {
- closer.close();
- } catch (ExecutionException e) {
- LOG.error("Error closing ressources", e);
- }
- }
-
- private CryptomatorComponent createCryptomatorComponent(Stage primaryStage) {
- try {
- return DaggerCryptomatorComponent.builder() //
- .cryptomatorModule(new CryptomatorModule(this, primaryStage)) //
- .secureRandomModule(new SecureRandomModule(SecureRandom.getInstanceStrong())) //
- .build();
- } catch (NoSuchAlgorithmException e) {
- throw new IllegalStateException("Every implementation of the Java platform is required to support at least one strong SecureRandom implementation.", e);
- }
- }
-
- private void setupFXMLClassLoader() {
- ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
- FXMLLoader.setDefaultClassLoader(contextClassLoader);
- Platform.runLater(() -> {
- /*
- * This fixes a bug on OSX where the magic file open handler leads to no context class loader being set in the AppKit (event)
- * thread if the application is not started opening a file.
- */
- if (Thread.currentThread().getContextClassLoader() == null) {
- Thread.currentThread().setContextClassLoader(contextClassLoader);
- }
- });
- }
-
- private void setupStylesheets() {
- loadFont("/css/ionicons.ttf");
- loadFont("/css/fontawesome-webfont.ttf");
- chooseNativeStylesheet();
- }
-
- private void loadFont(String resourcePath) {
- try (InputStream in = getClass().getResourceAsStream(resourcePath)) {
- Font.loadFont(in, 12.0);
- } catch (IOException e) {
- LOG.warn("Error loading font from path: " + resourcePath, e);
- }
- }
-
- private void initializeStage(Stage primaryStage, MainController mainCtrl) {
- mainCtrl.initStage(primaryStage);
- primaryStage.titleProperty().bind(mainCtrl.windowTitle());
- primaryStage.setResizable(false);
- if (SystemUtils.IS_OS_WINDOWS) {
- primaryStage.getIcons().add(new Image(MainApplication.class.getResourceAsStream("/window_icon.png")));
- }
- }
-
- private void showWindow(Stage primaryStage) {
- primaryStage.show();
- ActiveWindowStyleSupport.startObservingFocus(primaryStage);
- }
-
- private void registerExitHandler(CryptomatorComponent comp) {
- comp.exitUtil().initExitHandler(this::quit);
- }
-
- private void openFilesRequestedDuringStartup(Stage primaryStage, final MainController mainCtrl) {
- for (String arg : getParameters().getUnnamed()) {
- handleCommandLineArg(arg, primaryStage, mainCtrl);
- }
- if (SystemUtils.IS_OS_MAC_OSX) {
- Cryptomator.OPEN_FILE_HANDLER.complete(file -> handleCommandLineArg(file.getAbsolutePath(), primaryStage, mainCtrl));
- }
- }
-
- private void registerApplicationToProcessOpenFileRequests(Stage primaryStage, final CryptomatorComponent comp, final MainController mainCtrl) throws IOException {
- LocalInstance cryptomatorGuiInstance = closer.closeLater(SingleInstanceManager.startLocalInstance(APPLICATION_KEY, comp.executorService()), LocalInstance::close).get().get();
- cryptomatorGuiInstance.registerListener(arg -> handleCommandLineArg(arg, primaryStage, mainCtrl));
- }
-
- private void chooseNativeStylesheet() {
- if (SystemUtils.IS_OS_MAC_OSX) {
- setUserAgentStylesheet(getClass().getResource("/css/mac_theme.css").toString());
- } else if (SystemUtils.IS_OS_LINUX) {
- setUserAgentStylesheet(getClass().getResource("/css/linux_theme.css").toString());
- } else if (SystemUtils.IS_OS_WINDOWS) {
- setUserAgentStylesheet(getClass().getResource("/css/win_theme.css").toString());
- }
- }
-
- private void handleCommandLineArg(String arg, Stage primaryStage, MainController mainCtrl) {
- // find correct location:
- final Path path = FileSystems.getDefault().getPath(arg);
- final Path vaultPath;
- if (Files.isDirectory(path)) {
- vaultPath = path;
- } else if (Files.isRegularFile(path)) {
- vaultPath = path.getParent();
- } else {
- LOG.warn("Invalid vault path %s", arg);
- return;
- }
-
- // add vault to ctrl:
- Platform.runLater(() -> {
- mainCtrl.addVault(vaultPath, true);
- primaryStage.setIconified(false);
- primaryStage.show();
- primaryStage.toFront();
- primaryStage.requestFocus();
- });
- }
-
- private void quit() {
- Platform.runLater(() -> {
- stop();
- Platform.exit();
- System.exit(0);
- });
- }
-
-}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/CryptomatorModule.java b/main/ui/src/main/java/org/cryptomator/ui/UiModule.java
similarity index 72%
rename from main/ui/src/main/java/org/cryptomator/ui/CryptomatorModule.java
rename to main/ui/src/main/java/org/cryptomator/ui/UiModule.java
index 64f7399dc..9b4671162 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/CryptomatorModule.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/UiModule.java
@@ -11,17 +11,18 @@ package org.cryptomator.ui;
import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.function.Consumer;
import javax.inject.Named;
import javax.inject.Singleton;
import org.cryptomator.common.CommonsModule;
-import org.cryptomator.cryptolib.CryptoLibModule;
+import org.cryptomator.common.settings.Settings;
+import org.cryptomator.common.settings.SettingsProvider;
import org.cryptomator.frontend.webdav.WebDavServer;
import org.cryptomator.jni.JniModule;
import org.cryptomator.keychain.KeychainModule;
-import org.cryptomator.ui.settings.Settings;
-import org.cryptomator.ui.settings.SettingsProvider;
+import org.cryptomator.ui.model.VaultComponent;
import org.cryptomator.ui.util.DeferredCloser;
import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
@@ -29,40 +30,18 @@ import org.slf4j.LoggerFactory;
import dagger.Module;
import dagger.Provides;
-import javafx.application.Application;
import javafx.beans.binding.Binding;
-import javafx.stage.Stage;
-@Module(includes = {CommonsModule.class, KeychainModule.class, JniModule.class, CryptoLibModule.class})
-class CryptomatorModule {
+@Module(includes = {CommonsModule.class, KeychainModule.class, JniModule.class}, subcomponents = {VaultComponent.class})
+public class UiModule {
- private static final Logger LOG = LoggerFactory.getLogger(CryptomatorModule.class);
- private final Application application;
- private final Stage mainWindow;
-
- public CryptomatorModule(Application application, Stage mainWindow) {
- this.application = application;
- this.mainWindow = mainWindow;
- }
+ private static final Logger LOG = LoggerFactory.getLogger(UiModule.class);
@Provides
@Singleton
- Application provideApplication() {
- return application;
- }
-
- @Provides
- @Singleton
- @Named("mainWindow")
- Stage provideMainWindow() {
- return mainWindow;
- }
-
- @Provides
- @Singleton
- DeferredCloser provideDeferredCloser() {
+ DeferredCloser provideDeferredCloser(@Named("shutdownTaskScheduler") Consumer shutdownTaskScheduler) {
DeferredCloser closer = new DeferredCloser();
- Cryptomator.addShutdownTask(() -> {
+ shutdownTaskScheduler.accept(() -> {
try {
closer.close();
} catch (Exception e) {
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java
index a1eb9bb16..7b0de1f51 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java
@@ -11,6 +11,7 @@ package org.cryptomator.ui.controllers;
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -18,6 +19,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
import javax.inject.Inject;
import javax.inject.Named;
@@ -25,6 +28,9 @@ import javax.inject.Provider;
import javax.inject.Singleton;
import org.apache.commons.lang3.SystemUtils;
+import org.cryptomator.common.settings.Settings;
+import org.cryptomator.common.settings.VaultSettings;
+import org.cryptomator.ui.ExitUtil;
import org.cryptomator.ui.controls.DirectoryListCell;
import org.cryptomator.ui.model.UpgradeStrategies;
import org.cryptomator.ui.model.UpgradeStrategy;
@@ -32,15 +38,15 @@ import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.model.VaultFactory;
import org.cryptomator.ui.model.VaultList;
import org.cryptomator.ui.settings.Localization;
-import org.cryptomator.ui.settings.Settings;
-import org.cryptomator.ui.settings.VaultSettings;
import org.cryptomator.ui.util.DialogBuilderUtil;
import org.fxmisc.easybind.EasyBind;
+import org.fxmisc.easybind.Subscription;
import org.fxmisc.easybind.monadic.MonadicBinding;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import dagger.Lazy;
+import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Binding;
import javafx.beans.binding.Bindings;
@@ -60,8 +66,10 @@ import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.MenuItem;
import javafx.scene.control.ToggleButton;
+import javafx.scene.image.Image;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
+import javafx.scene.text.Font;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
@@ -69,8 +77,13 @@ import javafx.stage.Stage;
public class MainController extends LocalizedFXMLViewController {
private static final Logger LOG = LoggerFactory.getLogger(MainController.class);
+ private static final String ACTIVE_WINDOW_STYLE_CLASS = "active-window";
+ private static final String INACTIVE_WINDOW_STYLE_CLASS = "inactive-window";
private final Stage mainWindow;
+ private final ExitUtil exitUtil;
+ private final ExecutorService executorService;
+ private final BlockingQueue fileOpenRequests;
private final Settings settings;
private final VaultFactory vaultFactoy;
private final Lazy welcomeController;
@@ -91,13 +104,18 @@ public class MainController extends LocalizedFXMLViewController {
private final BooleanBinding isShowingSettings;
private final Map unlockedVaults = new HashMap<>();
+ private Subscription subs = Subscription.EMPTY;
+
@Inject
- public MainController(@Named("mainWindow") Stage mainWindow, Localization localization, Settings settings, VaultFactory vaultFactoy, Lazy welcomeController,
- Lazy initializeController, Lazy notFoundController, Lazy upgradeController, Lazy unlockController,
- Provider unlockedControllerProvider, Lazy changePasswordController, Lazy settingsController, UpgradeStrategies upgradeStrategies,
- VaultList vaults) {
+ public MainController(@Named("mainWindow") Stage mainWindow, ExecutorService executorService, @Named("fileOpenRequests") BlockingQueue fileOpenRequests, ExitUtil exitUtil, Localization localization,
+ Settings settings, VaultFactory vaultFactoy, Lazy welcomeController, Lazy initializeController, Lazy notFoundController,
+ Lazy upgradeController, Lazy unlockController, Provider unlockedControllerProvider, Lazy changePasswordController,
+ Lazy settingsController, UpgradeStrategies upgradeStrategies, VaultList vaults) {
super(localization);
this.mainWindow = mainWindow;
+ this.executorService = executorService;
+ this.fileOpenRequests = fileOpenRequests;
+ this.exitUtil = exitUtil;
this.settings = settings;
this.vaultFactoy = vaultFactoy;
this.welcomeController = welcomeController;
@@ -155,10 +173,58 @@ public class MainController extends LocalizedFXMLViewController {
emptyListInstructions.visibleProperty().bind(Bindings.isEmpty(vaults));
changePasswordMenuItem.visibleProperty().bind(isSelectedVaultValid.and(Bindings.isNull(upgradeStrategyForSelectedVault)));
- EasyBind.subscribe(selectedVault, this::selectedVaultDidChange);
- EasyBind.subscribe(activeController, this::activeControllerDidChange);
- EasyBind.subscribe(isShowingSettings, settingsButton::setSelected);
- EasyBind.subscribe(addVaultContextMenu.showingProperty(), addVaultButton::setSelected);
+ subs = subs.and(EasyBind.subscribe(selectedVault, this::selectedVaultDidChange));
+ subs = subs.and(EasyBind.subscribe(activeController, this::activeControllerDidChange));
+ subs = subs.and(EasyBind.subscribe(isShowingSettings, settingsButton::setSelected));
+ subs = subs.and(EasyBind.subscribe(addVaultContextMenu.showingProperty(), addVaultButton::setSelected));
+ }
+
+ @Override
+ public void initStage(Stage stage) {
+ super.initStage(stage);
+ stage.titleProperty().bind(windowTitle());
+ stage.setResizable(false);
+ loadFont("/css/ionicons.ttf");
+ loadFont("/css/fontawesome-webfont.ttf");
+ if (SystemUtils.IS_OS_MAC_OSX) {
+ subs = subs.and(EasyBind.includeWhen(mainWindow.getScene().getRoot().getStyleClass(), ACTIVE_WINDOW_STYLE_CLASS, mainWindow.focusedProperty()));
+ subs = subs.and(EasyBind.includeWhen(mainWindow.getScene().getRoot().getStyleClass(), INACTIVE_WINDOW_STYLE_CLASS, mainWindow.focusedProperty().not()));
+ Application.setUserAgentStylesheet(getClass().getResource("/css/mac_theme.css").toString());
+ } else if (SystemUtils.IS_OS_LINUX) {
+ Application.setUserAgentStylesheet(getClass().getResource("/css/linux_theme.css").toString());
+ } else if (SystemUtils.IS_OS_WINDOWS) {
+ stage.getIcons().add(new Image(getClass().getResourceAsStream("/window_icon.png")));
+ Application.setUserAgentStylesheet(getClass().getResource("/css/win_theme.css").toString());
+ }
+ exitUtil.initExitHandler();
+ listenToFileOpenRequests(stage);
+ }
+
+ private void loadFont(String resourcePath) {
+ try (InputStream in = getClass().getResourceAsStream(resourcePath)) {
+ Font.loadFont(in, 12.0);
+ } catch (IOException e) {
+ LOG.warn("Error loading font from path: " + resourcePath, e);
+ }
+ }
+
+ private void listenToFileOpenRequests(Stage stage) {
+ executorService.submit(() -> {
+ while (!Thread.interrupted()) {
+ try {
+ final Path path = fileOpenRequests.take();
+ Platform.runLater(() -> {
+ addVault(path, true);
+ stage.setIconified(false);
+ stage.show();
+ stage.toFront();
+ stage.requestFocus();
+ });
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ });
}
@Override
@@ -218,30 +284,31 @@ public class MainController extends LocalizedFXMLViewController {
/**
* adds the given directory or selects it if it is already in the list of directories.
*
- * @param path non-null, writable, existing directory
+ * @param path to a vault directory or masterkey file
*/
public void addVault(final Path path, boolean select) {
- // TODO: `|| !Files.isWritable(path)` is broken on windows. Fix in Java 8u72, see https://bugs.openjdk.java.net/browse/JDK-8034057
- if (path == null) {
- return;
- }
-
final Path vaultPath;
if (path != null && Files.isDirectory(path)) {
vaultPath = path;
} else if (path != null && Files.isRegularFile(path)) {
vaultPath = path.getParent();
} else {
+ LOG.warn("Ignoring attempt to add vault with invalid path: {}", path);
return;
}
- final VaultSettings vaultSettings = VaultSettings.withRandomId(settings);
- vaultSettings.path().set(vaultPath);
- final Vault vault = vaultFactoy.get(vaultSettings);
+ final Vault vault = vaults.stream().filter(v -> v.getPath().equals(vaultPath)).findAny().orElseGet(() -> {
+ VaultSettings vaultSettings = VaultSettings.withRandomId(settings);
+ vaultSettings.path().set(vaultPath);
+ return vaultFactoy.get(vaultSettings);
+ });
+
if (!vaults.contains(vault)) {
vaults.add(vault);
}
- vaultList.getSelectionModel().select(vault);
+ if (select) {
+ vaultList.getSelectionModel().select(vault);
+ }
}
@FXML
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java
index 6e1bdf263..1cf66d10f 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java
@@ -16,8 +16,8 @@ import javax.inject.Singleton;
import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.SystemUtils;
+import org.cryptomator.common.settings.Settings;
import org.cryptomator.ui.settings.Localization;
-import org.cryptomator.ui.settings.Settings;
import javafx.beans.binding.Bindings;
import javafx.event.ActionEvent;
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java
index 40fd97fd6..b635fdaf6 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java
@@ -15,6 +15,7 @@ import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Comparator;
import java.util.Map;
+import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Named;
@@ -27,9 +28,8 @@ import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
+import org.cryptomator.common.settings.Settings;
import org.cryptomator.ui.settings.Localization;
-import org.cryptomator.ui.settings.Settings;
-import org.cryptomator.ui.util.ApplicationVersion;
import org.cryptomator.ui.util.AsyncTaskService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -53,14 +53,17 @@ public class WelcomeController extends LocalizedFXMLViewController {
private static final Logger LOG = LoggerFactory.getLogger(WelcomeController.class);
private final Application app;
+ private final Optional applicationVersion;
private final Settings settings;
private final Comparator semVerComparator;
private final AsyncTaskService asyncTaskService;
@Inject
- public WelcomeController(Application app, Localization localization, Settings settings, @Named("SemVer") Comparator semVerComparator, AsyncTaskService asyncTaskService) {
+ public WelcomeController(Application app, @Named("applicationVersion") Optional applicationVersion, Localization localization, Settings settings, @Named("SemVer") Comparator semVerComparator,
+ AsyncTaskService asyncTaskService) {
super(localization);
this.app = app;
+ this.applicationVersion = applicationVersion;
this.settings = settings;
this.semVerComparator = semVerComparator;
this.asyncTaskService = asyncTaskService;
@@ -112,7 +115,7 @@ public class WelcomeController extends LocalizedFXMLViewController {
HttpClientBuilder httpClientBuilder = HttpClients.custom() //
.disableCookieManagement() //
.setDefaultRequestConfig(requestConfig) //
- .setUserAgent("Cryptomator VersionChecker/" + ApplicationVersion.orElse("SNAPSHOT"));
+ .setUserAgent("Cryptomator VersionChecker/" + applicationVersion.orElse("SNAPSHOT"));
LOG.debug("Checking for updates...");
try (CloseableHttpClient client = httpClientBuilder.build()) {
HttpGet request = new HttpGet("https://cryptomator.org/downloads/latestVersion.json");
@@ -148,7 +151,7 @@ public class WelcomeController extends LocalizedFXMLViewController {
// no version check possible on unsupported OS
return;
}
- final String currentVersion = ApplicationVersion.orElse(null);
+ final String currentVersion = applicationVersion.orElse(null);
LOG.info("Current version: {}, lastest version: {}", currentVersion, latestVersion);
if (currentVersion != null && semVerComparator.compare(currentVersion, latestVersion) < 0) {
final String msg = String.format(localization.getString("welcome.newVersionMessage"), latestVersion, currentVersion);
diff --git a/main/ui/src/main/java/org/cryptomator/ui/logging/ConfigurableFileAppender.java b/main/ui/src/main/java/org/cryptomator/ui/logging/ConfigurableFileAppender.java
deleted file mode 100644
index 3a227bfd6..000000000
--- a/main/ui/src/main/java/org/cryptomator/ui/logging/ConfigurableFileAppender.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- * Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.ui.logging;
-
-import java.io.IOException;
-import java.io.Serializable;
-import java.net.URISyntaxException;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.regex.Pattern;
-
-import org.apache.commons.lang3.SystemUtils;
-import org.apache.logging.log4j.core.Filter;
-import org.apache.logging.log4j.core.Layout;
-import org.apache.logging.log4j.core.appender.AbstractAppender;
-import org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender;
-import org.apache.logging.log4j.core.appender.FileManager;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
-import org.apache.logging.log4j.core.layout.PatternLayout;
-import org.apache.logging.log4j.core.util.Booleans;
-import org.apache.logging.log4j.util.Strings;
-
-/**
- * A preconfigured FileAppender only relying on a configurable system property, e.g. -Dcryptomator.logPath=/var/log/cryptomator.log.
- * Other than the normal {@link org.apache.logging.log4j.core.appender.FileAppender} paths can be resolved relative to the users home directory.
- */
-@Plugin(name = "ConfigurableFile", category = "Core", elementType = "appender", printObject = true)
-public class ConfigurableFileAppender extends AbstractOutputStreamAppender {
-
- private static final long serialVersionUID = -6548221568069606389L;
- private static final int DEFAULT_BUFFER_SIZE = 8192;
- private static final Pattern DRIVE_LETTER_WITH_PRECEEDING_SLASH = Pattern.compile("^/[A-Z]:", Pattern.CASE_INSENSITIVE);
-
- protected ConfigurableFileAppender(String name, Layout extends Serializable> layout, Filter filter, FileManager manager) {
- super(name, layout, filter, true, true, manager);
- LOGGER.info("Logging to " + manager.getFileName());
- }
-
- @PluginFactory
- public static AbstractAppender createAppender(@PluginAttribute("name") final String name, @PluginAttribute("pathPropertyName") final String pathPropertyName, @PluginAttribute("append") final String append,
- @PluginElement("Layout") Layout extends Serializable> layout) {
- if (name == null) {
- LOGGER.error("No name provided for ConfigurableFileAppender");
- return null;
- }
-
- if (pathPropertyName == null) {
- LOGGER.error("No pathPropertyName provided for ConfigurableFileAppender with name " + name);
- return null;
- }
-
- final String fileName = System.getProperty(pathPropertyName);
- if (Strings.isEmpty(fileName)) {
- LOGGER.warn("No log file location provided in system property \"" + pathPropertyName + "\"");
- return null;
- }
-
- final Path filePath = parsePath(fileName);
- if (filePath == null) {
- LOGGER.warn("Invalid path \"" + fileName + "\"");
- return null;
- }
-
- if (!Files.exists(filePath.getParent())) {
- try {
- Files.createDirectories(filePath.getParent());
- } catch (IOException e) {
- LOGGER.error("Could not create parent directories for log file located at " + filePath.toString(), e);
- return null;
- }
- }
-
- final boolean shouldAppend = Booleans.parseBoolean(append, true);
- if (layout == null) {
- layout = PatternLayout.createDefaultLayout();
- }
-
- final FileManager manager = FileManager.getFileManager(filePath.toString(), shouldAppend, false, true, null, layout, DEFAULT_BUFFER_SIZE);
- return new ConfigurableFileAppender(name, layout, null, manager);
- }
-
- private static Path parsePath(String path) {
- if (path.startsWith("~/")) {
- // home-dir-relative Path:
- final Path userHome = FileSystems.getDefault().getPath(SystemUtils.USER_HOME);
- return userHome.resolve(path.substring(2));
- } else if (path.startsWith("/")) {
- // absolute Path:
- return FileSystems.getDefault().getPath(path);
- } else {
- // relative Path:
- try {
- String jarFileLocation = ConfigurableFileAppender.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath();
- if (SystemUtils.IS_OS_WINDOWS && DRIVE_LETTER_WITH_PRECEEDING_SLASH.matcher(jarFileLocation).find()) {
- // on windows we need to remove a preceeding slash from "/C:/foo/bar":
- jarFileLocation = jarFileLocation.substring(1);
- }
- final Path workingDir = FileSystems.getDefault().getPath(jarFileLocation).getParent();
- return workingDir.resolve(path);
- } catch (URISyntaxException e) {
- LOGGER.error("Unable to resolve working directory ", e);
- return null;
- }
- }
- }
-
-}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeStrategy.java b/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeStrategy.java
index b2c1b2e8f..40105140b 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeStrategy.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeStrategy.java
@@ -10,6 +10,8 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.api.CryptorProvider;
@@ -38,6 +40,14 @@ public abstract class UpgradeStrategy {
this.vaultVersionAfterUpgrade = vaultVersionAfterUpgrade;
}
+ static SecureRandom strongSecureRandom() {
+ try {
+ return SecureRandom.getInstanceStrong();
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException("A strong algorithm must exist in every Java platform.", e);
+ }
+ }
+
/**
* @return Localized title string to display to the user when an upgrade is needed.
*/
diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion3DropBundleExtension.java b/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion3DropBundleExtension.java
index e080aeea3..9bbc5f2d2 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion3DropBundleExtension.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion3DropBundleExtension.java
@@ -13,10 +13,8 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.lang3.StringUtils;
-import org.cryptomator.cryptolib.api.CryptoLibVersion;
-import org.cryptomator.cryptolib.api.CryptoLibVersion.Version;
+import org.cryptomator.cryptolib.Cryptors;
import org.cryptomator.cryptolib.api.Cryptor;
-import org.cryptomator.cryptolib.api.CryptorProvider;
import org.cryptomator.ui.settings.Localization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -29,8 +27,8 @@ class UpgradeVersion3DropBundleExtension extends UpgradeStrategy {
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion3DropBundleExtension.class);
@Inject
- public UpgradeVersion3DropBundleExtension(@CryptoLibVersion(Version.ONE) CryptorProvider version1CryptorProvider, Localization localization) {
- super(version1CryptorProvider, localization, 3, 3);
+ public UpgradeVersion3DropBundleExtension(Localization localization) {
+ super(Cryptors.version1(UpgradeStrategy.strongSecureRandom()), localization, 3, 3);
}
@Override
diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion3to4.java b/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion3to4.java
index e28e2ff8b..005392390 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion3to4.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion3to4.java
@@ -23,10 +23,8 @@ import javax.inject.Singleton;
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.BaseNCodec;
import org.apache.commons.lang3.StringUtils;
-import org.cryptomator.cryptolib.api.CryptoLibVersion;
-import org.cryptomator.cryptolib.api.CryptoLibVersion.Version;
+import org.cryptomator.cryptolib.Cryptors;
import org.cryptomator.cryptolib.api.Cryptor;
-import org.cryptomator.cryptolib.api.CryptorProvider;
import org.cryptomator.cryptolib.common.MessageDigestSupplier;
import org.cryptomator.ui.settings.Localization;
import org.slf4j.Logger;
@@ -50,8 +48,8 @@ class UpgradeVersion3to4 extends UpgradeStrategy {
private final BaseNCodec base32 = new Base32();
@Inject
- public UpgradeVersion3to4(@CryptoLibVersion(Version.ONE) CryptorProvider version1CryptorProvider, Localization localization) {
- super(version1CryptorProvider, localization, 3, 4);
+ public UpgradeVersion3to4(Localization localization) {
+ super(Cryptors.version1(UpgradeStrategy.strongSecureRandom()), localization, 3, 4);
}
@Override
diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion4to5.java b/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion4to5.java
index 53296c3cd..25d89c3e2 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion4to5.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/model/UpgradeVersion4to5.java
@@ -20,10 +20,7 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import org.cryptomator.cryptolib.Cryptors;
-import org.cryptomator.cryptolib.api.CryptoLibVersion;
-import org.cryptomator.cryptolib.api.CryptoLibVersion.Version;
import org.cryptomator.cryptolib.api.Cryptor;
-import org.cryptomator.cryptolib.api.CryptorProvider;
import org.cryptomator.cryptolib.api.FileHeader;
import org.cryptomator.ui.settings.Localization;
import org.slf4j.Logger;
@@ -40,8 +37,8 @@ class UpgradeVersion4to5 extends UpgradeStrategy {
private static final Pattern BASE32_PATTERN = Pattern.compile("^([A-Z2-7]{8})*[A-Z2-7=]{8}");
@Inject
- public UpgradeVersion4to5(@CryptoLibVersion(Version.ONE) CryptorProvider version1CryptorProvider, Localization localization) {
- super(version1CryptorProvider, localization, 4, 5);
+ public UpgradeVersion4to5(Localization localization) {
+ super(Cryptors.version1(UpgradeStrategy.strongSecureRandom()), localization, 4, 5);
}
@Override
diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java
index 03e39fb04..92ca44eff 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java
@@ -26,6 +26,8 @@ import javax.inject.Inject;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.LazyInitializer;
+import org.cryptomator.common.settings.Settings;
+import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.cryptofs.CryptoFileSystem;
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
@@ -37,8 +39,6 @@ import org.cryptomator.frontend.webdav.mount.Mounter.Mount;
import org.cryptomator.frontend.webdav.mount.Mounter.MountParam;
import org.cryptomator.frontend.webdav.servlet.WebDavServletController;
import org.cryptomator.ui.model.VaultModule.PerVault;
-import org.cryptomator.ui.settings.Settings;
-import org.cryptomator.ui.settings.VaultSettings;
import org.cryptomator.ui.util.DeferredCloser;
import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/VaultComponent.java b/main/ui/src/main/java/org/cryptomator/ui/model/VaultComponent.java
index eebba2a05..527aa4f85 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/model/VaultComponent.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/model/VaultComponent.java
@@ -15,4 +15,11 @@ public interface VaultComponent {
Vault vault();
+ @Subcomponent.Builder
+ interface Builder {
+ Builder vaultModule(VaultModule module);
+
+ VaultComponent build();
+ }
+
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/VaultFactory.java b/main/ui/src/main/java/org/cryptomator/ui/model/VaultFactory.java
index 51d3f8df6..7b4b553e2 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/model/VaultFactory.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/model/VaultFactory.java
@@ -14,18 +14,18 @@ import java.util.concurrent.ConcurrentMap;
import javax.inject.Inject;
import javax.inject.Singleton;
-import org.cryptomator.ui.CryptomatorComponent;
-import org.cryptomator.ui.settings.VaultSettings;
+import org.cryptomator.common.settings.VaultSettings;
+import org.cryptomator.ui.model.VaultComponent.Builder;
@Singleton
public class VaultFactory {
- private final CryptomatorComponent cryptomatorComponent;
+ private final Builder vaultComponentBuilder;
private final ConcurrentMap vaults = new ConcurrentHashMap<>();
@Inject
- public VaultFactory(CryptomatorComponent cryptomatorComponent) {
- this.cryptomatorComponent = cryptomatorComponent;
+ public VaultFactory(VaultComponent.Builder vaultComponentBuilder) {
+ this.vaultComponentBuilder = vaultComponentBuilder;
}
public Vault get(VaultSettings vaultSettings) {
@@ -34,7 +34,7 @@ public class VaultFactory {
private Vault create(VaultSettings vaultSettings) {
VaultModule module = new VaultModule(vaultSettings);
- VaultComponent comp = cryptomatorComponent.newVaultComponent(module);
+ VaultComponent comp = vaultComponentBuilder.vaultModule(module).build();
return comp.vault();
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/VaultList.java b/main/ui/src/main/java/org/cryptomator/ui/model/VaultList.java
index fdf22d9d9..519fea90f 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/model/VaultList.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/model/VaultList.java
@@ -11,8 +11,8 @@ import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
-import org.cryptomator.ui.settings.Settings;
-import org.cryptomator.ui.settings.VaultSettings;
+import org.cryptomator.common.settings.Settings;
+import org.cryptomator.common.settings.VaultSettings;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java b/main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java
index c24168378..c8a622233 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/model/VaultModule.java
@@ -12,7 +12,7 @@ import java.util.Objects;
import javax.inject.Scope;
-import org.cryptomator.ui.settings.VaultSettings;
+import org.cryptomator.common.settings.VaultSettings;
import dagger.Module;
import dagger.Provides;
diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/ActiveWindowStyleSupport.java b/main/ui/src/main/java/org/cryptomator/ui/util/ActiveWindowStyleSupport.java
index 769e951e6..72da6f48c 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/util/ActiveWindowStyleSupport.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/util/ActiveWindowStyleSupport.java
@@ -12,6 +12,10 @@ import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.stage.Window;
+/**
+ * @deprecated use https://github.com/TomasMikula/EasyBind#conditional-collection-membership
+ */
+@Deprecated
public class ActiveWindowStyleSupport implements ChangeListener {
public static final String ACTIVE_WINDOW_STYLE_CLASS = "active-window";
diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/SingleInstanceManager.java b/main/ui/src/main/java/org/cryptomator/ui/util/SingleInstanceManager.java
deleted file mode 100644
index adcf9bcd6..000000000
--- a/main/ui/src/main/java/org/cryptomator/ui/util/SingleInstanceManager.java
+++ /dev/null
@@ -1,374 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2014, 2016 cryptomator.org
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- * Tillmann Gaida - initial implementation
- ******************************************************************************/
-package org.cryptomator.ui.util;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.nio.ByteBuffer;
-import java.nio.channels.ClosedChannelException;
-import java.nio.channels.ClosedSelectorException;
-import java.nio.channels.ReadableByteChannel;
-import java.nio.channels.SelectableChannel;
-import java.nio.channels.SelectionKey;
-import java.nio.channels.Selector;
-import java.nio.channels.ServerSocketChannel;
-import java.nio.channels.SocketChannel;
-import java.nio.channels.WritableByteChannel;
-import java.nio.charset.StandardCharsets;
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.ExecutorService;
-import java.util.prefs.Preferences;
-
-import org.apache.commons.io.IOUtils;
-import org.cryptomator.ui.Cryptomator;
-import org.cryptomator.ui.util.ListenerRegistry.ListenerRegistration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Classes and methods to manage running this application in a mode, which only
- * shows one instance.
- *
- * @author Tillmann Gaida
- */
-public class SingleInstanceManager {
- private static final Logger LOG = LoggerFactory.getLogger(SingleInstanceManager.class);
-
- /**
- * Connection to a running instance
- */
- public static class RemoteInstance implements Closeable {
- final SocketChannel channel;
-
- RemoteInstance(SocketChannel channel) {
- super();
- this.channel = channel;
- }
-
- /**
- * Sends a message to the running instance.
- *
- * @param string
- * May not be longer than 2^16 - 1 bytes.
- * @param timeout
- * timeout in milliseconds. this should be larger than the
- * precision of {@link System#currentTimeMillis()}.
- * @return true if the message was sent within the given timeout.
- * @throws IOException
- */
- public boolean sendMessage(String string, long timeout) throws IOException {
- Objects.requireNonNull(string);
- byte[] message = string.getBytes(StandardCharsets.UTF_8);
- if (message.length >= 256 * 256) {
- throw new IOException("Message too long.");
- }
-
- ByteBuffer buf = ByteBuffer.allocate(message.length + 2);
- buf.put((byte) (message.length / 256));
- buf.put((byte) (message.length % 256));
- buf.put(message);
-
- buf.flip();
- TimeoutTask.attempt(t -> {
- if (channel.write(buf) < 0) {
- return true;
- }
- return !buf.hasRemaining();
- }, timeout, 10);
- return !buf.hasRemaining();
- }
-
- @Override
- public void close() throws IOException {
- channel.close();
- }
-
- public int getRemotePort() throws IOException {
- return ((InetSocketAddress) channel.getRemoteAddress()).getPort();
- }
- }
-
- public static interface MessageListener {
- void handleMessage(String message);
- }
-
- /**
- * Represents a socket making this the main instance of the application.
- */
- public static class LocalInstance implements Closeable {
- private class ChannelState {
- ByteBuffer write = ByteBuffer.wrap(applicationKey.getBytes(StandardCharsets.UTF_8));
- ByteBuffer readLength = ByteBuffer.allocate(2);
- ByteBuffer readMessage = null;
- }
-
- final ListenerRegistry registry = new ListenerRegistry<>(MessageListener::handleMessage);
- final String applicationKey;
- final ServerSocketChannel channel;
- final Selector selector;
- final int port;
-
- public LocalInstance(String applicationKey, ServerSocketChannel channel, Selector selector, int port) {
- Objects.requireNonNull(applicationKey);
- this.applicationKey = applicationKey;
- this.channel = channel;
- this.selector = selector;
- this.port = port;
- }
-
- /**
- * Register a listener for
- *
- * @param listener
- * @return
- */
- public ListenerRegistration registerListener(MessageListener listener) {
- Objects.requireNonNull(listener);
- return registry.registerListener(listener);
- }
-
- void handleSelection(SelectionKey key) throws IOException {
- if (key.isAcceptable()) {
- final SocketChannel accepted = channel.accept();
- SelectionKey keyOfAcceptedConnection = null;
- try {
- if (accepted != null) {
- LOG.debug("accepted incoming connection");
- accepted.configureBlocking(false);
- keyOfAcceptedConnection = accepted.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
- }
- } finally {
- if (keyOfAcceptedConnection == null) {
- accepted.close();
- }
- }
- }
-
- if (key.attachment() == null) {
- key.attach(new ChannelState());
- }
-
- ChannelState state = (ChannelState) key.attachment();
-
- if (key.isWritable() && state.write != null) {
- ((WritableByteChannel) key.channel()).write(state.write);
- if (!state.write.hasRemaining()) {
- state.write = null;
- }
- LOG.trace("wrote welcome. switching to read only.");
- key.interestOps(SelectionKey.OP_READ);
- }
-
- if (key.isReadable()) {
- ByteBuffer buffer = state.readLength != null ? state.readLength : state.readMessage;
-
- if (((ReadableByteChannel) key.channel()).read(buffer) < 0) {
- key.cancel();
- }
-
- if (!buffer.hasRemaining()) {
- buffer.flip();
- if (state.readLength != null) {
- int length = (buffer.get() + 256) % 256;
- length = length * 256 + ((buffer.get() + 256) % 256);
-
- state.readLength = null;
- state.readMessage = ByteBuffer.allocate(length);
- } else {
- byte[] bytes = new byte[buffer.limit()];
- buffer.get(bytes);
-
- state.readMessage = null;
- state.readLength = ByteBuffer.allocate(2);
-
- registry.broadcast(new String(bytes, "UTF-8"));
- }
- }
- }
- }
-
- @Override
- public void close() {
- IOUtils.closeQuietly(selector);
- IOUtils.closeQuietly(channel);
- if (getSavedPort(applicationKey).orElse(-1).equals(port)) {
- Preferences.userNodeForPackage(Cryptomator.class).remove(applicationKey);
- }
- }
-
- void selectionLoop() {
- try {
- final Set keysToRemove = new HashSet<>();
- while (selector.select() > 0) {
- final Set keys = selector.selectedKeys();
- for (SelectionKey key : keys) {
- if (Thread.interrupted()) {
- return;
- }
- try {
- handleSelection(key);
- } catch (IOException | IllegalStateException e) {
- LOG.error("exception in selector", e);
- } finally {
- keysToRemove.add(key);
- }
- }
- keys.removeAll(keysToRemove);
- }
- } catch (ClosedSelectorException e) {
- return;
- } catch (Exception e) {
- LOG.error("error while selecting", e);
- }
- }
- }
-
- /**
- * Checks if there is a valid port at
- * {@link Preferences#userNodeForPackage(Class)} for {@link Cryptomator} under the
- * given applicationKey, tries to connect to the port at the loopback
- * address and checks if the port identifies with the applicationKey.
- *
- * @param applicationKey
- * key used to load the port and check the identity of the
- * connection.
- * @return
- */
- public static Optional getRemoteInstance(String applicationKey) {
- Optional port = getSavedPort(applicationKey);
-
- if (!port.isPresent()) {
- return Optional.empty();
- }
-
- SocketChannel channel = null;
- boolean close = true;
- try {
- channel = SocketChannel.open();
- channel.configureBlocking(false);
- LOG.debug("connecting to instance {}", port.get());
- channel.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), port.get()));
-
- SocketChannel fChannel = channel;
- if (!TimeoutTask.attempt(t -> fChannel.finishConnect(), 1000, 10)) {
- return Optional.empty();
- }
-
- LOG.debug("connected to instance {}", port.get());
-
- final byte[] bytes = applicationKey.getBytes(StandardCharsets.UTF_8);
- ByteBuffer buf = ByteBuffer.allocate(bytes.length);
- tryFill(channel, buf, 1000);
- if (buf.hasRemaining()) {
- return Optional.empty();
- }
-
- buf.flip();
-
- for (int i = 0; i < bytes.length; i++) {
- if (buf.get() != bytes[i]) {
- return Optional.empty();
- }
- }
-
- close = false;
- return Optional.of(new RemoteInstance(channel));
- } catch (Exception e) {
- return Optional.empty();
- } finally {
- if (close) {
- IOUtils.closeQuietly(channel);
- }
- }
- }
-
- static Optional getSavedPort(String applicationKey) {
- int port = Preferences.userNodeForPackage(Cryptomator.class).getInt(applicationKey, -1);
-
- if (port == -1) {
- LOG.info("no running instance found");
- return Optional.empty();
- }
-
- return Optional.of(port);
- }
-
- /**
- * Creates a server socket on a free port and saves the port in
- * {@link Preferences#userNodeForPackage(Class)} for {@link Cryptomator} under the
- * given applicationKey.
- *
- * @param applicationKey
- * key used to save the port and identify upon connection.
- * @param exec
- * the task which is submitted is interruptable.
- * @return
- * @throws IOException
- */
- public static LocalInstance startLocalInstance(String applicationKey, ExecutorService exec) throws IOException {
- final ServerSocketChannel channel = ServerSocketChannel.open();
- boolean success = false;
- try {
- channel.configureBlocking(false);
- channel.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
-
- final int port = ((InetSocketAddress) channel.getLocalAddress()).getPort();
- Preferences.userNodeForPackage(Cryptomator.class).putInt(applicationKey, port);
- LOG.debug("InstanceManager bound to port {}", port);
-
- Selector selector = Selector.open();
- channel.register(selector, SelectionKey.OP_ACCEPT);
- LocalInstance instance = new LocalInstance(applicationKey, channel, selector, port);
- exec.submit(instance::selectionLoop);
-
- success = true;
- return instance;
- } finally {
- if (!success) {
- channel.close();
- }
- }
- }
-
- /**
- * tries to fill the given buffer for the given time
- *
- * @param channel
- * @param buf
- * @param timeout
- * @throws ClosedChannelException
- * @throws IOException
- */
- public static void tryFill(T channel, final ByteBuffer buf, int timeout) throws IOException {
- if (channel.isBlocking()) {
- throw new IllegalStateException("Channel is in blocking mode.");
- }
-
- try (Selector selector = Selector.open()) {
- channel.register(selector, SelectionKey.OP_READ);
-
- TimeoutTask.attempt(remainingTime -> {
- if (!buf.hasRemaining()) {
- return true;
- }
- if (selector.select(remainingTime) > 0) {
- if (channel.read(buf) < 0) {
- return true;
- }
- }
- return !buf.hasRemaining();
- }, timeout, 1);
- }
- }
-}
\ No newline at end of file
diff --git a/main/ui/src/test/java/org/cryptomator/ui/MainApplicationTest.java b/main/ui/src/test/java/org/cryptomator/ui/MainApplicationTest.java
deleted file mode 100644
index 58749a4bf..000000000
--- a/main/ui/src/test/java/org/cryptomator/ui/MainApplicationTest.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2016 Sebastian Stenzel and others.
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- * Sebastian Stenzel - initial API and implementation
- *******************************************************************************/
-package org.cryptomator.ui;
-
-import org.junit.Test;
-
-public class MainApplicationTest {
-
- @Test
- public void testInjection() throws Exception {
- new MainApplication();
- }
-
-}
diff --git a/main/ui/src/test/java/org/cryptomator/ui/util/SingleInstanceManagerTest.java b/main/ui/src/test/java/org/cryptomator/ui/util/SingleInstanceManagerTest.java
deleted file mode 100644
index 7ffe072a4..000000000
--- a/main/ui/src/test/java/org/cryptomator/ui/util/SingleInstanceManagerTest.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2014, 2016 cryptomator.org
- * This file is licensed under the terms of the MIT license.
- * See the LICENSE.txt file for more info.
- *
- * Contributors:
- * Tillmann Gaida - initial implementation
- ******************************************************************************/
-package org.cryptomator.ui.util;
-
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.*;
-
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.ServerSocket;
-import java.nio.ByteBuffer;
-import java.nio.channels.SocketChannel;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Optional;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentSkipListSet;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ForkJoinTask;
-import java.util.concurrent.TimeUnit;
-
-import org.cryptomator.ui.util.SingleInstanceManager.LocalInstance;
-import org.cryptomator.ui.util.SingleInstanceManager.MessageListener;
-import org.cryptomator.ui.util.SingleInstanceManager.RemoteInstance;
-import org.junit.Test;
-
-public class SingleInstanceManagerTest {
- @Test(timeout = 10000)
- public void testTryFillTimeout() throws Exception {
- try (final ServerSocket socket = new ServerSocket(0)) {
- // we need to asynchronously accept the connection
- final ForkJoinTask> forked = ForkJoinTask.adapt(() -> {
- try {
- socket.setSoTimeout(1000);
-
- socket.accept();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }).fork();
-
- try (SocketChannel channel = SocketChannel.open()) {
- channel.configureBlocking(false);
- channel.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), socket.getLocalPort()));
- TimeoutTask.attempt(t -> channel.finishConnect(), 1000, 1);
- final ByteBuffer buffer = ByteBuffer.allocate(1);
- SingleInstanceManager.tryFill(channel, buffer, 1000);
- assertTrue(buffer.hasRemaining());
- }
-
- forked.join();
- }
- }
-
- @Test(timeout = 10000)
- public void testTryFill() throws Exception {
- try (final ServerSocket socket = new ServerSocket(0)) {
- // we need to asynchronously accept the connection
- final ForkJoinTask> forked = ForkJoinTask.adapt(() -> {
- try {
- socket.setSoTimeout(1000);
-
- socket.accept().getOutputStream().write(1);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }).fork();
-
- try (SocketChannel channel = SocketChannel.open()) {
- channel.configureBlocking(false);
- channel.connect(new InetSocketAddress(InetAddress.getLoopbackAddress(), socket.getLocalPort()));
- TimeoutTask.attempt(t -> channel.finishConnect(), 1000, 1);
- final ByteBuffer buffer = ByteBuffer.allocate(1);
- SingleInstanceManager.tryFill(channel, buffer, 1000);
- assertFalse(buffer.hasRemaining());
- }
-
- forked.join();
- }
- }
-
- String appKey = "APPKEY";
-
- @Test
- public void testOneMessage() throws Exception {
- ExecutorService exec = Executors.newCachedThreadPool();
-
- try {
- final LocalInstance server = SingleInstanceManager.startLocalInstance(appKey, exec);
- final Optional r = SingleInstanceManager.getRemoteInstance(appKey);
-
- CountDownLatch latch = new CountDownLatch(1);
-
- final MessageListener listener = spy(new MessageListener() {
- @Override
- public void handleMessage(String message) {
- latch.countDown();
- }
- });
- server.registerListener(listener);
-
- assertTrue(r.isPresent());
-
- String message = "Is this thing on?";
- assertTrue(r.get().sendMessage(message, 1000));
- System.out.println("wrote message");
-
- latch.await(10, TimeUnit.SECONDS);
-
- verify(listener).handleMessage(message);
- } finally {
- exec.shutdownNow();
- }
- }
-
- @Test(timeout = 60000)
- public void testALotOfMessages() throws Exception {
- final int connectors = 256;
- final int messagesPerConnector = 256;
-
- ExecutorService exec = Executors.newSingleThreadExecutor();
- ExecutorService exec2 = Executors.newFixedThreadPool(16);
-
- try (final LocalInstance server = SingleInstanceManager.startLocalInstance(appKey, exec)) {
-
- Set sentMessages = new ConcurrentSkipListSet<>();
- Set receivedMessages = new HashSet<>();
-
- CountDownLatch sendLatch = new CountDownLatch(connectors);
- CountDownLatch receiveLatch = new CountDownLatch(connectors * messagesPerConnector);
-
- server.registerListener(message -> {
- receivedMessages.add(message);
- receiveLatch.countDown();
- });
-
- Set instances = Collections.synchronizedSet(new HashSet<>());
-
- for (int i = 0; i < connectors; i++) {
- exec2.submit(() -> {
- try {
- final Optional r = SingleInstanceManager.getRemoteInstance(appKey);
- assertTrue(r.isPresent());
- instances.add(r.get());
-
- for (int j = 0; j < messagesPerConnector; j++) {
- exec2.submit(() -> {
- try {
- for (;;) {
- final String message = UUID.randomUUID().toString();
- if (!sentMessages.add(message)) {
- continue;
- }
- r.get().sendMessage(message, 1000);
- break;
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- });
- }
-
- sendLatch.countDown();
- } catch (Throwable e) {
- e.printStackTrace();
- }
- });
- }
-
- assertTrue(sendLatch.await(1, TimeUnit.MINUTES));
-
- exec2.shutdown();
- assertTrue(exec2.awaitTermination(1, TimeUnit.MINUTES));
-
- assertTrue(receiveLatch.await(1, TimeUnit.MINUTES));
-
- assertEquals(sentMessages, receivedMessages);
-
- for (RemoteInstance remoteInstance : instances) {
- try {
- remoteInstance.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- } finally {
- exec.shutdownNow();
- exec2.shutdownNow();
- }
- }
-}