mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-21 04:01:27 +00:00
Merge pull request #3494 from cryptomator/feature/quick-access
Feature: Add unlocked vaults to an quick access area
This commit is contained in:
@@ -37,12 +37,18 @@ public class Settings {
|
||||
static final boolean DEFAULT_START_HIDDEN = false;
|
||||
static final boolean DEFAULT_AUTO_CLOSE_VAULTS = false;
|
||||
static final boolean DEFAULT_USE_KEYCHAIN = true;
|
||||
static final boolean DEFAULT_USE_QUICKACCESS = true;
|
||||
static final int DEFAULT_PORT = 42427;
|
||||
static final int DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
|
||||
static final boolean DEFAULT_DEBUG_MODE = false;
|
||||
static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
|
||||
@Deprecated // to be changed to "whatever is available" eventually
|
||||
static final String DEFAULT_KEYCHAIN_PROVIDER = SystemUtils.IS_OS_WINDOWS ? "org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess" : SystemUtils.IS_OS_MAC ? "org.cryptomator.macos.keychain.MacSystemKeychainAccess" : "org.cryptomator.linux.keychain.SecretServiceKeychainAccess";
|
||||
static final String DEFAULT_KEYCHAIN_PROVIDER = SystemUtils.IS_OS_WINDOWS ? "org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess" : //
|
||||
SystemUtils.IS_OS_MAC ? "org.cryptomator.macos.keychain.MacSystemKeychainAccess" : //
|
||||
"org.cryptomator.linux.keychain.SecretServiceKeychainAccess";
|
||||
static final String DEFAULT_QUICKACCESS_SERVICE = SystemUtils.IS_OS_WINDOWS ? "org.cryptomator.windows.quickaccess.ExplorerQuickAccessService" : //
|
||||
SystemUtils.IS_OS_LINUX ? "org.cryptomator.linux.quickaccess.NautilusBookmarks" : null;
|
||||
|
||||
static final String DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT.name();
|
||||
static final boolean DEFAULT_SHOW_MINIMIZE_BUTTON = false;
|
||||
public static final Instant DEFAULT_TIMESTAMP = Instant.parse("2000-01-01T00:00:00Z");
|
||||
@@ -57,6 +63,8 @@ public class Settings {
|
||||
public final BooleanProperty debugMode;
|
||||
public final ObjectProperty<UiTheme> theme;
|
||||
public final StringProperty keychainProvider;
|
||||
public final BooleanProperty useQuickAccess;
|
||||
public final StringProperty quickAccessService;
|
||||
public final ObjectProperty<NodeOrientation> userInterfaceOrientation;
|
||||
public final StringProperty licenseKey;
|
||||
public final BooleanProperty showMinimizeButton;
|
||||
@@ -89,6 +97,7 @@ public class Settings {
|
||||
this.startHidden = new SimpleBooleanProperty(this, "startHidden", json.startHidden);
|
||||
this.autoCloseVaults = new SimpleBooleanProperty(this, "autoCloseVaults", json.autoCloseVaults);
|
||||
this.useKeychain = new SimpleBooleanProperty(this, "useKeychain", json.useKeychain);
|
||||
this.useQuickAccess = new SimpleBooleanProperty(this, "addToQuickAccess", json.useQuickAccess);
|
||||
this.port = new SimpleIntegerProperty(this, "webDavPort", json.port);
|
||||
this.numTrayNotifications = new SimpleIntegerProperty(this, "numTrayNotifications", json.numTrayNotifications);
|
||||
this.debugMode = new SimpleBooleanProperty(this, "debugMode", json.debugMode);
|
||||
@@ -104,6 +113,7 @@ public class Settings {
|
||||
this.windowHeight = new SimpleIntegerProperty(this, "windowHeight", json.windowHeight);
|
||||
this.language = new SimpleStringProperty(this, "language", json.language);
|
||||
this.mountService = new SimpleStringProperty(this, "mountService", json.mountService);
|
||||
this.quickAccessService = new SimpleStringProperty(this, "quickAccessService", json.quickAccessService);
|
||||
this.lastSuccessfulUpdateCheck = new SimpleObjectProperty<>(this, "lastSuccessfulUpdateCheck", json.lastSuccessfulUpdateCheck);
|
||||
|
||||
this.directories.addAll(json.directories.stream().map(VaultSettings::new).toList());
|
||||
@@ -116,6 +126,7 @@ public class Settings {
|
||||
startHidden.addListener(this::somethingChanged);
|
||||
autoCloseVaults.addListener(this::somethingChanged);
|
||||
useKeychain.addListener(this::somethingChanged);
|
||||
useQuickAccess.addListener(this::somethingChanged);
|
||||
port.addListener(this::somethingChanged);
|
||||
numTrayNotifications.addListener(this::somethingChanged);
|
||||
debugMode.addListener(this::somethingChanged);
|
||||
@@ -131,6 +142,7 @@ public class Settings {
|
||||
windowHeight.addListener(this::somethingChanged);
|
||||
language.addListener(this::somethingChanged);
|
||||
mountService.addListener(this::somethingChanged);
|
||||
quickAccessService.addListener(this::somethingChanged);
|
||||
lastSuccessfulUpdateCheck.addListener(this::somethingChanged);
|
||||
}
|
||||
|
||||
@@ -170,6 +182,7 @@ public class Settings {
|
||||
json.startHidden = startHidden.get();
|
||||
json.autoCloseVaults = autoCloseVaults.get();
|
||||
json.useKeychain = useKeychain.get();
|
||||
json.useQuickAccess = useQuickAccess.get();
|
||||
json.port = port.get();
|
||||
json.numTrayNotifications = numTrayNotifications.get();
|
||||
json.debugMode = debugMode.get();
|
||||
@@ -185,6 +198,7 @@ public class Settings {
|
||||
json.windowHeight = windowHeight.get();
|
||||
json.language = language.get();
|
||||
json.mountService = mountService.get();
|
||||
json.quickAccessService = quickAccessService.get();
|
||||
json.lastSuccessfulUpdateCheck = lastSuccessfulUpdateCheck.get();
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -86,4 +86,9 @@ class SettingsJson {
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'", timezone = "UTC")
|
||||
Instant lastSuccessfulUpdateCheck = Settings.DEFAULT_TIMESTAMP;
|
||||
|
||||
@JsonProperty("useQuickAccess")
|
||||
boolean useQuickAccess = Settings.DEFAULT_USE_QUICKACCESS;
|
||||
|
||||
@JsonProperty("quickAccessService")
|
||||
String quickAccessService = Settings.DEFAULT_QUICKACCESS_SERVICE;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,10 @@ public class SettingsProvider implements Supplier<Settings> {
|
||||
}
|
||||
|
||||
private Settings load() {
|
||||
Settings settings = env.getSettingsPath().flatMap(this::tryLoad).findFirst().orElseGet(() -> Settings.create(env));
|
||||
Settings settings = env.getSettingsPath() //
|
||||
.flatMap(this::tryLoad) //
|
||||
.findFirst() //
|
||||
.orElseGet(() -> Settings.create(env));
|
||||
settings.setSaveCmd(this::scheduleSave);
|
||||
return settings;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ package org.cryptomator.common.vaults;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.Constants;
|
||||
import org.cryptomator.common.mount.Mounter;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.VaultSettings;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystem;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProperties;
|
||||
@@ -23,6 +24,9 @@ import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
|
||||
import org.cryptomator.integrations.mount.MountFailedException;
|
||||
import org.cryptomator.integrations.mount.Mountpoint;
|
||||
import org.cryptomator.integrations.mount.UnmountFailedException;
|
||||
import org.cryptomator.integrations.quickaccess.QuickAccessService;
|
||||
import org.cryptomator.integrations.quickaccess.QuickAccessServiceException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -54,6 +58,7 @@ public class Vault {
|
||||
|
||||
private final VaultSettings vaultSettings;
|
||||
private final AtomicReference<CryptoFileSystem> cryptoFileSystem;
|
||||
private final AtomicReference<QuickAccessService.QuickAccessEntry> quickAccessEntry;
|
||||
private final VaultState state;
|
||||
private final ObjectProperty<Exception> lastKnownException;
|
||||
private final VaultConfigCache configCache;
|
||||
@@ -67,6 +72,7 @@ public class Vault {
|
||||
private final BooleanBinding unknownError;
|
||||
private final ObjectBinding<Mountpoint> mountPoint;
|
||||
private final Mounter mounter;
|
||||
private final Settings settings;
|
||||
private final BooleanProperty showingStats;
|
||||
|
||||
private final AtomicReference<Mounter.MountHandle> mountHandle = new AtomicReference<>(null);
|
||||
@@ -78,7 +84,7 @@ public class Vault {
|
||||
VaultState state, //
|
||||
@Named("lastKnownException") ObjectProperty<Exception> lastKnownException, //
|
||||
VaultStats stats, //
|
||||
Mounter mounter) {
|
||||
Mounter mounter, Settings settings) {
|
||||
this.vaultSettings = vaultSettings;
|
||||
this.configCache = configCache;
|
||||
this.cryptoFileSystem = cryptoFileSystem;
|
||||
@@ -94,7 +100,9 @@ public class Vault {
|
||||
this.unknownError = Bindings.createBooleanBinding(this::isUnknownError, state);
|
||||
this.mountPoint = Bindings.createObjectBinding(this::getMountPoint, state);
|
||||
this.mounter = mounter;
|
||||
this.settings = settings;
|
||||
this.showingStats = new SimpleBooleanProperty(false);
|
||||
this.quickAccessEntry = new AtomicReference<>(null);
|
||||
}
|
||||
|
||||
// ******************************************************************************
|
||||
@@ -154,6 +162,9 @@ public class Vault {
|
||||
var rootPath = fs.getRootDirectories().iterator().next();
|
||||
var mountHandle = mounter.mount(vaultSettings, rootPath);
|
||||
success = this.mountHandle.compareAndSet(null, mountHandle);
|
||||
if (settings.useQuickAccess.getValue()) {
|
||||
addToQuickAccess();
|
||||
}
|
||||
} finally {
|
||||
if (!success) {
|
||||
destroyCryptoFileSystem();
|
||||
@@ -178,6 +189,7 @@ public class Vault {
|
||||
mountHandle.mountObj().close();
|
||||
mountHandle.specialCleanup().run();
|
||||
} finally {
|
||||
removeFromQuickAccess();
|
||||
destroyCryptoFileSystem();
|
||||
}
|
||||
|
||||
@@ -185,6 +197,52 @@ public class Vault {
|
||||
LOG.info("Locked vault '{}'", getDisplayName());
|
||||
}
|
||||
|
||||
private synchronized void addToQuickAccess() {
|
||||
if (quickAccessEntry.get() != null) {
|
||||
//we don't throw an exception since we don't wanna block unlocking
|
||||
LOG.warn("Vault already added to quick access area. Will be removed on next lock operation.");
|
||||
return;
|
||||
}
|
||||
|
||||
QuickAccessService.get() //
|
||||
.filter(s -> s.getClass().getName().equals(settings.quickAccessService.getValue())) //
|
||||
.findFirst() //
|
||||
.ifPresentOrElse( //
|
||||
this::addToQuickAccessInternal, //
|
||||
() -> LOG.warn("Unable to add Vault to quick access area: Desired implementation not available.") //
|
||||
);
|
||||
}
|
||||
|
||||
private void addToQuickAccessInternal(@NotNull QuickAccessService s) {
|
||||
if (getMountPoint() instanceof Mountpoint.WithPath mp) {
|
||||
try {
|
||||
var entry = s.add(mp.path(), getDisplayName());
|
||||
quickAccessEntry.set(entry);
|
||||
} catch (QuickAccessServiceException e) {
|
||||
LOG.error("Adding vault to quick access area failed", e);
|
||||
}
|
||||
} else {
|
||||
LOG.warn("Unable to add vault to quick access area: Vault is not mounted to local system path.");
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void removeFromQuickAccess() {
|
||||
if (quickAccessEntry.get() == null) {
|
||||
LOG.debug("Removing vault from quick access area: Entry not found, nothing to do.");
|
||||
return;
|
||||
}
|
||||
removeFromQuickAccessInternal();
|
||||
}
|
||||
|
||||
private void removeFromQuickAccessInternal() {
|
||||
try {
|
||||
quickAccessEntry.get().remove();
|
||||
quickAccessEntry.set(null);
|
||||
} catch (QuickAccessServiceException e) {
|
||||
LOG.error("Removing vault from quick access area failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
// ******************************************************************************
|
||||
// Observable Properties
|
||||
// *******************************************************************************
|
||||
|
||||
@@ -4,7 +4,9 @@ import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.integrations.autostart.AutoStartProvider;
|
||||
import org.cryptomator.integrations.autostart.ToggleAutoStartFailedException;
|
||||
import org.cryptomator.integrations.common.NamedServiceProvider;
|
||||
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
|
||||
import org.cryptomator.integrations.quickaccess.QuickAccessService;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationWindows;
|
||||
import org.slf4j.Logger;
|
||||
@@ -30,12 +32,15 @@ public class GeneralPreferencesController implements FxController {
|
||||
private final Stage window;
|
||||
private final Settings settings;
|
||||
private final Optional<AutoStartProvider> autoStartProvider;
|
||||
private final List<QuickAccessService> quickAccessServices;
|
||||
private final Application application;
|
||||
private final Environment environment;
|
||||
private final List<KeychainAccessProvider> keychainAccessProviders;
|
||||
private final FxApplicationWindows appWindows;
|
||||
public CheckBox useKeychainCheckbox;
|
||||
public ChoiceBox<KeychainAccessProvider> keychainBackendChoiceBox;
|
||||
public CheckBox useQuickAccessCheckbox;
|
||||
public ChoiceBox<QuickAccessService> quickAccessServiceChoiceBox;
|
||||
public CheckBox startHiddenCheckbox;
|
||||
public CheckBox autoCloseVaultsCheckbox;
|
||||
public CheckBox debugModeCheckbox;
|
||||
@@ -48,6 +53,7 @@ public class GeneralPreferencesController implements FxController {
|
||||
this.settings = settings;
|
||||
this.autoStartProvider = autoStartProvider;
|
||||
this.keychainAccessProviders = keychainAccessProviders;
|
||||
this.quickAccessServices = QuickAccessService.get().toList();
|
||||
this.application = application;
|
||||
this.environment = environment;
|
||||
this.appWindows = appWindows;
|
||||
@@ -60,13 +66,21 @@ public class GeneralPreferencesController implements FxController {
|
||||
debugModeCheckbox.selectedProperty().bindBidirectional(settings.debugMode);
|
||||
autoStartProvider.ifPresent(autoStart -> autoStartCheckbox.setSelected(autoStart.isEnabled()));
|
||||
|
||||
var keychainSettingsConverter = new KeychainProviderClassNameConverter(keychainAccessProviders);
|
||||
var keychainSettingsConverter = new ServiceToSettingsConverter<>(keychainAccessProviders);
|
||||
keychainBackendChoiceBox.getItems().addAll(keychainAccessProviders);
|
||||
keychainBackendChoiceBox.setValue(keychainSettingsConverter.fromString(settings.keychainProvider.get()));
|
||||
keychainBackendChoiceBox.setConverter(new KeychainProviderDisplayNameConverter());
|
||||
Bindings.bindBidirectional(settings.keychainProvider, keychainBackendChoiceBox.valueProperty(), keychainSettingsConverter);
|
||||
useKeychainCheckbox.selectedProperty().bindBidirectional(settings.useKeychain);
|
||||
keychainBackendChoiceBox.disableProperty().bind(useKeychainCheckbox.selectedProperty().not());
|
||||
|
||||
useQuickAccessCheckbox.selectedProperty().bindBidirectional(settings.useQuickAccess);
|
||||
var quickAccessSettingsConverter = new ServiceToSettingsConverter<>(quickAccessServices);
|
||||
quickAccessServiceChoiceBox.getItems().addAll(quickAccessServices);
|
||||
quickAccessServiceChoiceBox.setValue(quickAccessSettingsConverter.fromString(settings.quickAccessService.get()));
|
||||
quickAccessServiceChoiceBox.setConverter(new NamedServiceConverter<>());
|
||||
Bindings.bindBidirectional(settings.quickAccessService, quickAccessServiceChoiceBox.valueProperty(), quickAccessSettingsConverter);
|
||||
quickAccessServiceChoiceBox.disableProperty().bind(useQuickAccessCheckbox.selectedProperty().not());
|
||||
}
|
||||
|
||||
public boolean isAutoStartSupported() {
|
||||
@@ -91,6 +105,10 @@ public class GeneralPreferencesController implements FxController {
|
||||
});
|
||||
}
|
||||
|
||||
public boolean isSomeQuickAccessServiceAvailable() {
|
||||
return !quickAccessServices.isEmpty();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void showLogfileDirectory() {
|
||||
environment.getLogDir().ifPresent(logDirPath -> application.getHostServices().showDocument(logDirPath.toUri().toString()));
|
||||
@@ -116,29 +134,47 @@ public class GeneralPreferencesController implements FxController {
|
||||
|
||||
}
|
||||
|
||||
private static class KeychainProviderClassNameConverter extends StringConverter<KeychainAccessProvider> {
|
||||
|
||||
private final List<KeychainAccessProvider> keychainAccessProviders;
|
||||
|
||||
public KeychainProviderClassNameConverter(List<KeychainAccessProvider> keychainAccessProviders) {
|
||||
this.keychainAccessProviders = keychainAccessProviders;
|
||||
}
|
||||
private static class NamedServiceConverter<T extends NamedServiceProvider> extends StringConverter<T> {
|
||||
|
||||
@Override
|
||||
public String toString(KeychainAccessProvider provider) {
|
||||
if (provider == null) {
|
||||
public String toString(T namedService) {
|
||||
if (namedService == null) {
|
||||
return null;
|
||||
} else {
|
||||
return provider.getClass().getName();
|
||||
return namedService.getName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeychainAccessProvider fromString(String string) {
|
||||
public T fromString(String string) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ServiceToSettingsConverter<T> extends StringConverter<T> {
|
||||
|
||||
private final List<T> services;
|
||||
|
||||
public ServiceToSettingsConverter(List<T> services) {
|
||||
this.services = services;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(T service) {
|
||||
if (service == null) {
|
||||
return null;
|
||||
} else {
|
||||
return service.getClass().getName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T fromString(String string) {
|
||||
if (string == null) {
|
||||
return null;
|
||||
} else {
|
||||
return keychainAccessProviders.stream().filter(provider -> provider.getClass().getName().equals(string)).findAny().orElse(null);
|
||||
return services.stream().filter(provider -> provider.getClass().getName().equals(string)).findAny().orElse(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user