diff --git a/main/ant-kit/pom.xml b/main/ant-kit/pom.xml
index be78f8f92..e14674f85 100644
--- a/main/ant-kit/pom.xml
+++ b/main/ant-kit/pom.xml
@@ -8,7 +8,7 @@
org.cryptomator
main
- 1.3.0-rc6
+ 1.3.0-rc7
ant-kit
pom
diff --git a/main/ant-kit/src/main/resources/package/linux/postinst b/main/ant-kit/src/main/resources/package/linux/postinst
index e77fa6366..ee09f3b1d 100644
--- a/main/ant-kit/src/main/resources/package/linux/postinst
+++ b/main/ant-kit/src/main/resources/package/linux/postinst
@@ -22,6 +22,7 @@ case "$1" in
echo Adding shortcut to the menu
SECONDARY_LAUNCHERS_INSTALL
APP_CDS_CACHE
+ mkdir -pm 644 /usr/share/desktop-directories
xdg-desktop-menu install --novendor /opt/APPLICATION_FS_NAME/APPLICATION_LAUNCHER_FILENAME.desktop
FILE_ASSOCIATION_INSTALL
diff --git a/main/commons/pom.xml b/main/commons/pom.xml
index 8ac4900df..6ed07dfac 100644
--- a/main/commons/pom.xml
+++ b/main/commons/pom.xml
@@ -10,7 +10,7 @@
org.cryptomator
main
- 1.3.0-rc6
+ 1.3.0-rc7
commons
Cryptomator Commons
diff --git a/main/jacoco-report/pom.xml b/main/jacoco-report/pom.xml
index 62749fd3d..fd685566b 100644
--- a/main/jacoco-report/pom.xml
+++ b/main/jacoco-report/pom.xml
@@ -5,7 +5,7 @@
org.cryptomator
main
- 1.3.0-rc6
+ 1.3.0-rc7
jacoco-report
Cryptomator Code Coverage Report
diff --git a/main/keychain/pom.xml b/main/keychain/pom.xml
index 2f19f9c57..bea437ab9 100644
--- a/main/keychain/pom.xml
+++ b/main/keychain/pom.xml
@@ -3,7 +3,7 @@
org.cryptomator
main
- 1.3.0-rc6
+ 1.3.0-rc7
keychain
System Keychain Access
diff --git a/main/launcher/pom.xml b/main/launcher/pom.xml
index c0420d976..ea5bf3603 100644
--- a/main/launcher/pom.xml
+++ b/main/launcher/pom.xml
@@ -4,7 +4,7 @@
org.cryptomator
main
- 1.3.0-rc6
+ 1.3.0-rc7
launcher
Cryptomator Launcher
diff --git a/main/launcher/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java b/main/launcher/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java
index fdaafe483..3a0ac97c9 100644
--- a/main/launcher/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java
+++ b/main/launcher/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java
@@ -7,17 +7,13 @@
package org.cryptomator.launcher;
import java.io.File;
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
-import java.util.List;
import java.util.concurrent.BlockingQueue;
-import org.apache.commons.lang3.SystemUtils;
+import org.cryptomator.ui.util.EawtApplicationWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -28,9 +24,11 @@ class FileOpenRequestHandler {
public FileOpenRequestHandler(BlockingQueue fileOpenRequests) {
this.fileOpenRequests = fileOpenRequests;
- if (SystemUtils.IS_OS_MAC_OSX) {
- addOsxFileOpenHandler();
- }
+ EawtApplicationWrapper.getApplication().ifPresent(app -> {
+ app.setOpenFileHandler(files -> {
+ files.stream().map(File::toPath).forEach(fileOpenRequests::add);
+ });
+ });
}
public void handleLaunchArgs(String[] args) {
@@ -55,48 +53,4 @@ class FileOpenRequestHandler {
}
}
- /**
- * Event subscription code inspired by https://gitlab.com/axet/desktop/blob/master/java/src/main/java/com/github/axet/desktop/os/mac/AppleHandlers.java
- */
- private void addOsxFileOpenHandler() {
- try {
- final Class> applicationClass = Class.forName("com.apple.eawt.Application");
- final Class> openFilesHandlerClass = Class.forName("com.apple.eawt.OpenFilesHandler");
- final Method getApplication = applicationClass.getMethod("getApplication");
- final Object application = getApplication.invoke(null);
- final Method setOpenFileHandler = applicationClass.getMethod("setOpenFileHandler", openFilesHandlerClass);
- final ClassLoader openFilesHandlerClassLoader = openFilesHandlerClass.getClassLoader();
- final OpenFilesEventInvocationHandler openFilesHandler = new OpenFilesEventInvocationHandler();
- final Object openFilesHandlerObject = Proxy.newProxyInstance(openFilesHandlerClassLoader, new Class>[] {openFilesHandlerClass}, openFilesHandler);
- setOpenFileHandler.invoke(application, openFilesHandlerObject);
- } catch (ReflectiveOperationException | RuntimeException e) {
- // Since we're trying to call OS-specific code, we'll just have to hope for the best.
- LOG.error("Exception adding OS X file open handler", e);
- }
- }
-
- /**
- * Handler class inspired by https://gitlab.com/axet/desktop/blob/master/java/src/main/java/com/github/axet/desktop/os/mac/AppleHandlers.java
- */
- private class OpenFilesEventInvocationHandler implements InvocationHandler {
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- if (method.getName().equals("openFiles")) {
- final Class> openFilesEventClass = Class.forName("com.apple.eawt.AppEvent$OpenFilesEvent");
- final Method getFiles = openFilesEventClass.getMethod("getFiles");
- Object e = args[0];
- try {
- @SuppressWarnings("unchecked")
- final List ff = (List) getFiles.invoke(e);
- ff.stream().map(File::toPath).forEach(fileOpenRequests::add);
- } catch (RuntimeException ee) {
- throw ee;
- } catch (Exception ee) {
- throw new RuntimeException(ee);
- }
- }
- return null;
- }
- }
-
}
diff --git a/main/pom.xml b/main/pom.xml
index 98e37939f..ff9103ce2 100644
--- a/main/pom.xml
+++ b/main/pom.xml
@@ -6,7 +6,7 @@
4.0.0
org.cryptomator
main
- 1.3.0-rc6
+ 1.3.0-rc7
pom
Cryptomator
@@ -27,8 +27,8 @@
UTF-8
- 1.1.1
- 1.2.2
+ 1.1.2
+ 1.3.0
0.6.1
1.0.2
1.7.25
@@ -42,9 +42,9 @@
1.10
4.5.3
2.7.21
- 2.10
+ 2.11
1.0.3
- 21.0
+ 22.0
2.8.0
diff --git a/main/uber-jar/pom.xml b/main/uber-jar/pom.xml
index f59da0bc8..54f7b106a 100644
--- a/main/uber-jar/pom.xml
+++ b/main/uber-jar/pom.xml
@@ -5,7 +5,7 @@
org.cryptomator
main
- 1.3.0-rc6
+ 1.3.0-rc7
uber-jar
Single über jar with all dependencies
diff --git a/main/ui/pom.xml b/main/ui/pom.xml
index 8fa5cd2f5..1631e43ff 100644
--- a/main/ui/pom.xml
+++ b/main/ui/pom.xml
@@ -12,7 +12,7 @@
org.cryptomator
main
- 1.3.0-rc6
+ 1.3.0-rc7
ui
Cryptomator GUI
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java
index ba15b47e1..7a41bf1b7 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java
@@ -37,6 +37,7 @@ import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.model.VaultFactory;
import org.cryptomator.ui.model.VaultList;
import org.cryptomator.ui.util.DialogBuilderUtil;
+import org.cryptomator.ui.util.EawtApplicationWrapper;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import org.fxmisc.easybind.monadic.MonadicBinding;
@@ -119,6 +120,12 @@ public class MainController implements ViewController {
EasyBind.subscribe(areAllVaultsLocked, Platform::setImplicitExit);
autoUnlocker.unlockAllSilently();
+
+ EawtApplicationWrapper.getApplication().ifPresent(app -> {
+ app.setPreferencesHandler(() -> {
+ Platform.runLater(this::toggleShowSettings);
+ });
+ });
}
@FXML
@@ -328,10 +335,14 @@ public class MainController implements ViewController {
@FXML
private void didClickShowSettings(ActionEvent e) {
+ toggleShowSettings();
+ }
+
+ private void toggleShowSettings() {
if (isShowingSettings.get()) {
- activeController.set(viewControllerLoader.load("/fxml/welcome.fxml"));
+ showWelcomeView();
} else {
- activeController.set(viewControllerLoader.load("/fxml/settings.fxml"));
+ showPreferencesView();
}
vaultList.getSelectionModel().clearSelection();
}
@@ -375,6 +386,14 @@ public class MainController implements ViewController {
// Subcontroller for right panel
// ****************************************
+ private void showWelcomeView() {
+ activeController.set(viewControllerLoader.load("/fxml/welcome.fxml"));
+ }
+
+ private void showPreferencesView() {
+ activeController.set(viewControllerLoader.load("/fxml/settings.fxml"));
+ }
+
private void showNotFoundView() {
activeController.set(viewControllerLoader.load("/fxml/notfound.fxml"));
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java
index a0554c3e6..f8859c305 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java
@@ -21,7 +21,6 @@ import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
import org.cryptomator.frontend.webdav.ServerLifecycleException;
-import org.cryptomator.frontend.webdav.mount.Mounter.CommandFailedException;
import org.cryptomator.keychain.KeychainAccess;
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.l10n.Localization;
@@ -38,7 +37,6 @@ import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import javafx.application.Application;
-import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
@@ -345,59 +343,42 @@ public class UnlockController implements ViewController {
private void didClickUnlockButton(ActionEvent event) {
advancedOptions.setDisable(true);
progressIndicator.setVisible(true);
- downloadsPageLink.setVisible(false);
- CharSequence password = passwordField.getCharacters();
- asyncTaskService.asyncTaskOf(() -> this.unlock(password)).run();
- }
- private void unlock(CharSequence password) {
- try {
+ CharSequence password = passwordField.getCharacters();
+ asyncTaskService.asyncTaskOf(() -> {
vault.unlock(password);
- if (mountAfterUnlock.isSelected()) {
- vault.mount();
- if (revealAfterMount.isSelected()) {
- vault.reveal();
- }
- }
- Platform.runLater(() -> {
- messageText.setText(null);
- listener.ifPresent(lstnr -> lstnr.didUnlock(vault));
- });
if (keychainAccess.isPresent() && savePassword.isSelected()) {
keychainAccess.get().storePassphrase(vault.getId(), password);
- } else {
- Platform.runLater(passwordField::swipe);
}
- } catch (InvalidPassphraseException e) {
- Platform.runLater(() -> {
- messageText.setText(localization.getString("unlock.errorMessage.wrongPassword"));
- passwordField.selectAll();
- passwordField.requestFocus();
- });
- } catch (UnsupportedVaultFormatException e) {
- Platform.runLater(() -> {
- if (e.isVaultOlderThanSoftware()) {
- // whitespace after localized text used as separator between text and hyperlink
- messageText.setText(localization.getString("unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
- downloadsPageLink.setVisible(true);
- } else if (e.isSoftwareOlderThanVault()) {
- messageText.setText(localization.getString("unlock.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
- downloadsPageLink.setVisible(true);
- } else if (e.getDetectedVersion() == Integer.MAX_VALUE) {
- messageText.setText(localization.getString("unlock.errorMessage.unauthenticVersionMac"));
- }
- });
- } catch (ServerLifecycleException | CommandFailedException e) {
+ }).onSuccess(() -> {
+ messageText.setText(null);
+ downloadsPageLink.setVisible(false);
+ listener.ifPresent(lstnr -> lstnr.didUnlock(vault));
+ }).onError(InvalidPassphraseException.class, e -> {
+ messageText.setText(localization.getString("unlock.errorMessage.wrongPassword"));
+ passwordField.selectAll();
+ passwordField.requestFocus();
+ }).onError(UnsupportedVaultFormatException.class, e -> {
+ if (e.isVaultOlderThanSoftware()) {
+ // whitespace after localized text used as separator between text and hyperlink
+ messageText.setText(localization.getString("unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
+ downloadsPageLink.setVisible(true);
+ } else if (e.isSoftwareOlderThanVault()) {
+ messageText.setText(localization.getString("unlock.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
+ downloadsPageLink.setVisible(true);
+ } else if (e.getDetectedVersion() == Integer.MAX_VALUE) {
+ messageText.setText(localization.getString("unlock.errorMessage.unauthenticVersionMac"));
+ }
+ }).onError(ServerLifecycleException.class, e -> {
LOG.error("Unlock failed for technical reasons.", e);
- Platform.runLater(() -> {
- messageText.setText(localization.getString("unlock.errorMessage.mountingFailed"));
- });
- } finally {
- Platform.runLater(() -> {
- advancedOptions.setDisable(false);
- progressIndicator.setVisible(false);
- });
- }
+ messageText.setText(localization.getString("unlock.errorMessage.unlockFailed"));
+ }).andFinally(() -> {
+ if (!savePassword.isSelected()) {
+ passwordField.swipe();
+ }
+ advancedOptions.setDisable(false);
+ progressIndicator.setVisible(false);
+ }).run();
}
/* callback */
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java
index 52f70da77..ff8a57028 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java
@@ -10,10 +10,13 @@ package org.cryptomator.ui.controllers;
import static java.lang.String.format;
+import java.io.IOException;
import java.util.Optional;
import javax.inject.Inject;
+import org.cryptomator.frontend.webdav.ServerLifecycleException;
+import org.cryptomator.frontend.webdav.mount.Mounter.CommandFailedException;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.util.AsyncTaskService;
@@ -22,9 +25,12 @@ import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.util.concurrent.Runnables;
+
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
+import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
@@ -58,6 +64,7 @@ public class UnlockedController implements ViewController {
private final Localization localization;
private final AsyncTaskService asyncTaskService;
private final ObjectProperty vault = new SimpleObjectProperty<>();
+ private final BooleanExpression vaultMounted = BooleanExpression.booleanExpression(EasyBind.select(vault).selectObject(Vault::mountedProperty).orElse(false));
private Optional listener = Optional.empty();
private Timeline ioAnimation;
@@ -76,6 +83,12 @@ public class UnlockedController implements ViewController {
@FXML
private ContextMenu moreOptionsMenu;
+ @FXML
+ private MenuItem mountVaultMenuItem;
+
+ @FXML
+ private MenuItem unmountVaultMenuItem;
+
@FXML
private MenuItem revealVaultMenuItem;
@@ -90,7 +103,9 @@ public class UnlockedController implements ViewController {
@Override
public void initialize() {
- revealVaultMenuItem.disableProperty().bind(EasyBind.map(vault, vault -> vault != null && !vault.isMounted()));
+ mountVaultMenuItem.disableProperty().bind(vaultMounted);
+ unmountVaultMenuItem.disableProperty().bind(vaultMounted.not());
+ revealVaultMenuItem.disableProperty().bind(vaultMounted.not());
EasyBind.subscribe(vault, this::vaultChanged);
EasyBind.subscribe(moreOptionsMenu.showingProperty(), moreOptionsButton::setSelected);
@@ -106,9 +121,8 @@ public class UnlockedController implements ViewController {
return;
}
- if (newVault.getVaultSettings().mountAfterUnlock().get() && !newVault.isMounted()) {
- // TODO Markus Kreusch #393: hyperlink auf FAQ oder sowas?
- messageLabel.setText(localization.getString("unlocked.label.mountFailed"));
+ if (newVault.getVaultSettings().mountAfterUnlock().get()) {
+ mountVault(newVault);
}
// (re)start throughput statistics:
@@ -118,61 +132,16 @@ public class UnlockedController implements ViewController {
@FXML
private void didClickLockVault(ActionEvent event) {
- regularLockVault();
+ regularUnmountVault(this::lockVault);
}
- private void regularLockVault() {
- asyncTaskService.asyncTaskOf(() -> {
- vault.get().unmount();
+ private void lockVault() {
+ try {
vault.get().lock();
- }).onSuccess(() -> {
- listener.ifPresent(listener -> listener.didLock(this));
- LOG.trace("Regular lock succeeded");
- }).onError(Exception.class, e -> {
- onRegularLockVaultFailed(e);
- }).run();
- }
-
- private void forcedLockVault() {
- asyncTaskService.asyncTaskOf(() -> {
- vault.get().unmountForced();
- vault.get().lock();
- }).onSuccess(() -> {
- listener.ifPresent(listener -> listener.didLock(this));
- LOG.trace("Forced lock succeeded");
- }).onError(Exception.class, e -> {
- onForcedLockVaultFailed(e);
- }).run();
- }
-
- private void onRegularLockVaultFailed(Exception e) {
- if (vault.get().supportsForcedUnmount()) {
- LOG.trace("Regular unmount failed", e);
- Alert confirmDialog = DialogBuilderUtil.buildYesNoDialog( //
- format(localization.getString("unlocked.lock.force.confirmation.title"), vault.get().name().getValue()), //
- localization.getString("unlocked.lock.force.confirmation.header"), //
- localization.getString("unlocked.lock.force.confirmation.content"), //
- ButtonType.NO);
-
- Optional choice = confirmDialog.showAndWait();
- if (ButtonType.YES.equals(choice.get())) {
- forcedLockVault();
- } else {
- LOG.trace("Unmount cancelled", e);
- }
- } else {
- LOG.error("Regular unmount failed", e);
- showUnmountFailedMessage();
+ } catch (ServerLifecycleException | IOException e) {
+ LOG.error("Lock failed", e);
}
- }
-
- private void onForcedLockVaultFailed(Exception e) {
- LOG.error("Forced unmount failed", e);
- showUnmountFailedMessage();
- }
-
- private void showUnmountFailedMessage() {
- messageLabel.setText(localization.getString("unlocked.label.unmountFailed"));
+ listener.ifPresent(listener -> listener.didLock(this));
}
@FXML
@@ -186,11 +155,88 @@ public class UnlockedController implements ViewController {
}
@FXML
- private void didClickRevealVault(ActionEvent event) {
+ public void didClickMountVault(ActionEvent event) {
+ mountVault(vault.get());
+ }
+
+ private void mountVault(Vault vault) {
asyncTaskService.asyncTaskOf(() -> {
- vault.get().reveal();
- }).onError(RuntimeException.class, () -> {
- // TODO overheadhunter catch more specific exception type thrown by reveal()
+ vault.mount();
+ }).onSuccess(() -> {
+ LOG.trace("Mount succeeded.");
+ messageLabel.setText(null);
+ if (vault.getVaultSettings().revealAfterMount().get()) {
+ revealVault(vault);
+ }
+ }).onError(CommandFailedException.class, e -> {
+ LOG.error("Mount failed.", e);
+ // TODO Markus Kreusch #393: hyperlink auf FAQ oder sowas?
+ messageLabel.setText(localization.getString("unlocked.label.mountFailed"));
+ }).run();
+ }
+
+ @FXML
+ public void didClickUnmountVault(ActionEvent event) {
+ regularUnmountVault(Runnables.doNothing());
+ }
+
+ private void regularUnmountVault(Runnable onSuccess) {
+ asyncTaskService.asyncTaskOf(() -> {
+ vault.get().unmount();
+ }).onSuccess(() -> {
+ LOG.trace("Regular unmount succeeded.");
+ onSuccess.run();
+ }).onError(Exception.class, e -> {
+ onRegularUnmountVaultFailed(e, onSuccess);
+ }).run();
+ }
+
+ private void forcedUnmountVault(Runnable onSuccess) {
+ asyncTaskService.asyncTaskOf(() -> {
+ vault.get().unmountForced();
+ }).onSuccess(() -> {
+ LOG.trace("Forced unmount succeeded.");
+ onSuccess.run();
+ }).onError(Exception.class, e -> {
+ LOG.error("Forced unmount failed.", e);
+ messageLabel.setText(localization.getString("unlocked.label.unmountFailed"));
+ }).run();
+ }
+
+ private void onRegularUnmountVaultFailed(Exception e, Runnable onSuccess) {
+ if (vault.get().supportsForcedUnmount()) {
+ LOG.trace("Regular unmount failed.", e);
+ Alert confirmDialog = DialogBuilderUtil.buildYesNoDialog( //
+ format(localization.getString("unlocked.lock.force.confirmation.title"), vault.get().name().getValue()), //
+ localization.getString("unlocked.lock.force.confirmation.header"), //
+ localization.getString("unlocked.lock.force.confirmation.content"), //
+ ButtonType.NO);
+
+ Optional choice = confirmDialog.showAndWait();
+ if (ButtonType.YES.equals(choice.get())) {
+ forcedUnmountVault(onSuccess);
+ } else {
+ LOG.trace("Unmount cancelled.", e);
+ }
+ } else {
+ LOG.error("Regular unmount failed.", e);
+ messageLabel.setText(localization.getString("unlocked.label.unmountFailed"));
+ }
+ }
+
+ @FXML
+ private void didClickRevealVault(ActionEvent event) {
+ revealVault(vault.get());
+ }
+
+ private void revealVault(Vault vault) {
+ asyncTaskService.asyncTaskOf(() -> {
+ vault.reveal();
+ }).onSuccess(() -> {
+ LOG.trace("Reveal succeeded.");
+ messageLabel.setText(null);
+ }).onError(CommandFailedException.class, e -> {
+ LOG.error("Reveal failed.", e);
messageLabel.setText(localization.getString("unlocked.label.revealFailed"));
}).run();
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java
index d615dac5a..0f7e89e91 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java
@@ -16,6 +16,7 @@ import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.EnumSet;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
@@ -30,9 +31,11 @@ import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.cryptofs.CryptoFileSystem;
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
+import org.cryptomator.cryptofs.CryptoFileSystemProperties.FileSystemFlags;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
+import org.cryptomator.frontend.webdav.ServerLifecycleException;
import org.cryptomator.frontend.webdav.WebDavServer;
import org.cryptomator.frontend.webdav.mount.MountParams;
import org.cryptomator.frontend.webdav.mount.Mounter.CommandFailedException;
@@ -78,12 +81,13 @@ public class Vault {
// ********************************************************************************/
private CryptoFileSystem getCryptoFileSystem(CharSequence passphrase) throws IOException, CryptoException {
- return LazyInitializer.initializeLazily(cryptoFileSystem, () -> createCryptoFileSystem(passphrase), IOException.class);
+ return LazyInitializer.initializeLazily(cryptoFileSystem, () -> unlockCryptoFileSystem(passphrase), IOException.class);
}
- private CryptoFileSystem createCryptoFileSystem(CharSequence passphrase) throws IOException, CryptoException {
+ private CryptoFileSystem unlockCryptoFileSystem(CharSequence passphrase) throws IOException, CryptoException {
CryptoFileSystemProperties fsProps = CryptoFileSystemProperties.cryptoFileSystemProperties() //
.withPassphrase(passphrase) //
+ .withFlags(EnumSet.noneOf(FileSystemFlags.class)) // TODO: use withFlags() with CryptoFS 1.3.1
.withMasterkeyFilename(MASTERKEY_FILENAME) //
.build();
return CryptoFileSystemProvider.newFileSystem(getPath(), fsProps);
@@ -98,7 +102,7 @@ public class Vault {
}
}
if (!isValidVaultDirectory()) {
- createCryptoFileSystem(passphrase).close(); // implicitly creates a non-existing vault
+ CryptoFileSystemProvider.initialize(getPath(), MASTERKEY_FILENAME, passphrase);
} else {
throw new FileAlreadyExistsException(getPath().toString());
}
@@ -108,7 +112,7 @@ public class Vault {
CryptoFileSystemProvider.changePassphrase(getPath(), MASTERKEY_FILENAME, oldPassphrase, newPassphrase);
}
- public synchronized void unlock(CharSequence passphrase) {
+ public synchronized void unlock(CharSequence passphrase) throws ServerLifecycleException {
try {
FileSystem fs = getCryptoFileSystem(passphrase);
if (!server.isRunning()) {
@@ -161,7 +165,7 @@ public class Vault {
return mount != null && mount.forced().isPresent();
}
- public synchronized void lock() throws Exception {
+ public synchronized void lock() throws ServerLifecycleException, IOException {
if (servlet != null) {
servlet.stop();
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/AsyncTaskService.java b/main/ui/src/main/java/org/cryptomator/ui/util/AsyncTaskService.java
index 391d3ae77..a4aa3f96c 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/util/AsyncTaskService.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/util/AsyncTaskService.java
@@ -32,6 +32,12 @@ public class AsyncTaskService {
this.executor = executor;
}
+ /**
+ * Creates a new async task
+ *
+ * @param task Tasks to be invoked in a background thread.
+ * @return The async task
+ */
public AsyncTaskWithoutSuccessHandler asyncTaskOf(RunnableThrowingException> task) {
return new AsyncTaskImpl<>(() -> {
task.run();
@@ -39,6 +45,12 @@ public class AsyncTaskService {
});
}
+ /**
+ * Creates a new async task
+ *
+ * @param task Tasks to be invoked in a background thread.
+ * @return The async task
+ */
public AsyncTaskWithoutSuccessHandler asyncTaskOf(SupplierThrowingException task) {
return new AsyncTaskImpl<>(task);
}
@@ -153,27 +165,55 @@ public class AsyncTaskService {
public interface AsyncTaskWithoutSuccessHandler extends AsyncTaskWithoutErrorHandler {
+ /**
+ * @param handler Tasks to be invoked on the JavaFX application thread.
+ * @return The async task
+ */
AsyncTaskWithoutErrorHandler onSuccess(ConsumerThrowingException handler);
+ /**
+ * @param handler Tasks to be invoked on the JavaFX application thread.
+ * @return The async task
+ */
AsyncTaskWithoutErrorHandler onSuccess(RunnableThrowingException> handler);
}
public interface AsyncTaskWithoutErrorHandler extends AsyncTaskWithoutFinallyHandler {
+ /**
+ * @param type Exception type to catch
+ * @param handler Tasks to be invoked on the JavaFX application thread.
+ * @return The async task
+ */
AsyncTaskWithoutErrorHandler onError(Class type, ConsumerThrowingException handler);
+ /**
+ * @param type Exception type to catch
+ * @param handler Tasks to be invoked on the JavaFX application thread.
+ * @return The async task
+ */
AsyncTaskWithoutErrorHandler onError(Class type, RunnableThrowingException> handler);
}
public interface AsyncTaskWithoutFinallyHandler extends AsyncTask {
+ /**
+ * @param handler Tasks to be invoked on the JavaFX application thread.
+ * @return The async task
+ */
AsyncTask andFinally(RunnableThrowingException> handler);
}
public interface AsyncTask extends Runnable {
+
+ /**
+ * Starts the async task.
+ */
+ @Override
+ void run();
}
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/EawtApplicationWrapper.java b/main/ui/src/main/java/org/cryptomator/ui/util/EawtApplicationWrapper.java
new file mode 100644
index 000000000..3c524e418
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/util/EawtApplicationWrapper.java
@@ -0,0 +1,127 @@
+/*******************************************************************************
+ * 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.ui.util;
+
+import java.io.File;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import org.apache.commons.lang3.SystemUtils;
+import org.cryptomator.common.SupplierThrowingException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Reflection-based wrapper for com.apple.eawt.Application.
+ */
+public class EawtApplicationWrapper {
+
+ private static final Logger LOG = LoggerFactory.getLogger(EawtApplicationWrapper.class);
+
+ private final Class> applicationClass;
+ private final Object application;
+
+ private EawtApplicationWrapper() throws ReflectiveOperationException {
+ this.applicationClass = Class.forName("com.apple.eawt.Application");
+ this.application = applicationClass.getMethod("getApplication").invoke(null);
+ }
+
+ /**
+ * @return A wrapper for com.apple.ewat.Application if the current OS is macOS and the class is available in this JVM.
+ */
+ public static Optional getApplication() {
+ if (!SystemUtils.IS_OS_MAC_OSX) {
+ return Optional.empty();
+ }
+ try {
+ return Optional.of(new EawtApplicationWrapper());
+ } catch (ReflectiveOperationException e) {
+ return Optional.empty();
+ }
+ }
+
+ private void setOpenFileHandler(InvocationHandler handler) throws ReflectiveOperationException {
+ Class> handlerClass = Class.forName("com.apple.eawt.OpenFilesHandler");
+ Method setter = applicationClass.getMethod("setOpenFileHandler", handlerClass);
+ Object proxy = Proxy.newProxyInstance(applicationClass.getClassLoader(), new Class>[] {handlerClass}, handler);
+ setter.invoke(application, proxy);
+ }
+
+ public void setOpenFileHandler(Consumer> handler) {
+ try {
+ Class> openFilesEventClass = Class.forName("com.apple.eawt.AppEvent$OpenFilesEvent");
+ Method getFiles = openFilesEventClass.getMethod("getFiles");
+ setOpenFileHandler(methodSpecificInvocationHandler("openFiles", args -> {
+ Object openFilesEvent = args[0];
+ assert openFilesEventClass.isInstance(openFilesEvent);
+ @SuppressWarnings("unchecked")
+ List files = (List) uncheckedReflectiveOperation(() -> getFiles.invoke(openFilesEvent));
+ handler.accept(files);
+ return null;
+ }));
+ } catch (ReflectiveOperationException e) {
+ LOG.error("Exception setting openFileHandler.", e);
+ }
+ }
+
+ private void setPreferencesHandler(InvocationHandler handler) throws ReflectiveOperationException {
+ Class> handlerClass = Class.forName("com.apple.eawt.PreferencesHandler");
+ Method setter = applicationClass.getMethod("setPreferencesHandler", handlerClass);
+ Object proxy = Proxy.newProxyInstance(applicationClass.getClassLoader(), new Class>[] {handlerClass}, handler);
+ setter.invoke(application, proxy);
+ }
+
+ public void setPreferencesHandler(Runnable handler) {
+ try {
+ setPreferencesHandler(methodSpecificInvocationHandler("handlePreferences", args -> {
+ handler.run();
+ return null;
+ }));
+ } catch (ReflectiveOperationException e) {
+ LOG.error("Exception setting preferencesHandler.", e);
+ }
+ }
+
+ private static InvocationHandler methodSpecificInvocationHandler(String methodName, Function