Merge branch 'develop' into feature/improve-health-check

# Conflicts:
#	src/main/java/org/cryptomator/ui/health/HealthCheckTask.java
#	src/main/java/org/cryptomator/ui/health/ReportWriter.java
#	src/main/resources/fxml/health_check_list.fxml
#	src/main/resources/fxml/vault_options.fxml
This commit is contained in:
Armin Schrenk
2021-07-19 16:44:43 +02:00
67 changed files with 1003 additions and 657 deletions
+62
View File
@@ -0,0 +1,62 @@
import org.cryptomator.integrations.autostart.AutoStartProvider;
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
import org.cryptomator.integrations.tray.TrayIntegrationProvider;
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
module org.cryptomator.desktop {
requires org.cryptomator.cryptofs;
requires org.cryptomator.frontend.dokany;
requires org.cryptomator.frontend.fuse;
requires org.cryptomator.frontend.webdav;
requires org.cryptomator.integrations.api;
requires java.rmi;
requires java.desktop;
requires java.net.http;
requires javafx.base;
requires javafx.graphics;
requires javafx.controls;
requires javafx.fxml;
requires com.tobiasdiez.easybind;
requires com.google.common;
requires com.google.gson;
requires com.nulabinc.zxcvbn;
requires org.slf4j;
requires org.apache.commons.lang3;
requires dagger;
requires com.auth0.jwt;
/* TODO: filename-based modules: */
requires static javax.inject; /* ugly dagger/guava crap */
requires logback.classic;
requires logback.core;
uses AutoStartProvider;
uses KeychainAccessProvider;
uses TrayIntegrationProvider;
uses UiAppearanceProvider;
opens org.cryptomator.common.settings to com.google.gson;
opens org.cryptomator.launcher to java.rmi;
opens org.cryptomator.common to javafx.fxml;
opens org.cryptomator.common.vaults to javafx.fxml;
opens org.cryptomator.ui.addvaultwizard to javafx.fxml;
opens org.cryptomator.ui.changepassword to javafx.fxml;
opens org.cryptomator.ui.common to javafx.fxml;
opens org.cryptomator.ui.controls to javafx.fxml;
opens org.cryptomator.ui.forgetPassword to javafx.fxml;
opens org.cryptomator.ui.fxapp to javafx.fxml;
opens org.cryptomator.ui.health to javafx.fxml;
opens org.cryptomator.ui.keyloading.masterkeyfile to javafx.fxml;
opens org.cryptomator.ui.mainwindow to javafx.fxml;
opens org.cryptomator.ui.migration to javafx.fxml;
opens org.cryptomator.ui.preferences to javafx.fxml;
opens org.cryptomator.ui.quit to javafx.fxml;
opens org.cryptomator.ui.recoverykey to javafx.fxml;
opens org.cryptomator.ui.removevault to javafx.fxml;
opens org.cryptomator.ui.stats to javafx.fxml;
opens org.cryptomator.ui.unlock to javafx.fxml;
opens org.cryptomator.ui.vaultoptions to javafx.fxml;
opens org.cryptomator.ui.wrongfilealert to javafx.fxml;
}
@@ -33,7 +33,7 @@ public class Environment {
LOG.debug("user.region: {}", System.getProperty("user.region"));
LOG.debug("logback.configurationFile: {}", System.getProperty("logback.configurationFile"));
LOG.debug("cryptomator.settingsPath: {}", System.getProperty("cryptomator.settingsPath"));
LOG.debug("cryptomator.ipcPortPath: {}", System.getProperty("cryptomator.ipcPortPath"));
LOG.debug("cryptomator.ipcSocketPath: {}", System.getProperty("cryptomator.ipcSocketPath"));
LOG.debug("cryptomator.keychainPath: {}", System.getProperty("cryptomator.keychainPath"));
LOG.debug("cryptomator.logDir: {}", System.getProperty("cryptomator.logDir"));
LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir"));
@@ -51,8 +51,8 @@ public class Environment {
return getPaths("cryptomator.settingsPath");
}
public Stream<Path> getIpcPortPath() {
return getPaths("cryptomator.ipcPortPath");
public Stream<Path> ipcSocketPath() {
return getPaths("cryptomator.ipcSocketPath");
}
public Stream<Path> getKeychainPath() {
@@ -0,0 +1,8 @@
package org.cryptomator.common;
/**
* Replacement for JSR-305 to avoid runtime dependencies. Used in Dagger components.
*/
public @interface Nullable {
}
@@ -43,7 +43,7 @@ public class VaultSettings {
private static final Random RNG = new Random();
private final String id;
private final ObjectProperty<Path> path = new SimpleObjectProperty();
private final ObjectProperty<Path> path = new SimpleObjectProperty<>();
private final StringProperty displayName = new SimpleStringProperty();
private final StringProperty winDriveLetter = new SimpleStringProperty();
private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP);
@@ -7,10 +7,10 @@ package org.cryptomator.common.vaults;
import dagger.BindsInstance;
import dagger.Subcomponent;
import org.cryptomator.common.Nullable;
import org.cryptomator.common.mountpoint.MountPointChooserModule;
import org.cryptomator.common.settings.VaultSettings;
import javax.annotation.Nullable;
import javax.inject.Named;
@PerVault
@@ -8,6 +8,7 @@ package org.cryptomator.common.vaults;
import dagger.Module;
import dagger.Provides;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Nullable;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.settings.VolumeImpl;
@@ -15,7 +16,6 @@ import org.cryptomator.cryptofs.CryptoFileSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import javax.inject.Named;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.StringBinding;
@@ -0,0 +1,65 @@
package org.cryptomator.ipc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.UnixDomainSocketAddress;
import java.nio.channels.SocketChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.concurrent.Executor;
class Client implements IpcCommunicator {
private static final Logger LOG = LoggerFactory.getLogger(Client.class);
private final SocketChannel socketChannel;
private Client(SocketChannel socketChannel) {
this.socketChannel = socketChannel;
}
public static Client create(Path socketPath) throws IOException {
var address = UnixDomainSocketAddress.of(socketPath);
var socketChannel = SocketChannel.open(address);
LOG.info("Connected to IPC server on socket {}", socketPath);
return new Client(socketChannel);
}
@Override
public boolean isClient() {
return true;
}
@Override
public void listen(IpcMessageListener listener, Executor executor) {
executor.execute(() -> {
try {
while (socketChannel.isConnected()) {
var msg = IpcMessage.receive(socketChannel);
listener.handleMessage(msg);
}
} catch (IOException e) {
LOG.error("Failed to read IPC message", e);
}
});
}
@Override
public void send(IpcMessage message, Executor executor) {
executor.execute(() -> {
try {
message.send(socketChannel);
} catch (IOException e) {
LOG.error("Failed to send IPC message", e);
}
});
}
@Override
public void close() throws IOException {
socketChannel.close();
}
}
@@ -0,0 +1,30 @@
package org.cryptomator.ipc;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.List;
record HandleLaunchArgsMessage(List<String> args) implements IpcMessage {
private static final char DELIMITER = '\n';
public static HandleLaunchArgsMessage decode(ByteBuffer encoded) {
var str = StandardCharsets.UTF_8.decode(encoded).toString();
var args = Splitter.on(DELIMITER).omitEmptyStrings().splitToList(str);
return new HandleLaunchArgsMessage(args);
}
@Override
public MessageType getMessageType() {
return MessageType.HANDLE_LAUNCH_ARGS;
}
@Override
public ByteBuffer encodePayload() {
var str = Joiner.on(DELIMITER).join(args);
return StandardCharsets.UTF_8.encode(str);
}
}
@@ -0,0 +1,96 @@
package org.cryptomator.ipc;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.MoreExecutors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
public interface IpcCommunicator extends Closeable {
Logger LOG = LoggerFactory.getLogger(IpcCommunicator.class);
/**
* Attempts to establish a socket connection via one of the given paths.
* <p>
* If no connection to an existing sockets can be established, a new socket is created for the first given path.
* <p>
* If this fails as well, a fallback communicator is returned that allows process-internal communication mocking the API
* that would have been used for IPC.
*
* @param socketPaths The socket path(s)
* @return A communicator object that allows sending and receiving messages
*/
static IpcCommunicator create(Iterable<Path> socketPaths) {
Preconditions.checkArgument(socketPaths.iterator().hasNext(), "socketPaths must contain at least one element");
for (var p : socketPaths) {
try {
var attr = Files.readAttributes(p, BasicFileAttributes.class);
if (attr.isOther()) {
return Client.create(p);
}
} catch (IOException e) {
// attempt next socket path
}
}
// Didn't get any connection yet? I.e. we're the first app instance, so let's launch a server:
try {
return Server.create(socketPaths.iterator().next());
} catch (IOException e) {
LOG.warn("Failed to create IPC server", e);
return new LoopbackCommunicator();
}
}
boolean isClient();
/**
* Listens to incoming messages until the connection gets closed.
* @param listener The listener that should be notified of incoming messages
* @param executor An executor on which to listen. Listening will block, so you might want to use a background thread.
* @return
*/
void listen(IpcMessageListener listener, Executor executor);
/**
* Sends the given message.
*
* @param message The message to send
* @param executor An executor used to send the message. Sending will block, so you might want to use a background thread.
*/
void send(IpcMessage message, Executor executor);
default void sendRevealRunningApp() {
send(new RevealRunningAppMessage(), MoreExecutors.directExecutor());
}
default void sendHandleLaunchargs(List<String> args) {
send(new HandleLaunchArgsMessage(args), MoreExecutors.directExecutor());
}
/**
* Clean up resources.
*
* @implSpec Must be idempotent
* @throws IOException In case of I/O errors.
*/
@Override
void close() throws IOException;
default void closeUnchecked() throws UncheckedIOException {
try {
close();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
@@ -0,0 +1,68 @@
package org.cryptomator.ipc;
import org.cryptomator.cryptolib.common.ByteBuffers;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.function.Function;
// TODO make sealed, remove enum
interface IpcMessage {
enum MessageType {
REVEAL_RUNNING_APP(RevealRunningAppMessage::decode),
HANDLE_LAUNCH_ARGS(HandleLaunchArgsMessage::decode);
private final Function<ByteBuffer, IpcMessage> decoder;
MessageType(Function<ByteBuffer, IpcMessage> decoder) {
this.decoder = decoder;
}
static MessageType forOrdinal(int ordinal) {
try {
return values()[ordinal];
} catch (IndexOutOfBoundsException e) {
throw new IllegalArgumentException("No such message type: " + ordinal, e);
}
}
IpcMessage decodePayload(ByteBuffer payload) {
return decoder.apply(payload);
}
}
MessageType getMessageType();
ByteBuffer encodePayload();
static IpcMessage receive(ReadableByteChannel channel) throws IOException {
var header = ByteBuffer.allocate(2 * Integer.BYTES);
if (ByteBuffers.fill(channel, header) < header.capacity()) {
throw new EOFException();
}
header.flip();
int typeNo = header.getInt();
int length = header.getInt();
MessageType type = MessageType.forOrdinal(typeNo);
var payload = ByteBuffer.allocate(length);
ByteBuffers.fill(channel, payload);
payload.flip();
return type.decodePayload(payload);
}
default void send(WritableByteChannel channel) throws IOException {
var payload = encodePayload();
var buf = ByteBuffer.allocate(2 * Integer.BYTES + payload.remaining());
buf.putInt(getMessageType().ordinal()); // message type
buf.putInt(payload.remaining()); // message length
buf.put(payload); // message
buf.flip();
while (buf.hasRemaining()) {
channel.write(buf);
}
}
}
@@ -0,0 +1,19 @@
package org.cryptomator.ipc;
import java.util.List;
public interface IpcMessageListener {
default void handleMessage(IpcMessage message) {
if (message instanceof RevealRunningAppMessage) {
revealRunningApp();
} else if (message instanceof HandleLaunchArgsMessage m) {
handleLaunchArgs(m.args());
}
}
void revealRunningApp();
void handleLaunchArgs(List<String> args);
}
@@ -0,0 +1,50 @@
package org.cryptomator.ipc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.TransferQueue;
class LoopbackCommunicator implements IpcCommunicator {
private static final Logger LOG = LoggerFactory.getLogger(LoopbackCommunicator.class);
private final TransferQueue<IpcMessage> transferQueue = new LinkedTransferQueue<>();
@Override
public boolean isClient() {
return false;
}
@Override
public void listen(IpcMessageListener listener, Executor executor) {
executor.execute(() -> {
try {
var msg = transferQueue.take();
listener.handleMessage(msg);
} catch (InterruptedException e) {
LOG.error("Failed to read IPC message", e);
Thread.currentThread().interrupt();
}
});
}
@Override
public void send(IpcMessage message, Executor executor) {
executor.execute(() -> {
try {
transferQueue.put(message);
} catch (InterruptedException e) {
LOG.error("Failed to send IPC message", e);
Thread.currentThread().interrupt();
}
});
}
@Override
public void close() {
// no-op
}
}
@@ -0,0 +1,20 @@
package org.cryptomator.ipc;
import java.nio.ByteBuffer;
public record RevealRunningAppMessage() implements IpcMessage {
static RevealRunningAppMessage decode(ByteBuffer ignored) {
return new RevealRunningAppMessage();
}
@Override
public MessageType getMessageType() {
return MessageType.REVEAL_RUNNING_APP;
}
@Override
public ByteBuffer encodePayload() {
return ByteBuffer.allocate(0);
}
}
@@ -0,0 +1,82 @@
package org.cryptomator.ipc;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.EOFException;
import java.io.IOException;
import java.net.StandardProtocolFamily;
import java.net.UnixDomainSocketAddress;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ServerSocketChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.Executor;
class Server implements IpcCommunicator {
private static final Logger LOG = LoggerFactory.getLogger(Server.class);
private final ServerSocketChannel serverSocketChannel;
private final Path socketPath;
private Server(ServerSocketChannel serverSocketChannel, Path socketPath) {
this.serverSocketChannel = serverSocketChannel;
this.socketPath = socketPath;
}
public static Server create(Path socketPath) throws IOException {
var address = UnixDomainSocketAddress.of(socketPath);
var serverSocketChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX);
serverSocketChannel.bind(address);
LOG.info("Spawning IPC server listening on socket {}", socketPath);
return new Server(serverSocketChannel, socketPath);
}
@Override
public boolean isClient() {
return false;
}
@Override
public void listen(IpcMessageListener listener, Executor executor) {
executor.execute(() -> {
while (serverSocketChannel.isOpen()) {
try (var ch = serverSocketChannel.accept()) {
while (ch.isConnected()) {
var msg = IpcMessage.receive(ch);
listener.handleMessage(msg);
}
} catch (AsynchronousCloseException e) {
return; // serverSocketChannel closed or listener interrupted
} catch (EOFException | ClosedChannelException e) {
// continue with next connected client
} catch (IOException e) {
LOG.error("Failed to read IPC message", e);
}
}
});
}
@Override
public void send(IpcMessage message, Executor executor) {
executor.execute(() -> {
try (var ch = serverSocketChannel.accept()) {
message.send(ch);
} catch (IOException e) {
LOG.error("Failed to send IPC message", e);
}
});
}
@Override
public void close() throws IOException {
try {
serverSocketChannel.close();
} finally {
Files.deleteIfExists(socketPath);
LOG.debug("IPC server closed");
}
}
}
@@ -5,7 +5,12 @@
*******************************************************************************/
package org.cryptomator.launcher;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import dagger.Lazy;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Environment;
import org.cryptomator.common.ShutdownHook;
import org.cryptomator.ipc.IpcCommunicator;
import org.cryptomator.logging.DebugMode;
import org.cryptomator.logging.LoggerConfiguration;
import org.cryptomator.ui.launcher.UiLauncher;
@@ -16,8 +21,10 @@ import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
@Singleton
public class Cryptomator {
@@ -29,23 +36,28 @@ public class Cryptomator {
private final LoggerConfiguration logConfig;
private final DebugMode debugMode;
private final IpcFactory ipcFactory;
private final Environment env;
private final Lazy<IpcMessageHandler> ipcMessageHandler;
private final Optional<String> applicationVersion;
private final CountDownLatch shutdownLatch;
private final UiLauncher uiLauncher;
private final ShutdownHook shutdownHook;
private final Lazy<UiLauncher> uiLauncher;
@Inject
Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, IpcFactory ipcFactory, @Named("applicationVersion") Optional<String> applicationVersion, @Named("shutdownLatch") CountDownLatch shutdownLatch, UiLauncher uiLauncher) {
Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, Environment env, Lazy<IpcMessageHandler> ipcMessageHandler, @Named("applicationVersion") Optional<String> applicationVersion, @Named("shutdownLatch") CountDownLatch shutdownLatch, ShutdownHook shutdownHook, Lazy<UiLauncher> uiLauncher) {
this.logConfig = logConfig;
this.debugMode = debugMode;
this.ipcFactory = ipcFactory;
this.env = env;
this.ipcMessageHandler = ipcMessageHandler;
this.applicationVersion = applicationVersion;
this.shutdownLatch = shutdownLatch;
this.shutdownHook = shutdownHook;
this.uiLauncher = uiLauncher;
}
public static void main(String[] args) {
int exitCode = CRYPTOMATOR_COMPONENT.application().run(args);
LOG.info("Exit {}", exitCode);
System.exit(exitCode); // end remaining non-daemon threads.
}
@@ -64,19 +76,24 @@ public class Cryptomator {
* Attempts to create an IPC connection to a running Cryptomator instance and sends it the given args.
* If no external process could be reached, the args will be handled by the loopback IPC endpoint.
*/
try (IpcFactory.IpcEndpoint endpoint = ipcFactory.create()) {
endpoint.getRemote().handleLaunchArgs(args); // if we are the server, getRemote() returns self.
if (endpoint.isConnectedToRemote()) {
endpoint.getRemote().revealRunningApp();
try (var communicator = IpcCommunicator.create(env.ipcSocketPath().toList())) {
if (communicator.isClient()) {
communicator.sendHandleLaunchargs(List.of(args));
communicator.sendRevealRunningApp();
LOG.info("Found running application instance. Shutting down...");
return 2;
} else {
shutdownHook.runOnShutdown(communicator::closeUnchecked);
var executor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("IPC-%d").build());
var msgHandler = ipcMessageHandler.get();
msgHandler.handleLaunchArgs(List.of(args));
communicator.listen(msgHandler, executor);
LOG.debug("Did not find running application instance. Launching GUI...");
return runGuiApplication();
}
} catch (IOException e) {
LOG.error("Failed to initiate inter-process communication.", e);
return runGuiApplication();
} catch (Throwable e) {
LOG.error("Running application failed", e);
return 1;
}
}
@@ -88,7 +105,7 @@ public class Cryptomator {
*/
private int runGuiApplication() {
try {
uiLauncher.launch();
uiLauncher.get().launch();
shutdownLatch.await();
LOG.info("UI shut down");
return 0;
@@ -98,5 +115,4 @@ public class Cryptomator {
}
}
}
@@ -22,6 +22,7 @@ import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.stream.Collectors;
@@ -46,13 +47,13 @@ class FileOpenRequestHandler {
tryToEnqueueFileOpenRequest(launchEvent);
}
public void handleLaunchArgs(String[] args) {
public void handleLaunchArgs(List<String> args) {
handleLaunchArgs(FileSystems.getDefault(), args);
}
// visible for testing
void handleLaunchArgs(FileSystem fs, String[] args) {
Collection<Path> pathsToOpen = Arrays.stream(args).map(str -> {
void handleLaunchArgs(FileSystem fs, List<String> args) {
Collection<Path> pathsToOpen = args.stream().map(str -> {
try {
return fs.getPath(str);
} catch (InvalidPathException e) {
@@ -1,258 +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 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<Path> 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<IpcEndpoint> 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<IpcEndpoint> 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
}
}
}
@@ -1,5 +1,6 @@
package org.cryptomator.launcher;
import org.cryptomator.ipc.IpcMessageListener;
import org.cryptomator.ui.launcher.AppLaunchEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -7,20 +8,20 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.BlockingQueue;
@Singleton
class IpcProtocolImpl implements IpcProtocol {
class IpcMessageHandler implements IpcMessageListener {
private static final Logger LOG = LoggerFactory.getLogger(IpcProtocolImpl.class);
private static final Logger LOG = LoggerFactory.getLogger(IpcMessageHandler.class);
private final FileOpenRequestHandler fileOpenRequestHandler;
private final BlockingQueue<AppLaunchEvent> launchEventQueue;
@Inject
public IpcProtocolImpl(FileOpenRequestHandler fileOpenRequestHandler, @Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue) {
public IpcMessageHandler(FileOpenRequestHandler fileOpenRequestHandler, @Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue) {
this.fileOpenRequestHandler = fileOpenRequestHandler;
this.launchEventQueue = launchEventQueue;
}
@@ -31,8 +32,8 @@ class IpcProtocolImpl implements IpcProtocol {
}
@Override
public void handleLaunchArgs(String... args) {
LOG.debug("Received launch args: {}", Arrays.stream(args).reduce((a, b) -> a + ", " + b).orElse(""));
public void handleLaunchArgs(List<String> args) {
LOG.debug("Received launch args: {}", args.stream().reduce((a, b) -> a + ", " + b).orElse(""));
fileOpenRequestHandler.handleLaunchArgs(args);
}
@@ -1,17 +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.rmi.Remote;
import java.rmi.RemoteException;
interface IpcProtocol extends Remote {
void revealRunningApp() throws RemoteException;
void handleLaunchArgs(String... args) throws RemoteException;
}
@@ -2,8 +2,8 @@ package org.cryptomator.ui.common;
import dagger.BindsInstance;
import dagger.Subcomponent;
import org.cryptomator.common.Nullable;
import javax.annotation.Nullable;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.stage.Stage;
@@ -1,6 +1,7 @@
package org.cryptomator.ui.common;
import javax.annotation.Nullable;
import org.cryptomator.common.Nullable;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.fxml.FXML;
@@ -5,7 +5,6 @@ import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.Map;
import java.util.ResourceBundle;
@@ -26,11 +25,9 @@ public class FxmlLoaderFactory {
/**
* @return A new FXMLLoader instance
*/
public FXMLLoader construct() {
FXMLLoader loader = new FXMLLoader();
loader.setControllerFactory(this::constructController);
loader.setResources(resourceBundle);
return loader;
private FXMLLoader construct(String fxmlResourceName) {
var url = getClass().getResource(fxmlResourceName);
return new FXMLLoader(url, resourceBundle, null, this::constructController);
}
/**
@@ -41,10 +38,8 @@ public class FxmlLoaderFactory {
* @throws IOException if an error occurs while loading the FXML file
*/
public FXMLLoader load(String fxmlResourceName) throws IOException {
FXMLLoader loader = construct();
try (InputStream in = getClass().getResourceAsStream(fxmlResourceName)) {
loader.load(in);
}
FXMLLoader loader = construct(fxmlResourceName);
loader.load();
return loader;
}
@@ -35,9 +35,9 @@ public class Check {
String getLocalizedName() {
try {
return resourceBundle.getString(LOCALIZE_PREFIX+check.identifier());
return resourceBundle.getString(LOCALIZE_PREFIX+check.name());
} catch (MissingResourceException e){
return check.identifier();
return check.name();
}
}
@@ -62,7 +62,7 @@ public class ReportWriter {
var writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))) {
writer.write(REPORT_HEADER.formatted(vaultConfig.getId(), vault.getDisplayName(), vault.getPath()));
for (var check : performedChecks) {
writer.write(REPORT_CHECK_HEADER.formatted(check.getHealthCheck().identifier()));
writer.write(REPORT_CHECK_HEADER.formatted(check.getHealthCheck().name()));
switch (check.getState()) {
case SUCCEEDED -> {
writer.write("STATUS: SUCCESS\nRESULTS:\n");
@@ -19,6 +19,7 @@ import java.awt.Desktop;
import java.awt.EventQueue;
import java.awt.desktop.AboutEvent;
import java.awt.desktop.QuitResponse;
import java.awt.desktop.QuitStrategy;
import java.util.EnumSet;
import java.util.EventObject;
import java.util.Set;
@@ -61,6 +62,11 @@ public class AppLifecycleListener {
Desktop.getDesktop().setQuitHandler(this::handleQuitRequest);
}
// set quit strategy (cmd+q would call `System.exit(0)` otherwise)
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_QUIT_STRATEGY)) {
Desktop.getDesktop().setQuitStrategy(QuitStrategy.CLOSE_ALL_WINDOWS);
}
shutdownHook.runOnShutdown(this::forceUnmountRemainingVaults);
}
@@ -71,7 +77,7 @@ public class AppLifecycleListener {
handleQuitRequest(null, new QuitResponse() {
@Override
public void performQuit() {
System.exit(0);
// no-op
}
@Override
@@ -96,7 +102,7 @@ public class AppLifecycleListener {
public void performQuit() {
Platform.exit(); // will be no-op, if JavaFX never started.
shutdownLatch.countDown(); // main thread is waiting for this latch
EventQueue.invokeLater(originalQuitResponse::performQuit); // this will eventually call System.exit(0)
originalQuitResponse.performQuit();
}
@Override
@@ -23,7 +23,7 @@
<Insets topRightBottomLeft="24"/>
</padding>
<children>
<fx:include fx:id="newPasswordScene" source="/fxml/new_password.fxml"/>
<fx:include fx:id="newPasswordScene" source="new_password.fxml"/>
<Region VBox.vgrow="ALWAYS"/>
@@ -18,7 +18,7 @@
<children>
<Region VBox.vgrow="ALWAYS"/>
<fx:include source="/fxml/recoverykey_display.fxml"/>
<fx:include source="recoverykey_display.fxml"/>
<Region VBox.vgrow="ALWAYS"/>
@@ -21,7 +21,7 @@
<Region VBox.vgrow="ALWAYS"/>
<ImageView VBox.vgrow="ALWAYS" fitHeight="128" preserveRatio="true" smooth="true" cache="true">
<Image url="/img/bot/bot.png"/>
<Image url="@../img/bot/bot.png"/>
</ImageView>
<Region VBox.vgrow="ALWAYS"/>
+1 -1
View File
@@ -25,7 +25,7 @@
<Region prefHeight="12" VBox.vgrow="NEVER"/>
<fx:include fx:id="newPassword" source="/fxml/new_password.fxml"/>
<fx:include fx:id="newPassword" source="new_password.fxml"/>
<CheckBox fx:id="finalConfirmationCheckbox" text="%changepassword.finalConfirmation" wrapText="true"/>
@@ -36,7 +36,7 @@
<VBox minWidth="300" alignment="CENTER" visible="${!controller.anyCheckSelected}" managed="${!controller.anyCheckSelected}" >
<Label text="%health.check.detail.noSelectedCheck" wrapText="true" alignment="CENTER" />
</VBox>
<fx:include source="/fxml/health_check_details.fxml" visible="${controller.anyCheckSelected}" managed="${controller.anyCheckSelected}" HBox.hgrow="ALWAYS"/>
<fx:include source="health_check_details.fxml" visible="${controller.anyCheckSelected}" managed="${controller.anyCheckSelected}" HBox.hgrow="ALWAYS"/>
</StackPane>
</HBox>
<ButtonBar buttonMinWidth="120" buttonOrder="+CX">
+1 -1
View File
@@ -26,7 +26,7 @@
<HBox VBox.vgrow="ALWAYS">
<VBox alignment="CENTER" minWidth="175" maxWidth="175">
<ImageView VBox.vgrow="ALWAYS" fitHeight="128" preserveRatio="true" smooth="true" cache="true">
<Image url="/img/bot/bot.png"/>
<Image url="@../img/bot/bot.png"/>
</ImageView>
</VBox>
<VBox HBox.hgrow="ALWAYS" alignment="CENTER">
+4 -4
View File
@@ -13,11 +13,11 @@
fx:controller="org.cryptomator.ui.mainwindow.MainWindowController"
styleClass="main-window">
<VBox>
<fx:include source="/fxml/main_window_title.fxml" VBox.vgrow="NEVER"/>
<fx:include source="main_window_title.fxml" VBox.vgrow="NEVER"/>
<StackPane VBox.vgrow="ALWAYS">
<SplitPane dividerPositions="0.33" orientation="HORIZONTAL">
<fx:include source="/fxml/vault_list.fxml" SplitPane.resizableWithParent="false"/>
<fx:include source="/fxml/vault_detail.fxml" SplitPane.resizableWithParent="true"/>
<fx:include source="vault_list.fxml" SplitPane.resizableWithParent="false"/>
<fx:include source="vault_detail.fxml" SplitPane.resizableWithParent="true"/>
</SplitPane>
<VBox styleClass="drag-n-drop-indicator" visible="${controller.draggingOver}" alignment="TOP_CENTER">
@@ -33,5 +33,5 @@
</VBox>
</StackPane>
</VBox>
<fx:include source="/fxml/main_window_resize.fxml"/>
<fx:include source="main_window_resize.fxml"/>
</StackPane>
+5 -5
View File
@@ -19,7 +19,7 @@
<FontAwesome5IconView glyph="WRENCH"/>
</graphic>
<content>
<fx:include source="/fxml/preferences_general.fxml"/>
<fx:include source="preferences_general.fxml"/>
</content>
</Tab>
<Tab fx:id="volumeTab" id="VOLUME" text="%preferences.volume">
@@ -27,7 +27,7 @@
<FontAwesome5IconView glyph="HDD"/>
</graphic>
<content>
<fx:include source="/fxml/preferences_volume.fxml"/>
<fx:include source="preferences_volume.fxml"/>
</content>
</Tab>
<Tab fx:id="updatesTab" id="UPDATES" text="%preferences.updates">
@@ -35,7 +35,7 @@
<FontAwesome5IconView glyph="SYNC"/>
</graphic>
<content>
<fx:include source="/fxml/preferences_updates.fxml"/>
<fx:include source="preferences_updates.fxml"/>
</content>
</Tab>
<Tab fx:id="contributeTab" id="CONTRIBUTE" text="%preferences.contribute">
@@ -43,7 +43,7 @@
<FontAwesome5IconView glyph="HEART"/>
</graphic>
<content>
<fx:include source="/fxml/preferences_contribute.fxml"/>
<fx:include source="preferences_contribute.fxml"/>
</content>
</Tab>
<Tab fx:id="aboutTab" id="ABOUT" text="%preferences.about">
@@ -51,7 +51,7 @@
<FontAwesome5IconView glyph="INFO_CIRCLE"/>
</graphic>
<content>
<fx:include source="/fxml/preferences_about.fxml"/>
<fx:include source="preferences_about.fxml"/>
</content>
</Tab>
</tabs>
@@ -18,7 +18,7 @@
<children>
<HBox spacing="12" VBox.vgrow="NEVER">
<ImageView VBox.vgrow="ALWAYS" fitHeight="64" preserveRatio="true" smooth="true" cache="true">
<Image url="/img/bot/bot.png"/>
<Image url="@../img/bot/bot.png"/>
</ImageView>
<VBox spacing="3" HBox.hgrow="ALWAYS" alignment="CENTER_LEFT">
<FormattedLabel styleClass="label-large" format="Cryptomator %s" arg1="${controller.applicationVersion}"/>
@@ -17,7 +17,7 @@
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<fx:include fx:id="newPassword" source="/fxml/new_password.fxml"/>
<fx:include fx:id="newPassword" source="new_password.fxml"/>
<Region VBox.vgrow="ALWAYS"/>
@@ -17,7 +17,7 @@
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<fx:include source="/fxml/recoverykey_display.fxml"/>
<fx:include source="recoverykey_display.fxml"/>
<Region VBox.vgrow="ALWAYS"/>
@@ -26,23 +26,23 @@
<HBox spacing="12" VBox.vgrow="ALWAYS">
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
<ImageView VBox.vgrow="ALWAYS" fitWidth="64" preserveRatio="true" smooth="true" cache="true" fx:id="face" visible="false">
<Image url="/img/bot/face.png"/>
<Image url="@../img/bot/face.png"/>
</ImageView>
<ImageView VBox.vgrow="ALWAYS" fitWidth="64" preserveRatio="true" smooth="true" cache="true" fx:id="leftArm" visible="false">
<Image url="/img/bot/arm-l.png"/>
<Image url="@../img/bot/arm-l.png"/>
</ImageView>
<ImageView VBox.vgrow="ALWAYS" fitWidth="64" preserveRatio="true" smooth="true" cache="true" fx:id="rightArm" visible="false">
<Image url="/img/bot/arm-r.png"/>
<Image url="@../img/bot/arm-r.png"/>
</ImageView>
<ImageView VBox.vgrow="ALWAYS" fitWidth="64" preserveRatio="true" smooth="true" cache="true" fx:id="legs" visible="false">
<Image url="/img/bot/legs.png"/>
<Image url="@../img/bot/legs.png"/>
</ImageView>
<ImageView VBox.vgrow="ALWAYS" fitWidth="64" preserveRatio="true" smooth="true" cache="true" fx:id="body">
<Image url="/img/bot/body.png"/>
<Image url="@../img/bot/body.png"/>
</ImageView>
</StackPane>
<VBox spacing="6" HBox.hgrow="ALWAYS">
@@ -11,7 +11,7 @@
spacing="24">
<children>
<ImageView VBox.vgrow="ALWAYS" fitHeight="128" preserveRatio="true" smooth="true" cache="true">
<Image url="/img/bot/bot.png"/>
<Image url="@../img/bot/bot.png"/>
</ImageView>
<TextFlow styleClass="text-flow" prefWidth="-Infinity" visible="${controller.noVaultPresent}" managed="${controller.noVaultPresent}">
+3 -3
View File
@@ -17,7 +17,7 @@
<FontAwesome5IconView glyph="WRENCH"/>
</graphic>
<content>
<fx:include source="/fxml/vault_options_general.fxml"/>
<fx:include source="vault_options_general.fxml"/>
</content>
</Tab>
<Tab fx:id="mountTab" id="MOUNT" text="%vaultOptions.mount">
@@ -25,7 +25,7 @@
<FontAwesome5IconView glyph="HDD"/>
</graphic>
<content>
<fx:include source="/fxml/vault_options_mount.fxml"/>
<fx:include source="vault_options_mount.fxml"/>
</content>
</Tab>
<Tab fx:id="keyTab" id="KEY" text="%vaultOptions.masterkey">
@@ -33,7 +33,7 @@
<FontAwesome5IconView glyph="KEY"/>
</graphic>
<content>
<fx:include source="/fxml/vault_options_masterkey.fxml"/>
<fx:include source="vault_options_masterkey.fxml"/>
</content>
</Tab>
</tabs>
@@ -1,49 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.shape.Box?>
<?import javafx.scene.Group?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.collections.ObservableList?>
<?import javafx.collections.FXCollections?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.CheckBox?>
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.vaultoptions.HealthVaultOptionsController"
spacing="6">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<Label text="%vaultOptions.health.introduction" wrapText="true"/>
<Label text="%vaultOptions.health.remarks" wrapText="true"/>
<GridPane >
<padding>
<Insets left="6"/>
</padding>
<columnConstraints>
<ColumnConstraints minWidth="20" halignment="LEFT"/>
<ColumnConstraints fillWidth="true"/>
</columnConstraints>
<rowConstraints>
<RowConstraints valignment="TOP"/>
<RowConstraints valignment="TOP"/>
<RowConstraints valignment="TOP"/>
</rowConstraints>
<Label text="1." GridPane.rowIndex="0" GridPane.columnIndex="0" />
<Label text="%vaultOptions.health.remarkSync" wrapText="true" GridPane.rowIndex="0" GridPane.columnIndex="1" />
<Label text="2." GridPane.rowIndex="1" GridPane.columnIndex="0" />
<Label text="%vaultOptions.health.remarkFix" wrapText="true" GridPane.rowIndex="1" GridPane.columnIndex="1" />
<Label text="3." GridPane.rowIndex="2" GridPane.columnIndex="0" />
<Label text="%vaultOptions.health.remarkBackup" wrapText="true" GridPane.rowIndex="2" GridPane.columnIndex="1" />
</GridPane>
<CheckBox text="%vaultOptions.health.affirmation" fx:id="affirmationBox"/>
<Button text="%vaultOptions.general.startBtn" disable="${!affirmationBox.selected}" onAction="#startHealthCheck"/>
</children>
</VBox>
+16 -21
View File
@@ -11,20 +11,16 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see http://www.gnu.org/licenses/.
Cryptomator uses 45 third-party dependencies under the following licenses:
Cryptomator uses 40 third-party dependencies under the following licenses:
Apache License v2.0:
- jffi (com.github.jnr:jffi:1.2.23 - http://github.com/jnr/jffi)
- jnr-a64asm (com.github.jnr:jnr-a64asm:1.0.0 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-a64asm)
- jnr-constants (com.github.jnr:jnr-constants:0.9.15 - http://github.com/jnr/jnr-constants)
- jnr-ffi (com.github.jnr:jnr-ffi:2.1.12 - http://github.com/jnr/jnr-ffi)
- FindBugs-jsr305 (com.google.code.findbugs:jsr305:3.0.2 - http://findbugs.sourceforge.net/)
- Gson (com.google.code.gson:gson:2.8.6 - https://github.com/google/gson/gson)
- Gson (com.google.code.gson:gson:2.8.7 - https://github.com/google/gson/gson)
- Dagger (com.google.dagger:dagger:2.37 - https://github.com/google/dagger)
- error-prone annotations (com.google.errorprone:error_prone_annotations:2.5.1 - http://nexus.sonatype.org/oss-repository-hosting.html/error_prone_parent/error_prone_annotations)
- Guava InternalFutureFailureAccess and InternalFutures (com.google.guava:failureaccess:1.0.1 - https://github.com/google/guava/failureaccess)
- Guava: Google Core Libraries for Java (com.google.guava:guava:30.1.1-jre - https://github.com/google/guava/guava)
- Guava ListenableFuture only (com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava - https://github.com/google/guava/listenablefuture)
- J2ObjC Annotations (com.google.j2objc:j2objc-annotations:1.3 - https://github.com/google/j2objc/)
- Apache Commons CLI (commons-cli:commons-cli:1.4 - http://commons.apache.org/proper/commons-cli/)
- javax.inject (javax.inject:javax.inject:1 - http://code.google.com/p/atinject/)
- Java Native Access (net.java.dev.jna:jna:5.7.0 - https://github.com/java-native-access/jna)
@@ -32,12 +28,12 @@ Cryptomator uses 45 third-party dependencies under the following licenses:
- Apache Commons Lang (org.apache.commons:commons-lang3:3.12.0 - https://commons.apache.org/proper/commons-lang/)
- Apache HttpCore (org.apache.httpcomponents:httpcore:4.4.14 - http://hc.apache.org/httpcomponents-core-ga)
- Jackrabbit WebDAV Library (org.apache.jackrabbit:jackrabbit-webdav:2.21.5 - http://jackrabbit.apache.org/jackrabbit-webdav/)
- Jetty :: Http Utility (org.eclipse.jetty:jetty-http:10.0.3 - https://eclipse.org/jetty/jetty-http)
- Jetty :: IO Utility (org.eclipse.jetty:jetty-io:10.0.3 - https://eclipse.org/jetty/jetty-io)
- Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.3 - https://eclipse.org/jetty/jetty-security)
- Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.3 - https://eclipse.org/jetty/jetty-server)
- Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.3 - https://eclipse.org/jetty/jetty-servlet)
- Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.3 - https://eclipse.org/jetty/jetty-util)
- Jetty :: Http Utility (org.eclipse.jetty:jetty-http:10.0.6 - https://eclipse.org/jetty/jetty-http)
- Jetty :: IO Utility (org.eclipse.jetty:jetty-io:10.0.6 - https://eclipse.org/jetty/jetty-io)
- Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.6 - https://eclipse.org/jetty/jetty-security)
- Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.6 - https://eclipse.org/jetty/jetty-server)
- Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.6 - https://eclipse.org/jetty/jetty-servlet)
- Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.6 - https://eclipse.org/jetty/jetty-util)
- Jetty :: Servlet API and Schemas for JPMS and OSGi (org.eclipse.jetty.toolchain:jetty-servlet-api:4.0.6 - https://eclipse.org/jetty/jetty-servlet-api)
BSD:
- asm (org.ow2.asm:asm:7.1 - http://asm.ow2.org/)
@@ -48,12 +44,12 @@ Cryptomator uses 45 third-party dependencies under the following licenses:
Eclipse Public License - Version 1.0:
- Jetty :: Servlet API and Schemas for JPMS and OSGi (org.eclipse.jetty.toolchain:jetty-servlet-api:4.0.6 - https://eclipse.org/jetty/jetty-servlet-api)
Eclipse Public License - Version 2.0:
- Jetty :: Http Utility (org.eclipse.jetty:jetty-http:10.0.3 - https://eclipse.org/jetty/jetty-http)
- Jetty :: IO Utility (org.eclipse.jetty:jetty-io:10.0.3 - https://eclipse.org/jetty/jetty-io)
- Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.3 - https://eclipse.org/jetty/jetty-security)
- Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.3 - https://eclipse.org/jetty/jetty-server)
- Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.3 - https://eclipse.org/jetty/jetty-servlet)
- Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.3 - https://eclipse.org/jetty/jetty-util)
- Jetty :: Http Utility (org.eclipse.jetty:jetty-http:10.0.6 - https://eclipse.org/jetty/jetty-http)
- Jetty :: IO Utility (org.eclipse.jetty:jetty-io:10.0.6 - https://eclipse.org/jetty/jetty-io)
- Jetty :: Security (org.eclipse.jetty:jetty-security:10.0.6 - https://eclipse.org/jetty/jetty-security)
- Jetty :: Server Core (org.eclipse.jetty:jetty-server:10.0.6 - https://eclipse.org/jetty/jetty-server)
- Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:10.0.6 - https://eclipse.org/jetty/jetty-servlet)
- Jetty :: Utilities (org.eclipse.jetty:jetty-util:10.0.6 - https://eclipse.org/jetty/jetty-util)
Eclipse Public License - v 1.0:
- Logback Classic Module (ch.qos.logback:logback-classic:1.2.3 - http://logback.qos.ch/logback-classic)
- Logback Core Module (ch.qos.logback:logback-core:1.2.3 - http://logback.qos.ch/logback-core)
@@ -74,11 +70,10 @@ Cryptomator uses 45 third-party dependencies under the following licenses:
- Java Native Access (net.java.dev.jna:jna:5.7.0 - https://github.com/java-native-access/jna)
- Java Native Access Platform (net.java.dev.jna:jna-platform:5.7.0 - https://github.com/java-native-access/jna)
MIT License:
- java jwt (com.auth0:java-jwt:3.17.0 - https://github.com/auth0/java-jwt)
- java jwt (com.auth0:java-jwt:3.18.1 - https://github.com/auth0/java-jwt)
- jnr-x86asm (com.github.jnr:jnr-x86asm:1.0.2 - http://github.com/jnr/jnr-x86asm)
- jnr-fuse (com.github.serceman:jnr-fuse:0.5.5 - https://github.com/SerCeMan/jnr-fuse)
- zxcvbn4j (com.nulab-inc:zxcvbn:1.3.0 - https://github.com/nulab/zxcvbn4j)
- Checker Qual (org.checkerframework:checker-qual:3.8.0 - https://checkerframework.org)
- zxcvbn4j (com.nulab-inc:zxcvbn:1.5.2 - https://github.com/nulab/zxcvbn4j)
- SLF4J API Module (org.slf4j:slf4j-api:1.7.31 - http://www.slf4j.org)
The BSD 2-Clause License:
- EasyBind (com.tobiasdiez:easybind:2.2 - https://github.com/tobiasdiez/EasyBind)
+3 -2
View File
@@ -1,12 +1,13 @@
#!/bin/sh
cd $(dirname $0)
java \
-p "mods" \
-cp "libs/*" \
-Dcryptomator.settingsPath="~/.config/Cryptomator/settings.json" \
-Dcryptomator.ipcPortPath="~/.config/Cryptomator/ipcPort.bin" \
-Dcryptomator.ipcSocketPath="~/.config/Cryptomator/ipc.socket" \
-Dcryptomator.logDir="~/.local/share/Cryptomator/logs" \
-Dcryptomator.mountPointsDir="~/.local/share/Cryptomator/mnt" \
-Djdk.gtk.version=2 \
-Xss2m \
-Xmx512m \
org.cryptomator.launcher.Cryptomator
-m org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
+3 -2
View File
@@ -1,11 +1,12 @@
#!/bin/sh
cd $(dirname $0)
java \
-p "mods" \
-cp "libs/*" \
-Dcryptomator.settingsPath="~/Library/Application Support/Cryptomator/settings.json" \
-Dcryptomator.ipcPortPath="~/Library/Application Support/Cryptomator/ipcPort.bin" \
-Dcryptomator.ipcSocketPath="~/Library/Application Support/Cryptomator/ipc.socket" \
-Dcryptomator.logDir="~/Library/Logs/Cryptomator" \
-Dcryptomator.mountPointsDir="/Volumes" \
-Xss20m \
-Xmx512m \
org.cryptomator.launcher.Cryptomator
-m org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
+3 -2
View File
@@ -1,11 +1,12 @@
@echo off
java ^
-p "mods" ^
-cp "libs/*" ^
-Dcryptomator.settingsPath="~/AppData/Roaming/Cryptomator/settings.json" ^
-Dcryptomator.ipcPortPath="~/AppData/Roaming/Cryptomator/ipcPort.bin" ^
-Dcryptomator.ipcSocketPath="~/AppData/Roaming/Cryptomator/ipc.socket" ^
-Dcryptomator.logDir="~/AppData/Roaming/Cryptomator" ^
-Dcryptomator.mountPointsDir="~/Cryptomator" ^
-Dcryptomator.keychainPath="~/AppData/Roaming/Cryptomator/keychain.json" ^
-Xss20m ^
-Xmx512m ^
org.cryptomator.launcher.Cryptomator
-m org.cryptomator.desktop/org.cryptomator.launcher.Cryptomator
@@ -3,7 +3,6 @@ package org.cryptomator.common;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
@@ -14,7 +13,6 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@DisplayName("Environment Variables Test")
public class EnvironmentTest {
@@ -39,14 +37,14 @@ public class EnvironmentTest {
}
@Test
@DisplayName("cryptomator.ipcPortPath=~/.config/Cryptomator/ipcPort.bin:~/.Cryptomator/ipcPort.bin")
public void testIpcPortPath() {
System.setProperty("cryptomator.ipcPortPath", "~/.config/Cryptomator/ipcPort.bin:~/.Cryptomator/ipcPort.bin");
@DisplayName("cryptomator.ipcSocketPath=~/.config/Cryptomator/ipc.socket:~/.Cryptomator/ipc.socket")
public void testIpcSocketPath() {
System.setProperty("cryptomator.ipcSocketPath", "~/.config/Cryptomator/ipc.socket:~/.Cryptomator/ipc.socket");
List<Path> result = env.getIpcPortPath().toList();
List<Path> result = env.ipcSocketPath().toList();
MatcherAssert.assertThat(result, Matchers.hasSize(2));
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/ipcPort.bin"), //
Paths.get("/home/testuser/.Cryptomator/ipcPort.bin")));
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/ipc.socket"), //
Paths.get("/home/testuser/.Cryptomator/ipc.socket")));
}
@Test
@@ -0,0 +1,46 @@
package org.cryptomator.ipc;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;
public class HandleLaunchArgsMessageTest {
@Test
public void testSendAndReceive(@TempDir Path tmpDir) throws IOException {
var message = new HandleLaunchArgsMessage(List.of("hello world", "foo bar"));
var file = tmpDir.resolve("tmp.file");
try (var ch = FileChannel.open(file, StandardOpenOption.CREATE_NEW, StandardOpenOption.READ, StandardOpenOption.WRITE)) {
message.send(ch);
ch.position(0);
if (IpcMessage.receive(ch) instanceof HandleLaunchArgsMessage received) {
Assertions.assertArrayEquals(message.args().toArray(), received.args().toArray());
} else {
Assertions.fail("Received message of unexpected class");
}
}
}
@Test
public void testSendAndReceiveEmpty(@TempDir Path tmpDir) throws IOException {
var message = new HandleLaunchArgsMessage(List.of());
var file = tmpDir.resolve("tmp.file");
try (var ch = FileChannel.open(file, StandardOpenOption.CREATE_NEW, StandardOpenOption.READ, StandardOpenOption.WRITE)) {
message.send(ch);
ch.position(0);
if (IpcMessage.receive(ch) instanceof HandleLaunchArgsMessage received) {
Assertions.assertArrayEquals(message.args().toArray(), received.args().toArray());
} else {
Assertions.fail("Received message of unexpected class");
}
}
}
}
@@ -0,0 +1,44 @@
package org.cryptomator.ipc;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.nio.file.Path;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
public class IpcCommunicatorTest {
@Test
public void testSendAndReceive(@TempDir Path tmpDir) throws IOException, InterruptedException {
var socketPath = tmpDir.resolve("foo.sock");
try (var server = IpcCommunicator.create(List.of(socketPath));
var client = IpcCommunicator.create(List.of(socketPath))) {
Assertions.assertNotSame(server, client);
var cdl = new CountDownLatch(1);
var executor = Executors.newSingleThreadExecutor();
server.listen(new IpcMessageListener() {
@Override
public void revealRunningApp() {
cdl.countDown();
}
@Override
public void handleLaunchArgs(List<String> args) {
}
}, executor);
client.sendRevealRunningApp();
Assertions.assertTimeoutPreemptively(Duration.ofMillis(300), (Executable) cdl::await);
executor.shutdown();
}
}
}
@@ -0,0 +1,37 @@
package org.cryptomator.ipc;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
public class LoopbackCommunicatorTest {
@Test
public void testSendAndReceive() {
try (var communicator = new LoopbackCommunicator()) {
var cdl = new CountDownLatch(1);
var executor = Executors.newSingleThreadExecutor();
communicator.listen(new IpcMessageListener() {
@Override
public void revealRunningApp() {
cdl.countDown();
}
@Override
public void handleLaunchArgs(List<String> args) {
}
}, executor);
communicator.sendRevealRunningApp();
Assertions.assertTimeoutPreemptively(Duration.ofMillis(300), (Executable) cdl::await);
executor.shutdown();
}
}
}
@@ -0,0 +1,30 @@
package org.cryptomator.ipc;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;
public class RevealRunningAppMessageTest {
@Test
public void testSendAndReceive(@TempDir Path tmpDir) throws IOException {
var message = new RevealRunningAppMessage();
var file = tmpDir.resolve("tmp.file");
try (var ch = FileChannel.open(file, StandardOpenOption.CREATE_NEW, StandardOpenOption.READ, StandardOpenOption.WRITE)) {
message.send(ch);
ch.position(0);
if (IpcMessage.receive(ch) instanceof RevealRunningAppMessage received) {
Assertions.assertNotNull(received);
} else {
Assertions.fail("Received message of unexpected class");
}
}
}
}
@@ -21,6 +21,7 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
@@ -38,7 +39,7 @@ public class FileOpenRequestHandlerTest {
@Test
@DisplayName("./cryptomator.exe foo bar")
public void testOpenArgsWithCorrectPaths() {
inTest.handleLaunchArgs(new String[]{"foo", "bar"});
inTest.handleLaunchArgs(List.of("foo", "bar"));
AppLaunchEvent evt = queue.poll();
Assertions.assertNotNull(evt);
@@ -51,7 +52,7 @@ public class FileOpenRequestHandlerTest {
public void testOpenArgsWithIncorrectPaths() {
FileSystem fs = Mockito.mock(FileSystem.class);
Mockito.when(fs.getPath("foo")).thenThrow(new InvalidPathException("foo", "foo is not a path"));
inTest.handleLaunchArgs(fs, new String[]{"foo"});
inTest.handleLaunchArgs(fs, List.of("foo"));
AppLaunchEvent evt = queue.poll();
Assertions.assertNull(evt);
@@ -63,7 +64,7 @@ public class FileOpenRequestHandlerTest {
queue.add(new AppLaunchEvent(AppLaunchEvent.EventType.OPEN_FILE, Collections.emptyList()));
Assumptions.assumeTrue(queue.remainingCapacity() == 0);
inTest.handleLaunchArgs(new String[]{"foo"});
inTest.handleLaunchArgs(List.of("foo"));
}
}
@@ -1,71 +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.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("Without 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"});
}
}
}
}