Implemented #40, tested on macOS

This commit is contained in:
Sebastian Stenzel
2017-05-04 12:47:15 +02:00
parent e7157a64ed
commit d2a2e2304d
18 changed files with 245 additions and 99 deletions

View File

@@ -43,6 +43,7 @@ public final class LazyInitializer {
try {
return reference.updateAndGet(invokeFactoryIfNull(factory));
} catch (InitializationException e) {
Throwables.throwIfUnchecked(e);
Throwables.throwIfInstanceOf(e.getCause(), exceptionType);
throw e;
}

View File

@@ -34,20 +34,19 @@ public class Settings {
public static final String DEFAULT_GVFS_SCHEME = "dav";
public static final boolean DEFAULT_DEBUG_MODE = false;
private final Consumer<Settings> saveCmd;
private final ObservableList<VaultSettings> directories = FXCollections.observableArrayList();
private final ObservableList<VaultSettings> directories = FXCollections.observableArrayList(VaultSettings::observables);
private final BooleanProperty checkForUpdates = new SimpleBooleanProperty(DEFAULT_CHECK_FOR_UDPATES);
private final IntegerProperty port = new SimpleIntegerProperty(DEFAULT_PORT);
private final BooleanProperty useIpv6 = new SimpleBooleanProperty(DEFAULT_USE_IPV6);
private final IntegerProperty numTrayNotifications = new SimpleIntegerProperty(DEFAULT_NUM_TRAY_NOTIFICATIONS);
private final StringProperty preferredGvfsScheme = new SimpleStringProperty(DEFAULT_GVFS_SCHEME);
private final BooleanProperty debugMode = new SimpleBooleanProperty(DEFAULT_DEBUG_MODE);
private Consumer<Settings> saveCmd;
/**
* Package-private constructor; use {@link SettingsProvider}.
*/
Settings(Consumer<Settings> saveCmd) {
this.saveCmd = saveCmd;
Settings() {
directories.addListener((ListChangeListener.Change<? extends VaultSettings> change) -> this.save());
checkForUpdates.addListener(this::somethingChanged);
port.addListener(this::somethingChanged);
@@ -57,6 +56,10 @@ public class Settings {
debugMode.addListener(this::somethingChanged);
}
void setSaveCmd(Consumer<Settings> saveCmd) {
this.saveCmd = saveCmd;
}
private void somethingChanged(ObservableValue<?> observable, Object oldValue, Object newValue) {
this.save();
}

View File

@@ -8,7 +8,6 @@ package org.cryptomator.common.settings;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -22,13 +21,8 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
private static final Logger LOG = LoggerFactory.getLogger(SettingsJsonAdapter.class);
private final Consumer<Settings> saveCmd;
private final VaultSettingsJsonAdapter vaultSettingsJsonAdapter = new VaultSettingsJsonAdapter();
public SettingsJsonAdapter(Consumer<Settings> saveCmd) {
this.saveCmd = saveCmd;
}
@Override
public void write(JsonWriter out, Settings value) throws IOException {
out.beginObject();
@@ -53,14 +47,14 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
@Override
public Settings read(JsonReader in) throws IOException {
Settings settings = new Settings(saveCmd);
Settings settings = new Settings();
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
switch (name) {
case "directories":
settings.getDirectories().addAll(readVaultSettingsArray(in, settings));
settings.getDirectories().addAll(readVaultSettingsArray(in));
break;
case "checkForUpdatesEnabled":
settings.checkForUpdates().set(in.nextBoolean());
@@ -93,11 +87,11 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
return settings;
}
private List<VaultSettings> readVaultSettingsArray(JsonReader in, Settings settings) throws IOException {
private List<VaultSettings> readVaultSettingsArray(JsonReader in) throws IOException {
List<VaultSettings> result = new ArrayList<>();
in.beginArray();
while (!JsonToken.END_ARRAY.equals(in.peek())) {
result.add(vaultSettingsJsonAdapter.read(in, settings));
result.add(vaultSettingsJsonAdapter.read(in));
}
in.endArray();
return result;

View File

@@ -62,7 +62,7 @@ public class SettingsProvider implements Provider<Settings> {
private final ScheduledExecutorService saveScheduler = Executors.newSingleThreadScheduledExecutor();
private final AtomicReference<ScheduledFuture<?>> scheduledSaveCmd = new AtomicReference<>();
private final AtomicReference<Settings> settings = new AtomicReference<>();
private final SettingsJsonAdapter settingsJsonAdapter = new SettingsJsonAdapter(this::scheduleSave);
private final SettingsJsonAdapter settingsJsonAdapter = new SettingsJsonAdapter();
private final Gson gson;
@Inject
@@ -100,8 +100,9 @@ public class SettingsProvider implements Provider<Settings> {
LOG.info("Settings loaded from " + settingsPath);
} catch (IOException e) {
LOG.info("Failed to load settings, creating new one.");
settings = new Settings(this::scheduleSave);
settings = new Settings();
}
settings.setSaveCmd(this::scheduleSave);
return settings;
}

View File

@@ -15,41 +15,36 @@ import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.fxmisc.easybind.EasyBind;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
public class VaultSettings {
public static final boolean DEFAULT_UNLOCK_AFTER_STARTUP = false;
public static final boolean DEFAULT_MOUNT_AFTER_UNLOCK = true;
public static final boolean DEFAULT_REAVEAL_AFTER_MOUNT = true;
private final Settings settings;
private final String id;
private final ObjectProperty<Path> path = new SimpleObjectProperty<>();
private final StringProperty mountName = new SimpleStringProperty();
private final StringProperty winDriveLetter = new SimpleStringProperty();
private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP);
private final BooleanProperty mountAfterUnlock = new SimpleBooleanProperty(DEFAULT_MOUNT_AFTER_UNLOCK);
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REAVEAL_AFTER_MOUNT);
public VaultSettings(Settings settings, String id) {
this.settings = settings;
public VaultSettings(String id) {
this.id = Objects.requireNonNull(id);
EasyBind.subscribe(path, this::deriveMountNameFromPath);
path.addListener(this::somethingChanged);
mountName.addListener(this::somethingChanged);
winDriveLetter.addListener(this::somethingChanged);
mountAfterUnlock.addListener(this::somethingChanged);
revealAfterMount.addListener(this::somethingChanged);
}
private void somethingChanged(ObservableValue<?> observable, Object oldValue, Object newValue) {
settings.save();
Observable[] observables() {
return new Observable[] {path, mountName, winDriveLetter, unlockAfterStartup, mountAfterUnlock, revealAfterMount};
}
private void deriveMountNameFromPath(Path path) {
@@ -58,8 +53,8 @@ public class VaultSettings {
}
}
public static VaultSettings withRandomId(Settings settings) {
return new VaultSettings(settings, generateId());
public static VaultSettings withRandomId() {
return new VaultSettings(generateId());
}
private static String generateId() {
@@ -116,6 +111,10 @@ public class VaultSettings {
return winDriveLetter;
}
public BooleanProperty unlockAfterStartup() {
return unlockAfterStartup;
}
public BooleanProperty mountAfterUnlock() {
return mountAfterUnlock;
}

View File

@@ -24,18 +24,20 @@ class VaultSettingsJsonAdapter {
out.name("path").value(value.path().get().toString());
out.name("mountName").value(value.mountName().get());
out.name("winDriveLetter").value(value.winDriveLetter().get());
out.name("unlockAfterStartup").value(value.unlockAfterStartup().get());
out.name("mountAfterUnlock").value(value.mountAfterUnlock().get());
out.name("revealAfterMount").value(value.revealAfterMount().get());
out.endObject();
}
public VaultSettings read(JsonReader in, Settings settings) throws IOException {
public VaultSettings read(JsonReader in) throws IOException {
String id = null;
String path = null;
String mountName = null;
String winDriveLetter = null;
boolean mountAfterUnlock = true;
boolean revealAfterMount = true;
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
boolean mountAfterUnlock = VaultSettings.DEFAULT_MOUNT_AFTER_UNLOCK;
boolean revealAfterMount = VaultSettings.DEFAULT_REAVEAL_AFTER_MOUNT;
in.beginObject();
while (in.hasNext()) {
@@ -53,6 +55,9 @@ class VaultSettingsJsonAdapter {
case "winDriveLetter":
winDriveLetter = in.nextString();
break;
case "unlockAfterStartup":
unlockAfterStartup = in.nextBoolean();
break;
case "mountAfterUnlock":
mountAfterUnlock = in.nextBoolean();
break;
@@ -66,10 +71,11 @@ class VaultSettingsJsonAdapter {
}
in.endObject();
VaultSettings vaultSettings = (id == null) ? VaultSettings.withRandomId(settings) : new VaultSettings(settings, id);
VaultSettings vaultSettings = (id == null) ? VaultSettings.withRandomId() : new VaultSettings(id);
vaultSettings.mountName().set(mountName);
vaultSettings.path().set(Paths.get(path));
vaultSettings.winDriveLetter().set(winDriveLetter);
vaultSettings.unlockAfterStartup().set(unlockAfterStartup);
vaultSettings.mountAfterUnlock().set(mountAfterUnlock);
vaultSettings.revealAfterMount().set(revealAfterMount);
return vaultSettings;

View File

@@ -12,10 +12,7 @@ import org.junit.Test;
public class SettingsJsonAdapterTest {
private final SettingsJsonAdapter adapter = new SettingsJsonAdapter(this::noop);
private void noop(Settings settings) {
}
private final SettingsJsonAdapter adapter = new SettingsJsonAdapter();
@Test
public void testDeserialize() throws IOException {

View File

@@ -0,0 +1,33 @@
package org.cryptomator.common.settings;
import java.io.IOException;
import java.util.function.Consumer;
import org.junit.Test;
import org.mockito.Mockito;
public class SettingsTest {
@Test
public void testAutoSave() throws IOException {
@SuppressWarnings("unchecked")
Consumer<Settings> changeListener = Mockito.mock(Consumer.class);
Settings settings = new Settings();
settings.setSaveCmd(changeListener);
VaultSettings vaultSettings = VaultSettings.withRandomId();
Mockito.verify(changeListener, Mockito.times(0)).accept(settings);
// first change (to property):
settings.preferredGvfsScheme().set("asd");
Mockito.verify(changeListener, Mockito.times(1)).accept(settings);
// second change (to list):
settings.getDirectories().add(vaultSettings);
Mockito.verify(changeListener, Mockito.times(2)).accept(settings);
// third change (to property of list item):
vaultSettings.mountName().set("asd");
Mockito.verify(changeListener, Mockito.times(3)).accept(settings);
}
}

View File

@@ -11,7 +11,6 @@ import java.nio.file.Paths;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
import com.google.gson.stream.JsonReader;
@@ -23,9 +22,8 @@ public class VaultSettingsJsonAdapterTest {
public void testDeserialize() throws IOException {
String json = "{\"id\": \"foo\", \"path\": \"/foo/bar\", \"mountName\": \"test\", \"winDriveLetter\": \"X\", \"shouldBeIgnored\": true}";
JsonReader jsonReader = new JsonReader(new StringReader(json));
Settings settings = Mockito.mock(Settings.class);
VaultSettings vaultSettings = adapter.read(jsonReader, settings);
VaultSettings vaultSettings = adapter.read(jsonReader);
Assert.assertEquals("foo", vaultSettings.getId());
Assert.assertEquals(Paths.get("/foo/bar"), vaultSettings.path().get());
Assert.assertEquals("test", vaultSettings.mountName().get());

View File

@@ -26,10 +26,10 @@ import javax.inject.Named;
import javax.inject.Singleton;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.ui.ExitUtil;
import org.cryptomator.ui.controls.DirectoryListCell;
import org.cryptomator.ui.model.AutoUnlocker;
import org.cryptomator.ui.model.UpgradeStrategies;
import org.cryptomator.ui.model.UpgradeStrategy;
import org.cryptomator.ui.model.Vault;
@@ -50,7 +50,9 @@ import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.geometry.Side;
@@ -83,11 +85,11 @@ public class MainController implements ViewController {
private final Localization localization;
private final ExecutorService executorService;
private final BlockingQueue<Path> fileOpenRequests;
private final Settings settings;
private final VaultFactory vaultFactoy;
private final ViewControllerLoader viewControllerLoader;
private final ObjectProperty<ViewController> activeController = new SimpleObjectProperty<>();
private final VaultList vaults;
private final ObservableList<Vault> vaults;
private final BooleanBinding areAllVaultsLocked;
private final ObjectProperty<Vault> selectedVault = new SimpleObjectProperty<>();
private final BooleanExpression isSelectedVaultUnlocked = BooleanExpression.booleanExpression(EasyBind.select(selectedVault).selectObject(Vault::unlockedProperty).orElse(false));
private final BooleanExpression isSelectedVaultValid = BooleanExpression.booleanExpression(EasyBind.monadic(selectedVault).map(Vault::isValidVaultDirectory).orElse(false));
@@ -100,13 +102,12 @@ public class MainController implements ViewController {
@Inject
public MainController(@Named("mainWindow") Stage mainWindow, ExecutorService executorService, @Named("fileOpenRequests") BlockingQueue<Path> fileOpenRequests, ExitUtil exitUtil, Localization localization,
Settings settings, VaultFactory vaultFactoy, ViewControllerLoader viewControllerLoader, UpgradeStrategies upgradeStrategies, VaultList vaults) {
VaultFactory vaultFactoy, ViewControllerLoader viewControllerLoader, UpgradeStrategies upgradeStrategies, VaultList vaults, AutoUnlocker autoUnlocker) {
this.mainWindow = mainWindow;
this.executorService = executorService;
this.fileOpenRequests = fileOpenRequests;
this.exitUtil = exitUtil;
this.localization = localization;
this.settings = settings;
this.vaultFactoy = vaultFactoy;
this.viewControllerLoader = viewControllerLoader;
this.vaults = vaults;
@@ -114,6 +115,10 @@ public class MainController implements ViewController {
// derived bindings:
this.isShowingSettings = Bindings.equal(SettingsController.class, EasyBind.monadic(activeController).map(ViewController::getClass));
this.upgradeStrategyForSelectedVault = EasyBind.monadic(selectedVault).map(upgradeStrategies::getUpgradeStrategy);
this.areAllVaultsLocked = new SimpleBooleanProperty(false).not(); // = Bindings.isEmpty(FXCollections.observableList(vaults, Vault::observables).filtered(Vault::isUnlocked));
EasyBind.subscribe(areAllVaultsLocked, Platform::setImplicitExit);
autoUnlocker.unlockAllSilently();
}
@FXML
@@ -281,7 +286,7 @@ public class MainController implements ViewController {
}
final Vault vault = vaults.stream().filter(v -> v.getPath().equals(vaultPath)).findAny().orElseGet(() -> {
VaultSettings vaultSettings = VaultSettings.withRandomId(settings);
VaultSettings vaultSettings = VaultSettings.withRandomId();
vaultSettings.path().set(vaultPath);
return vaultFactoy.get(vaultSettings);
});
@@ -399,7 +404,6 @@ public class MainController implements ViewController {
}
public void didUnlock(Vault vault) {
Platform.setImplicitExit(false);
if (vault.equals(selectedVault.getValue())) {
this.showUnlockedView(vault);
}
@@ -417,9 +421,6 @@ public class MainController implements ViewController {
public void didLock(UnlockedController ctrl) {
unlockedVaults.remove(ctrl.getVault());
showUnlockView();
if (!vaults.stream().anyMatch(Vault::isUnlocked)) {
Platform.setImplicitExit(true);
}
}
private void showChangePasswordView() {

View File

@@ -17,6 +17,7 @@ import javax.inject.Inject;
import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
import org.cryptomator.frontend.webdav.ServerLifecycleException;
@@ -28,6 +29,8 @@ import org.cryptomator.ui.model.WindowsDriveLetters;
import org.cryptomator.ui.settings.Localization;
import org.cryptomator.ui.util.AsyncTaskService;
import org.cryptomator.ui.util.DialogBuilderUtil;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -72,6 +75,7 @@ public class UnlockController implements ViewController {
private final Optional<KeychainAccess> keychainAccess;
private Vault vault;
private Optional<UnlockListener> listener = Optional.empty();
private Subscription vaultSubs = Subscription.EMPTY;
@Inject
public UnlockController(Application app, Localization localization, AsyncTaskService asyncTaskService, WindowsDriveLetters driveLetters, Optional<KeychainAccess> keychainAccess) {
@@ -124,6 +128,9 @@ public class UnlockController implements ViewController {
@FXML
private GridPane root;
@FXML
private CheckBox unlockAfterStartup;
@Override
public void initialize() {
advancedOptions.managedProperty().bind(advancedOptions.visibleProperty());
@@ -133,6 +140,7 @@ public class UnlockController implements ViewController {
mountName.addEventFilter(KeyEvent.KEY_TYPED, this::filterAlphanumericKeyEvents);
mountName.textProperty().addListener(this::mountNameDidChange);
savePassword.setDisable(!keychainAccess.isPresent());
unlockAfterStartup.disableProperty().bind(savePassword.disabledProperty().or(savePassword.selectedProperty().not()));
if (SystemUtils.IS_OS_WINDOWS) {
winDriveLetter.setConverter(new WinDriveLetterLabelConverter());
} else {
@@ -149,18 +157,17 @@ public class UnlockController implements ViewController {
}
void setVault(Vault vault) {
// TODO overheadhunter refactor
if (this.vault != null) {
this.vault.getVaultSettings().mountAfterUnlock().unbind();
this.vault.getVaultSettings().revealAfterMount().unbind();
}
vaultSubs.unsubscribe();
vaultSubs = Subscription.EMPTY;
// trigger "default" change to refresh key bindings:
unlockButton.setDefaultButton(false);
unlockButton.setDefaultButton(true);
if (vault.equals(this.vault)) {
if (Objects.equals(this.vault, Objects.requireNonNull(vault))) {
return;
}
this.vault = Objects.requireNonNull(vault);
assert vault != null;
this.vault = vault;
passwordField.swipe();
advancedOptions.setVisible(false);
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.show"));
@@ -190,10 +197,18 @@ public class UnlockController implements ViewController {
Arrays.fill(storedPw, ' ');
}
}
mountAfterUnlock.setSelected(this.vault.getVaultSettings().mountAfterUnlock().get());
revealAfterMount.setSelected(this.vault.getVaultSettings().revealAfterMount().get());
this.vault.getVaultSettings().mountAfterUnlock().bind(mountAfterUnlock.selectedProperty());
this.vault.getVaultSettings().revealAfterMount().bind(revealAfterMount.selectedProperty());
VaultSettings settings = vault.getVaultSettings();
unlockAfterStartup.setSelected(savePassword.isSelected() && settings.unlockAfterStartup().get());
mountAfterUnlock.setSelected(settings.mountAfterUnlock().get());
revealAfterMount.setSelected(settings.revealAfterMount().get());
// settings.unlockAfterStartup().bind(unlockAfterStartup.selectedProperty());
// settings.mountAfterUnlock().bind(mountAfterUnlock.selectedProperty());
// settings.revealAfterMount().bind(revealAfterMount.selectedProperty());
vaultSubs = vaultSubs.and(EasyBind.subscribe(unlockAfterStartup.selectedProperty(), settings.unlockAfterStartup()::set));
vaultSubs = vaultSubs.and(EasyBind.subscribe(mountAfterUnlock.selectedProperty(), settings.mountAfterUnlock()::set));
vaultSubs = vaultSubs.and(EasyBind.subscribe(revealAfterMount.selectedProperty(), settings.revealAfterMount()::set));
}
// ****************************************

View File

@@ -0,0 +1,85 @@
package org.cryptomator.ui.model;
import java.nio.CharBuffer;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.frontend.webdav.mount.Mounter.CommandFailedException;
import org.cryptomator.keychain.KeychainAccess;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
public class AutoUnlocker {
private static final Logger LOG = LoggerFactory.getLogger(AutoUnlocker.class);
private final Optional<KeychainAccess> keychainAccess;
private final VaultList vaults;
private final ExecutorService executor;
@Inject
public AutoUnlocker(Optional<KeychainAccess> keychainAccess, VaultList vaults, ExecutorService executor) {
this.keychainAccess = keychainAccess;
this.vaults = vaults;
this.executor = executor;
}
public void unlockAllSilently() {
if (keychainAccess.isPresent()) {
vaults.stream().filter(this::shouldUnlockAfterStartup).map(this::createUnlockTask).forEach(executor::submit);
}
}
private boolean shouldUnlockAfterStartup(Vault vault) {
return vault.getVaultSettings().unlockAfterStartup().get();
}
private Runnable createUnlockTask(Vault vault) {
return () -> unlockSilently(vault);
}
private void unlockSilently(Vault vault) {
char[] storedPw = keychainAccess.get().loadPassphrase(vault.getId());
if (storedPw == null) {
LOG.warn("No passphrase stored in keychain for vault registered for auto unlocking: {}", vault.getPath());
}
try {
vault.unlock(CharBuffer.wrap(storedPw));
mountSilently(vault);
} catch (CryptoException e) {
LOG.error("Auto unlock failed.", e);
} finally {
Arrays.fill(storedPw, ' ');
}
}
private void mountSilently(Vault unlockedVault) {
if (!unlockedVault.getVaultSettings().mountAfterUnlock().get()) {
return;
}
try {
unlockedVault.mount();
revealSilently(unlockedVault);
} catch (CommandFailedException e) {
LOG.error("Auto unlock succeded, but mounting the drive failed.", e);
}
}
private void revealSilently(Vault mountedVault) {
if (!mountedVault.getVaultSettings().revealAfterMount().get()) {
return;
}
try {
mountedVault.reveal();
} catch (CommandFailedException e) {
LOG.error("Auto unlock succeded, but revealing the drive failed.", e);
}
}
}

View File

@@ -44,6 +44,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.binding.Binding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
@@ -77,7 +78,7 @@ public class Vault {
// Commands
// ********************************************************************************/
private CryptoFileSystem getCryptoFileSystem(CharSequence passphrase) throws IOException {
private CryptoFileSystem getCryptoFileSystem(CharSequence passphrase) throws IOException, CryptoException {
return LazyInitializer.initializeLazily(cryptoFileSystem, () -> createCryptoFileSystem(passphrase), IOException.class);
}
@@ -126,7 +127,7 @@ public class Vault {
}
}
public synchronized void mount() {
public synchronized void mount() throws CommandFailedException {
if (servlet == null) {
throw new IllegalStateException("Mounting requires unlocked WebDAV servlet.");
}
@@ -136,14 +137,10 @@ public class Vault {
.withPreferredGvfsScheme(settings.preferredGvfsScheme().get()) //
.build();
try {
mount = servlet.mount(mountParams);
Platform.runLater(() -> {
mounted.set(true);
});
} catch (CommandFailedException e) {
LOG.error("Unable to mount filesystem", e);
}
mount = servlet.mount(mountParams);
Platform.runLater(() -> {
mounted.set(true);
});
}
public synchronized void unmount() throws Exception {
@@ -190,6 +187,10 @@ public class Vault {
// Getter/Setter
// *******************************************************************************/
public Observable[] observables() {
return new Observable[] {unlockedProperty(), mountedProperty()};
}
public VaultSettings getVaultSettings() {
return vaultSettings;
}

View File

@@ -15,12 +15,11 @@ import javax.inject.Inject;
import javax.inject.Singleton;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.ui.model.VaultComponent.Builder;
@Singleton
public class VaultFactory {
private final Builder vaultComponentBuilder;
private final VaultComponent.Builder vaultComponentBuilder;
private final ConcurrentMap<VaultSettings, Vault> vaults = new ConcurrentHashMap<>();
@Inject

View File

@@ -5,8 +5,8 @@
*******************************************************************************/
package org.cryptomator.ui.model;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -14,7 +14,9 @@ import javax.inject.Singleton;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import javafx.collections.ListChangeListener.Change;
import com.google.common.collect.Lists;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.transformation.TransformationList;
@@ -59,15 +61,15 @@ public class VaultList extends TransformationList<Vault, VaultSettings> {
}
@Override
protected void sourceChanged(Change<? extends VaultSettings> c) {
protected void sourceChanged(ListChangeListener.Change<? extends VaultSettings> c) {
this.fireChange(new VaultListChange(c));
}
private class VaultListChange extends Change<Vault> {
private class VaultListChange extends ListChangeListener.Change<Vault> {
private final Change<? extends VaultSettings> delegate;
private final ListChangeListener.Change<? extends VaultSettings> delegate;
public VaultListChange(Change<? extends VaultSettings> delegate) {
public VaultListChange(ListChangeListener.Change<? extends VaultSettings> delegate) {
super(VaultList.this);
this.delegate = delegate;
}
@@ -77,6 +79,11 @@ public class VaultList extends TransformationList<Vault, VaultSettings> {
return delegate.next();
}
@Override
public boolean wasUpdated() {
return delegate.wasUpdated();
}
@Override
public void reset() {
delegate.reset();
@@ -94,27 +101,28 @@ public class VaultList extends TransformationList<Vault, VaultSettings> {
@Override
public List<Vault> getRemoved() {
List<Vault> removed = new ArrayList<>();
for (VaultSettings s : delegate.getRemoved()) {
removed.add(vaultFactory.get(s));
}
return removed;
return Lists.transform(delegate.getRemoved(), vaultFactory::get);
}
@Override
public boolean wasPermutated() {
return delegate.wasPermutated();
}
@Override
protected int[] getPermutation() {
if (delegate.wasPermutated()) {
int len = getTo() - getFrom();
int[] permutations = new int[len];
for (int i = 0; i < len; i++) {
permutations[i] = getPermutation(i);
}
return permutations;
return IntStream.range(getFrom(), getTo()).map(delegate::getPermutation).toArray();
} else {
return new int[0];
}
}
@Override
public String toString() {
return delegate.toString();
}
}
}

View File

@@ -53,7 +53,7 @@
<ChoiceBox GridPane.rowIndex="3" GridPane.columnIndex="1" fx:id="prefGvfsScheme" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
<!-- Row 4 -->
<Label GridPane.rowIndex="4" GridPane.columnIndex="0" fx:id="debugModeLabel" text="%settings.debugMode.label" cacheShape="true" cache="true" />
<Label GridPane.rowIndex="4" GridPane.columnIndex="0" text="%settings.debugMode.label" cacheShape="true" cache="true" />
<CheckBox GridPane.rowIndex="4" GridPane.columnIndex="1" fx:id="debugModeCheckbox" cacheShape="true" cache="true" />
</children>

View File

@@ -72,20 +72,24 @@
<CheckBox GridPane.rowIndex="1" GridPane.columnIndex="1" fx:id="savePassword" onAction="#didClickSavePasswordCheckbox" cacheShape="true" cache="true" />
<!-- Row 3.2 -->
<Label GridPane.rowIndex="2" GridPane.columnIndex="0" text="%unlock.label.mountAfterUnlock" cacheShape="true" cache="true" />
<CheckBox GridPane.rowIndex="2" GridPane.columnIndex="1" fx:id="mountAfterUnlock" cacheShape="true" cache="true" />
<Label GridPane.rowIndex="2" GridPane.columnIndex="0" text="%unlock.label.unlockAfterStartup" cacheShape="true" cache="true" />
<CheckBox GridPane.rowIndex="2" GridPane.columnIndex="1" fx:id="unlockAfterStartup" cacheShape="true" cache="true" />
<!-- Row 3.3 -->
<Label GridPane.rowIndex="3" GridPane.columnIndex="0" text="%unlock.label.mountName" cacheShape="true" cache="true" />
<TextField GridPane.rowIndex="3" GridPane.columnIndex="1" fx:id="mountName" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
<Label GridPane.rowIndex="3" GridPane.columnIndex="0" text="%unlock.label.mountAfterUnlock" cacheShape="true" cache="true" />
<CheckBox GridPane.rowIndex="3" GridPane.columnIndex="1" fx:id="mountAfterUnlock" cacheShape="true" cache="true" />
<!-- Row 3.4 -->
<Label GridPane.rowIndex="4" GridPane.columnIndex="0" text="%unlock.label.revealAfterMount" cacheShape="true" cache="true" />
<CheckBox GridPane.rowIndex="4" GridPane.columnIndex="1" fx:id="revealAfterMount" cacheShape="true" cache="true" />
<Label GridPane.rowIndex="4" GridPane.columnIndex="0" text="%unlock.label.mountName" cacheShape="true" cache="true" />
<TextField GridPane.rowIndex="4" GridPane.columnIndex="1" fx:id="mountName" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
<!-- Row 3.5 -->
<Label GridPane.rowIndex="5" GridPane.columnIndex="0" fx:id="winDriveLetterLabel" text="%unlock.label.winDriveLetter" cacheShape="true" cache="true" />
<ChoiceBox GridPane.rowIndex="5" GridPane.columnIndex="1" fx:id="winDriveLetter" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
<Label GridPane.rowIndex="5" GridPane.columnIndex="0" text="%unlock.label.revealAfterMount" cacheShape="true" cache="true" />
<CheckBox GridPane.rowIndex="5" GridPane.columnIndex="1" fx:id="revealAfterMount" cacheShape="true" cache="true" />
<!-- Row 3.6 -->
<Label GridPane.rowIndex="6" GridPane.columnIndex="0" fx:id="winDriveLetterLabel" text="%unlock.label.winDriveLetter" cacheShape="true" cache="true" />
<ChoiceBox GridPane.rowIndex="6" GridPane.columnIndex="1" fx:id="winDriveLetter" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
</GridPane>
<!-- Row 4 -->

View File

@@ -59,6 +59,7 @@ unlock.label.password=Password
unlock.label.savePassword=Save Password
unlock.label.mountAfterUnlock=Mount Drive
unlock.label.mountName=Drive Name
unlock.label.unlockAfterStartup=Unlock On Start
unlock.label.revealAfterMount=Reveal Drive
unlock.label.winDriveLetter=Drive Letter
unlock.label.downloadsPageLink=All Cryptomator versions