From f4265e1d7375536677d634d177934cf6bf3964ec Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sat, 27 May 2017 01:20:44 +0200 Subject: [PATCH 1/8] added cmd+, shortcut to macOS menubar --- .../launcher/FileOpenRequestHandler.java | 55 +-------- .../ui/controllers/MainController.java | 23 +++- .../ui/util/EawtApplicationWrapper.java | 105 ++++++++++++++++++ 3 files changed, 132 insertions(+), 51 deletions(-) create mode 100644 main/ui/src/main/java/org/cryptomator/ui/util/EawtApplicationWrapper.java 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..97306d231 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,14 @@ 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; @@ -29,7 +26,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); + }); + }); } } @@ -55,48 +56,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/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/util/EawtApplicationWrapper.java b/main/ui/src/main/java/org/cryptomator/ui/util/EawtApplicationWrapper.java new file mode 100644 index 000000000..8c260fe6f --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/util/EawtApplicationWrapper.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * 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 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); + } + + public static Optional getApplication() { + 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(newMethodSpecificInvocationHandler("openFiles", args -> { + try { + Object openFilesEvent = args[0]; + @SuppressWarnings("unchecked") + List files = (List) getFiles.invoke(openFilesEvent); + handler.accept(files); + } catch (ReflectiveOperationException e) { + LOG.error("Error invoking openFileHandler.", e); + } + 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(newMethodSpecificInvocationHandler("handlePreferences", args -> { + handler.run(); + return null; + })); + } catch (ReflectiveOperationException e) { + LOG.error("Exception setting preferencesHandler.", e); + } + } + + @FunctionalInterface + private static interface MethodSpecificInvocationHandler { + Object invoke(Object[] args); + } + + private static InvocationHandler newMethodSpecificInvocationHandler(String methodName, MethodSpecificInvocationHandler handler) { + return new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (method.getName().equals(methodName)) { + return handler.invoke(args); + } else { + return null; + } + } + }; + } + +} From 8c55946cf032edd467a0142fcec36ca620d991fc Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sun, 28 May 2017 00:08:08 +0200 Subject: [PATCH 2/8] cleanup --- .../launcher/FileOpenRequestHandler.java | 11 ++-- .../ui/util/EawtApplicationWrapper.java | 58 +++++++++++++------ 2 files changed, 44 insertions(+), 25 deletions(-) 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 97306d231..3a0ac97c9 100644 --- a/main/launcher/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java +++ b/main/launcher/src/main/java/org/cryptomator/launcher/FileOpenRequestHandler.java @@ -13,7 +13,6 @@ import java.nio.file.InvalidPathException; import java.nio.file.Path; 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; @@ -25,13 +24,11 @@ class FileOpenRequestHandler { public FileOpenRequestHandler(BlockingQueue fileOpenRequests) { this.fileOpenRequests = fileOpenRequests; - if (SystemUtils.IS_OS_MAC_OSX) { - EawtApplicationWrapper.getApplication().ifPresent(app -> { - app.setOpenFileHandler(files -> { - files.stream().map(File::toPath).forEach(fileOpenRequests::add); - }); + EawtApplicationWrapper.getApplication().ifPresent(app -> { + app.setOpenFileHandler(files -> { + files.stream().map(File::toPath).forEach(fileOpenRequests::add); }); - } + }); } public void handleLaunchArgs(String[] args) { 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 index 8c260fe6f..3c524e418 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/util/EawtApplicationWrapper.java +++ b/main/ui/src/main/java/org/cryptomator/ui/util/EawtApplicationWrapper.java @@ -12,7 +12,10 @@ 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; @@ -31,7 +34,13 @@ public class EawtApplicationWrapper { 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) { @@ -50,15 +59,12 @@ public class EawtApplicationWrapper { try { Class openFilesEventClass = Class.forName("com.apple.eawt.AppEvent$OpenFilesEvent"); Method getFiles = openFilesEventClass.getMethod("getFiles"); - setOpenFileHandler(newMethodSpecificInvocationHandler("openFiles", args -> { - try { - Object openFilesEvent = args[0]; - @SuppressWarnings("unchecked") - List files = (List) getFiles.invoke(openFilesEvent); - handler.accept(files); - } catch (ReflectiveOperationException e) { - LOG.error("Error invoking openFileHandler.", e); - } + 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) { @@ -75,7 +81,7 @@ public class EawtApplicationWrapper { public void setPreferencesHandler(Runnable handler) { try { - setPreferencesHandler(newMethodSpecificInvocationHandler("handlePreferences", args -> { + setPreferencesHandler(methodSpecificInvocationHandler("handlePreferences", args -> { handler.run(); return null; })); @@ -84,22 +90,38 @@ public class EawtApplicationWrapper { } } - @FunctionalInterface - private static interface MethodSpecificInvocationHandler { - Object invoke(Object[] args); - } - - private static InvocationHandler newMethodSpecificInvocationHandler(String methodName, MethodSpecificInvocationHandler handler) { + private static InvocationHandler methodSpecificInvocationHandler(String methodName, Function handler) { return new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals(methodName)) { - return handler.invoke(args); + return handler.apply(args); } else { - return null; + throw new UnsupportedOperationException("Unexpected invocation " + method.getName() + ", expected " + methodName); } } }; } + /** + * Wraps {@link ReflectiveOperationException}s as {@link UncheckedReflectiveOperationException}. + * + * @param operation Invokation throwing an ReflectiveOperationException + * @return Result returned by operation + * @throws UncheckedReflectiveOperationException in case operation throws an ReflectiveOperationException. + */ + private static T uncheckedReflectiveOperation(SupplierThrowingException operation) throws UncheckedReflectiveOperationException { + try { + return operation.get(); + } catch (ReflectiveOperationException e) { + throw new UncheckedReflectiveOperationException(e); + } + } + + private static class UncheckedReflectiveOperationException extends RuntimeException { + public UncheckedReflectiveOperationException(ReflectiveOperationException cause) { + super(cause); + } + } + } From 4ea3e8de8bdb1900f4b4d298f79d49bb207decbb Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 29 May 2017 01:03:22 +0200 Subject: [PATCH 3/8] moved mount + reveal from UnlockController to UnlockedController --- .../ui/controllers/UnlockController.java | 79 +++++++------------ .../ui/controllers/UnlockedController.java | 46 +++++++++-- .../java/org/cryptomator/ui/model/Vault.java | 3 +- .../cryptomator/ui/util/AsyncTaskService.java | 40 ++++++++++ main/ui/src/main/resources/fxml/unlocked.fxml | 3 + .../ui/src/main/resources/localization/en.txt | 3 +- 6 files changed, 115 insertions(+), 59 deletions(-) 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..509b07b17 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 @@ -14,6 +14,7 @@ import java.util.Optional; import javax.inject.Inject; +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; @@ -25,6 +26,7 @@ import org.slf4j.LoggerFactory; 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 +60,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 +79,9 @@ public class UnlockedController implements ViewController { @FXML private ContextMenu moreOptionsMenu; + @FXML + private MenuItem mountVaultMenuItem; + @FXML private MenuItem revealVaultMenuItem; @@ -90,7 +96,8 @@ public class UnlockedController implements ViewController { @Override public void initialize() { - revealVaultMenuItem.disableProperty().bind(EasyBind.map(vault, vault -> vault != null && !vault.isMounted())); + revealVaultMenuItem.disableProperty().bind(vaultMounted.not()); + mountVaultMenuItem.disableProperty().bind(vaultMounted); EasyBind.subscribe(vault, this::vaultChanged); EasyBind.subscribe(moreOptionsMenu.showingProperty(), moreOptionsButton::setSelected); @@ -106,9 +113,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: @@ -186,11 +192,35 @@ 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(() -> { + messageLabel.setText(null); + if (vault.getVaultSettings().revealAfterMount().get()) { + revealVault(vault); + } + }).onError(CommandFailedException.class, e -> { + // TODO Markus Kreusch #393: hyperlink auf FAQ oder sowas? + messageLabel.setText(localization.getString("unlocked.label.mountFailed")); + }).run(); + } + + @FXML + private void didClickRevealVault(ActionEvent event) { + revealVault(vault.get()); + } + + private void revealVault(Vault vault) { + asyncTaskService.asyncTaskOf(() -> { + vault.reveal(); + }).onSuccess(() -> { + messageLabel.setText(null); + }).onError(CommandFailedException.class, () -> { 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..07d3ccc74 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 @@ -33,6 +33,7 @@ import org.cryptomator.cryptofs.CryptoFileSystemProperties; 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; @@ -108,7 +109,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()) { 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/resources/fxml/unlocked.fxml b/main/ui/src/main/resources/fxml/unlocked.fxml index 91f65ecc7..72eec0a7b 100644 --- a/main/ui/src/main/resources/fxml/unlocked.fxml +++ b/main/ui/src/main/resources/fxml/unlocked.fxml @@ -28,6 +28,9 @@ + + + diff --git a/main/ui/src/main/resources/localization/en.txt b/main/ui/src/main/resources/localization/en.txt index 541808120..716e58b35 100644 --- a/main/ui/src/main/resources/localization/en.txt +++ b/main/ui/src/main/resources/localization/en.txt @@ -72,7 +72,7 @@ unlock.savePassword.delete.confirmation.header=Do you really want to delete the unlock.savePassword.delete.confirmation.content=The saved password of this vault will be immediately deleted from your system keychain. If you'd like to save your password again, you'd have to unlock your vault with the "Save Password" option enabled. unlock.choicebox.winDriveLetter.auto=Assign automatically unlock.errorMessage.wrongPassword=Wrong password -unlock.errorMessage.mountingFailed=Mounting failed. See log file for details. +unlock.errorMessage.unlockFailed=Unlock failed. See log file for details. unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware=Unsupported vault. This vault has been created with an older version of Cryptomator. unlock.errorMessage.unsupportedVersion.softwareOlderThanVault=Unsupported vault. This vault has been created with a newer version of Cryptomator. unlock.errorMessage.unauthenticVersionMac=Could not authenticate version MAC. @@ -89,6 +89,7 @@ changePassword.errorMessage.decryptionFailed=Decryption failed # unlocked.fxml unlocked.button.lock=Lock Vault +unlocked.moreOptions.mount=Mount Drive unlocked.moreOptions.reveal=Reveal Drive unlocked.moreOptions.copyUrl=Copy WebDAV URL unlocked.label.mountFailed=Connecting drive failed From c8dadca564d0d59ffd55f788c5986f74d9a1e9f5 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 29 May 2017 09:31:04 +0200 Subject: [PATCH 4/8] =?UTF-8?q?Added=20=E2=80=9Cunmount=20without=20lock?= =?UTF-8?q?=E2=80=9D=20to=20UnlockedController?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/controllers/UnlockedController.java | 122 ++++++++++-------- .../java/org/cryptomator/ui/model/Vault.java | 2 +- main/ui/src/main/resources/fxml/unlocked.fxml | 3 + .../ui/src/main/resources/localization/en.txt | 1 + 4 files changed, 74 insertions(+), 54 deletions(-) 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 509b07b17..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,12 @@ 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; @@ -23,6 +25,8 @@ 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; @@ -82,6 +86,9 @@ public class UnlockedController implements ViewController { @FXML private MenuItem mountVaultMenuItem; + @FXML + private MenuItem unmountVaultMenuItem; + @FXML private MenuItem revealVaultMenuItem; @@ -96,8 +103,9 @@ public class UnlockedController implements ViewController { @Override public void initialize() { - revealVaultMenuItem.disableProperty().bind(vaultMounted.not()); 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); @@ -124,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 @@ -200,16 +163,67 @@ public class UnlockedController implements ViewController { asyncTaskService.asyncTaskOf(() -> { 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()); @@ -219,8 +233,10 @@ public class UnlockedController implements ViewController { asyncTaskService.asyncTaskOf(() -> { vault.reveal(); }).onSuccess(() -> { + LOG.trace("Reveal succeeded."); messageLabel.setText(null); - }).onError(CommandFailedException.class, () -> { + }).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 07d3ccc74..c7c4c9224 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 @@ -162,7 +162,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/resources/fxml/unlocked.fxml b/main/ui/src/main/resources/fxml/unlocked.fxml index 72eec0a7b..c29268378 100644 --- a/main/ui/src/main/resources/fxml/unlocked.fxml +++ b/main/ui/src/main/resources/fxml/unlocked.fxml @@ -31,6 +31,9 @@ + + + diff --git a/main/ui/src/main/resources/localization/en.txt b/main/ui/src/main/resources/localization/en.txt index 716e58b35..3cad2a15d 100644 --- a/main/ui/src/main/resources/localization/en.txt +++ b/main/ui/src/main/resources/localization/en.txt @@ -90,6 +90,7 @@ changePassword.errorMessage.decryptionFailed=Decryption failed # unlocked.fxml unlocked.button.lock=Lock Vault unlocked.moreOptions.mount=Mount Drive +unlocked.moreOptions.unmount=Eject Drive unlocked.moreOptions.reveal=Reveal Drive unlocked.moreOptions.copyUrl=Copy WebDAV URL unlocked.label.mountFailed=Connecting drive failed From c4b2a661523cd7f08be514c47dc2cc293879c9bc Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 29 May 2017 11:14:42 +0200 Subject: [PATCH 5/8] fixes #498 [ci skip] --- main/ant-kit/src/main/resources/package/linux/postinst | 1 + 1 file changed, 1 insertion(+) 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 From 8a6c43ee69e994b9f17cf53e42041f51914e9430 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 30 May 2017 17:58:36 +0200 Subject: [PATCH 6/8] Updated dependencies --- main/pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main/pom.xml b/main/pom.xml index a503e0c0a..745719e5d 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -28,7 +28,7 @@ 1.1.1 - 1.2.2 + 1.2.3 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 From df9fa9ebad4ea557bfc622fe1e3d822da7303ca5 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 1 Jun 2017 23:52:23 +0200 Subject: [PATCH 7/8] Fixes #507 --- main/pom.xml | 4 ++-- .../ui/src/main/java/org/cryptomator/ui/model/Vault.java | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/main/pom.xml b/main/pom.xml index 745719e5d..d1f8131ed 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -27,8 +27,8 @@ UTF-8 - 1.1.1 - 1.2.3 + 1.1.2 + 1.3.0 0.6.1 1.0.2 1.7.25 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 c7c4c9224..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,6 +31,7 @@ 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; @@ -79,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); @@ -99,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()); } From f9596ec2c1efb771413f998a914e5f9565dbb5a9 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 1 Jun 2017 23:53:30 +0200 Subject: [PATCH 8/8] Preparing release 1.3.0-rc7 --- main/ant-kit/pom.xml | 2 +- main/commons/pom.xml | 2 +- main/jacoco-report/pom.xml | 2 +- main/keychain/pom.xml | 2 +- main/launcher/pom.xml | 2 +- main/pom.xml | 2 +- main/uber-jar/pom.xml | 2 +- main/ui/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/main/ant-kit/pom.xml b/main/ant-kit/pom.xml index a2ebbfa6f..e14674f85 100644 --- a/main/ant-kit/pom.xml +++ b/main/ant-kit/pom.xml @@ -8,7 +8,7 @@ org.cryptomator main - 1.3.0-SNAPSHOT + 1.3.0-rc7 ant-kit pom diff --git a/main/commons/pom.xml b/main/commons/pom.xml index 2d4648849..6ed07dfac 100644 --- a/main/commons/pom.xml +++ b/main/commons/pom.xml @@ -10,7 +10,7 @@ org.cryptomator main - 1.3.0-SNAPSHOT + 1.3.0-rc7 commons Cryptomator Commons diff --git a/main/jacoco-report/pom.xml b/main/jacoco-report/pom.xml index 0ad5961f0..fd685566b 100644 --- a/main/jacoco-report/pom.xml +++ b/main/jacoco-report/pom.xml @@ -5,7 +5,7 @@ org.cryptomator main - 1.3.0-SNAPSHOT + 1.3.0-rc7 jacoco-report Cryptomator Code Coverage Report diff --git a/main/keychain/pom.xml b/main/keychain/pom.xml index e8e2cc871..bea437ab9 100644 --- a/main/keychain/pom.xml +++ b/main/keychain/pom.xml @@ -3,7 +3,7 @@ org.cryptomator main - 1.3.0-SNAPSHOT + 1.3.0-rc7 keychain System Keychain Access diff --git a/main/launcher/pom.xml b/main/launcher/pom.xml index 21cfb26ac..ea5bf3603 100644 --- a/main/launcher/pom.xml +++ b/main/launcher/pom.xml @@ -4,7 +4,7 @@ org.cryptomator main - 1.3.0-SNAPSHOT + 1.3.0-rc7 launcher Cryptomator Launcher diff --git a/main/pom.xml b/main/pom.xml index d1f8131ed..ff9103ce2 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -6,7 +6,7 @@ 4.0.0 org.cryptomator main - 1.3.0-SNAPSHOT + 1.3.0-rc7 pom Cryptomator diff --git a/main/uber-jar/pom.xml b/main/uber-jar/pom.xml index ef23b7fc6..54f7b106a 100644 --- a/main/uber-jar/pom.xml +++ b/main/uber-jar/pom.xml @@ -5,7 +5,7 @@ org.cryptomator main - 1.3.0-SNAPSHOT + 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 3d998b557..1631e43ff 100644 --- a/main/ui/pom.xml +++ b/main/ui/pom.xml @@ -12,7 +12,7 @@ org.cryptomator main - 1.3.0-SNAPSHOT + 1.3.0-rc7 ui Cryptomator GUI