Merge branch 'release/1.3.0-rc7'

This commit is contained in:
Sebastian Stenzel
2017-06-02 00:15:01 +02:00
18 changed files with 360 additions and 180 deletions

View File

@@ -8,7 +8,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.3.0-rc6</version>
<version>1.3.0-rc7</version>
</parent>
<artifactId>ant-kit</artifactId>
<packaging>pom</packaging>

View File

@@ -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

View File

@@ -10,7 +10,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.3.0-rc6</version>
<version>1.3.0-rc7</version>
</parent>
<artifactId>commons</artifactId>
<name>Cryptomator Commons</name>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.3.0-rc6</version>
<version>1.3.0-rc7</version>
</parent>
<artifactId>jacoco-report</artifactId>
<name>Cryptomator Code Coverage Report</name>

View File

@@ -3,7 +3,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.3.0-rc6</version>
<version>1.3.0-rc7</version>
</parent>
<artifactId>keychain</artifactId>
<name>System Keychain Access</name>

View File

@@ -4,7 +4,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.3.0-rc6</version>
<version>1.3.0-rc7</version>
</parent>
<artifactId>launcher</artifactId>
<name>Cryptomator Launcher</name>

View File

@@ -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<Path> 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<File> ff = (List<File>) 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;
}
}
}

View File

@@ -6,7 +6,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.3.0-rc6</version>
<version>1.3.0-rc7</version>
<packaging>pom</packaging>
<name>Cryptomator</name>
@@ -27,8 +27,8 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- dependency versions -->
<cryptomator.cryptolib.version>1.1.1</cryptomator.cryptolib.version>
<cryptomator.cryptofs.version>1.2.2</cryptomator.cryptofs.version>
<cryptomator.cryptolib.version>1.1.2</cryptomator.cryptolib.version>
<cryptomator.cryptofs.version>1.3.0</cryptomator.cryptofs.version>
<cryptomator.webdav.version>0.6.1</cryptomator.webdav.version>
<cryptomator.jni.version>1.0.2</cryptomator.jni.version>
<slf4j.version>1.7.25</slf4j.version>
@@ -42,9 +42,9 @@
<commons-codec.version>1.10</commons-codec.version>
<httpclient.version>4.5.3</httpclient.version>
<mockito.version>2.7.21</mockito.version>
<dagger.version>2.10</dagger.version>
<dagger.version>2.11</dagger.version>
<easybind.version>1.0.3</easybind.version>
<guava.version>21.0</guava.version>
<guava.version>22.0</guava.version>
<gson.version>2.8.0</gson.version>
</properties>

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.3.0-rc6</version>
<version>1.3.0-rc7</version>
</parent>
<artifactId>uber-jar</artifactId>
<name>Single über jar with all dependencies</name>

View File

@@ -12,7 +12,7 @@
<parent>
<groupId>org.cryptomator</groupId>
<artifactId>main</artifactId>
<version>1.3.0-rc6</version>
<version>1.3.0-rc7</version>
</parent>
<artifactId>ui</artifactId>
<name>Cryptomator GUI</name>

View File

@@ -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"));
}

View File

@@ -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 */

View File

@@ -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> vault = new SimpleObjectProperty<>();
private final BooleanExpression vaultMounted = BooleanExpression.booleanExpression(EasyBind.select(vault).selectObject(Vault::mountedProperty).orElse(false));
private Optional<LockListener> 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<ButtonType> 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<ButtonType> 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();
}

View File

@@ -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();
}

View File

@@ -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<Void> 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 <ResultType> AsyncTaskWithoutSuccessHandler<ResultType> asyncTaskOf(SupplierThrowingException<ResultType, ?> task) {
return new AsyncTaskImpl<>(task);
}
@@ -153,27 +165,55 @@ public class AsyncTaskService {
public interface AsyncTaskWithoutSuccessHandler<ResultType> extends AsyncTaskWithoutErrorHandler {
/**
* @param handler Tasks to be invoked on the JavaFX application thread.
* @return The async task
*/
AsyncTaskWithoutErrorHandler onSuccess(ConsumerThrowingException<ResultType, ?> 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
*/
<ErrorType extends Throwable> AsyncTaskWithoutErrorHandler onError(Class<ErrorType> type, ConsumerThrowingException<ErrorType, ?> handler);
/**
* @param type Exception type to catch
* @param handler Tasks to be invoked on the JavaFX application thread.
* @return The async task
*/
<ErrorType extends Throwable> AsyncTaskWithoutErrorHandler onError(Class<ErrorType> 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();
}
}

View File

@@ -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<EawtApplicationWrapper> 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<List<File>> 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<File> files = (List<File>) 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<Object[], Object> handler) {
return new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals(methodName)) {
return handler.apply(args);
} else {
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 <code>operation</code>
* @throws UncheckedReflectiveOperationException in case <code>operation</code> throws an ReflectiveOperationException.
*/
private static <T> T uncheckedReflectiveOperation(SupplierThrowingException<T, ReflectiveOperationException> 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);
}
}
}

View File

@@ -28,6 +28,12 @@
<fx:define>
<ContextMenu fx:id="moreOptionsMenu">
<items>
<MenuItem fx:id="mountVaultMenuItem" text="%unlocked.moreOptions.mount" onAction="#didClickMountVault">
<graphic><Label text="&#xf139;" styleClass="ionicons"/></graphic>
</MenuItem>
<MenuItem fx:id="unmountVaultMenuItem" text="%unlocked.moreOptions.unmount" onAction="#didClickUnmountVault">
<graphic><Label text="&#xf131;" styleClass="ionicons"/></graphic>
</MenuItem>
<MenuItem fx:id="revealVaultMenuItem" text="%unlocked.moreOptions.reveal" onAction="#didClickRevealVault">
<graphic><Label text="&#xf133;" styleClass="ionicons"/></graphic>
</MenuItem>

View File

@@ -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,8 @@ 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