mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-08-19 21:56:07 +00:00
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:
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+7
-6
@@ -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"/>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}">
|
||||
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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"});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user