mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-22 20:51:27 +00:00
@@ -15,6 +15,7 @@ import org.cryptomator.common.settings.SettingsProvider;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultComponent;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.common.vaults.VaultListModule;
|
||||
import org.cryptomator.cryptolib.common.MasterkeyFileAccess;
|
||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
||||
import org.slf4j.Logger;
|
||||
@@ -37,7 +38,7 @@ import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@Module(subcomponents = {VaultComponent.class}, includes = {KeychainModule.class})
|
||||
@Module(subcomponents = {VaultComponent.class}, includes = {VaultListModule.class, KeychainModule.class})
|
||||
public abstract class CommonsModule {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CommonsModule.class);
|
||||
@@ -87,12 +88,6 @@ public abstract class CommonsModule {
|
||||
return settingsProvider.get();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static ObservableList<Vault> provideVaultList(VaultListManager vaultListManager) {
|
||||
return vaultListManager.getVaultList();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static ScheduledExecutorService provideScheduledExecutorService(ShutdownHook shutdownHook) {
|
||||
|
||||
@@ -37,6 +37,8 @@ public class VaultSettings {
|
||||
public static final String DEFAULT_MOUNT_FLAGS = "";
|
||||
public static final int DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH = -1;
|
||||
public static final WhenUnlocked DEFAULT_ACTION_AFTER_UNLOCK = WhenUnlocked.ASK;
|
||||
public static final boolean DEFAULT_AUTOLOCK_WHEN_IDLE = false;
|
||||
public static final int DEFAULT_AUTOLOCK_IDLE_SECONDS = 30 * 60;
|
||||
|
||||
private static final Random RNG = new Random();
|
||||
|
||||
@@ -52,7 +54,8 @@ public class VaultSettings {
|
||||
private final StringProperty mountFlags = new SimpleStringProperty(DEFAULT_MOUNT_FLAGS);
|
||||
private final IntegerProperty maxCleartextFilenameLength = new SimpleIntegerProperty(DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH);
|
||||
private final ObjectProperty<WhenUnlocked> actionAfterUnlock = new SimpleObjectProperty<>(DEFAULT_ACTION_AFTER_UNLOCK);
|
||||
|
||||
private final BooleanProperty autoLockWhenIdle = new SimpleBooleanProperty(DEFAULT_AUTOLOCK_WHEN_IDLE);
|
||||
private final IntegerProperty autoLockIdleSeconds = new SimpleIntegerProperty(DEFAULT_AUTOLOCK_IDLE_SECONDS);
|
||||
private final StringBinding mountName;
|
||||
|
||||
public VaultSettings(String id) {
|
||||
@@ -61,7 +64,7 @@ public class VaultSettings {
|
||||
}
|
||||
|
||||
Observable[] observables() {
|
||||
return new Observable[]{path, displayName, winDriveLetter, unlockAfterStartup, revealAfterMount, useCustomMountPath, customMountPath, usesReadOnlyMode, mountFlags, maxCleartextFilenameLength, actionAfterUnlock};
|
||||
return new Observable[]{path, displayName, winDriveLetter, unlockAfterStartup, revealAfterMount, useCustomMountPath, customMountPath, usesReadOnlyMode, mountFlags, maxCleartextFilenameLength, actionAfterUnlock, autoLockWhenIdle, autoLockIdleSeconds};
|
||||
}
|
||||
|
||||
public static VaultSettings withRandomId() {
|
||||
@@ -162,6 +165,14 @@ public class VaultSettings {
|
||||
return actionAfterUnlock.get();
|
||||
}
|
||||
|
||||
public BooleanProperty autoLockWhenIdle() {
|
||||
return autoLockWhenIdle;
|
||||
}
|
||||
|
||||
public IntegerProperty autoLockIdleSeconds() {
|
||||
return autoLockIdleSeconds;
|
||||
}
|
||||
|
||||
/* Hashcode/Equals */
|
||||
|
||||
@Override
|
||||
|
||||
@@ -31,6 +31,8 @@ class VaultSettingsJsonAdapter {
|
||||
out.name("mountFlags").value(value.mountFlags().get());
|
||||
out.name("maxCleartextFilenameLength").value(value.maxCleartextFilenameLength().get());
|
||||
out.name("actionAfterUnlock").value(value.actionAfterUnlock().get().name());
|
||||
out.name("autoLockWhenIdle").value(value.autoLockWhenIdle().get());
|
||||
out.name("autoLockIdleSeconds").value(value.autoLockIdleSeconds().get());
|
||||
out.endObject();
|
||||
}
|
||||
|
||||
@@ -48,6 +50,8 @@ class VaultSettingsJsonAdapter {
|
||||
String mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS;
|
||||
int maxCleartextFilenameLength = VaultSettings.DEFAULT_MAX_CLEARTEXT_FILENAME_LENGTH;
|
||||
WhenUnlocked actionAfterUnlock = VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
|
||||
boolean autoLockWhenIdle = VaultSettings.DEFAULT_AUTOLOCK_WHEN_IDLE;
|
||||
int autoLockIdleSeconds = VaultSettings.DEFAULT_AUTOLOCK_IDLE_SECONDS;
|
||||
|
||||
in.beginObject();
|
||||
while (in.hasNext()) {
|
||||
@@ -66,6 +70,8 @@ class VaultSettingsJsonAdapter {
|
||||
case "mountFlags" -> mountFlags = in.nextString();
|
||||
case "maxCleartextFilenameLength" -> maxCleartextFilenameLength = in.nextInt();
|
||||
case "actionAfterUnlock" -> actionAfterUnlock = parseActionAfterUnlock(in.nextString());
|
||||
case "autoLockWhenIdle" -> autoLockWhenIdle = in.nextBoolean();
|
||||
case "autoLockIdleSeconds" -> autoLockIdleSeconds = in.nextInt();
|
||||
default -> {
|
||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||
in.skipValue();
|
||||
@@ -90,6 +96,8 @@ class VaultSettingsJsonAdapter {
|
||||
vaultSettings.mountFlags().set(mountFlags);
|
||||
vaultSettings.maxCleartextFilenameLength().set(maxCleartextFilenameLength);
|
||||
vaultSettings.actionAfterUnlock().set(actionAfterUnlock);
|
||||
vaultSettings.autoLockWhenIdle().set(autoLockWhenIdle);
|
||||
vaultSettings.autoLockIdleSeconds().set(autoLockIdleSeconds);
|
||||
return vaultSettings;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import javafx.collections.ObservableList;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Singleton
|
||||
public class AutoLocker {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AutoLocker.class);
|
||||
|
||||
private final ScheduledExecutorService scheduler;
|
||||
private final ObservableList<Vault> vaultList;
|
||||
|
||||
@Inject
|
||||
public AutoLocker(ScheduledExecutorService scheduler, ObservableList<Vault> vaultList) {
|
||||
this.scheduler = scheduler;
|
||||
this.vaultList = vaultList;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
scheduler.scheduleAtFixedRate(this::tick, 0, 1, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
private void tick() {
|
||||
vaultList.stream() // all vaults
|
||||
.filter(Vault::isUnlocked) // unlocked vaults
|
||||
.filter(this::exceedsIdleTime) // idle vaults
|
||||
.forEach(this::autolock);
|
||||
}
|
||||
|
||||
private void autolock(Vault vault) {
|
||||
try {
|
||||
vault.lock(false);
|
||||
LOG.info("Autolocked {} after idle timeout", vault.getDisplayName());
|
||||
} catch (Volume.VolumeException | LockNotCompletedException e) {
|
||||
LOG.error("Autolocking failed.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean exceedsIdleTime(Vault vault) {
|
||||
assert vault.isUnlocked();
|
||||
// TODO: shouldn't we read these properties from within FX Application Thread?
|
||||
if (vault.getVaultSettings().autoLockWhenIdle().get()) {
|
||||
int maxIdleSeconds = vault.getVaultSettings().autoLockIdleSeconds().get();
|
||||
var deadline = vault.getStats().getLastActivity().plusSeconds(maxIdleSeconds);
|
||||
return deadline.isBefore(Instant.now());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -37,22 +37,21 @@ public class VaultListManager {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(VaultListManager.class);
|
||||
|
||||
private final AutoLocker autoLocker;
|
||||
private final VaultComponent.Builder vaultComponentBuilder;
|
||||
private final ObservableList<Vault> vaultList;
|
||||
private final String defaultVaultName;
|
||||
|
||||
@Inject
|
||||
public VaultListManager(VaultComponent.Builder vaultComponentBuilder, ResourceBundle resourceBundle, Settings settings) {
|
||||
public VaultListManager(ObservableList<Vault> vaultList, AutoLocker autoLocker, VaultComponent.Builder vaultComponentBuilder, ResourceBundle resourceBundle, Settings settings) {
|
||||
this.vaultList = vaultList;
|
||||
this.autoLocker = autoLocker;
|
||||
this.vaultComponentBuilder = vaultComponentBuilder;
|
||||
this.defaultVaultName = resourceBundle.getString("defaults.vault.vaultName");
|
||||
this.vaultList = FXCollections.observableArrayList(Vault::observables);
|
||||
|
||||
addAll(settings.getDirectories());
|
||||
vaultList.addListener(new VaultListChangeListener(settings.getDirectories()));
|
||||
}
|
||||
|
||||
public ObservableList<Vault> getVaultList() {
|
||||
return vaultList;
|
||||
autoLocker.init();
|
||||
}
|
||||
|
||||
public Vault add(Path pathToVault) throws IOException {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.cryptomator.common.vaults;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
@Module
|
||||
public class VaultListModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public ObservableList<Vault> provideVaultList() {
|
||||
return FXCollections.observableArrayList(Vault::observables);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,9 +13,11 @@ import javafx.beans.property.LongProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleLongProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.concurrent.ScheduledService;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.util.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
@@ -39,6 +41,7 @@ public class VaultStats {
|
||||
private final LongProperty totalBytesDecrypted = new SimpleLongProperty();
|
||||
private final LongProperty filesRead = new SimpleLongProperty();
|
||||
private final LongProperty filesWritten = new SimpleLongProperty();
|
||||
private final ObjectProperty<Instant> lastActivity = new SimpleObjectProperty<>();
|
||||
|
||||
@Inject
|
||||
VaultStats(AtomicReference<CryptoFileSystem> fs, VaultState state, ExecutorService executor) {
|
||||
@@ -73,9 +76,15 @@ public class VaultStats {
|
||||
toalBytesWritten.set(stats.map(CryptoFileSystemStats::pollTotalBytesWritten).orElse(0L));
|
||||
totalBytesEncrypted.set(stats.map(CryptoFileSystemStats::pollTotalBytesEncrypted).orElse(0L));
|
||||
totalBytesDecrypted.set(stats.map(CryptoFileSystemStats::pollTotalBytesDecrypted).orElse(0L));
|
||||
var oldAccessCount = filesRead.get() + filesWritten.get();
|
||||
filesRead.set(stats.map(CryptoFileSystemStats::pollAmountOfAccessesRead).orElse(0L));
|
||||
filesWritten.set(stats.map(CryptoFileSystemStats::pollAmountOfAccessesWritten).orElse(0L));
|
||||
var newAccessCount = filesRead.get() + filesWritten.get();
|
||||
|
||||
// check for any I/O activity
|
||||
if (newAccessCount > oldAccessCount) {
|
||||
lastActivity.set(Instant.now());
|
||||
}
|
||||
}
|
||||
|
||||
private double getCacheHitRate(CryptoFileSystemStats stats) {
|
||||
@@ -175,4 +184,12 @@ public class VaultStats {
|
||||
public LongProperty filesWritten() {return filesWritten;}
|
||||
|
||||
public long getFilesWritten() {return filesWritten.get();}
|
||||
|
||||
public ObjectProperty<Instant> lastActivityProperty() {
|
||||
return lastActivity;
|
||||
}
|
||||
|
||||
public Instant getLastActivity() {
|
||||
return lastActivity.get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ public enum FontAwesome5Icon {
|
||||
REDO("\uF01E"), //
|
||||
SEARCH("\uF002"), //
|
||||
SPINNER("\uF110"), //
|
||||
STOPWATCH("\uF2F2"), //
|
||||
SYNC("\uF021"), //
|
||||
TIMES("\uF00D"), //
|
||||
TRASH("\uF1F8"), //
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
package org.cryptomator.ui.fxapp;
|
||||
|
||||
import dagger.Lazy;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
import org.cryptomator.common.LicenseHolder;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.UiTheme;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.integrations.tray.TrayIntegrationProvider;
|
||||
import org.cryptomator.integrations.uiappearance.Theme;
|
||||
@@ -24,18 +33,12 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
import java.awt.desktop.QuitResponse;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@FxApplicationScoped
|
||||
public class FxApplication extends Application {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.cryptomator.ui.vaultoptions;
|
||||
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.controls.NumericTextField;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
@VaultOptionsScoped
|
||||
public class AutoLockVaultOptionsController implements FxController {
|
||||
|
||||
private final Vault vault;
|
||||
|
||||
public CheckBox lockAfterTimeCheckbox;
|
||||
public NumericTextField lockTimeInMinutesTextField;
|
||||
|
||||
@Inject
|
||||
AutoLockVaultOptionsController(@VaultOptionsWindow Vault vault) {
|
||||
this.vault = vault;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
lockAfterTimeCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().autoLockWhenIdle());
|
||||
Bindings.bindBidirectional(lockTimeInMinutesTextField.textProperty(), vault.getVaultSettings().autoLockIdleSeconds(), new IdleTimeSecondsConverter());
|
||||
}
|
||||
|
||||
private static class IdleTimeSecondsConverter extends StringConverter<Number> {
|
||||
|
||||
@Override
|
||||
public String toString(Number seconds) {
|
||||
int minutes = seconds.intValue() / 60; // int-truncate
|
||||
return Integer.toString(minutes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number fromString(String string) {
|
||||
try {
|
||||
int minutes = Integer.valueOf(string);
|
||||
return minutes * 60;
|
||||
} catch (NumberFormatException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,4 +20,10 @@ public enum SelectedVaultOptionsTab {
|
||||
* Show password tab
|
||||
*/
|
||||
KEY,
|
||||
|
||||
/**
|
||||
* Show Auto-Lock tab
|
||||
*
|
||||
*/
|
||||
AUTOLOCK,
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ public class VaultOptionsController implements FxController {
|
||||
public Tab generalTab;
|
||||
public Tab mountTab;
|
||||
public Tab keyTab;
|
||||
public Tab autoLockTab;
|
||||
|
||||
@Inject
|
||||
VaultOptionsController(@VaultOptionsWindow Stage window, ObjectProperty<SelectedVaultOptionsTab> selectedTabProperty) {
|
||||
@@ -47,6 +48,7 @@ public class VaultOptionsController implements FxController {
|
||||
case ANY, GENERAL -> generalTab;
|
||||
case MOUNT -> mountTab;
|
||||
case KEY -> keyTab;
|
||||
case AUTOLOCK -> autoLockTab;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -84,4 +84,9 @@ abstract class VaultOptionsModule {
|
||||
@FxControllerKey(MasterkeyOptionsController.class)
|
||||
abstract FxController bindMasterkeyOptionsController(MasterkeyOptionsController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(AutoLockVaultOptionsController.class)
|
||||
abstract FxController bindAutoLockVaultOptionsController(AutoLockVaultOptionsController controller);
|
||||
|
||||
}
|
||||
|
||||
@@ -36,5 +36,13 @@
|
||||
<fx:include source="/fxml/vault_options_masterkey.fxml"/>
|
||||
</content>
|
||||
</Tab>
|
||||
<Tab fx:id="autoLockTab" id="AUTOLOCK" text="%vaultOptions.autoLock">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="STOPWATCH"/>
|
||||
</graphic>
|
||||
<content>
|
||||
<fx:include source="/fxml/vault_options_autolock.fxml"/>
|
||||
</content>
|
||||
</Tab>
|
||||
</tabs>
|
||||
</TabPane>
|
||||
|
||||
26
main/ui/src/main/resources/fxml/vault_options_autolock.fxml
Normal file
26
main/ui/src/main/resources/fxml/vault_options_autolock.fxml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import org.cryptomator.ui.controls.FormattedLabel?>
|
||||
<?import org.cryptomator.ui.controls.NumericTextField?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.CheckBox?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Text?>
|
||||
<?import javafx.scene.text.TextFlow?>
|
||||
<VBox xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="org.cryptomator.ui.vaultoptions.AutoLockVaultOptionsController"
|
||||
spacing="6">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<children>
|
||||
<TextFlow styleClass="text-flow" prefWidth="-Infinity">
|
||||
<CheckBox text="%vaultOptions.autoLock.lockAfterTimePart1" fx:id="lockAfterTimeCheckbox"/>
|
||||
<Text text=" "/>
|
||||
<NumericTextField fx:id="lockTimeInMinutesTextField" prefWidth="50"/>
|
||||
<Text text=" "/>
|
||||
<FormattedLabel format="%vaultOptions.autoLock.lockAfterTimePart2"/>
|
||||
</TextFlow>
|
||||
</children>
|
||||
</VBox>
|
||||
@@ -329,6 +329,10 @@ vaultOptions.masterkey.forgetSavedPasswordBtn=Forget Saved Password
|
||||
vaultOptions.masterkey.recoveryKeyExpanation=A recovery key is your only means to restore access to a vault if you lose your password.
|
||||
vaultOptions.masterkey.showRecoveryKeyBtn=Display Recovery Key
|
||||
vaultOptions.masterkey.recoverPasswordBtn=Recover Password
|
||||
## Auto Lock
|
||||
vaultOptions.autoLock=Auto-Lock
|
||||
vaultOptions.autoLock.lockAfterTimePart1=Lock when idle for
|
||||
vaultOptions.autoLock.lockAfterTimePart2=minutes
|
||||
|
||||
# Recovery Key
|
||||
recoveryKey.title=Recovery Key
|
||||
|
||||
Reference in New Issue
Block a user