diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index eb6e630bd..90cd3c60e 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -29,5 +29,8 @@
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 000000000..bca3878d6
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/main/commons/src/main/java/org/cryptomator/common/Environment.java b/main/commons/src/main/java/org/cryptomator/common/Environment.java
index 6b582d7f0..b45b743f2 100644
--- a/main/commons/src/main/java/org/cryptomator/common/Environment.java
+++ b/main/commons/src/main/java/org/cryptomator/common/Environment.java
@@ -28,7 +28,7 @@ public class Environment {
public Environment() {
LOG.debug("cryptomator.settingsPath: {}", System.getProperty("cryptomator.settingsPath"));
LOG.debug("cryptomator.ipcPortPath: {}", System.getProperty("cryptomator.ipcPortPath"));
- LOG.debug("cryptomator.keychainPath: {}", System.getProperty("cryptomator.ipcPortPath"));
+ LOG.debug("cryptomator.keychainPath: {}", System.getProperty("cryptomator.keychainPath"));
}
public Stream getSettingsPath() {
diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/Cryptomator.java b/main/launcher/src/main/java/org/cryptomator/launcher/Cryptomator.java
index 11a49112d..12d1e5a65 100644
--- a/main/launcher/src/main/java/org/cryptomator/launcher/Cryptomator.java
+++ b/main/launcher/src/main/java/org/cryptomator/launcher/Cryptomator.java
@@ -13,21 +13,34 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Arrays;
-import java.util.Optional;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
public class Cryptomator {
private static final Logger LOG = LoggerFactory.getLogger(Cryptomator.class);
- private static final CryptomatorComponent CRYPTOMATOR_COMPONENT = DaggerCryptomatorComponent.create();
- private static final Path DEFAULT_IPC_PATH = Paths.get(".ipcPort.tmp");
+ private static final CryptomatorComponent CRYPTOMATOR_COMPONENT = DaggerCryptomatorComponent.create(); // DaggerCryptomatorComponent gets generated by Dagger. Run Maven and include target/generated-sources/annotations in your IDE.
- // We need a separate FX Application class.
- // If org.cryptomator.launcher.Cryptomator simply extended Application, the module system magically kicks in and throws exceptions
+ public static void main(String[] args) {
+ LOG.info("Starting Cryptomator {} on {} {} ({})", CRYPTOMATOR_COMPONENT.applicationVersion().orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
+
+ try (IpcFactory.IpcEndpoint endpoint = CRYPTOMATOR_COMPONENT.ipcFactory().create()) {
+ endpoint.getRemote().handleLaunchArgs(args); // if we are the server, getRemote() returns self.
+ if (endpoint.isConnectedToRemote()) {
+ LOG.info("Found running application instance. Shutting down.");
+ } else {
+ CleanShutdownPerformer.registerShutdownHook();
+ Application.launch(MainApp.class, args);
+ }
+ } catch (IOException e) {
+ LOG.error("Failed to initiate inter-process communication.", e);
+ System.exit(2);
+ } catch (Throwable e) {
+ LOG.error("Error during startup", e);
+ System.exit(1);
+ }
+ System.exit(0); // end remaining non-daemon threads.
+ }
+
+ // We need a separate FX Application class, until we can use the module system. See https://stackoverflow.com/q/54756176/4014509
public static class MainApp extends Application {
private Stage primaryStage;
@@ -60,45 +73,4 @@ public class Cryptomator {
}
- public static void main(String[] args) {
- LOG.info("Starting Cryptomator {} on {} {} ({})", CRYPTOMATOR_COMPONENT.applicationVersion().orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
-
- FileOpenRequestHandler fileOpenRequestHandler = CRYPTOMATOR_COMPONENT.fileOpenRequestHanlder();
- Path ipcPortPath = CRYPTOMATOR_COMPONENT.environment().getIpcPortPath().findFirst().orElse(DEFAULT_IPC_PATH);
- try (InterProcessCommunicator communicator = InterProcessCommunicator.start(ipcPortPath, new IpcProtocolImpl(fileOpenRequestHandler))) {
- if (communicator.isServer()) {
- fileOpenRequestHandler.handleLaunchArgs(args);
- CleanShutdownPerformer.registerShutdownHook();
- Application.launch(MainApp.class, args);
- } else {
- communicator.handleLaunchArgs(args);
- LOG.info("Found running application instance. Shutting down.");
- }
- System.exit(0); // end remaining non-daemon threads.
- } catch (IOException e) {
- LOG.error("Failed to initiate inter-process communication.", e);
- System.exit(2);
- } catch (Throwable e) {
- LOG.error("Error during startup", e);
- System.exit(1);
- }
- }
-
- 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/CryptomatorComponent.java b/main/launcher/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java
index 6cd57024e..eaedf7585 100644
--- a/main/launcher/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java
+++ b/main/launcher/src/main/java/org/cryptomator/launcher/CryptomatorComponent.java
@@ -6,17 +6,13 @@ import org.cryptomator.common.Environment;
import javax.inject.Named;
import javax.inject.Singleton;
-import java.nio.file.Path;
import java.util.Optional;
-import java.util.concurrent.BlockingQueue;
@Singleton
@Component(modules = {CryptomatorModule.class, CommonsModule.class})
public interface CryptomatorComponent {
- Environment environment();
-
- FileOpenRequestHandler fileOpenRequestHanlder();
+ IpcFactory ipcFactory();
@Named("applicationVersion")
Optional applicationVersion();
diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/InterProcessCommunicator.java b/main/launcher/src/main/java/org/cryptomator/launcher/InterProcessCommunicator.java
deleted file mode 100644
index 9363b6246..000000000
--- a/main/launcher/src/main/java/org/cryptomator/launcher/InterProcessCommunicator.java
+++ /dev/null
@@ -1,252 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the accompanying LICENSE file.
- *******************************************************************************/
-package org.cryptomator.launcher;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.SocketException;
-import java.net.UnknownHostException;
-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.ConnectIOException;
-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;
-
-import com.google.common.io.MoreFiles;
-
-/**
- * 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 portFilePath Path to a file containing the IPC port
- * @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(Path portFilePath, InterProcessCommunicationProtocol endpoint) throws IOException {
- System.setProperty("java.rmi.server.hostname", "localhost");
- try {
- // try to connect to existing server:
- ClientCommunicator client = new ClientCommunicator(portFilePath);
- LOG.trace("Connected to running process.");
- return client;
- } catch (ConnectException | ConnectIOException | NotBoundException e) {
- LOG.debug("Could not connect to running process.");
- // continue
- }
-
- // spawn a new server:
- LOG.trace("Spawning new server...");
- ServerCommunicator server = new ServerCommunicator(endpoint, portFilePath);
- LOG.debug("Server listening on port {}.", server.getPort());
- return server;
- }
-
- public static class ClientCommunicator extends InterProcessCommunicator {
-
- private final IpcProtocolRemote remote;
-
- private ClientCommunicator(Path portFilePath) throws ConnectException, NotBoundException, RemoteException {
- if (Files.notExists(portFilePath)) {
- throw new ConnectException("No IPC port file.");
- }
- try {
- int port = ClientCommunicator.readPort(portFilePath);
- LOG.debug("Connecting to port {}...", port);
- Registry registry = LocateRegistry.getRegistry("localhost", port, new ClientSocketFactory());
- this.remote = (IpcProtocolRemote) registry.lookup(RMI_NAME);
- } catch (IOException e) {
- throw new ConnectException("Error reading IPC port file.");
- }
- }
-
- private static int readPort(Path portFilePath) throws IOException {
- ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
- try (ReadableByteChannel ch = Files.newByteChannel(portFilePath, StandardOpenOption.READ)) {
- if (ch.read(buf) == Integer.BYTES) {
- buf.flip();
- return buf.getInt();
- } else {
- throw new IOException("Invalid IPC port file.");
- }
- }
- }
-
- @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() {
- // no-op
- }
-
- }
-
- public static class ServerCommunicator extends InterProcessCommunicator {
-
- private final ServerSocket socket;
- private final Registry registry;
- private final IpcProtocolRemoteImpl remote;
- private final Path portFilePath;
-
- private ServerCommunicator(InterProcessCommunicationProtocol delegate, Path portFilePath) throws IOException {
- this.socket = new ServerSocket(0, Byte.MAX_VALUE, InetAddress.getByName("localhost"));
- 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);
- this.portFilePath = portFilePath;
- ServerCommunicator.writePort(portFilePath, socket.getLocalPort());
- }
-
- private static void writePort(Path portFilePath, int port) throws IOException {
- ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
- buf.putInt(port);
- buf.flip();
- MoreFiles.createParentDirectories(portFilePath);
- try (WritableByteChannel ch = Files.newByteChannel(portFilePath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
- if (ch.write(buf) != Integer.BYTES) {
- throw new IOException("Did not write expected number of bytes.");
- }
- }
- }
-
- @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() {
- try {
- registry.unbind(RMI_NAME);
- UnicastRemoteObject.unexportObject(remote, true);
- socket.close();
- Files.deleteIfExists(portFilePath);
- LOG.debug("Server shut down.");
- } catch (NotBoundException | IOException e) {
- LOG.warn("Failed to close IPC Server.", e);
- }
- }
-
- }
-
- 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;
- }
-
- }
-
- /**
- * Creates client sockets with short timeouts.
- */
- private static class ClientSocketFactory implements RMIClientSocketFactory {
-
- @Override
- public Socket createSocket(String host, int port) throws IOException {
- return new SocketWithFixedTimeout(host, port, 1000);
- }
-
- }
-
- private static class SocketWithFixedTimeout extends Socket {
-
- public SocketWithFixedTimeout(String host, int port, int timeoutInMs) throws UnknownHostException, IOException {
- super(host, port);
- super.setSoTimeout(timeoutInMs);
- }
-
- @Override
- public synchronized void setSoTimeout(int timeout) throws SocketException {
- // do nothing, timeout is fixed
- }
-
- }
-
-}
diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/IpcFactory.java b/main/launcher/src/main/java/org/cryptomator/launcher/IpcFactory.java
new file mode 100644
index 000000000..112050d26
--- /dev/null
+++ b/main/launcher/src/main/java/org/cryptomator/launcher/IpcFactory.java
@@ -0,0 +1,258 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the accompanying LICENSE file.
+ *******************************************************************************/
+package org.cryptomator.launcher;
+
+import com.google.common.io.MoreFiles;
+import org.cryptomator.common.Environment;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+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.StandardOpenOption;
+import java.rmi.NotBoundException;
+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 java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * First running application on a machine opens a server socket. Further processes will connect as clients.
+ */
+@Singleton
+class IpcFactory {
+
+ private static final Logger LOG = LoggerFactory.getLogger(IpcFactory.class);
+ private static final String RMI_NAME = "Cryptomator";
+
+ private final List portFilePaths;
+ private final IpcProtocolImpl ipcHandler;
+
+ @Inject
+ public IpcFactory(Environment env, IpcProtocolImpl ipcHandler) {
+ this.portFilePaths = env.getIpcPortPath().collect(Collectors.toUnmodifiableList());
+ this.ipcHandler = ipcHandler;
+ }
+
+ public IpcEndpoint create() {
+ if (portFilePaths.isEmpty()) {
+ LOG.warn("No IPC port file path specified.");
+ return new SelfEndpoint(ipcHandler);
+ } else {
+ System.setProperty("java.rmi.server.hostname", "localhost");
+ return attemptClientConnection().or(this::createServerEndpoint).orElseGet(() -> new SelfEndpoint(ipcHandler));
+ }
+ }
+
+ private Optional attemptClientConnection() {
+ for (Path portFilePath : portFilePaths) {
+ try {
+ int port = readPort(portFilePath);
+ LOG.debug("[Client] Connecting to port {}...", port);
+ Registry registry = LocateRegistry.getRegistry("localhost", port, new ClientSocketFactory());
+ IpcProtocol remoteInterface = (IpcProtocol) registry.lookup(RMI_NAME);
+ return Optional.of(new ClientEndpoint(remoteInterface));
+ } catch (NotBoundException | IOException e) {
+ LOG.debug("[Client] Failed to connect.");
+ // continue with next portFilePath...
+ }
+ }
+ return Optional.empty();
+ }
+
+ private int readPort(Path portFilePath) throws IOException {
+ try (ReadableByteChannel ch = Files.newByteChannel(portFilePath, StandardOpenOption.READ)) {
+ LOG.debug("[Client] Reading IPC port from {}", portFilePath);
+ ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
+ if (ch.read(buf) == Integer.BYTES) {
+ buf.flip();
+ return buf.getInt();
+ } else {
+ throw new IOException("Invalid IPC port file.");
+ }
+ }
+ }
+
+ private Optional createServerEndpoint() {
+ assert !portFilePaths.isEmpty();
+ Path portFilePath = portFilePaths.get(0);
+ try {
+ ServerSocket socket = new ServerSocket(0, Byte.MAX_VALUE, InetAddress.getByName("localhost"));
+ RMIClientSocketFactory csf = RMISocketFactory.getDefaultSocketFactory();
+ SingletonServerSocketFactory ssf = new SingletonServerSocketFactory(socket);
+ Registry registry = LocateRegistry.createRegistry(0, csf, ssf);
+ UnicastRemoteObject.exportObject(ipcHandler, 0);
+ registry.rebind(RMI_NAME, ipcHandler);
+ writePort(portFilePath, socket.getLocalPort());
+ return Optional.of(new ServerEndpoint(ipcHandler, socket, registry, portFilePath));
+ } catch (IOException e) {
+ LOG.warn("[Server] Failed to create IPC server.", e);
+ return Optional.empty();
+ }
+ }
+
+ private void writePort(Path portFilePath, int port) throws IOException {
+ ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
+ buf.putInt(port);
+ buf.flip();
+ MoreFiles.createParentDirectories(portFilePath);
+ try (WritableByteChannel ch = Files.newByteChannel(portFilePath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
+ if (ch.write(buf) != Integer.BYTES) {
+ throw new IOException("Did not write expected number of bytes.");
+ }
+ }
+ LOG.debug("[Server] Wrote IPC port {} to {}", port, portFilePath);
+ }
+
+ interface IpcEndpoint extends Closeable {
+
+ boolean isConnectedToRemote();
+
+ IpcProtocol getRemote();
+
+ }
+
+ static class SelfEndpoint implements IpcEndpoint {
+
+ protected final IpcProtocol remoteObject;
+
+ SelfEndpoint(IpcProtocol remoteObject) {
+ this.remoteObject = remoteObject;
+ }
+
+ @Override
+ public boolean isConnectedToRemote() {
+ return false;
+ }
+
+ @Override
+ public IpcProtocol getRemote() {
+ return remoteObject;
+ }
+
+ @Override
+ public void close() {
+ // no-op
+ }
+ }
+
+ static class ClientEndpoint implements IpcEndpoint {
+
+ private final IpcProtocol remoteInterface;
+
+ public ClientEndpoint(IpcProtocol remoteInterface) {
+ this.remoteInterface = remoteInterface;
+ }
+
+ public IpcProtocol getRemote() {
+ return remoteInterface;
+ }
+
+ @Override
+ public boolean isConnectedToRemote() {
+ return true;
+ }
+
+ @Override
+ public void close() {
+ // no-op
+ }
+
+ }
+
+ class ServerEndpoint extends SelfEndpoint {
+
+ private final ServerSocket socket;
+ private final Registry registry;
+ private final Path portFilePath;
+
+ private ServerEndpoint(IpcProtocol remoteObject, ServerSocket socket, Registry registry, Path portFilePath) {
+ super(remoteObject);
+ this.socket = socket;
+ this.registry = registry;
+ this.portFilePath = portFilePath;
+ }
+
+ @Override
+ public void close() {
+ try {
+ registry.unbind(RMI_NAME);
+ UnicastRemoteObject.unexportObject(remoteObject, true);
+ socket.close();
+ Files.deleteIfExists(portFilePath);
+ LOG.debug("[Server] Shut down");
+ } catch (NotBoundException | IOException e) {
+ LOG.warn("[Server] Error shutting down:", e);
+ }
+ }
+
+ }
+
+ /**
+ * 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;
+ }
+
+ }
+
+ /**
+ * Creates client sockets with short timeouts.
+ */
+ private static class ClientSocketFactory implements RMIClientSocketFactory {
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException {
+ return new SocketWithFixedTimeout(host, port, 1000);
+ }
+
+ }
+
+ private static class SocketWithFixedTimeout extends Socket {
+
+ public SocketWithFixedTimeout(String host, int port, int timeoutInMs) throws UnknownHostException, IOException {
+ super(host, port);
+ super.setSoTimeout(timeoutInMs);
+ }
+
+ @Override
+ public synchronized void setSoTimeout(int timeout) throws SocketException {
+ // do nothing, timeout is fixed
+ }
+
+ }
+
+}
diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/InterProcessCommunicationProtocol.java b/main/launcher/src/main/java/org/cryptomator/launcher/IpcProtocol.java
similarity index 71%
rename from main/launcher/src/main/java/org/cryptomator/launcher/InterProcessCommunicationProtocol.java
rename to main/launcher/src/main/java/org/cryptomator/launcher/IpcProtocol.java
index 1b3cc1fc8..40b4ded51 100644
--- a/main/launcher/src/main/java/org/cryptomator/launcher/InterProcessCommunicationProtocol.java
+++ b/main/launcher/src/main/java/org/cryptomator/launcher/IpcProtocol.java
@@ -5,6 +5,11 @@
*******************************************************************************/
package org.cryptomator.launcher;
-public interface InterProcessCommunicationProtocol {
- void handleLaunchArgs(String[] args);
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+interface IpcProtocol extends Remote {
+
+ void handleLaunchArgs(String[] args) throws RemoteException;
+
}
\ No newline at end of file
diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/IpcProtocolImpl.java b/main/launcher/src/main/java/org/cryptomator/launcher/IpcProtocolImpl.java
new file mode 100644
index 000000000..158ec290d
--- /dev/null
+++ b/main/launcher/src/main/java/org/cryptomator/launcher/IpcProtocolImpl.java
@@ -0,0 +1,28 @@
+package org.cryptomator.launcher;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.util.Arrays;
+
+@Singleton
+class IpcProtocolImpl implements IpcProtocol {
+
+ private static final Logger LOG = LoggerFactory.getLogger(IpcProtocolImpl.class);
+
+ private final FileOpenRequestHandler fileOpenRequestHandler;
+
+ @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/test/java/org/cryptomator/launcher/InterProcessCommunicatorTest.java b/main/launcher/src/test/java/org/cryptomator/launcher/InterProcessCommunicatorTest.java
deleted file mode 100644
index 2b7d553b9..000000000
--- a/main/launcher/src/test/java/org/cryptomator/launcher/InterProcessCommunicatorTest.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the accompanying LICENSE file.
- *******************************************************************************/
-package org.cryptomator.launcher;
-
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.mockito.Mockito;
-
-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.attribute.BasicFileAttributes;
-import java.nio.file.spi.FileSystemProvider;
-import java.util.concurrent.atomic.AtomicInteger;
-
-public class InterProcessCommunicatorTest {
-
- Path portFilePath = Mockito.mock(Path.class);
- Path portFileParentPath = Mockito.mock(Path.class);
- BasicFileAttributes portFileParentPathAttrs = Mockito.mock(BasicFileAttributes.class);
- FileSystem fs = Mockito.mock(FileSystem.class);
- FileSystemProvider provider = Mockito.mock(FileSystemProvider.class);
- SeekableByteChannel portFileChannel = Mockito.mock(SeekableByteChannel.class);
- AtomicInteger port = new AtomicInteger(-1);
-
- @BeforeEach
- public void setup() throws IOException {
- Mockito.when(portFilePath.getFileSystem()).thenReturn(fs);
- Mockito.when(portFilePath.toAbsolutePath()).thenReturn(portFilePath);
- Mockito.when(portFilePath.normalize()).thenReturn(portFilePath);
- Mockito.when(portFilePath.getParent()).thenReturn(portFileParentPath);
- Mockito.when(portFileParentPath.getFileSystem()).thenReturn(fs);
- Mockito.when(fs.provider()).thenReturn(provider);
- Mockito.when(provider.readAttributes(portFileParentPath, BasicFileAttributes.class)).thenReturn(portFileParentPathAttrs);
- Mockito.when(portFileParentPathAttrs.isDirectory()).thenReturn(false, true); // Guava's MoreFiles will check if dir exists before attempting to create them.
- 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
- public void testStartWithDummyPort1() throws IOException {
- port.set(0);
- InterProcessCommunicationProtocol protocol = Mockito.mock(InterProcessCommunicationProtocol.class);
- try (InterProcessCommunicator result = InterProcessCommunicator.start(portFilePath, protocol)) {
- Assertions.assertTrue(result.isServer());
- Mockito.verify(provider).createDirectory(portFileParentPath);
- Mockito.verifyZeroInteractions(protocol);
- Assertions.assertThrows(UnsupportedOperationException.class, () -> {
- 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)) {
- Assertions.assertTrue(result.isServer());
- Mockito.verify(provider).createDirectory(portFileParentPath);
- 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)) {
- Assertions.assertTrue(result1.isServer());
- Mockito.verify(provider, Mockito.times(1)).createDirectory(portFileParentPath);
- Mockito.verifyZeroInteractions(protocol);
-
- try (InterProcessCommunicator result2 = InterProcessCommunicator.start(portFilePath, null)) {
- Assertions.assertFalse(result2.isServer());
- Mockito.verify(provider, Mockito.times(1)).createDirectory(portFileParentPath);
- Assertions.assertNotSame(result1, result2);
-
- result2.handleLaunchArgs(new String[] {"foo"});
- Mockito.verify(protocol).handleLaunchArgs(new String[] {"foo"});
- }
- }
- }
-
-}
diff --git a/main/launcher/src/test/java/org/cryptomator/launcher/IpcFactoryTest.java b/main/launcher/src/test/java/org/cryptomator/launcher/IpcFactoryTest.java
new file mode 100644
index 000000000..e4fef5556
--- /dev/null
+++ b/main/launcher/src/test/java/org/cryptomator/launcher/IpcFactoryTest.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the accompanying LICENSE file.
+ *******************************************************************************/
+package org.cryptomator.launcher;
+
+import org.cryptomator.common.Environment;
+import org.cryptomator.launcher.IpcFactory.IpcEndpoint;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.mockito.Mockito;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.stream.Stream;
+
+public class IpcFactoryTest {
+
+ private Environment environment = Mockito.mock(Environment.class);
+ private IpcProtocolImpl protocolHandler = Mockito.mock(IpcProtocolImpl.class);
+
+ @Test
+ @DisplayName("Wihout IPC port files")
+ public void testNoIpcWithoutPortFile() throws IOException {
+ IpcFactory inTest = new IpcFactory(environment, protocolHandler);
+
+ Mockito.when(environment.getIpcPortPath()).thenReturn(Stream.empty());
+ try (IpcEndpoint endpoint1 = inTest.create()) {
+ Assertions.assertEquals(IpcFactory.SelfEndpoint.class, endpoint1.getClass());
+ Assertions.assertFalse(endpoint1.isConnectedToRemote());
+ Assertions.assertSame(protocolHandler, endpoint1.getRemote());
+ try (IpcEndpoint endpoint2 = inTest.create()) {
+ Assertions.assertEquals(IpcFactory.SelfEndpoint.class, endpoint2.getClass());
+ Assertions.assertNotSame(endpoint1, endpoint2);
+ Assertions.assertFalse(endpoint2.isConnectedToRemote());
+ Assertions.assertSame(protocolHandler, endpoint2.getRemote());
+ }
+ }
+ }
+
+ @Test
+ @DisplayName("Start server and client with port shared via file")
+ public void testInterProcessCommunication(@TempDir Path tmpDir) throws IOException {
+ Path portFile = tmpDir.resolve("testPortFile");
+ Mockito.when(environment.getIpcPortPath()).thenReturn(Stream.of(portFile));
+ IpcFactory inTest = new IpcFactory(environment, protocolHandler);
+
+ Assertions.assertFalse(Files.exists(portFile));
+ try (IpcEndpoint endpoint1 = inTest.create()) {
+ Assertions.assertEquals(IpcFactory.ServerEndpoint.class, endpoint1.getClass());
+ Assertions.assertFalse(endpoint1.isConnectedToRemote());
+ Assertions.assertTrue(Files.exists(portFile));
+ Assertions.assertSame(protocolHandler, endpoint1.getRemote());
+ Mockito.verifyZeroInteractions(protocolHandler);
+ try (IpcEndpoint endpoint2 = inTest.create()) {
+ Assertions.assertEquals(IpcFactory.ClientEndpoint.class, endpoint2.getClass());
+ Assertions.assertNotSame(endpoint1, endpoint2);
+ Assertions.assertTrue(endpoint2.isConnectedToRemote());
+ Assertions.assertNotSame(protocolHandler, endpoint2.getRemote());
+ Mockito.verifyZeroInteractions(protocolHandler);
+ endpoint2.getRemote().handleLaunchArgs(new String[] {"foo"});
+ Mockito.verify(protocolHandler).handleLaunchArgs(new String[] {"foo"});
+ }
+ }
+ }
+
+}