diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md
index 4775a0aa2..528dd76ad 100644
--- a/.github/ISSUE_TEMPLATE/bug.md
+++ b/.github/ISSUE_TEMPLATE/bug.md
@@ -5,8 +5,11 @@ labels: type:bug
---
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 000000000..fd88223e4
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,8 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Cryptomator Community
+ url: https://community.cryptomator.org/
+ about: Please ask and answer questions here
+ - name: Documentation
+ url: https://docs.cryptomator.org/
+ about: Get instructions on how to use Cryptomator
diff --git a/main/buildkit/pom.xml b/main/buildkit/pom.xml
index 5fb81e1b7..7249ec1da 100644
--- a/main/buildkit/pom.xml
+++ b/main/buildkit/pom.xml
@@ -4,7 +4,7 @@
org.cryptomatormain
- 1.5.10
+ 1.5.11buildkitpom
diff --git a/main/commons/pom.xml b/main/commons/pom.xml
index d973da126..8391a20ac 100644
--- a/main/commons/pom.xml
+++ b/main/commons/pom.xml
@@ -4,7 +4,7 @@
org.cryptomatormain
- 1.5.10
+ 1.5.11commonsCryptomator Commons
diff --git a/main/commons/src/main/java/org/cryptomator/common/LazyInitializer.java b/main/commons/src/main/java/org/cryptomator/common/LazyInitializer.java
deleted file mode 100644
index 7bca69457..000000000
--- a/main/commons/src/main/java/org/cryptomator/common/LazyInitializer.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*******************************************************************************
- * 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.common;
-
-import com.google.common.base.Throwables;
-
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Supplier;
-import java.util.function.UnaryOperator;
-
-public final class LazyInitializer {
-
- private LazyInitializer() {
- }
-
- /**
- * Same as {@link #initializeLazily(AtomicReference, SupplierThrowingException, Class)} except that no checked exception may be thrown by the factory function.
- *
- * @param Type of the value
- * @param reference A reference to a maybe not yet initialized value.
- * @param factory A factory providing a value for the reference, if it doesn't exist yet. The factory may be invoked multiple times, but only one result will survive.
- * @return The initialized value
- */
- public static T initializeLazily(AtomicReference reference, Supplier factory) {
- SupplierThrowingException factoryThrowingRuntimeExceptions = () -> factory.get();
- return initializeLazily(reference, factoryThrowingRuntimeExceptions, RuntimeException.class);
- }
-
- /**
- * Threadsafe lazy initialization pattern as proposed on http://stackoverflow.com/a/30247202/4014509
- *
- * @param Type of the value
- * @param Type of the any expected exception that may occur during initialization
- * @param reference A reference to a maybe not yet initialized value.
- * @param factory A factory providing a value for the reference, if it doesn't exist yet. The factory may be invoked multiple times, but only one result will survive.
- * @param exceptionType Expected exception type.
- * @return The initialized value
- * @throws E Exception thrown by the factory function.
- */
- public static T initializeLazily(AtomicReference reference, SupplierThrowingException factory, Class exceptionType) throws E {
- final T existing = reference.get();
- if (existing != null) {
- return existing;
- } else {
- try {
- return reference.updateAndGet(invokeFactoryIfNull(factory));
- } catch (InitializationException e) {
- Throwables.throwIfUnchecked(e.getCause());
- Throwables.throwIfInstanceOf(e.getCause(), exceptionType);
- throw e;
- }
- }
- }
-
- private static UnaryOperator invokeFactoryIfNull(SupplierThrowingException factory) throws InitializationException {
- return currentValue -> {
- if (currentValue == null) {
- try {
- return factory.get();
- } catch (Exception e) {
- throw new InitializationException(e);
- }
- } else {
- return currentValue;
- }
- };
- }
-
- private static class InitializationException extends RuntimeException {
-
- public InitializationException(Throwable cause) {
- super(cause);
- }
-
- }
-}
diff --git a/main/commons/src/main/java/org/cryptomator/common/mountpoint/MacVolumeMountChooser.java b/main/commons/src/main/java/org/cryptomator/common/mountpoint/MacVolumeMountChooser.java
index d6ebb4da5..334746860 100644
--- a/main/commons/src/main/java/org/cryptomator/common/mountpoint/MacVolumeMountChooser.java
+++ b/main/commons/src/main/java/org/cryptomator/common/mountpoint/MacVolumeMountChooser.java
@@ -3,25 +3,22 @@ package org.cryptomator.common.mountpoint;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.vaults.Volume;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import javax.inject.Inject;
-import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
class MacVolumeMountChooser implements MountPointChooser {
- private static final Logger LOG = LoggerFactory.getLogger(MacVolumeMountChooser.class);
- private static final int MAX_MOUNTPOINT_CREATION_RETRIES = 10;
private static final Path VOLUME_PATH = Path.of("/Volumes");
private final VaultSettings vaultSettings;
+ private final MountPointHelper helper;
@Inject
- public MacVolumeMountChooser(VaultSettings vaultSettings) {
+ public MacVolumeMountChooser(VaultSettings vaultSettings, MountPointHelper helper) {
this.vaultSettings = vaultSettings;
+ this.helper = helper;
}
@Override
@@ -31,26 +28,7 @@ class MacVolumeMountChooser implements MountPointChooser {
@Override
public Optional chooseMountPoint(Volume caller) {
- String basename = this.vaultSettings.mountName().get();
- // regular
- Path mountPoint = VOLUME_PATH.resolve(basename);
- if (Files.notExists(mountPoint)) {
- return Optional.of(mountPoint);
- }
- // with id
- mountPoint = VOLUME_PATH.resolve(basename + " (" + vaultSettings.getId() + ")");
- if (Files.notExists(mountPoint)) {
- return Optional.of(mountPoint);
- }
- // with id and count
- for (int i = 1; i < MAX_MOUNTPOINT_CREATION_RETRIES; i++) {
- mountPoint = VOLUME_PATH.resolve(basename + "_(" + vaultSettings.getId() + ")_" + i);
- if (Files.notExists(mountPoint)) {
- return Optional.of(mountPoint);
- }
- }
- LOG.error("Failed to find feasible mountpoint at /Volumes/{}_x. Giving up after {} attempts.", basename, MAX_MOUNTPOINT_CREATION_RETRIES);
- return Optional.empty();
+ return Optional.of(helper.chooseTemporaryMountPoint(vaultSettings, VOLUME_PATH));
}
@Override
diff --git a/main/commons/src/main/java/org/cryptomator/common/mountpoint/IrregularUnmountCleaner.java b/main/commons/src/main/java/org/cryptomator/common/mountpoint/MountPointHelper.java
similarity index 60%
rename from main/commons/src/main/java/org/cryptomator/common/mountpoint/IrregularUnmountCleaner.java
rename to main/commons/src/main/java/org/cryptomator/common/mountpoint/MountPointHelper.java
index 6bcb36f9a..704f2f62d 100644
--- a/main/commons/src/main/java/org/cryptomator/common/mountpoint/IrregularUnmountCleaner.java
+++ b/main/commons/src/main/java/org/cryptomator/common/mountpoint/MountPointHelper.java
@@ -1,11 +1,13 @@
package org.cryptomator.common.mountpoint;
import org.cryptomator.common.Environment;
+import org.cryptomator.common.settings.VaultSettings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
+import java.io.File;
import java.io.IOException;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.Files;
@@ -15,27 +17,50 @@ import java.nio.file.attribute.BasicFileAttributes;
import java.util.Optional;
@Singleton
-class IrregularUnmountCleaner {
+class MountPointHelper {
- public static Logger LOG = LoggerFactory.getLogger(IrregularUnmountCleaner.class);
+ public static Logger LOG = LoggerFactory.getLogger(MountPointHelper.class);
+ private static final int MAX_TMPMOUNTPOINT_CREATION_RETRIES = 10;
private final Optional tmpMountPointDir;
- private volatile boolean alreadyChecked = false;
+ private volatile boolean unmountDebrisCleared = false;
@Inject
- public IrregularUnmountCleaner(Environment env) {
+ public MountPointHelper(Environment env) {
this.tmpMountPointDir = env.getMountPointsDir();
}
+ public Path chooseTemporaryMountPoint(VaultSettings vaultSettings, Path parentDir) {
+ String basename = vaultSettings.mountName().get();
+ //regular
+ Path mountPoint = parentDir.resolve(basename);
+ if (Files.notExists(mountPoint)) {
+ return mountPoint;
+ }
+ //with id
+ mountPoint = parentDir.resolve(basename + " (" + vaultSettings.getId() + ")");
+ if (Files.notExists(mountPoint)) {
+ return mountPoint;
+ }
+ //with id and count
+ for (int i = 1; i < MAX_TMPMOUNTPOINT_CREATION_RETRIES; i++) {
+ mountPoint = parentDir.resolve(basename + "_(" + vaultSettings.getId() + ")_" + i);
+ if (Files.notExists(mountPoint)) {
+ return mountPoint;
+ }
+ }
+ LOG.error("Failed to find feasible mountpoint at {}{}{}_x. Giving up after {} attempts.", parentDir, File.separator, basename, MAX_TMPMOUNTPOINT_CREATION_RETRIES);
+ return null;
+ }
public synchronized void clearIrregularUnmountDebrisIfNeeded() {
- if (alreadyChecked || tmpMountPointDir.isEmpty()) {
- return; //nuthin to do
+ if (unmountDebrisCleared || tmpMountPointDir.isEmpty()) {
+ return; // nothing to do
}
if (Files.exists(tmpMountPointDir.get(), LinkOption.NOFOLLOW_LINKS)) {
clearIrregularUnmountDebris(tmpMountPointDir.get());
}
- alreadyChecked = true;
+ unmountDebrisCleared = true;
}
private void clearIrregularUnmountDebris(Path dirContainingMountPoints) {
@@ -66,13 +91,14 @@ class IrregularUnmountCleaner {
} catch (IOException e) {
LOG.warn("Unable to perform cleanup of mountpoint dir {}.", dirContainingMountPoints, e);
} finally {
- alreadyChecked = true;
+ unmountDebrisCleared = true;
}
}
private void deleteEmptyDir(Path dir) throws IOException {
assert Files.isDirectory(dir, LinkOption.NOFOLLOW_LINKS);
try {
+ ensureIsEmpty(dir);
Files.delete(dir); // attempt to delete dir non-recursively (will fail, if there are contents)
} catch (DirectoryNotEmptyException e) {
LOG.info("Found non-empty directory in mountpoint dir: {}", dir);
@@ -86,4 +112,9 @@ class IrregularUnmountCleaner {
}
}
+ private void ensureIsEmpty(Path dir) throws IOException {
+ if (Files.newDirectoryStream(dir).iterator().hasNext()) {
+ throw new DirectoryNotEmptyException(dir.toString());
+ }
+ }
}
diff --git a/main/commons/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java b/main/commons/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java
index 071ed7d1a..eb1d8d0b1 100644
--- a/main/commons/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java
+++ b/main/commons/src/main/java/org/cryptomator/common/mountpoint/TemporaryMountPointChooser.java
@@ -7,7 +7,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
-import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -16,18 +15,16 @@ import java.util.Optional;
class TemporaryMountPointChooser implements MountPointChooser {
private static final Logger LOG = LoggerFactory.getLogger(TemporaryMountPointChooser.class);
- private static final int MAX_TMPMOUNTPOINT_CREATION_RETRIES = 10;
private final VaultSettings vaultSettings;
private final Environment environment;
- private final IrregularUnmountCleaner cleaner;
- private volatile boolean clearedDebris;
+ private final MountPointHelper helper;
@Inject
- public TemporaryMountPointChooser(VaultSettings vaultSettings, Environment environment, IrregularUnmountCleaner cleaner) {
+ public TemporaryMountPointChooser(VaultSettings vaultSettings, Environment environment, MountPointHelper helper) {
this.vaultSettings = vaultSettings;
this.environment = environment;
- this.cleaner = cleaner;
+ this.helper = helper;
}
@Override
@@ -44,32 +41,8 @@ class TemporaryMountPointChooser implements MountPointChooser {
assert environment.getMountPointsDir().isPresent();
//clean leftovers of not-regularly unmounted vaults
//see https://github.com/cryptomator/cryptomator/issues/1013 and https://github.com/cryptomator/cryptomator/issues/1061
- cleaner.clearIrregularUnmountDebrisIfNeeded();
- return this.environment.getMountPointsDir().map(this::choose);
- }
-
-
- private Path choose(Path parent) {
- String basename = this.vaultSettings.mountName().get();
- //regular
- Path mountPoint = parent.resolve(basename);
- if (Files.notExists(mountPoint)) {
- return mountPoint;
- }
- //with id
- mountPoint = parent.resolve(basename + " (" + vaultSettings.getId() + ")");
- if (Files.notExists(mountPoint)) {
- return mountPoint;
- }
- //with id and count
- for (int i = 1; i < MAX_TMPMOUNTPOINT_CREATION_RETRIES; i++) {
- mountPoint = parent.resolve(basename + "_(" + vaultSettings.getId() + ")_" + i);
- if (Files.notExists(mountPoint)) {
- return mountPoint;
- }
- }
- LOG.error("Failed to find feasible mountpoint at {}{}{}_x. Giving up after {} attempts.", parent, File.separator, basename, MAX_TMPMOUNTPOINT_CREATION_RETRIES);
- return null;
+ helper.clearIrregularUnmountDebrisIfNeeded();
+ return this.environment.getMountPointsDir().map(dir -> this.helper.chooseTemporaryMountPoint(this.vaultSettings, dir));
}
@Override
diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/SettingsProvider.java b/main/commons/src/main/java/org/cryptomator/common/settings/SettingsProvider.java
index 390e40f5a..85bc436a2 100644
--- a/main/commons/src/main/java/org/cryptomator/common/settings/SettingsProvider.java
+++ b/main/commons/src/main/java/org/cryptomator/common/settings/SettingsProvider.java
@@ -8,13 +8,13 @@
*******************************************************************************/
package org.cryptomator.common.settings;
+import com.google.common.base.Suppliers;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import org.cryptomator.common.Environment;
-import org.cryptomator.common.LazyInitializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -48,7 +48,7 @@ public class SettingsProvider implements Supplier {
private static final long SAVE_DELAY_MS = 1000;
private final AtomicReference> scheduledSaveCmd = new AtomicReference<>();
- private final AtomicReference settings = new AtomicReference<>();
+ private final Supplier settings = Suppliers.memoize(this::load);
private final SettingsJsonAdapter settingsJsonAdapter = new SettingsJsonAdapter();
private final Environment env;
private final ScheduledExecutorService scheduler;
@@ -66,7 +66,7 @@ public class SettingsProvider implements Supplier {
@Override
public Settings get() {
- return LazyInitializer.initializeLazily(settings, this::load);
+ return settings.get();
}
private Settings load() {
diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/AbstractVolume.java b/main/commons/src/main/java/org/cryptomator/common/vaults/AbstractVolume.java
index 6a09683ca..d66ad19d8 100644
--- a/main/commons/src/main/java/org/cryptomator/common/vaults/AbstractVolume.java
+++ b/main/commons/src/main/java/org/cryptomator/common/vaults/AbstractVolume.java
@@ -20,7 +20,8 @@ public abstract class AbstractVolume implements Volume {
}
protected Path determineMountPoint() throws InvalidMountPointException {
- for (var chooser : Iterables.filter(choosers, c -> c.isApplicable(this))) {
+ var applicableChoosers = Iterables.filter(choosers, c -> c.isApplicable(this));
+ for (var chooser : applicableChoosers) {
Optional chosenPath = chooser.chooseMountPoint(this);
if (chosenPath.isEmpty()) { // chooser couldn't find a feasible mountpoint
continue;
@@ -29,7 +30,7 @@ public abstract class AbstractVolume implements Volume {
this.usedChooser = chooser;
return chosenPath.get();
}
- throw new InvalidMountPointException("No feasible MountPoint found!");
+ throw new InvalidMountPointException(String.format("No feasible MountPoint found by choosers: %s", applicableChoosers));
}
protected void cleanupMountPoint() {
diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java b/main/commons/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java
index 89fd73203..e0379e9db 100644
--- a/main/commons/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java
+++ b/main/commons/src/main/java/org/cryptomator/common/vaults/DokanyVolume.java
@@ -13,7 +13,6 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
-import java.util.SortedSet;
import java.util.concurrent.ExecutorService;
public class DokanyVolume extends AbstractVolume {
@@ -42,7 +41,6 @@ public class DokanyVolume extends AbstractVolume {
@Override
public void mount(CryptoFileSystem fs, String mountFlags) throws InvalidMountPointException, VolumeException {
this.mountPoint = determineMountPoint();
- String mountName = vaultSettings.mountName().get();
try {
this.mount = mountFactory.mount(fs.getPath("/"), mountPoint, vaultSettings.mountName().get(), FS_TYPE_NAME, mountFlags.strip());
} catch (MountFailedException e) {
@@ -62,11 +60,25 @@ public class DokanyVolume extends AbstractVolume {
}
@Override
- public void unmount() {
- mount.close();
+ public void unmount() throws VolumeException {
+ try {
+ mount.unmount();
+ } catch (IllegalStateException e) {
+ throw new VolumeException("Unmount Failed.", e);
+ }
cleanupMountPoint();
}
+ @Override
+ public void unmountForced() {
+ mount.unmountForced();
+ cleanupMountPoint();
+ }
+
+ @Override
+ public boolean supportsForcedUnmount() {
+ return true;
+ }
@Override
public boolean isSupported() {
return DokanyVolume.isSupportedStatic();
diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/Vault.java b/main/commons/src/main/java/org/cryptomator/common/vaults/Vault.java
index f2ab973f6..730f0fb6d 100644
--- a/main/commons/src/main/java/org/cryptomator/common/vaults/Vault.java
+++ b/main/commons/src/main/java/org/cryptomator/common/vaults/Vault.java
@@ -10,7 +10,6 @@ package org.cryptomator.common.vaults;
import com.google.common.base.Strings;
import org.apache.commons.lang3.SystemUtils;
-import org.cryptomator.common.LazyInitializer;
import org.cryptomator.common.mountpoint.InvalidMountPointException;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.common.vaults.Volume.VolumeException;
@@ -100,11 +99,7 @@ public class Vault {
// Commands
// ********************************************************************************/
- private CryptoFileSystem getCryptoFileSystem(CharSequence passphrase) throws NoSuchFileException, IOException, InvalidPassphraseException, CryptoException {
- return LazyInitializer.initializeLazily(cryptoFileSystem, () -> unlockCryptoFileSystem(passphrase), IOException.class);
- }
-
- private CryptoFileSystem unlockCryptoFileSystem(CharSequence passphrase) throws NoSuchFileException, IOException, InvalidPassphraseException, CryptoException {
+ private CryptoFileSystem createCryptoFileSystem(CharSequence passphrase) throws NoSuchFileException, IOException, InvalidPassphraseException, CryptoException {
Set flags = EnumSet.noneOf(FileSystemFlags.class);
if (vaultSettings.usesReadOnlyMode().get()) {
flags.add(FileSystemFlags.READONLY);
@@ -127,9 +122,14 @@ public class Vault {
}
public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException, VolumeException, InvalidMountPointException {
- CryptoFileSystem fs = getCryptoFileSystem(passphrase);
- volume = volumeProvider.get();
- volume.mount(fs, getEffectiveMountFlags());
+ if (cryptoFileSystem.get() == null) {
+ CryptoFileSystem fs = createCryptoFileSystem(passphrase);
+ cryptoFileSystem.set(fs);
+ volume = volumeProvider.get();
+ volume.mount(fs, getEffectiveMountFlags());
+ } else {
+ throw new IllegalStateException("Already unlocked.");
+ }
}
public synchronized void lock(boolean forced) throws VolumeException {
diff --git a/main/commons/src/main/java/org/cryptomator/common/vaults/VaultModule.java b/main/commons/src/main/java/org/cryptomator/common/vaults/VaultModule.java
index e9fe26957..56a2814cf 100644
--- a/main/commons/src/main/java/org/cryptomator/common/vaults/VaultModule.java
+++ b/main/commons/src/main/java/org/cryptomator/common/vaults/VaultModule.java
@@ -156,7 +156,7 @@ public class VaultModule {
//See: https://github.com/billziss-gh/winfsp/issues/319
if (!readOnly.get()) {
flags.append(" -ouid=-1");
- flags.append(" -ogid=-1");
+ flags.append(" -ogid=11");
}
flags.append(" -ovolname=").append('"').append(mountName.get()).append('"');
//Dokany requires this option to be set, WinFSP doesn't seem to share this peculiarity,
diff --git a/main/commons/src/test/java/org/cryptomator/common/vaults/VaultModuleTest.java b/main/commons/src/test/java/org/cryptomator/common/vaults/VaultModuleTest.java
index 9b25ebb08..835c05e16 100644
--- a/main/commons/src/test/java/org/cryptomator/common/vaults/VaultModuleTest.java
+++ b/main/commons/src/test/java/org/cryptomator/common/vaults/VaultModuleTest.java
@@ -43,7 +43,7 @@ public class VaultModuleTest {
StringBinding result = module.provideDefaultMountFlags(settings, vaultSettings);
- MatcherAssert.assertThat(result.get(), CoreMatchers.containsString("-ovolname=TEST"));
+ MatcherAssert.assertThat(result.get(), CoreMatchers.containsString("-ovolname=\"TEST\""));
MatcherAssert.assertThat(result.get(), CoreMatchers.containsString("-ordonly"));
}
diff --git a/main/launcher/pom.xml b/main/launcher/pom.xml
index b21876369..0c6192086 100644
--- a/main/launcher/pom.xml
+++ b/main/launcher/pom.xml
@@ -4,7 +4,7 @@
org.cryptomatormain
- 1.5.10
+ 1.5.11launcherCryptomator Launcher
diff --git a/main/pom.xml b/main/pom.xml
index a558f8dea..6e8157d1a 100644
--- a/main/pom.xml
+++ b/main/pom.xml
@@ -3,7 +3,7 @@
4.0.0org.cryptomatormain
- 1.5.10
+ 1.5.11pomCryptomator
@@ -26,12 +26,12 @@
1.9.130.1.6
- 0.1.0-beta1
+ 0.2.00.1.0-beta30.1.0-beta2
- 1.2.5
- 1.2.0
- 1.0.13
+ 1.2.6
+ 1.2.1
+ 1.0.1415
@@ -175,6 +175,13 @@
java-jwt${jwt.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.10.5.1
+
+
@@ -223,6 +230,13 @@
${javafx.version}test
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.10.5.1
+
@@ -325,6 +339,32 @@
+
+ dependency-check
+
+
+
+ org.owasp
+ dependency-check-maven
+ 6.0.3
+
+ 24
+ 0
+ true
+ true
+ suppression.xml
+
+
+
+
+ check
+
+
+
+
+
+
+
diff --git a/main/suppression.xml b/main/suppression.xml
new file mode 100644
index 000000000..6fe12f417
--- /dev/null
+++ b/main/suppression.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+ com.fasterxml.jackson.core:jackson-databind:2.10.5.1
+ CVE-2020-25649
+
+
+
+ ^org\.cryptomator:fuse-nio-adapter:.*$
+ 9
+
+
+
+ ^com\.github\.serceman:jnr-fuse:.*$
+ 9
+
+
\ No newline at end of file
diff --git a/main/ui/pom.xml b/main/ui/pom.xml
index 08251656b..00a3d46d9 100644
--- a/main/ui/pom.xml
+++ b/main/ui/pom.xml
@@ -4,7 +4,7 @@
org.cryptomatormain
- 1.5.10
+ 1.5.11uiCryptomator GUI
diff --git a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java
index 1efd5ba50..4eeea5c3d 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java
@@ -1,6 +1,5 @@
package org.cryptomator.ui.addvaultwizard;
-import com.tobiasdiez.easybind.EasyBind;
import dagger.Lazy;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;
@@ -84,11 +83,11 @@ public class CreateNewVaultLocationController implements FxController {
public void initialize() {
predefinedLocationToggler.selectedToggleProperty().addListener(this::togglePredefinedLocation);
usePresetPath.bind(predefinedLocationToggler.selectedToggleProperty().isNotEqualTo(customRadioButton));
- EasyBind.subscribe(vaultPath, this::vaultPathDidChange);
+ vaultPath.addListener(this::vaultPathDidChange);
}
- private void vaultPathDidChange(Path newValue) {
- if (newValue != null && !Files.notExists(newValue)) {
+ private void vaultPathDidChange(@SuppressWarnings("unused") ObservableValue extends Path> observable, @SuppressWarnings("unused") Path oldValue, Path newValue) {
+ if (!Files.notExists(newValue)) {
warningText.set(resourceBundle.getString("addvaultwizard.new.fileAlreadyExists"));
} else {
warningText.set(null);
diff --git a/main/ui/src/main/java/org/cryptomator/ui/common/FxmlFile.java b/main/ui/src/main/java/org/cryptomator/ui/common/FxmlFile.java
index 43074a605..310e11747 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/common/FxmlFile.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/common/FxmlFile.java
@@ -11,6 +11,8 @@ public enum FxmlFile {
CHANGEPASSWORD("/fxml/changepassword.fxml"), //
ERROR("/fxml/error.fxml"), //
FORGET_PASSWORD("/fxml/forget_password.fxml"), //
+ LOCK_FORCED("/fxml/lock_forced.fxml"), //
+ LOCK_FAILED("/fxml/lock_failed.fxml"), //
MAIN_WINDOW("/fxml/main_window.fxml"), //
MIGRATION_CAPABILITY_ERROR("/fxml/migration_capability_error.fxml"), //
MIGRATION_IMPOSSIBLE("/fxml/migration_impossible.fxml"),
diff --git a/main/ui/src/main/java/org/cryptomator/ui/common/VaultService.java b/main/ui/src/main/java/org/cryptomator/ui/common/VaultService.java
index 35ed49870..1f4ffc3fd 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/common/VaultService.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/common/VaultService.java
@@ -65,6 +65,7 @@ public class VaultService {
public Task createLockTask(Vault vault, boolean forced) {
Task task = new LockVaultTask(vault, forced);
task.setOnSucceeded(evt -> LOG.info("Locked {}", vault.getDisplayName()));
+ task.setOnFailed(evt -> LOG.info("Failed to lock {}.", vault.getDisplayName(), evt.getSource().getException()));
return task;
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java b/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java
index 650254649..1a4cb84de 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java
@@ -1,6 +1,5 @@
package org.cryptomator.ui.fxapp;
-import com.tobiasdiez.easybind.EasyBind;
import dagger.Lazy;
import org.cryptomator.common.LicenseHolder;
import org.cryptomator.common.settings.Settings;
@@ -12,6 +11,7 @@ import org.cryptomator.integrations.uiappearance.UiAppearanceException;
import org.cryptomator.integrations.uiappearance.UiAppearanceListener;
import org.cryptomator.integrations.uiappearance.UiAppearanceProvider;
import org.cryptomator.ui.common.VaultService;
+import org.cryptomator.ui.lock.LockComponent;
import org.cryptomator.ui.mainwindow.MainWindowComponent;
import org.cryptomator.ui.preferences.PreferencesComponent;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
@@ -27,8 +27,9 @@ import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.value.ObservableValue;
-import javafx.collections.ObservableSet;
+import javafx.collections.ObservableList;
import javafx.stage.Stage;
+import javafx.stage.Window;
import java.awt.desktop.QuitResponse;
import java.util.Optional;
@@ -40,34 +41,38 @@ public class FxApplication extends Application {
private final Settings settings;
private final Lazy mainWindow;
private final Lazy preferencesWindow;
+ private final Lazy quitWindow;
private final Provider unlockWindowBuilderProvider;
- private final Provider quitWindowBuilderProvider;
+ private final Provider lockWindowBuilderProvider;
private final Optional trayIntegration;
private final Optional appearanceProvider;
private final VaultService vaultService;
private final LicenseHolder licenseHolder;
- private final BooleanBinding hasVisibleStages;
+ private final ObservableList visibleWindows;
+ private final BooleanBinding hasVisibleWindows;
private final UiAppearanceListener systemInterfaceThemeListener = this::systemInterfaceThemeChanged;
@Inject
- FxApplication(Settings settings, Lazy mainWindow, Lazy preferencesWindow, Provider unlockWindowBuilderProvider, Provider quitWindowBuilderProvider, Optional trayIntegration, Optional appearanceProvider, VaultService vaultService, LicenseHolder licenseHolder, ObservableSet visibleStages) {
+ FxApplication(Settings settings, Lazy mainWindow, Lazy preferencesWindow, Provider unlockWindowBuilderProvider, Provider lockWindowBuilderProvider, Lazy quitWindow, Optional trayIntegration, Optional appearanceProvider, VaultService vaultService, LicenseHolder licenseHolder) {
this.settings = settings;
this.mainWindow = mainWindow;
this.preferencesWindow = preferencesWindow;
this.unlockWindowBuilderProvider = unlockWindowBuilderProvider;
- this.quitWindowBuilderProvider = quitWindowBuilderProvider;
+ this.lockWindowBuilderProvider = lockWindowBuilderProvider;
+ this.quitWindow = quitWindow;
this.trayIntegration = trayIntegration;
this.appearanceProvider = appearanceProvider;
this.vaultService = vaultService;
this.licenseHolder = licenseHolder;
- this.hasVisibleStages = Bindings.isNotEmpty(visibleStages);
+ this.visibleWindows = Stage.getWindows().filtered(Window::isShowing);
+ this.hasVisibleWindows = Bindings.isNotEmpty(visibleWindows);
}
public void start() {
LOG.trace("FxApplication.start()");
Platform.setImplicitExit(false);
- EasyBind.subscribe(hasVisibleStages, this::hasVisibleStagesChanged);
+ hasVisibleWindows.addListener(this::hasVisibleStagesChanged);
settings.theme().addListener(this::appThemeChanged);
loadSelectedStyleSheet(settings.theme().get());
@@ -78,7 +83,8 @@ public class FxApplication extends Application {
throw new UnsupportedOperationException("Use start() instead.");
}
- private void hasVisibleStagesChanged(boolean newValue) {
+ private void hasVisibleStagesChanged(@SuppressWarnings("unused") ObservableValue extends Boolean> observableValue, @SuppressWarnings("unused") boolean oldValue, boolean newValue) {
+ LOG.warn("has visible stages: {}", newValue);
if (newValue) {
trayIntegration.ifPresent(TrayIntegrationProvider::restoredFromTray);
} else {
@@ -107,9 +113,16 @@ public class FxApplication extends Application {
});
}
+ public void startLockWorkflow(Vault vault, Optional owner) {
+ Platform.runLater(() -> {
+ lockWindowBuilderProvider.get().vault(vault).owner(owner).build().startLockWorkflow();
+ LOG.debug("Start lock workflow for {}", vault.getDisplayName());
+ });
+ }
+
public void showQuitWindow(QuitResponse response) {
Platform.runLater(() -> {
- quitWindowBuilderProvider.get().quitResponse(response).build().showQuitWindow();
+ quitWindow.get().showQuitWindow(response);
LOG.debug("Showing QuitWindow");
});
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java b/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java
index a6297ce4a..74c201372 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java
@@ -11,6 +11,7 @@ import dagger.Provides;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.StageFactory;
+import org.cryptomator.ui.lock.LockComponent;
import org.cryptomator.ui.mainwindow.MainWindowComponent;
import org.cryptomator.ui.preferences.PreferencesComponent;
import org.cryptomator.ui.quit.QuitComponent;
@@ -18,7 +19,6 @@ import org.cryptomator.ui.unlock.UnlockComponent;
import javax.inject.Named;
import javafx.application.Application;
-import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import javafx.scene.image.Image;
import javafx.stage.Stage;
@@ -28,15 +28,9 @@ import java.io.UncheckedIOException;
import java.util.Collections;
import java.util.List;
-@Module(includes = {UpdateCheckerModule.class}, subcomponents = {MainWindowComponent.class, PreferencesComponent.class, UnlockComponent.class, QuitComponent.class, ErrorComponent.class})
+@Module(includes = {UpdateCheckerModule.class}, subcomponents = {MainWindowComponent.class, PreferencesComponent.class, UnlockComponent.class, LockComponent.class, QuitComponent.class, ErrorComponent.class})
abstract class FxApplicationModule {
- @Provides
- @FxApplicationScoped
- static ObservableSet provideVisibleStages() {
- return FXCollections.observableSet();
- }
-
@Provides
@Named("windowIcons")
@FxApplicationScoped
@@ -56,16 +50,9 @@ abstract class FxApplicationModule {
@Provides
@FxApplicationScoped
- static StageFactory provideStageFactory(@Named("windowIcons") List windowIcons, ObservableSet visibleStages) {
+ static StageFactory provideStageFactory(@Named("windowIcons") List windowIcons) {
return new StageFactory(stage -> {
stage.getIcons().addAll(windowIcons);
- stage.showingProperty().addListener((observableValue, wasShowing, isShowing) -> {
- if (isShowing) {
- visibleStages.add(stage);
- } else {
- visibleStages.remove(stage);
- }
- });
});
}
@@ -88,4 +75,8 @@ abstract class FxApplicationModule {
return builder.build();
}
+ @Provides
+ static QuitComponent provideQuitComponent(QuitComponent.Builder builder) {
+ return builder.build();
+ }
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/lock/LockComponent.java b/main/ui/src/main/java/org/cryptomator/ui/lock/LockComponent.java
new file mode 100644
index 000000000..9796c88c7
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/lock/LockComponent.java
@@ -0,0 +1,39 @@
+package org.cryptomator.ui.lock;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+import org.cryptomator.common.vaults.Vault;
+
+import javax.inject.Named;
+import javafx.stage.Stage;
+import java.util.Optional;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+
+@LockScoped
+@Subcomponent(modules = {LockModule.class})
+public interface LockComponent {
+
+ ExecutorService defaultExecutorService();
+
+ LockWorkflow lockWorkflow();
+
+ default Future startLockWorkflow() {
+ LockWorkflow workflow = lockWorkflow();
+ defaultExecutorService().submit(workflow);
+ return workflow;
+ }
+
+ @Subcomponent.Builder
+ interface Builder {
+
+ @BindsInstance
+ LockComponent.Builder vault(@LockWindow Vault vault);
+
+ @BindsInstance
+ LockComponent.Builder owner(@Named("lockWindowOwner") Optional owner);
+
+ LockComponent build();
+ }
+}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/lock/LockFailedController.java b/main/ui/src/main/java/org/cryptomator/ui/lock/LockFailedController.java
new file mode 100644
index 000000000..e46d1d868
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/lock/LockFailedController.java
@@ -0,0 +1,31 @@
+package org.cryptomator.ui.lock;
+
+import org.cryptomator.common.vaults.Vault;
+import org.cryptomator.ui.common.FxController;
+
+import javax.inject.Inject;
+import javafx.fxml.FXML;
+import javafx.stage.Stage;
+
+@LockScoped
+public class LockFailedController implements FxController {
+
+ private final Stage window;
+ private final Vault vault;
+
+ @Inject
+ public LockFailedController(@LockWindow Stage window, @LockWindow Vault vault) {
+ this.window = window;
+ this.vault = vault;
+ }
+
+ @FXML
+ public void close() {
+ window.close();
+ }
+
+ // ----- Getter & Setter -----
+ public String getVaultName() {
+ return vault.getDisplayName();
+ }
+}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/lock/LockForcedController.java b/main/ui/src/main/java/org/cryptomator/ui/lock/LockForcedController.java
new file mode 100644
index 000000000..8d4ce32d3
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/lock/LockForcedController.java
@@ -0,0 +1,57 @@
+package org.cryptomator.ui.lock;
+
+import org.cryptomator.common.vaults.Vault;
+import org.cryptomator.ui.common.FxController;
+import org.cryptomator.ui.common.UserInteractionLock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import javafx.fxml.FXML;
+import javafx.stage.Stage;
+import javafx.stage.WindowEvent;
+
+@LockScoped
+public class LockForcedController implements FxController {
+
+ private static final Logger LOG = LoggerFactory.getLogger(LockForcedController.class);
+
+ private final Stage window;
+ private final Vault vault;
+ private final UserInteractionLock forceLockDecisionLock;
+
+ @Inject
+ public LockForcedController(@LockWindow Stage window, @LockWindow Vault vault, UserInteractionLock forceLockDecisionLock) {
+ this.window = window;
+ this.vault = vault;
+ this.forceLockDecisionLock = forceLockDecisionLock;
+ this.window.setOnHiding(this::windowClosed);
+ }
+
+ @FXML
+ public void cancel() {
+ forceLockDecisionLock.interacted(LockModule.ForceLockDecision.CANCEL);
+ window.close();
+ }
+
+ @FXML
+ public void confirmForcedLock() {
+ forceLockDecisionLock.interacted(LockModule.ForceLockDecision.FORCE);
+ window.close();
+ }
+
+ private void windowClosed(WindowEvent windowEvent) {
+ // if not already interacted, set the decision to CANCEL
+ if (forceLockDecisionLock.awaitingInteraction().get()) {
+ LOG.debug("Lock canceled in force-lock-phase by user.");
+ forceLockDecisionLock.interacted(LockModule.ForceLockDecision.CANCEL);
+ }
+ }
+
+ // ----- Getter & Setter -----
+
+ public String getVaultName() {
+ return vault.getDisplayName();
+ }
+
+}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/lock/LockModule.java b/main/ui/src/main/java/org/cryptomator/ui/lock/LockModule.java
new file mode 100644
index 000000000..bbc8c2209
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/lock/LockModule.java
@@ -0,0 +1,89 @@
+package org.cryptomator.ui.lock;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+import dagger.multibindings.IntoMap;
+import org.cryptomator.common.vaults.Vault;
+import org.cryptomator.ui.common.DefaultSceneFactory;
+import org.cryptomator.ui.common.FXMLLoaderFactory;
+import org.cryptomator.ui.common.FxController;
+import org.cryptomator.ui.common.FxControllerKey;
+import org.cryptomator.ui.common.FxmlFile;
+import org.cryptomator.ui.common.FxmlScene;
+import org.cryptomator.ui.common.StageFactory;
+import org.cryptomator.ui.common.UserInteractionLock;
+
+import javax.inject.Named;
+import javax.inject.Provider;
+import javafx.scene.Scene;
+import javafx.stage.Modality;
+import javafx.stage.Stage;
+import java.util.Map;
+import java.util.Optional;
+import java.util.ResourceBundle;
+
+@Module
+abstract class LockModule {
+
+ enum ForceLockDecision {
+ CANCEL,
+ FORCE;
+ }
+
+ @Provides
+ @LockScoped
+ static UserInteractionLock provideForceLockDecisionLock() {
+ return new UserInteractionLock<>(null);
+ }
+
+ @Provides
+ @LockWindow
+ @LockScoped
+ static FXMLLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
+ return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle);
+ }
+
+ @Provides
+ @LockWindow
+ @LockScoped
+ static Stage provideWindow(StageFactory factory, @LockWindow Vault vault, @Named("lockWindowOwner") Optional owner) {
+ Stage stage = factory.create();
+ stage.setTitle(vault.getDisplayName());
+ stage.setResizable(false);
+ if (owner.isPresent()) {
+ stage.initOwner(owner.get());
+ stage.initModality(Modality.WINDOW_MODAL);
+ } else {
+ stage.initModality(Modality.APPLICATION_MODAL);
+ }
+ return stage;
+ }
+
+ @Provides
+ @FxmlScene(FxmlFile.LOCK_FORCED)
+ @LockScoped
+ static Scene provideForceLockScene(@LockWindow FXMLLoaderFactory fxmlLoaders) {
+ return fxmlLoaders.createScene("/fxml/lock_forced.fxml");
+ }
+
+ @Provides
+ @FxmlScene(FxmlFile.LOCK_FAILED)
+ @LockScoped
+ static Scene provideLockFailedScene(@LockWindow FXMLLoaderFactory fxmlLoaders) {
+ return fxmlLoaders.createScene("/fxml/lock_failed.fxml");
+ }
+
+ // ------------------
+
+ @Binds
+ @IntoMap
+ @FxControllerKey(LockForcedController.class)
+ abstract FxController bindLockForcedController(LockForcedController controller);
+
+ @Binds
+ @IntoMap
+ @FxControllerKey(LockFailedController.class)
+ abstract FxController bindLockFailedController(LockFailedController controller);
+
+}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/lock/LockScoped.java b/main/ui/src/main/java/org/cryptomator/ui/lock/LockScoped.java
new file mode 100644
index 000000000..68d05f6e0
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/lock/LockScoped.java
@@ -0,0 +1,13 @@
+package org.cryptomator.ui.lock;
+
+import javax.inject.Scope;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Scope
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@interface LockScoped {
+
+}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/lock/LockWindow.java b/main/ui/src/main/java/org/cryptomator/ui/lock/LockWindow.java
new file mode 100644
index 000000000..10d6445ab
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/lock/LockWindow.java
@@ -0,0 +1,14 @@
+package org.cryptomator.ui.lock;
+
+import javax.inject.Qualifier;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+@interface LockWindow {
+
+}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/lock/LockWorkflow.java b/main/ui/src/main/java/org/cryptomator/ui/lock/LockWorkflow.java
new file mode 100644
index 000000000..acb6d1355
--- /dev/null
+++ b/main/ui/src/main/java/org/cryptomator/ui/lock/LockWorkflow.java
@@ -0,0 +1,105 @@
+package org.cryptomator.ui.lock;
+
+import dagger.Lazy;
+import org.cryptomator.common.vaults.Vault;
+import org.cryptomator.common.vaults.VaultState;
+import org.cryptomator.common.vaults.Volume;
+import org.cryptomator.ui.common.FxmlFile;
+import org.cryptomator.ui.common.FxmlScene;
+import org.cryptomator.ui.common.UserInteractionLock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import javafx.application.Platform;
+import javafx.concurrent.Task;
+import javafx.scene.Scene;
+import javafx.stage.Stage;
+import javafx.stage.Window;
+
+/**
+ * The sequence of actions performed and checked during lock of a vault.
+ *
+ * This class implements the Task interface, sucht that it can run in the background with some possible forground operations/requests to the ui, without blocking the main app.
+ * If the task state is
+ *
- * Two strategies are implemented for this feature, the first uses the registry and the second one the autostart folder.
- *
- * The registry strategy checks/add/removes at the registry key {@value HKCU_AUTOSTART_KEY} an entry for Cryptomator.
- * The folder strategy checks/add/removes at the location {@value WINDOWS_START_MENU_ENTRY}.
- *
- * To check if the feature is active, both strategies are applied.
- * To enable the feature, first the registry is tried and only on failure the autostart folder is used.
- * To disable it, first it is determined by an internal state, which strategies must be used and in the second step those are executed.
- *
- * @apiNote This class is not thread safe, hence it should be avoided to call its methods simultaniously by different threads.
- * @deprecated To be moved to integration-win project
- */
-@Deprecated
-class AutoStartWinStrategy implements AutoStartStrategy {
-
- private static final Logger LOG = LoggerFactory.getLogger(AutoStartWinStrategy.class);
- private static final String HKCU_AUTOSTART_KEY = "\"HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\"";
- private static final String AUTOSTART_VALUE = "Cryptomator";
- private static final String WINDOWS_START_MENU_ENTRY = "\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Cryptomator.lnk";
-
- private final String exePath;
-
- private boolean activatedUsingFolder;
- private boolean activatedUsingRegistry;
-
- public AutoStartWinStrategy(String exePath) {
- this.exePath = exePath;
- this.activatedUsingFolder = false;
- this.activatedUsingRegistry = false;
- }
-
- @Override
- public CompletionStage isAutoStartEnabled() {
- return isAutoStartEnabledUsingRegistry().thenCombine(isAutoStartEnabledUsingFolder(), (bReg, bFolder) -> bReg || bFolder);
- }
-
- private CompletableFuture isAutoStartEnabledUsingFolder() {
- Path autoStartEntry = Path.of(System.getProperty("user.home") + WINDOWS_START_MENU_ENTRY);
- this.activatedUsingFolder = Files.exists(autoStartEntry);
- return CompletableFuture.completedFuture(activatedUsingFolder);
- }
-
- private CompletableFuture isAutoStartEnabledUsingRegistry() {
- ProcessBuilder regQuery = new ProcessBuilder("reg", "query", HKCU_AUTOSTART_KEY, //
- "/v", AUTOSTART_VALUE);
- try {
- Process proc = regQuery.start();
- return proc.onExit().thenApply(p -> {
- this.activatedUsingRegistry = p.exitValue() == 0;
- return activatedUsingRegistry;
- });
- } catch (IOException e) {
- LOG.debug("Failed to query {} from registry key {}", AUTOSTART_VALUE, HKCU_AUTOSTART_KEY);
- return CompletableFuture.completedFuture(false);
- }
- }
-
- @Override
- public void enableAutoStart() throws TogglingAutoStartFailedException {
- try {
- enableAutoStartUsingRegistry().thenAccept((Void v) -> this.activatedUsingRegistry = true).exceptionallyCompose(e -> {
- LOG.debug("Falling back to using autostart folder.");
- return this.enableAutoStartUsingFolder();
- }).thenAccept((Void v) -> this.activatedUsingFolder = true).get();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- throw new TogglingAutoStartFailedException("Execution of enabling auto start setting was interrupted.");
- } catch (ExecutionException e) {
- throw new TogglingAutoStartFailedException("Enabling auto start failed both using registry and auto start folder.");
- }
- }
-
- private CompletableFuture enableAutoStartUsingRegistry() {
- ProcessBuilder regAdd = new ProcessBuilder("reg", "add", HKCU_AUTOSTART_KEY, //
- "/v", AUTOSTART_VALUE, //
- "/t", "REG_SZ", //
- "/d", "\"" + exePath + "\"", //
- "/f");
- try {
- Process proc = regAdd.start();
- boolean finishedInTime = waitForProcessOrCancel(proc, 5, TimeUnit.SECONDS);
- if (finishedInTime && proc.exitValue() == 0) {
- LOG.debug("Added {} to registry key {}.", AUTOSTART_VALUE, HKCU_AUTOSTART_KEY);
- return CompletableFuture.completedFuture(null);
- } else {
- throw new IOException("Process exited with error code " + proc.exitValue());
- }
- } catch (IOException e) {
- LOG.debug("Registry could not be edited to set auto start.", e);
- return CompletableFuture.failedFuture(new SystemCommandException("Adding registry value failed."));
- }
- }
-
- private CompletableFuture enableAutoStartUsingFolder() {
- String autoStartFolderEntry = System.getProperty("user.home") + WINDOWS_START_MENU_ENTRY;
- String createShortcutCommand = "$s=(New-Object -COM WScript.Shell).CreateShortcut('" + autoStartFolderEntry + "');$s.TargetPath='" + exePath + "';$s.Save();";
- ProcessBuilder shortcutAdd = new ProcessBuilder("cmd", "/c", "Start powershell " + createShortcutCommand);
- try {
- Process proc = shortcutAdd.start();
- boolean finishedInTime = waitForProcessOrCancel(proc, 5, TimeUnit.SECONDS);
- if (finishedInTime && proc.exitValue() == 0) {
- LOG.debug("Created file {} for auto start.", autoStartFolderEntry);
- return CompletableFuture.completedFuture(null);
- } else {
- throw new IOException("Process exited with error code " + proc.exitValue());
- }
- } catch (IOException e) {
- LOG.debug("Adding entry to auto start folder failed.", e);
- return CompletableFuture.failedFuture(new SystemCommandException("Adding entry to auto start folder failed."));
- }
- }
-
-
- @Override
- public void disableAutoStart() throws TogglingAutoStartFailedException {
- if (activatedUsingRegistry) {
- disableAutoStartUsingRegistry().whenComplete((voit, ex) -> {
- if (ex == null) {
- this.activatedUsingRegistry = false;
- }
- });
- }
-
- if (activatedUsingFolder) {
- disableAutoStartUsingFolder().whenComplete((voit, ex) -> {
- if (ex == null) {
- this.activatedUsingFolder = false;
- }
- });
- }
-
- if (activatedUsingRegistry || activatedUsingFolder) {
- throw new TogglingAutoStartFailedException("Disabling auto start failed using registry and/or auto start folder.");
- }
- }
-
- public CompletableFuture disableAutoStartUsingRegistry() {
- ProcessBuilder regRemove = new ProcessBuilder("reg", "delete", HKCU_AUTOSTART_KEY, //
- "/v", AUTOSTART_VALUE, //
- "/f");
- try {
- Process proc = regRemove.start();
- boolean finishedInTime = waitForProcessOrCancel(proc, 5, TimeUnit.SECONDS);
- if (finishedInTime && proc.exitValue() == 0) {
- LOG.debug("Removed {} from registry key {}.", AUTOSTART_VALUE, HKCU_AUTOSTART_KEY);
- return CompletableFuture.completedFuture(null);
- } else {
- throw new IOException("Process exited with error code " + proc.exitValue());
- }
- } catch (IOException e) {
- LOG.debug("Registry could not be edited to remove auto start.", e);
- return CompletableFuture.failedFuture(new SystemCommandException("Removing registry value failed."));
- }
- }
-
- private CompletableFuture disableAutoStartUsingFolder() {
- try {
- Files.delete(Path.of(WINDOWS_START_MENU_ENTRY));
- LOG.debug("Successfully deleted {}.", WINDOWS_START_MENU_ENTRY);
- return CompletableFuture.completedFuture(null);
- } catch (NoSuchFileException e) {
- //that is also okay
- return CompletableFuture.completedFuture(null);
- } catch (IOException e) {
- LOG.debug("Failed to delete entry from auto start folder.", e);
- return CompletableFuture.failedFuture(e);
- }
- }
-
- private static boolean waitForProcessOrCancel(Process proc, int timeout, TimeUnit timeUnit) {
- boolean finishedInTime = false;
- try {
- finishedInTime = proc.waitFor(timeout, timeUnit);
- } catch (InterruptedException e) {
- LOG.error("Timeout while reading registry", e);
- Thread.currentThread().interrupt();
- } finally {
- if (!finishedInTime) {
- proc.destroyForcibly();
- }
- }
- return finishedInTime;
- }
-
- private class SystemCommandException extends RuntimeException {
-
- public SystemCommandException(String msg) {
- super(msg);
- }
- }
-
-}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/preferences/DonationKeyPreferencesController.java b/main/ui/src/main/java/org/cryptomator/ui/preferences/DonationKeyPreferencesController.java
index e7708af81..a4814ec82 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/preferences/DonationKeyPreferencesController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/preferences/DonationKeyPreferencesController.java
@@ -1,5 +1,6 @@
package org.cryptomator.ui.preferences;
+import com.google.common.base.CharMatcher;
import org.cryptomator.common.LicenseHolder;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.UiTheme;
@@ -10,6 +11,7 @@ import javafx.application.Application;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.control.TextArea;
+import javafx.scene.control.TextFormatter;
@PreferencesScoped
public class DonationKeyPreferencesController implements FxController {
@@ -32,6 +34,15 @@ public class DonationKeyPreferencesController implements FxController {
public void initialize() {
donationKeyField.setText(licenseHolder.getLicenseKey().orElse(null));
donationKeyField.textProperty().addListener(this::registrationKeyChanged);
+ donationKeyField.setTextFormatter(new TextFormatter<>(this::checkVaultNameLength));
+ }
+
+ private TextFormatter.Change checkVaultNameLength(TextFormatter.Change change) {
+ if (change.isContentChange()) {
+ var strippedText = CharMatcher.whitespace().removeFrom(change.getText());
+ change.setText(strippedText);
+ }
+ return change;
}
private void registrationKeyChanged(@SuppressWarnings("unused") ObservableValue extends String> observable, @SuppressWarnings("unused") String oldValue, String newValue) {
diff --git a/main/ui/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java b/main/ui/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java
index 496ada2ce..6af9a156e 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/preferences/GeneralPreferencesController.java
@@ -5,6 +5,8 @@ import org.cryptomator.common.LicenseHolder;
import org.cryptomator.common.settings.KeychainBackend;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.UiTheme;
+import org.cryptomator.integrations.autostart.AutoStartProvider;
+import org.cryptomator.integrations.autostart.ToggleAutoStartFailedException;
import org.cryptomator.integrations.keychain.KeychainAccessProvider;
import org.cryptomator.ui.common.ErrorComponent;
import org.cryptomator.ui.common.FxController;
@@ -14,10 +16,8 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javafx.application.Application;
-import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
import javafx.beans.value.ObservableValue;
-import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.geometry.NodeOrientation;
import javafx.scene.control.CheckBox;
@@ -42,7 +42,7 @@ public class GeneralPreferencesController implements FxController {
private final Stage window;
private final Settings settings;
private final boolean trayMenuSupported;
- private final Optional autoStartStrategy;
+ private final Optional autoStartProvider;
private final ObjectProperty selectedTabProperty;
private final LicenseHolder licenseHolder;
private final ExecutorService executor;
@@ -61,11 +61,11 @@ public class GeneralPreferencesController implements FxController {
public RadioButton nodeOrientationRtl;
@Inject
- GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, @Named("trayMenuSupported") boolean trayMenuSupported, Optional autoStartStrategy, Set keychainAccessProviders, ObjectProperty selectedTabProperty, LicenseHolder licenseHolder, ExecutorService executor, ResourceBundle resourceBundle, Application application, Environment environment, ErrorComponent.Builder errorComponent) {
+ GeneralPreferencesController(@PreferencesWindow Stage window, Settings settings, @Named("trayMenuSupported") boolean trayMenuSupported, Optional autoStartProvider, Set keychainAccessProviders, ObjectProperty selectedTabProperty, LicenseHolder licenseHolder, ExecutorService executor, ResourceBundle resourceBundle, Application application, Environment environment, ErrorComponent.Builder errorComponent) {
this.window = window;
this.settings = settings;
this.trayMenuSupported = trayMenuSupported;
- this.autoStartStrategy = autoStartStrategy;
+ this.autoStartProvider = autoStartProvider;
this.keychainAccessProviders = keychainAccessProviders;
this.selectedTabProperty = selectedTabProperty;
this.licenseHolder = licenseHolder;
@@ -89,11 +89,7 @@ public class GeneralPreferencesController implements FxController {
debugModeCheckbox.selectedProperty().bindBidirectional(settings.debugMode());
- autoStartStrategy.ifPresent(autoStart -> {
- autoStart.isAutoStartEnabled().thenAccept(enabled -> {
- Platform.runLater(() -> autoStartCheckbox.setSelected(enabled));
- });
- });
+ autoStartProvider.ifPresent(autoStart -> autoStartCheckbox.setSelected(autoStart.isEnabled()));
nodeOrientationLtr.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.LEFT_TO_RIGHT);
nodeOrientationRtl.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.RIGHT_TO_LEFT);
@@ -114,7 +110,7 @@ public class GeneralPreferencesController implements FxController {
}
public boolean isAutoStartSupported() {
- return autoStartStrategy.isPresent();
+ return autoStartProvider.isPresent();
}
private void toggleNodeOrientation(@SuppressWarnings("unused") ObservableValue extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
@@ -129,15 +125,19 @@ public class GeneralPreferencesController implements FxController {
@FXML
public void toggleAutoStart() {
- autoStartStrategy.ifPresent(autoStart -> {
+ autoStartProvider.ifPresent(autoStart -> {
boolean enableAutoStart = autoStartCheckbox.isSelected();
- Task toggleTask = new ToggleAutoStartTask(autoStart, enableAutoStart);
- toggleTask.setOnFailed(event -> {
+ try {
+ if (enableAutoStart) {
+ autoStart.enable();
+ } else {
+ autoStart.disable();
+ }
+ } catch (ToggleAutoStartFailedException e) {
autoStartCheckbox.setSelected(!enableAutoStart); // restore previous state
- LOG.error("Failed to toggle autostart.", event.getSource().getException());
- errorComponent.cause(event.getSource().getException()).window(window).returnToScene(window.getScene()).build().showErrorScene();
- });
- executor.execute(toggleTask);
+ LOG.error("Failed to toggle autostart.", e);
+ errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
+ }
});
}
@@ -196,27 +196,4 @@ public class GeneralPreferencesController implements FxController {
}
}
- private static class ToggleAutoStartTask extends Task {
-
- private final AutoStartStrategy autoStart;
- private final boolean enable;
-
- public ToggleAutoStartTask(AutoStartStrategy autoStart, boolean enable) {
- this.autoStart = autoStart;
- this.enable = enable;
-
- setOnFailed(event -> LOG.error("Failed to toggle Autostart", getException()));
- }
-
- @Override
- protected Void call() throws Exception {
- if (enable) {
- autoStart.enableAutoStart();
- } else {
- autoStart.disableAutoStart();
- }
- return null;
- }
- }
-
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java b/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java
index 2558582fd..3cc012b03 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java
@@ -20,7 +20,7 @@ import javafx.stage.Stage;
import java.util.Map;
import java.util.ResourceBundle;
-@Module(includes = {AutoStartModule.class})
+@Module
abstract class PreferencesModule {
@Provides
diff --git a/main/ui/src/main/java/org/cryptomator/ui/quit/QuitComponent.java b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitComponent.java
index 23ddf311a..e100c52e9 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/quit/QuitComponent.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitComponent.java
@@ -5,7 +5,6 @@
*******************************************************************************/
package org.cryptomator.ui.quit;
-import dagger.BindsInstance;
import dagger.Lazy;
import dagger.Subcomponent;
import org.cryptomator.ui.common.FxmlFile;
@@ -25,7 +24,10 @@ public interface QuitComponent {
@FxmlScene(FxmlFile.QUIT)
Lazy scene();
- default Stage showQuitWindow() {
+ QuitController controller();
+
+ default Stage showQuitWindow(QuitResponse response) {
+ controller().updateQuitRequest(response);
Stage stage = window();
stage.setScene(scene().get());
stage.show();
@@ -36,9 +38,6 @@ public interface QuitComponent {
@Subcomponent.Builder
interface Builder {
- @BindsInstance
- Builder quitResponse(QuitResponse response);
-
QuitComponent build();
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/quit/QuitController.java b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitController.java
index 130556ef2..12ddbbca3 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/quit/QuitController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitController.java
@@ -16,6 +16,8 @@ import javafx.stage.Stage;
import java.awt.desktop.QuitResponse;
import java.util.Collection;
import java.util.concurrent.ExecutorService;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
@QuitScoped
@@ -24,26 +26,40 @@ public class QuitController implements FxController {
private static final Logger LOG = LoggerFactory.getLogger(QuitController.class);
private final Stage window;
- private final QuitResponse response;
private final ObservableList unlockedVaults;
private final ExecutorService executorService;
private final VaultService vaultService;
+ private final AtomicReference quitResponse = new AtomicReference<>();
public Button lockAndQuitButton;
@Inject
- QuitController(@QuitWindow Stage window, QuitResponse response, ObservableList vaults, ExecutorService executorService, VaultService vaultService) {
+ QuitController(@QuitWindow Stage window, ObservableList vaults, ExecutorService executorService, VaultService vaultService) {
this.window = window;
- this.response = response;
this.unlockedVaults = vaults.filtered(Vault::isUnlocked);
this.executorService = executorService;
this.vaultService = vaultService;
+ window.setOnCloseRequest(windowEvent -> cancel());
+ }
+
+ public void updateQuitRequest(QuitResponse newResponse) {
+ var oldResponse = quitResponse.getAndSet(newResponse);
+ if (oldResponse != null) {
+ oldResponse.cancelQuit();
+ }
+ }
+
+ private void respondToQuitRequest(Consumer action) {
+ var response = quitResponse.getAndSet(null);
+ if (response != null) {
+ action.accept(response);
+ }
}
@FXML
public void cancel() {
LOG.info("Quitting application canceled by user.");
window.close();
- response.cancelQuit();
+ respondToQuitRequest(QuitResponse::cancelQuit);
}
@FXML
@@ -56,16 +72,16 @@ public class QuitController implements FxController {
LOG.info("Locked {}", lockAllTask.getValue().stream().map(Vault::getDisplayName).collect(Collectors.joining(", ")));
if (unlockedVaults.isEmpty()) {
window.close();
- response.performQuit();
+ respondToQuitRequest(QuitResponse::performQuit);
}
});
lockAllTask.setOnFailed(evt -> {
LOG.warn("Locking failed", lockAllTask.getException());
lockAndQuitButton.setDisable(false);
lockAndQuitButton.setContentDisplay(ContentDisplay.TEXT_ONLY);
- // TODO: show force lock or force quit scene (and DO NOT cancelQuit() here!)
- // see https://github.com/cryptomator/cryptomator/blob/1.4.16/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java#L151-L163
- response.cancelQuit();
+ // TODO: show force lock or force quit scene (and DO NOT cancelQuit() here!) (see https://github.com/cryptomator/cryptomator/pull/1416)
+ window.close();
+ respondToQuitRequest(QuitResponse::cancelQuit);
});
executorService.execute(lockAllTask);
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/quit/QuitModule.java b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitModule.java
index e73fa9d46..9c779f434 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/quit/QuitModule.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitModule.java
@@ -43,7 +43,7 @@ abstract class QuitModule {
@Provides
@FxmlScene(FxmlFile.QUIT)
@QuitScoped
- static Scene provideUnlockScene(@QuitWindow FXMLLoaderFactory fxmlLoaders) {
+ static Scene provideQuitScene(@QuitWindow FXMLLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene("/fxml/quit.fxml");
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java b/main/ui/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java
index 51f7c2225..a65f1e493 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/traymenu/TrayMenuController.java
@@ -108,7 +108,7 @@ class TrayMenuController {
}
private void lockVault(Vault vault) {
- fxApplicationStarter.get(true).thenAccept(app -> app.getVaultService().lock(vault, false));
+ fxApplicationStarter.get(true).thenAccept(app -> app.startLockWorkflow(vault, Optional.empty()));
}
private void lockAllVaults(ActionEvent actionEvent) {
diff --git a/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java b/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java
index b760ebf9a..e860ee811 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java
@@ -39,7 +39,7 @@ public class GeneralVaultOptionsController implements FxController {
public void initialize() {
vaultName.textProperty().set(vault.getVaultSettings().displayName().get());
vaultName.focusedProperty().addListener(this::trimVaultNameOnFocusLoss);
- vaultName.setTextFormatter(new TextFormatter<>(this::checkVaultNameLength));
+ vaultName.setTextFormatter(new TextFormatter<>(this::removeWhitespaces));
unlockOnStartupCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().unlockAfterStartup());
actionAfterUnlockChoiceBox.getItems().addAll(WhenUnlocked.values());
actionAfterUnlockChoiceBox.valueProperty().bindBidirectional(vault.getVaultSettings().actionAfterUnlock());
@@ -53,7 +53,7 @@ public class GeneralVaultOptionsController implements FxController {
}
}
- private TextFormatter.Change checkVaultNameLength(TextFormatter.Change change) {
+ private TextFormatter.Change removeWhitespaces(TextFormatter.Change change) {
if (change.isContentChange() && change.getControlNewText().length() > VAULTNAME_TRUNCATE_THRESHOLD) {
return null; // reject any change that would lead to a text exceeding threshold
} else {
diff --git a/main/ui/src/main/resources/fxml/lock_failed.fxml b/main/ui/src/main/resources/fxml/lock_failed.fxml
new file mode 100644
index 000000000..f599b0fd8
--- /dev/null
+++ b/main/ui/src/main/resources/fxml/lock_failed.fxml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/main/ui/src/main/resources/fxml/lock_forced.fxml b/main/ui/src/main/resources/fxml/lock_forced.fxml
new file mode 100644
index 000000000..4596af119
--- /dev/null
+++ b/main/ui/src/main/resources/fxml/lock_forced.fxml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/main/ui/src/main/resources/i18n/strings.properties b/main/ui/src/main/resources/i18n/strings.properties
index b76bba812..bab24819f 100644
--- a/main/ui/src/main/resources/i18n/strings.properties
+++ b/main/ui/src/main/resources/i18n/strings.properties
@@ -108,6 +108,15 @@ unlock.error.heading=Unable to unlock vault
unlock.error.invalidMountPoint.notExisting=Mount point "%s" is not a directory, not empty or does not exist.
unlock.error.invalidMountPoint.existing=Mount point "%s" already exists or parent folder is missing.
+# Lock
+## Force
+lock.forced.heading=Graceful lock failed
+lock.forced.message=Locking "%s" was blocked by pending operations or open files. You can force lock this vault, however interrupting I/O may result in the loss of unsaved data.
+lock.forced.confirmBtn=Force Lock
+## Failure
+lock.fail.heading=Locking vault failed.
+lock.fail.message=Vault "%s" could not be locked. Ensure unsaved work is saved elsewhere and important Read/Write operations are finished. In order to close the vault, kill the Cryptomator process.
+
# Migration
migration.title=Upgrade Vault
## Start
diff --git a/main/ui/src/main/resources/i18n/strings_ar.properties b/main/ui/src/main/resources/i18n/strings_ar.properties
index f8cfaa754..0b0d938c5 100644
--- a/main/ui/src/main/resources/i18n/strings_ar.properties
+++ b/main/ui/src/main/resources/i18n/strings_ar.properties
@@ -106,6 +106,10 @@ unlock.success.revealBtn=افتح الحافظة
unlock.error.invalidMountPoint.notExisting=نقطة التحميل ليست مجلد فارغ أو غير موجودة: %s
unlock.error.invalidMountPoint.existing=نقطة/مجلد التحميل موجود بالفعل أو المجلد الأصل مفقود: %s
+# Lock
+## Force
+## Failure
+
# Migration
migration.title=ترقية الحافظة
## Start
diff --git a/main/ui/src/main/resources/i18n/strings_bs.properties b/main/ui/src/main/resources/i18n/strings_bs.properties
index e8d8579d6..d5781878b 100644
--- a/main/ui/src/main/resources/i18n/strings_bs.properties
+++ b/main/ui/src/main/resources/i18n/strings_bs.properties
@@ -106,6 +106,10 @@ unlock.error.heading=Sef nije moguće otključati
### Invalid Mount Point
unlock.error.invalidMountPoint.notExisting=Tačka postavljanja "%s" nije direktorij, nije prazna ili ne postoji.
+# Lock
+## Force
+## Failure
+
# Migration
## Start
## Run
diff --git a/main/ui/src/main/resources/i18n/strings_ca.properties b/main/ui/src/main/resources/i18n/strings_ca.properties
index e4a5a44b6..46da16d3e 100644
--- a/main/ui/src/main/resources/i18n/strings_ca.properties
+++ b/main/ui/src/main/resources/i18n/strings_ca.properties
@@ -17,6 +17,7 @@ generic.error.title=S'ha produït un error inesperat
generic.error.instruction=Això no hauria d'haver passat. Si us plau, informeu del text de l'error i inclogueu una descripció de quins passos han dut a aquest error.
# Defaults
+defaults.vault.vaultName=Caixa forta
# Tray Menu
traymenu.showMainWindow=Mostra
@@ -44,6 +45,7 @@ addvaultwizard.new.directoryPickerLabel=Ubicació personalitzada
addvaultwizard.new.directoryPickerButton=Trieu…
addvaultwizard.new.directoryPickerTitle=Seleccioneu el directori
addvaultwizard.new.fileAlreadyExists=No es pot crear una caixa forta en aquest camí perquè ja hi ha algun objecte.
+addvaultwizard.new.locationDoesNotExist=No ha estat possible crear la caixa forta en aquest camí perquè, si més no, un component no existeix.
addvaultwizard.new.invalidName=El nom de la caixa forta no és vàlid. Si us plau, escribiu un mom de directori amb caràcters estàndard.
### Password
addvaultwizard.new.createVaultBtn=Crea la caixa forta
@@ -100,10 +102,19 @@ unlock.success.message="%s" s'ha desbloquejat correctament! Ja es pot accedir a
unlock.success.rememberChoice=Recorda l'elecció. No ho tornis a mostrar.
unlock.success.revealBtn=Mostra la caixa forta
## Failure
+unlock.error.heading=No ha estat possible desblocar la caixa forta
### Invalid Mount Point
unlock.error.invalidMountPoint.notExisting=El punt de muntatge no és un directori buit, o no existeix: %s
unlock.error.invalidMountPoint.existing=El punt de muntatge o la carpeta ja existeix, o no es pot accedir al directori superior: %s
+# Lock
+## Force
+lock.forced.message=No s'ha blocat "%s" perquè hi ha operacions pendents o fitxers oberts. Podeu forçar-ne el blocatge però heu de saber que interrompre l'entrada/sortida pot produir la pèrdua de dades.
+lock.forced.confirmBtn=Força el blocatge
+## Failure
+lock.fail.heading=Ha fallat el blocatge de la caixa forta.
+lock.fail.message=La caixa forta "%s" no s'ha pogut blocar. Assegureu-vos que el treball s'ha desat en algun altre lloc i que les operacions de lectura/escriptura han acabat. Per tal de tancar la caixa, mateu el procés Cryptomator.
+
# Migration
migration.title=Actualitza la caixa forta
## Start
@@ -133,13 +144,18 @@ preferences.title=Preferències
## General
preferences.general=General
preferences.general.theme=Apariència
+preferences.general.theme.automatic=Automàtic
preferences.general.theme.light=Clar
preferences.general.theme.dark=Fosc
preferences.general.unlockThemes=Desbloqueja el tema fosc
-preferences.general.startHidden=Amaga la finestra al iniciar Cryptomator
+preferences.general.startHidden=Amaga la finestra quan s'inicia Cryptomator
preferences.general.debugLogging=Habilita el registre de depuració
preferences.general.debugDirectory=Mostra els fitxers de registres
preferences.general.autoStart=Executa Cryptomator en engegar el sistema
+preferences.general.keychainBackend=Desar contrasenyes amb
+preferences.general.keychainBackend.org.cryptomator.linux.keychain.SecretServiceKeychainAccess=Anell de claus de Gnome
+preferences.general.keychainBackend.org.cryptomator.linux.keychain.KDEWalletKeychainAccess=Cartera de KDE
+preferences.general.keychainBackend.org.cryptomator.macos.keychain.MacSystemKeychainAccess=Accés a clauers macOS
preferences.general.interfaceOrientation=Orientació de la interfície
preferences.general.interfaceOrientation.ltr=Esquerra a dreta
preferences.general.interfaceOrientation.rtl=Dreta a esquerra
@@ -163,38 +179,72 @@ preferences.donationKey.getDonationKey=Obtingau una clau de donació
preferences.about=Quant a
# Vault Statistics
+stats.title=Estadístiques per a %s
## Read
+stats.read.throughput.idle=Llegit: inactiu
+stats.read.throughput.kibs=Llegit: %.2f kiB/s
+stats.read.throughput.mibs=Llegit: %.2f MiB/s
+stats.read.total.data.none=Dades llegides: -
+stats.read.total.data.kib=Dades llegides: %.1f kiB
+stats.read.total.data.mib=Dades llegides: %.1f MiB
+stats.read.total.data.gib=Dades llegides: %.1f GiB
+stats.decr.total.data.none=Dades desxifrades: -
+stats.decr.total.data.kib=Dades desxifrades: %.1f kiB
+stats.decr.total.data.mib=Dades desxifrades: %.1f MiB
+stats.decr.total.data.gib=Dades desxifrades: %.1f GiB
+stats.read.accessCount=Lectures en total: %d
## Write
+stats.write.throughput.idle=Escriu: inactiu
+stats.write.throughput.kibs=Escriu: %.2f kiB/s
+stats.write.throughput.mibs=Escriu: %.2f MiB/s
+stats.write.total.data.none=Dades llegides: -
+stats.write.total.data.kib=Dades escrites: %.1f kiB
+stats.write.total.data.mib=Dades escrites: %.1f MiB
+stats.write.total.data.gib=Dades escrites: %.1f GiB
+stats.encr.total.data.none=Dades xifrades: -
+stats.encr.total.data.kib=Dades xifrades: %.1f kiB
+stats.encr.total.data.mib=Dades xifrades: %.1f MiB
+stats.encr.total.data.gib=Dades xifrades: %.1f GiB
+stats.write.accessCount=Total escrits: %d
# Main Window
main.closeBtn.tooltip=Tanca
main.minimizeBtn.tooltip=Minimitza
main.preferencesBtn.tooltip=Preferències
+main.debugModeEnabled.tooltip=Mode depuració activat
main.donationKeyMissing.tooltip=Si us plau, considereu fer una donació
## Drag 'n' Drop
main.dropZone.dropVault=Afegeix aquesta caixa forta
main.dropZone.unknownDragboardContent=Si voleu afegir una caixa forta, arrossegueu-la a aquesta finestra
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Feu clic aquí per afegir una caixa forta
+main.vaultlist.contextMenu.remove=Elimina la caixa forta…
main.vaultlist.addVaultBtn=Afegir una caixa forta
## Vault Detail
### Welcome
main.vaultDetail.welcomeOnboarding=Gràcies per escollir Cryptomator per protegir els vostres fitxers. Si vos cal ajuda, llegiu les nostres guies per donar els Primers passos:
### Locked
main.vaultDetail.lockedStatus=BLOQUEJADA
+main.vaultDetail.unlockBtn=Desbloca…
main.vaultDetail.unlockNowBtn=Desbloqueja ara
main.vaultDetail.optionsBtn=Opcions de la caixa forta
+main.vaultDetail.passwordSavedInKeychain=Contrasenya desada
### Unlocked
main.vaultDetail.unlockedStatus=DESBLOQUEJADA
main.vaultDetail.accessLocation=Els continguts de la vostra caixa forta són accessibles aquí:
main.vaultDetail.revealBtn=Mostra la unitat
main.vaultDetail.lockBtn=Bloqueja
main.vaultDetail.bytesPerSecondRead=Lectura:
+main.vaultDetail.bytesPerSecondWritten=Escriu:
main.vaultDetail.throughput.idle=inactiu
main.vaultDetail.throughput.kbps=%.1f kiB/s
main.vaultDetail.throughput.mbps=%.1f MiB/s
+main.vaultDetail.stats=Estadístiques de la caixa forta
### Missing
main.vaultDetail.missing.info=Cryptomator no ha trobat una caixa forta en aquest camí.
+main.vaultDetail.missing.recheck=Torna a comprovar
+main.vaultDetail.missing.remove=Eliminar de la llista de la caixa forta…
+main.vaultDetail.missing.changeLocation=Canvia la localització de la caixa forta…
### Needs Migration
main.vaultDetail.migrateButton=Actualitza la caixa forta
main.vaultDetail.migratePrompt=Per accedir a la vostra caixa forta abans cal actualitzar-la al nou format
@@ -213,7 +263,7 @@ wrongFileAlert.link=Per rebre assistència, visiteu
## General
vaultOptions.general=General
vaultOptions.general.vaultName=Nom de la caixa forta
-vaultOptions.general.unlockAfterStartup=Desbloqueja la caixa forta al iniciar Cryptomator
+vaultOptions.general.unlockAfterStartup=Desbloqueja la caixa forta quan s'inicia Cryptomator
vaultOptions.general.actionAfterUnlock=Després d'un desbloqueig correcte
vaultOptions.general.actionAfterUnlock.ignore=No facis res
vaultOptions.general.actionAfterUnlock.reveal=Mostra la unitat
@@ -232,6 +282,7 @@ vaultOptions.mount.mountPoint.directoryPickerTitle=Esculliu un directori buit
## Master Key
vaultOptions.masterkey=Contrasenya
vaultOptions.masterkey.changePasswordBtn=Canvi de contrasenya
+vaultOptions.masterkey.forgetSavedPasswordBtn=Oblida la contrasenya desada
vaultOptions.masterkey.recoveryKeyExpanation=La clau de recuperació és l'unic mitjà de restaurar l'accès a la caixa forta en cas de perdre la contrasenya.
vaultOptions.masterkey.showRecoveryKeyBtn=Mostra la clau de recuperació
vaultOptions.masterkey.recoverPasswordBtn=Recupera la contrasenya
diff --git a/main/ui/src/main/resources/i18n/strings_cs.properties b/main/ui/src/main/resources/i18n/strings_cs.properties
index 5243b692d..667b151f2 100644
--- a/main/ui/src/main/resources/i18n/strings_cs.properties
+++ b/main/ui/src/main/resources/i18n/strings_cs.properties
@@ -107,6 +107,10 @@ unlock.error.heading=Nelze odemknout trezor
unlock.error.invalidMountPoint.notExisting=Připojovací bod %s není složkou, není prázdný nebo neexistuje.
unlock.error.invalidMountPoint.existing=Připojovací bod %s již existuje nebo nadřazená složka chybí.
+# Lock
+## Force
+## Failure
+
# Migration
migration.title=Upgrade trezoru
## Start
diff --git a/main/ui/src/main/resources/i18n/strings_de.properties b/main/ui/src/main/resources/i18n/strings_de.properties
index 350e6a3a0..847c504f9 100644
--- a/main/ui/src/main/resources/i18n/strings_de.properties
+++ b/main/ui/src/main/resources/i18n/strings_de.properties
@@ -107,6 +107,15 @@ unlock.error.heading=Tresor konnte nicht entsperrt werden
unlock.error.invalidMountPoint.notExisting=Einhängepunkt ist kein leeres Verzeichnis oder existiert nicht: %s
unlock.error.invalidMountPoint.existing=Einhängepunkt/Ordner bereits vorhanden oder übergeordneter Ordner fehlt: %s
+# Lock
+## Force
+lock.forced.heading=Tresor konnte nicht kontrolliert gesperrt werden
+lock.forced.message=Das Sperren von „%s“ wurde durch noch ablaufende Vorgänge oder offene Dateien verhindert. Sie können das Sperren dieses Tresors erzwingen, allerdings kann dies zum Verlust ungespeicherter Daten führen.
+lock.forced.confirmBtn=Sperren erzwingen
+## Failure
+lock.fail.heading=Der Tresor konnte nicht gesperrt werden.
+lock.fail.message=Der Tresor „%s“ konnte nicht gesperrt werden. Stellen Sie sicher, dass Sie Ihre ungespeicherten Arbeit an anderer Stelle speichern und wichtige Lese-/Schreibvorgänge abgeschlossen sind. Um den Tresor zu schließen, beenden Sie den Cryptomator-Prozess.
+
# Migration
migration.title=Tresor aktualisieren
## Start
diff --git a/main/ui/src/main/resources/i18n/strings_el.properties b/main/ui/src/main/resources/i18n/strings_el.properties
index e3d908e21..df49fdb2b 100644
--- a/main/ui/src/main/resources/i18n/strings_el.properties
+++ b/main/ui/src/main/resources/i18n/strings_el.properties
@@ -102,10 +102,20 @@ unlock.success.message="%s" ξεκλειδώθηκε επιτυχώς! Το vaul
unlock.success.rememberChoice=Απομνημόνευση επιλογής, μην ρωτήσεις ξανά
unlock.success.revealBtn=Αποκάλυψη Vault
## Failure
+unlock.error.heading=Αδυναμία ξεκλειδώματος vault
### Invalid Mount Point
unlock.error.invalidMountPoint.notExisting=Το σημείο προσάρτησης δεν είναι κενός φάκελος ή δεν υπάρχει: %s
unlock.error.invalidMountPoint.existing=Το σημείο/φάκελος προσάρτησης υπάρχει ήδη ή ο γονικός φάκελος λείπει: %s
+# Lock
+## Force
+lock.forced.heading=Το κανονικό κλείδωμα απέτυχε
+lock.forced.message=Το κλείδωμα "%s" μπλοκαρίστηκε από εκκρεμείς διεργασίες ή ανοιχτά αρχεία. Μπορείτε να εξαναγκάσετε το κλείδωμα του vault, αλλά η διακοπή Ι/Ο ενδέχεται να οδηγήσει σε απώλεια μη αποθηκευμένων δεδομένων.
+lock.forced.confirmBtn=Εξαναγκασμένο κλείδωμα
+## Failure
+lock.fail.heading=Το κλείδωμα του vault απέτυχε.
+lock.fail.message=Το Vault "%s" δεν κλειδώθηκε. Εξασφαλίστε την αποθήκευση της εργασίας σε άλλο σημείο και πως οι σημαντικές διεργασίας Ανάγνωσης/Εγγραφής έχουν ολοκληρωθεί. Για να κλείσετε το vault, τερματίστε τη διεργασία του Cryptomator.
+
# Migration
migration.title=Αναβάθμιση Vault
## Start
diff --git a/main/ui/src/main/resources/i18n/strings_es.properties b/main/ui/src/main/resources/i18n/strings_es.properties
index b8ccdd8a4..d87305886 100644
--- a/main/ui/src/main/resources/i18n/strings_es.properties
+++ b/main/ui/src/main/resources/i18n/strings_es.properties
@@ -107,6 +107,14 @@ unlock.error.heading=No se puede desbloquear la bóveda
unlock.error.invalidMountPoint.notExisting=El punto de montaje no es un directorio vacío o no existe: %s
unlock.error.invalidMountPoint.existing=El punto de montaje/carpeta ya existe o falta la carpeta padre: %s
+# Lock
+## Force
+lock.forced.message=El bloqueo de "%s" fue bloqueado por operaciones pendientes o archivos abiertos. Puede forzar el bloqueo de esta bóveda, sin embargo, interrumpir la I/O puede provocar la pérdida de datos no guardados.
+lock.forced.confirmBtn=Forzar bloqueo
+## Failure
+lock.fail.heading=Falló al bloquear la bóveda.
+lock.fail.message=No se pudo bloquear la bóveda "%s". Asegúrese de que el trabajo no guardado se ha guardado en otro lugar y las operaciones de lectura/escritura importantes han finalizado. Para cerrar la bóveda termine el proceso de Cryptomator.
+
# Migration
migration.title=Mejorar bóveda
## Start
diff --git a/main/ui/src/main/resources/i18n/strings_fr.properties b/main/ui/src/main/resources/i18n/strings_fr.properties
index a9f1cd195..9cc95fece 100644
--- a/main/ui/src/main/resources/i18n/strings_fr.properties
+++ b/main/ui/src/main/resources/i18n/strings_fr.properties
@@ -107,6 +107,15 @@ unlock.error.heading=Impossible de déverrouiller le coffre
unlock.error.invalidMountPoint.notExisting=Le point de montage «%s» n'est pas un répertoire, n'est pas vide ou n'existe pas.
unlock.error.invalidMountPoint.existing=Le point de montage/le répertoire existe déjà ou le répertoire parent est manquant: %s
+# Lock
+## Force
+lock.forced.heading=Le verrouillage normal a échoué
+lock.forced.message=Le verrouillage de «%s» a été bloqué par des opérations en attente ou des fichiers ouverts. Vous pouvez forcer le verrouillage de ce coffre, mais l'interruption d'E/S peut entraîner la perte de données non enregistrées.
+lock.forced.confirmBtn=Forcer le verrouillage
+## Failure
+lock.fail.heading=Le verrouillage du coffre-fort a échoué.
+lock.fail.message=Le coffre-fort "%s" n'a pas pu être verrouillé. Assurez-vous que le travail non sauvegardé est sauvegardé ailleurs et que les opérations importantes de lecture/écriture sont bien terminées. Pour fermer le coffre-fort, tuez le processus Cryptomator.
+
# Migration
migration.title=Mettre à jour le coffre
## Start
diff --git a/main/ui/src/main/resources/i18n/strings_hi.properties b/main/ui/src/main/resources/i18n/strings_hi.properties
index 4cd4fd450..3d512de62 100644
--- a/main/ui/src/main/resources/i18n/strings_hi.properties
+++ b/main/ui/src/main/resources/i18n/strings_hi.properties
@@ -62,6 +62,10 @@ unlock.unlockBtn=अनलॉक करें
## Failure
### Invalid Mount Point
+# Lock
+## Force
+## Failure
+
# Migration
migration.title=वाउल्ट को अपग्रेड करें
## Start
diff --git a/main/ui/src/main/resources/i18n/strings_hr.properties b/main/ui/src/main/resources/i18n/strings_hr.properties
index acd501750..9f2898f25 100644
--- a/main/ui/src/main/resources/i18n/strings_hr.properties
+++ b/main/ui/src/main/resources/i18n/strings_hr.properties
@@ -29,6 +29,10 @@
## Failure
### Invalid Mount Point
+# Lock
+## Force
+## Failure
+
# Migration
## Start
## Run
diff --git a/main/ui/src/main/resources/i18n/strings_id.properties b/main/ui/src/main/resources/i18n/strings_id.properties
index ec8e7477a..96ea796fb 100644
--- a/main/ui/src/main/resources/i18n/strings_id.properties
+++ b/main/ui/src/main/resources/i18n/strings_id.properties
@@ -81,6 +81,10 @@ unlock.unlockBtn=Buka Gembok
## Failure
### Invalid Mount Point
+# Lock
+## Force
+## Failure
+
# Migration
## Start
## Run
diff --git a/main/ui/src/main/resources/i18n/strings_it.properties b/main/ui/src/main/resources/i18n/strings_it.properties
index ee1d0db85..502eefa5e 100644
--- a/main/ui/src/main/resources/i18n/strings_it.properties
+++ b/main/ui/src/main/resources/i18n/strings_it.properties
@@ -102,10 +102,15 @@ unlock.success.message=Sbloccato "%s" con successo! La tua cassaforte è ora acc
unlock.success.rememberChoice=Ricorda la scelta, non mostrare ancora
unlock.success.revealBtn=Rivela Cassaforte
## Failure
+unlock.error.heading=Impossibile sbloccare la cassaforte
### Invalid Mount Point
unlock.error.invalidMountPoint.notExisting=Il punto di montaggio non è una directory vuota o non esiste: %s
unlock.error.invalidMountPoint.existing=Il punto di Mount/cartella esiste già o la cartella superiore è mancante: %s
+# Lock
+## Force
+## Failure
+
# Migration
migration.title=Aggiorna Cassaforte
## Start
@@ -171,8 +176,11 @@ preferences.donationKey.getDonationKey=Ottieni una chiave di donazione
preferences.about=Informazioni
# Vault Statistics
+stats.title=Statistiche per %s
## Read
+stats.read.total.data.none=Dati letti: -
## Write
+stats.write.total.data.none=Dati letti: -
# Main Window
main.closeBtn.tooltip=Chiudi
diff --git a/main/ui/src/main/resources/i18n/strings_ja.properties b/main/ui/src/main/resources/i18n/strings_ja.properties
index d4693138e..85e5de9da 100644
--- a/main/ui/src/main/resources/i18n/strings_ja.properties
+++ b/main/ui/src/main/resources/i18n/strings_ja.properties
@@ -106,6 +106,15 @@ unlock.error.heading=金庫の解錠に失敗
### Invalid Mount Point
unlock.error.invalidMountPoint.notExisting=マウントポイントが空のディレクトリか存在していません: %s
+# Lock
+## Force
+lock.forced.heading=正常な施錠に失敗しました
+lock.forced.message=保留中の操作または、開かれたファイルによって、"%s" の施錠が中断されました。この金庫を強制的に施錠することはできますが、I/O を中断すると保存されていないデータを失う可能性があります。
+lock.forced.confirmBtn=強制施錠
+## Failure
+lock.fail.heading=金庫の施錠に失敗しました。
+lock.fail.message=金庫 "%s" を施錠できませんでした。保存されていないデータがほかの場所に保存され、重要な読み込み/書き込み操作が完了していることを確認してください。金庫を閉じるには、Cryptomator のプロセスを強制終了してください。
+
# Migration
migration.title=金庫をアップグレード
## Start
diff --git a/main/ui/src/main/resources/i18n/strings_ko.properties b/main/ui/src/main/resources/i18n/strings_ko.properties
index 9ad4a5bde..8aeea1acf 100644
--- a/main/ui/src/main/resources/i18n/strings_ko.properties
+++ b/main/ui/src/main/resources/i18n/strings_ko.properties
@@ -107,6 +107,15 @@ unlock.error.heading=Vault 잠금을 해제 할 수 없습니다.
unlock.error.invalidMountPoint.notExisting=구성지점이 존재하지 않거나 빈 디렉터리가 아닙니다: %s
unlock.error.invalidMountPoint.existing=구성지점/폴더가 이미 존재하거나 부모 폴더가 없습니다: %s
+# Lock
+## Force
+lock.forced.heading=정상적인 잠금을 실패하였습니다.
+lock.forced.message=대기 중인 작동이나 파일이 열려있어 "%s"를 잠그는데 실패하였습니다. 이 Vault를 강제로 잠글 수 있으나, 입/출력의 중단은 저장되지 않은 데이터의 유실을 초래할 수 있습니다.
+lock.forced.confirmBtn=강제 잠금
+## Failure
+lock.fail.heading=Vault 잠금에 실패하였습니다.
+lock.fail.message="%s" Vault를 잠글 수 없습니다. 저장되지 않은 작업이 다른 곳에 저장된 것과 중요한 읽기/쓰기 동작이 완료되었는지 확인 하십시요. Vault를 닫기 위해, Cryptomator 프로세스를 강제로 종료 하십시요.
+
# Migration
migration.title=Vault 업그레이드
## Start
diff --git a/main/ui/src/main/resources/i18n/strings_lv.properties b/main/ui/src/main/resources/i18n/strings_lv.properties
index e1d23e934..8bcd88139 100644
--- a/main/ui/src/main/resources/i18n/strings_lv.properties
+++ b/main/ui/src/main/resources/i18n/strings_lv.properties
@@ -101,6 +101,10 @@ unlock.success.revealBtn=Atklāt glabātuvi
## Failure
### Invalid Mount Point
+# Lock
+## Force
+## Failure
+
# Migration
migration.title=Jaunināt glabātuvi
## Start
diff --git a/main/ui/src/main/resources/i18n/strings_nb.properties b/main/ui/src/main/resources/i18n/strings_nb.properties
index 51282a600..bd97b4d00 100644
--- a/main/ui/src/main/resources/i18n/strings_nb.properties
+++ b/main/ui/src/main/resources/i18n/strings_nb.properties
@@ -102,7 +102,16 @@ unlock.success.message=Vellykket opplåsning av "%s"! Hvelvet ditt er nå tilgje
unlock.success.rememberChoice=Husk valget - ikke vis dette igjen
unlock.success.revealBtn=Gjør hvelvet synlig
## Failure
+unlock.error.heading=Klarer ikke å låse opp hvelvet
### Invalid Mount Point
+unlock.error.invalidMountPoint.notExisting=Monteringspunktet "%s" er enten ikke en mappe, ikke tom eller eksisterer ikke.
+unlock.error.invalidMountPoint.existing=Monteringspunktet "%s" finnes enten allerede eller at overordnet mappe mangler.
+
+# Lock
+## Force
+lock.forced.confirmBtn=Tving låsing
+## Failure
+lock.fail.heading=Låsing av hvelvet mislyktes.
# Migration
migration.title=Oppgrader hvelv
@@ -141,6 +150,7 @@ preferences.general.startHidden=Skjul vinduet når du starter Cryptomator
preferences.general.debugLogging=Aktiver loggføring av feilsøk
preferences.general.debugDirectory=Vis loggfiler
preferences.general.autoStart=Start Cryptomator ved systemstart
+preferences.general.keychainBackend=Lagre passord med
preferences.general.interfaceOrientation=Grensesnittorientering
preferences.general.interfaceOrientation.ltr=Fra venstre til høyre
preferences.general.interfaceOrientation.rtl=Fra høyre til venstre
@@ -164,8 +174,34 @@ preferences.donationKey.getDonationKey=Få en donasjonsnøkkel
preferences.about=Om
# Vault Statistics
+stats.title=Statistikk for %s
+stats.cacheHitRate=Cache treffrate
## Read
+stats.read.throughput.idle=Les: inaktiv
+stats.read.throughput.kibs=Lest: %.2f kiB/s
+stats.read.throughput.mibs=Lest: %.2f MiB/s
+stats.read.total.data.none=Data lest: -
+stats.read.total.data.kib=Data lest: %.1f kiB
+stats.read.total.data.mib=Data lest: %.1f MiB
+stats.read.total.data.gib=Data lest: %.1f GiB
+stats.decr.total.data.none=Data dekryptert: -
+stats.decr.total.data.kib=Data dekryptert: %.1f kiB
+stats.decr.total.data.mib=Data dekryptert: %.1f MiB
+stats.decr.total.data.gib=Data dekryptert: %.1f GiB
+stats.read.accessCount=Lesninger totalt: %d
## Write
+stats.write.throughput.idle=Skrive: inaktiv
+stats.write.throughput.kibs=Skriver: %.2f kiB/s
+stats.write.throughput.mibs=Skriver: %.2f MiB/s
+stats.write.total.data.none=Data lest: -
+stats.write.total.data.kib=Data skrevet: %.1f kiB
+stats.write.total.data.mib=Data skrevet: %.1f MiB
+stats.write.total.data.gib=Data skrevet: %.1f GiB
+stats.encr.total.data.none=Data kryptert: -
+stats.encr.total.data.kib=Data kryptert: %.1f kiB
+stats.encr.total.data.mib=Data kryptert: %.1f MiB
+stats.encr.total.data.gib=Data kryptert: %.1f GiB
+stats.write.accessCount=Skrivninger totalt: %d
# Main Window
main.closeBtn.tooltip=Lukk
@@ -195,9 +231,11 @@ main.vaultDetail.accessLocation=Innholdet i hvelvet ditt er tilgjengelig her:
main.vaultDetail.revealBtn=Gjør enheten synlig
main.vaultDetail.lockBtn=Lås
main.vaultDetail.bytesPerSecondRead=Lesehastighet:
+main.vaultDetail.bytesPerSecondWritten=Skriv:
main.vaultDetail.throughput.idle=inaktiv
main.vaultDetail.throughput.kbps=%.1f kiB/s
main.vaultDetail.throughput.mbps=%.1f MiB/s
+main.vaultDetail.stats=Hvelvstatistikk
### Missing
main.vaultDetail.missing.info=Cryptomator kunne ikke finne et hvelv på denne søkestien.
main.vaultDetail.missing.recheck=Kontroller igjen
diff --git a/main/ui/src/main/resources/i18n/strings_nl.properties b/main/ui/src/main/resources/i18n/strings_nl.properties
index 58e8ed2e4..736574510 100644
--- a/main/ui/src/main/resources/i18n/strings_nl.properties
+++ b/main/ui/src/main/resources/i18n/strings_nl.properties
@@ -102,6 +102,10 @@ unlock.success.revealBtn=Toon kluis
## Failure
### Invalid Mount Point
+# Lock
+## Force
+## Failure
+
# Migration
migration.title=Kluis upgraden
## Start
@@ -165,6 +169,7 @@ main.dropZone.dropVault=Voeg deze kluis toe
main.dropZone.unknownDragboardContent=Als u een kluis wilt toevoegen, sleep deze dan naar dit venster
## Vault List
main.vaultlist.emptyList.onboardingInstruction=Klik hier om een kluis toe te voegen
+main.vaultlist.contextMenu.remove=Verwijder Kluis…
main.vaultlist.addVaultBtn=Kluis toevoegen
## Vault Detail
### Welcome
@@ -184,6 +189,7 @@ main.vaultDetail.throughput.kbps=%.1f kiB/s
main.vaultDetail.throughput.mbps=%.1f MiB/s
### Missing
main.vaultDetail.missing.info=Cryptomator kon op dit pad geen kluis vinden.
+main.vaultDetail.missing.remove=Verwijderen van kluislijst…
### Needs Migration
main.vaultDetail.migrateButton=Kluis upgraden
main.vaultDetail.migratePrompt=Uw kluis moet worden bijgewerkt naar een nieuw formaat, voordat u deze kunt openen
diff --git a/main/ui/src/main/resources/i18n/strings_nn.properties b/main/ui/src/main/resources/i18n/strings_nn.properties
index 840bd6153..a6c575137 100644
--- a/main/ui/src/main/resources/i18n/strings_nn.properties
+++ b/main/ui/src/main/resources/i18n/strings_nn.properties
@@ -103,6 +103,10 @@ unlock.success.revealBtn=Gjer kvelven synleg
## Failure
### Invalid Mount Point
+# Lock
+## Force
+## Failure
+
# Migration
migration.title=Oppgrader kvelv
## Start
diff --git a/main/ui/src/main/resources/i18n/strings_pa.properties b/main/ui/src/main/resources/i18n/strings_pa.properties
index 907eedf3f..528f5568f 100644
--- a/main/ui/src/main/resources/i18n/strings_pa.properties
+++ b/main/ui/src/main/resources/i18n/strings_pa.properties
@@ -107,6 +107,15 @@ unlock.error.heading=ਵਾਲਟ ਅਣ-ਲਾਕ ਕਰਨ ਲਈ ਅਸਮਰ
unlock.error.invalidMountPoint.notExisting="%s" ਮਾਊਂਟ ਪੁਆਇੰਟ ਡਾਇਰੈਕਟਰੀ ਨਹੀਂ, ਖਾਲੀ ਨਹੀਂ ਜਾਂ ਮੌਜੂਦ ਹੀ ਨਹੀਂ ਹੈ।
unlock.error.invalidMountPoint.existing="%s" ਮਾਊਂਟ ਪੁਆਇੰਟ ਪਹਿਲਾਂ ਹੀ ਮੌਜੂਦ ਹੈ ਜਾਂ ਉਸ ਦਾ ਮੂਲ ਫੋਲਡਰ ਗੁੰਮ ਹੈ।
+# Lock
+## Force
+lock.forced.heading=ਸਧਾਰਨ ਲਾਕ ਕਰਨਾ ਅਸਫ਼ਲ ਹੈ
+lock.forced.message=ਬਾਕੀ ਰਹਿੰਦੀਆਂ ਕਾਰਵਾਈਆਂ ਜਾਂ ਫ਼ਾਈਲਾਂ ਖੁੱਲ੍ਹਣ ਕਰਕੇ "%s" ਲਾਕ ਕਰਨ ਨੂੰ ਰੋਕਿਆ ਗਿਆ ਹੈ। ਤੁਸੀਂ ਇਸ ਵਾਲਟ ਨੂੰ ਧੱਕੇ ਨਾਲ ਲਾਕ ਕਰ ਸਕਦੇ ਹੋ, ਪਰ I/O ਵਿੱਚ ਰੁਕਾਵਟ ਪਾਉਣ ਨਾਲ ਨਾ-ਸੰਭਾਲਿਆ ਡਾਟਾ ਖਤਮ ਹੋ ਜਾ ਸਕਦਾ ਹੈ।
+lock.forced.confirmBtn=ਧੱਕੇ ਨਾਲ ਲਾਕ ਕਰੋ
+## Failure
+lock.fail.heading=ਵਾਲਟ ਲਾਕ ਕਰਨਾ ਅਸਫ਼ਲ ਹੈ।
+lock.fail.message=ਵਾਲਟ "%s" ਨੂੰ ਲਾਕ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ। ਯਕੀਨੀ ਬਣਾਓ ਕਿ ਨਾ-ਸੰਭਾਲੇ ਕੰਮ ਨੂੰ ਹੋਰ ਥਾਂ ਸੰਭਾਲ ਲਿਆ ਹੈ ਅਤੇ ਖਾਸ ਪੜ੍ਹਨ/ਲਿਖਣ ਕਾਰਵਾਈਆਂ ਪੂਰੀਆਂ ਹੋਈਆਂ ਹਨ। ਵਾਲਟ ਨੂੰ ਬੰਦ ਕਰਨ ਲਈ Cryptomator ਕਾਰਵਾਈ ਨੂੰ ਖਤਮ ਕਰੋ।
+
# Migration
migration.title=ਵਾਲਟ ਅੱਪਗਰੇਡ ਕਰੋ
## Start
diff --git a/main/ui/src/main/resources/i18n/strings_pl.properties b/main/ui/src/main/resources/i18n/strings_pl.properties
index f392f4ab3..54d9adead 100644
--- a/main/ui/src/main/resources/i18n/strings_pl.properties
+++ b/main/ui/src/main/resources/i18n/strings_pl.properties
@@ -107,6 +107,15 @@ unlock.error.heading=Nie można odblokować sejfu
unlock.error.invalidMountPoint.notExisting=Punkt montowania nie jest pustym katalogiem lub nie istnieje: %s
unlock.error.invalidMountPoint.existing=Punkt montowania już istnieje lub brakuje katalogu nadrzędnego: %s
+# Lock
+## Force
+lock.forced.heading=Blokada nie powiodła się
+lock.forced.message=Zamknięcie "%s" zostało zablokowane przez oczekujące operacje lub otwarte pliki. Możesz wymusić zamknięcie tego sejfu, ale może to spowodować utratę niezapisanych danych.
+lock.forced.confirmBtn=Wymuś zablokowanie
+## Failure
+lock.fail.heading=Błąd blokowania sejfu.
+lock.fail.message=Nie można zablokować sejfu "%s". Zapisz wszelkie zmiany w bezpiecznym miejscu i upewnij się, że nie ma żadnych ważnych oczekujących operacji odczytu/zapisu. W celu zamknięcia sejfu ubij Cryptomator.
+
# Migration
migration.title=Aktualizuj sejf
## Start
@@ -144,6 +153,7 @@ preferences.general.startHidden=Ukryj okno podczas uruchamiania programu Cryptom
preferences.general.debugLogging=Włącz logowanie w trybie debug
preferences.general.debugDirectory=Pokaż pliki logowania
preferences.general.autoStart=Uruchom Cryptomator po uruchomieniu systemu
+preferences.general.keychainBackend=Przechowuj hasła za pomocą
preferences.general.keychainBackend.org.cryptomator.linux.keychain.SecretServiceKeychainAccess=Brelok kluczy Gnome
preferences.general.keychainBackend.org.cryptomator.linux.keychain.KDEWalletKeychainAccess=Portfel KDE
preferences.general.keychainBackend.org.cryptomator.macos.keychain.MacSystemKeychainAccess=Dostęp do pęku kluczy macOS
@@ -171,6 +181,8 @@ preferences.donationKey.getDonationKey=Zdobądź klucz donacji
preferences.about=O programie
# Vault Statistics
+stats.title=Statystyki dla %s
+stats.cacheHitRate=Trafność cache
## Read
stats.read.throughput.idle=Odczyt: bezczynny
stats.read.throughput.kibs=Odczyt: %.2f kiB/s
diff --git a/main/ui/src/main/resources/i18n/strings_pt.properties b/main/ui/src/main/resources/i18n/strings_pt.properties
index e29f15f90..d2d2ec046 100644
--- a/main/ui/src/main/resources/i18n/strings_pt.properties
+++ b/main/ui/src/main/resources/i18n/strings_pt.properties
@@ -95,6 +95,10 @@ unlock.success.revealBtn=Revelar Cofre
## Failure
### Invalid Mount Point
+# Lock
+## Force
+## Failure
+
# Migration
migration.title=Atualizar Cofre
## Start
diff --git a/main/ui/src/main/resources/i18n/strings_pt_BR.properties b/main/ui/src/main/resources/i18n/strings_pt_BR.properties
index af0a1130d..cbe8a2acd 100644
--- a/main/ui/src/main/resources/i18n/strings_pt_BR.properties
+++ b/main/ui/src/main/resources/i18n/strings_pt_BR.properties
@@ -106,6 +106,10 @@ unlock.success.revealBtn=Revelar Cofre
unlock.error.invalidMountPoint.notExisting=O ponto de montagem não é um diretório vazio ou não existe: %s
unlock.error.invalidMountPoint.existing=Ponto de montagem/pasta já existe ou a pasta pai está faltando: %s
+# Lock
+## Force
+## Failure
+
# Migration
migration.title=Atualizar Cofre
## Start
diff --git a/main/ui/src/main/resources/i18n/strings_ro.properties b/main/ui/src/main/resources/i18n/strings_ro.properties
index acd501750..9f2898f25 100644
--- a/main/ui/src/main/resources/i18n/strings_ro.properties
+++ b/main/ui/src/main/resources/i18n/strings_ro.properties
@@ -29,6 +29,10 @@
## Failure
### Invalid Mount Point
+# Lock
+## Force
+## Failure
+
# Migration
## Start
## Run
diff --git a/main/ui/src/main/resources/i18n/strings_ru.properties b/main/ui/src/main/resources/i18n/strings_ru.properties
index e976ce012..ac93b57d4 100644
--- a/main/ui/src/main/resources/i18n/strings_ru.properties
+++ b/main/ui/src/main/resources/i18n/strings_ru.properties
@@ -38,7 +38,7 @@ addvaultwizard.welcome.existingButton=Открыть имеющееся хран
addvaultwizard.new.nameInstruction=Выберите имя для хранилища
addvaultwizard.new.namePrompt=Имя хранилища
### Location
-addvaultwizard.new.locationInstruction=Где Cryptomator должен хранить зашифрованные файлы вашего хранилища?
+addvaultwizard.new.locationInstruction=Где Cryptomator должен хранить зашифрованные файлы хранилища?
addvaultwizard.new.locationLabel=Место хранения
addvaultwizard.new.locationPrompt=…
addvaultwizard.new.directoryPickerLabel=Своё место
@@ -51,7 +51,7 @@ addvaultwizard.new.invalidName=Неверное имя хранилища. Ук
addvaultwizard.new.createVaultBtn=Создать хранилище
addvaultwizard.new.generateRecoveryKeyChoice=Вы не сможете получить доступ к своим данным без пароля. Хотите создать ключ для восстановления на случай потери пароля?
addvaultwizard.new.generateRecoveryKeyChoice.yes=Да, лучше предостеречься, чем потом жалеть
-addvaultwizard.new.generateRecoveryKeyChoice.no=Нет, спасибо, я не потеряю свой пароль
+addvaultwizard.new.generateRecoveryKeyChoice.no=Нет, спасибо, я не потеряю пароль
### Information
addvault.new.readme.storageLocation.fileName=ВАЖНО.rtf
addvault.new.readme.storageLocation.1=⚠️ ФАЙЛЫ ХРАНИЛИЩА ⚠️
@@ -107,6 +107,15 @@ unlock.error.heading=Невозможно разблокировать хран
unlock.error.invalidMountPoint.notExisting=Точка монтирования %s - не папка, не пуста или не существует.
unlock.error.invalidMountPoint.existing=Точка монтирования %s уже существует, либо отсутствует родительская папка.
+# Lock
+## Force
+lock.forced.heading=Ошибка корректной блокировки
+lock.forced.message=Блокировка "%s" была отменена из-за текущих операций или открытых файлов. Вы можете заблокировать это хранилище принудительно, однако прерывание ввода-вывода может привести к потере несохранённых данных.
+lock.forced.confirmBtn=Принудительная блокировка
+## Failure
+lock.fail.heading=Ошибка блокировки хранилища.
+lock.fail.message=Хранилище "%s" не удалось заблокировать. Убедитесь, что несохранённая работа сохранена в другом месте и завершены важные операции чтения/записи. Чтобы закрыть хранилище, удалите процесс Cryptomator.
+
# Migration
migration.title=Обновить хранилище
## Start
@@ -144,11 +153,12 @@ preferences.general.startHidden=Скрывать окно при запуске
preferences.general.debugLogging=Вести журнал отладки
preferences.general.debugDirectory=Показать файлы журнала
preferences.general.autoStart=Запускать Cryptomator при старте системы
+preferences.general.keychainBackend=Хранить пароли с
preferences.general.keychainBackend.org.cryptomator.linux.keychain.SecretServiceKeychainAccess=Связка ключей Gnome
preferences.general.keychainBackend.org.cryptomator.linux.keychain.KDEWalletKeychainAccess=Бумажник KDE
preferences.general.keychainBackend.org.cryptomator.macos.keychain.MacSystemKeychainAccess=Доступ к связке ключей macOS
preferences.general.keychainBackend.org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess=Защита данных Windows
-preferences.general.interfaceOrientation=Ориентация интерфейса
+preferences.general.interfaceOrientation=Интерфейс
preferences.general.interfaceOrientation.ltr=Слева направо
preferences.general.interfaceOrientation.rtl=Справа налево
## Volume
@@ -165,7 +175,7 @@ preferences.updates.updateAvailable=Доступно обновление до
## Donation Key
preferences.donationKey=Пожертвование
preferences.donationKey.registeredFor=Зарегистрировано: %s
-preferences.donationKey.noDonationKey=Не найден ключ пожертвования. Он напоминает лицензионный ключ, но для удивительных людей, использующих свободное ПО ;-)
+preferences.donationKey.noDonationKey=Не найден ключ пожертвования. Этот ключ похож на лицензионный, но для тех, кто использует бесплатное ПО. ;-)
preferences.donationKey.getDonationKey=Получить ключ пожертвования
## About
preferences.about=О программе
@@ -221,7 +231,7 @@ main.vaultDetail.lockedStatus=ЗАБЛОКИРОВАНО
main.vaultDetail.unlockBtn=Разблокировка…
main.vaultDetail.unlockNowBtn=Разблокировать
main.vaultDetail.optionsBtn=Параметры хранилища
-main.vaultDetail.passwordSavedInKeychain=Пароль сохранен
+main.vaultDetail.passwordSavedInKeychain=Пароль сохранён
### Unlocked
main.vaultDetail.unlockedStatus=РАЗБЛОКИРОВАНО
main.vaultDetail.accessLocation=Содержимое хранилища доступно здесь:
@@ -247,7 +257,7 @@ wrongFileAlert.title=Как шифровать файлы
wrongFileAlert.header.title=Вы пытались зашифровать эти файлы?
wrongFileAlert.header.lead=Для этого Cryptomator создаёт том в системном диспетчере файлов.
wrongFileAlert.instruction.0=Чтобы зашифровать файлы, выполните следующее:
-wrongFileAlert.instruction.1=1. Разблокируйте ваше хранилище.
+wrongFileAlert.instruction.1=1. Разблокируйте хранилище.
wrongFileAlert.instruction.2=2. Нажмите кнопку "Показать", чтобы открыть том в диспетчере файлов.
wrongFileAlert.instruction.3=3. Добавьте файлы в этот том.
wrongFileAlert.link=Если нужна помощь, посетите
diff --git a/main/ui/src/main/resources/i18n/strings_sk.properties b/main/ui/src/main/resources/i18n/strings_sk.properties
index 51a434533..710173c14 100644
--- a/main/ui/src/main/resources/i18n/strings_sk.properties
+++ b/main/ui/src/main/resources/i18n/strings_sk.properties
@@ -104,6 +104,15 @@ unlock.error.heading=Nie je možné odomknúť účet
unlock.error.invalidMountPoint.notExisting=Bod pripojenia "%s" nie je adresár, nie je prázdny alebo neexistuje.
unlock.error.invalidMountPoint.existing=Bod pripojenia "%s" už existuje alebo chýba nadradený adresár.
+# Lock
+## Force
+lock.forced.heading=Bežné uzamknutie zlyhalo
+lock.forced.message=Zamknutie "%s" bolo zablokované prebiehajúcimi operáciami alebo otvorenými súbormi. Smiete vynútiť uzamknutie tejto peňaženky, ale prerušením I/O môže viesť k strate alebo neuloženiu dát.
+lock.forced.confirmBtn=Vynútené uzamknutie
+## Failure
+lock.fail.heading=Uzatváranie peňaženky zlyhalo.
+lock.fail.message=Peňaženku "%s" nie je možné uzamknúť. Uistite sa že neuložená páca je uložená inde a dôležité Read/Write operácie sú ukončené. Ináč uzavretím peňaženky, ukončíte proces Cryptomator-a.
+
# Migration
## Start
## Run
diff --git a/main/ui/src/main/resources/i18n/strings_sv.properties b/main/ui/src/main/resources/i18n/strings_sv.properties
index a29a44c69..98a487633 100644
--- a/main/ui/src/main/resources/i18n/strings_sv.properties
+++ b/main/ui/src/main/resources/i18n/strings_sv.properties
@@ -107,6 +107,10 @@ unlock.error.heading=Kan inte låsa upp valvet
unlock.error.invalidMountPoint.notExisting=Monteringspunkten "%s" saknas eller är inte tom.
unlock.error.invalidMountPoint.existing=Monteringspunkten "%s" finns redan eller så saknas överordnad mapp.
+# Lock
+## Force
+## Failure
+
# Migration
migration.title=Uppgradera valv
## Start
diff --git a/main/ui/src/main/resources/i18n/strings_tr.properties b/main/ui/src/main/resources/i18n/strings_tr.properties
index 1e3d9f47e..bd9a1180c 100644
--- a/main/ui/src/main/resources/i18n/strings_tr.properties
+++ b/main/ui/src/main/resources/i18n/strings_tr.properties
@@ -102,10 +102,20 @@ unlock.success.message="%s" 'nin kilidi başarıyla açıldı! Kasanız şimdi e
unlock.success.rememberChoice=Seçimi hatırla, bunu bir daha gösterme
unlock.success.revealBtn=Kasayı Göster
## Failure
+unlock.error.heading=Kasanın kilidi açılamıyor
### Invalid Mount Point
unlock.error.invalidMountPoint.notExisting=Bağlantı noktası boş bir dizin değil veya mevcut değil: %s
unlock.error.invalidMountPoint.existing=Bağlama noktası / klasör zaten var veya ana klasör eksik: %s
+# Lock
+## Force
+lock.forced.heading=Normal kilitleme başarısız oldu
+lock.forced.message="%s" nin kilitlenmesi, bekleyen işlemler veya açık dosyalar tarafından engellendi. Bu kasayı zorla kilitleyebilirsiniz, ancak G/Ç'nin kesilmesi kaydedilmemiş verilerin kaybına neden olabilir.
+lock.forced.confirmBtn=Kilitlemeyi Zorla
+## Failure
+lock.fail.heading=Kasa kilitlenemedi.
+lock.fail.message="%s" kasası kilitlenemedi. Kaydedilmemiş çalışmanın başka bir yere kaydedildiğinden ve önemli Okuma / Yazma işlemlerinin tamamlandığından emin olun. Kasayı kapatmak için Cryptomator işlemini sonlandırın.
+
# Migration
migration.title=Kasayı Güncelle
## Start
@@ -145,7 +155,9 @@ preferences.general.debugDirectory=Kayıt dosyalarını göster
preferences.general.autoStart=Cryptomator'u sistem başlangıcında çalıştır
preferences.general.keychainBackend=Şifreleri şununla depola:
preferences.general.keychainBackend.org.cryptomator.linux.keychain.SecretServiceKeychainAccess=Gnome Keyring
+preferences.general.keychainBackend.org.cryptomator.linux.keychain.KDEWalletKeychainAccess=KDE Cüzdan
preferences.general.keychainBackend.org.cryptomator.macos.keychain.MacSystemKeychainAccess=macOS Anahtar Zinciri Erişimi
+preferences.general.keychainBackend.org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess=Windows Veri Koruması
preferences.general.interfaceOrientation=Arayüz Yönü
preferences.general.interfaceOrientation.ltr=Sola Yaslı
preferences.general.interfaceOrientation.rtl=Sağa Yaslı
@@ -169,8 +181,34 @@ preferences.donationKey.getDonationKey=Bir bağış anahtarı al
preferences.about=Hakkında
# Vault Statistics
+stats.title=%s İçin İstatistikler
+stats.cacheHitRate=Önbellek Kullanım Oranı
## Read
+stats.read.throughput.idle=Okuma: boşta
+stats.read.throughput.kibs=Okuma: %.2f kB/s
+stats.read.throughput.mibs=Okuma: %.2f MB/s
+stats.read.total.data.none=Okunan veri: -
+stats.read.total.data.kib=Okunan veri: %.1f kB
+stats.read.total.data.mib=Okunan veri: %.1f MB
+stats.read.total.data.gib=Okunan veri: %.1f GB
+stats.decr.total.data.none=Şifresi çözülen veri: -
+stats.decr.total.data.kib=Şifresi çözülen veri: %.1f kB
+stats.decr.total.data.mib=Şifresi çözülen veri: %.1f MB
+stats.decr.total.data.gib=Şifresi çözülen veri: %.1f GB
+stats.read.accessCount=Toplam okuma: %d
## Write
+stats.write.throughput.idle=Yazma: boşta
+stats.write.throughput.kibs=Yazma: %.2f kB/s
+stats.write.throughput.mibs=Yazma: %.2f MB/s
+stats.write.total.data.none=Yazılan veri: -
+stats.write.total.data.kib=Yazılan veri: %.1f kB
+stats.write.total.data.mib=Yazılan veri: %.1f MB
+stats.write.total.data.gib=Yazılan veri: %.1f GB
+stats.encr.total.data.none=Şifrelenen veri: -
+stats.encr.total.data.kib=Şifrelenen veri: %.1f kB
+stats.encr.total.data.mib=Şifrelenen veri: %.1f MB
+stats.encr.total.data.gib=Şifrelenen veri: %.1f GB
+stats.write.accessCount=Toplam yazma: %d
# Main Window
main.closeBtn.tooltip=Kapat
@@ -200,9 +238,11 @@ main.vaultDetail.accessLocation=Kasa içeriğinize buradan erişilebilir:
main.vaultDetail.revealBtn=Sürücüyü Göster
main.vaultDetail.lockBtn=Kilitle
main.vaultDetail.bytesPerSecondRead=Okuma:
+main.vaultDetail.bytesPerSecondWritten=Yazma:
main.vaultDetail.throughput.idle=boşta
main.vaultDetail.throughput.kbps=%.1f kiB/s
main.vaultDetail.throughput.mbps=%.1f MiB/s
+main.vaultDetail.stats=Kasa İstatistikleri
### Missing
main.vaultDetail.missing.info=Şifreleyici bu dosya yolunda bir kasa bulamadı.
main.vaultDetail.missing.recheck=Yeniden denetle
diff --git a/main/ui/src/main/resources/i18n/strings_zh.properties b/main/ui/src/main/resources/i18n/strings_zh.properties
index bdfbfbc00..be0607b0b 100644
--- a/main/ui/src/main/resources/i18n/strings_zh.properties
+++ b/main/ui/src/main/resources/i18n/strings_zh.properties
@@ -99,7 +99,7 @@ unlock.savePassword=保存密码
unlock.unlockBtn=解锁
## Success
unlock.success.message=已成功解锁 "%s"! 您现在可以访问该保险库
-unlock.success.rememberChoice=记住该选择,不要再显示
+unlock.success.rememberChoice=记住选项且不再显示
unlock.success.revealBtn=显示保险库
## Failure
unlock.error.heading=无法解锁保险库
@@ -107,6 +107,15 @@ unlock.error.heading=无法解锁保险库
unlock.error.invalidMountPoint.notExisting=挂载点 "%s" 不是目录、非空或不存在
unlock.error.invalidMountPoint.existing=挂载点 "%s" 已存在或缺少父文件夹
+# Lock
+## Force
+lock.forced.heading=常规锁定失败
+lock.forced.message=锁定 "%s" 被挂起的操作或使用中的文件中断。您可以强制锁定此保险库,不过请注意打断 I/O 可能导致未保存的数据丢失
+lock.forced.confirmBtn=强制锁定
+## Failure
+lock.fail.heading=锁定保险库失败
+lock.fail.message=保险库 "%s" 无法锁定。请确保在其他地方保存未保存的工作,以及重要的 "读/写" 操作已完成。为了顺利关闭保险库,请查杀 Cryptomator 进程
+
# Migration
migration.title=升级保险库
## Start
diff --git a/main/ui/src/main/resources/i18n/strings_zh_TW.properties b/main/ui/src/main/resources/i18n/strings_zh_TW.properties
index 631bf65a4..bca076f87 100644
--- a/main/ui/src/main/resources/i18n/strings_zh_TW.properties
+++ b/main/ui/src/main/resources/i18n/strings_zh_TW.properties
@@ -102,10 +102,20 @@ unlock.success.message=成功解鎖 "%s"!您現在可以存取您的加密檔
unlock.success.rememberChoice=記得這個決定,不要再顯示
unlock.success.revealBtn=顯示加密檔案庫
## Failure
+unlock.error.heading=無法解鎖加密檔案庫
### Invalid Mount Point
unlock.error.invalidMountPoint.notExisting=掛載點不是空目錄或是不存在:%s
unlock.error.invalidMountPoint.existing=掛載點已經存在或上層資料夾不存在:%s
+# Lock
+## Force
+lock.forced.heading=正常鎖定失敗
+lock.forced.message=仍有未完成的操作或開啟中的檔案以致無法鎖定 "%s"。您可以強制鎖定這個加密檔案庫,不過中斷讀寫可能會導致資料遺失或未被儲存。
+lock.forced.confirmBtn=強制鎖定
+## Failure
+lock.fail.heading=鎖定加密檔案庫失敗。
+lock.fail.message=加密檔案庫 "%s" 無法被鎖定。請確保未存檔的工作已儲存在別的地方以及重要的讀寫工作都已經完成。請強制結束 Cryptomator 以關閉加密檔案庫。
+
# Migration
migration.title=升級加密檔案庫
## Start
@@ -143,8 +153,11 @@ preferences.general.startHidden=啟動 Cryptomator 時隱藏視窗
preferences.general.debugLogging=啟用除錯日誌
preferences.general.debugDirectory=顯示日誌檔
preferences.general.autoStart=系統啟動時同時啟動 Cryptomator
+preferences.general.keychainBackend=儲存密碼使用
preferences.general.keychainBackend.org.cryptomator.linux.keychain.SecretServiceKeychainAccess=Gnome 鑰匙圈
+preferences.general.keychainBackend.org.cryptomator.linux.keychain.KDEWalletKeychainAccess=KDE 錢包
preferences.general.keychainBackend.org.cryptomator.macos.keychain.MacSystemKeychainAccess=macOS 鑰匙圈
+preferences.general.keychainBackend.org.cryptomator.windows.keychain.WindowsProtectedKeychainAccess=Windows 數據保護
preferences.general.interfaceOrientation=界面排版方向
preferences.general.interfaceOrientation.ltr=由左至右
preferences.general.interfaceOrientation.rtl=由右至左
@@ -168,8 +181,34 @@ preferences.donationKey.getDonationKey=取得贊助金鑰
preferences.about=關於
# Vault Statistics
+stats.title=%s 統計數據
+stats.cacheHitRate=快取命中率
## Read
+stats.read.throughput.idle=讀取:閒置
+stats.read.throughput.kibs=讀取:%.2f kiB/s
+stats.read.throughput.mibs=讀取:%.2f MiB/s
+stats.read.total.data.none=資料讀取:無
+stats.read.total.data.kib=資料讀取:%.1f kiB
+stats.read.total.data.mib=資料讀取:%.1f MiB
+stats.read.total.data.gib=資料讀取:%.1f GiB
+stats.decr.total.data.none=資料解密:無
+stats.decr.total.data.kib=資料解密:%.1f kiB
+stats.decr.total.data.mib=資料解密:%.1f MiB
+stats.decr.total.data.gib=資料解密:%.1f GiB
+stats.read.accessCount=總讀取:%d
## Write
+stats.write.throughput.idle=寫入:閒置
+stats.write.throughput.kibs=寫入:%.2f kiB/s
+stats.write.throughput.mibs=寫入:%.2f MiB/s
+stats.write.total.data.none=資料讀取:無
+stats.write.total.data.kib=資料寫入:%.1f kiB
+stats.write.total.data.mib=資料寫入:%.1f MiB
+stats.write.total.data.gib=資料寫入:%.1f GiB
+stats.encr.total.data.none=資料加密:無
+stats.encr.total.data.kib=資料加密:%.1f kiB
+stats.encr.total.data.mib=資料加密:%.1f MiB
+stats.encr.total.data.gib=資料加密:%.1f GiB
+stats.write.accessCount=總寫入:%d
# Main Window
main.closeBtn.tooltip=關閉
@@ -199,9 +238,11 @@ main.vaultDetail.accessLocation=您可以從這裡取得您加密檔案庫的內
main.vaultDetail.revealBtn=顯示磁碟
main.vaultDetail.lockBtn=鎖定
main.vaultDetail.bytesPerSecondRead=讀取:
+main.vaultDetail.bytesPerSecondWritten=寫入:
main.vaultDetail.throughput.idle=閒置
main.vaultDetail.throughput.kbps=%.1f kiB/s
main.vaultDetail.throughput.mbps=%.1f MiB/s
+main.vaultDetail.stats=加密檔案庫統計數據
### Missing
main.vaultDetail.missing.info=Cryptomator 無法在指定位置找到加密檔案庫。
main.vaultDetail.missing.recheck=重新檢查
diff --git a/main/ui/src/main/resources/license/THIRD-PARTY.txt b/main/ui/src/main/resources/license/THIRD-PARTY.txt
index dedb408c3..b4f3ee28e 100644
--- a/main/ui/src/main/resources/license/THIRD-PARTY.txt
+++ b/main/ui/src/main/resources/license/THIRD-PARTY.txt
@@ -11,7 +11,7 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see http://www.gnu.org/licenses/.
-Cryptomator uses 46 third-party dependencies under the following licenses:
+Cryptomator uses 47 third-party dependencies under the following licenses:
Apache License v2.0:
- jffi (com.github.jnr:jffi:1.2.23 - http://github.com/jnr/jffi)
- jnr-a64asm (com.github.jnr:jnr-a64asm:1.0.0 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-a64asm)
@@ -28,19 +28,20 @@ Cryptomator uses 46 third-party dependencies under the following licenses:
- Apache Commons CLI (commons-cli:commons-cli:1.4 - http://commons.apache.org/proper/commons-cli/)
- Apache Commons IO (commons-io:commons-io:2.6 - http://commons.apache.org/proper/commons-io/)
- javax.inject (javax.inject:javax.inject:1 - http://code.google.com/p/atinject/)
- - Java Native Access (net.java.dev.jna:jna:5.5.0 - https://github.com/java-native-access/jna)
+ - Java Native Access (net.java.dev.jna:jna:5.6.0 - https://github.com/java-native-access/jna)
- Java Native Access Platform (net.java.dev.jna:jna-platform:5.5.0 - https://github.com/java-native-access/jna)
- Apache Commons Lang (org.apache.commons:commons-lang3:3.11 - https://commons.apache.org/proper/commons-lang/)
- Apache HttpCore (org.apache.httpcomponents:httpcore:4.4.13 - http://hc.apache.org/httpcomponents-core-ga)
- Jackrabbit WebDAV Library (org.apache.jackrabbit:jackrabbit-webdav:2.21.3 - http://jackrabbit.apache.org/jackrabbit-webdav/)
- - Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.33.v20201020 - https://eclipse.org/jetty/jetty-http)
- - Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.33.v20201020 - https://eclipse.org/jetty/jetty-io)
- - Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.33.v20201020 - https://eclipse.org/jetty/jetty-security)
- - Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.33.v20201020 - https://eclipse.org/jetty/jetty-server)
- - Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.33.v20201020 - https://eclipse.org/jetty/jetty-servlet)
- - Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.33.v20201020 - https://eclipse.org/jetty/jetty-util)
- - Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.33.v20201020 - https://eclipse.org/jetty/jetty-webapp)
- - Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.33.v20201020 - https://eclipse.org/jetty/jetty-xml)
+ - Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.35.v20201120 - https://eclipse.org/jetty/jetty-http)
+ - Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.35.v20201120 - https://eclipse.org/jetty/jetty-io)
+ - Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.35.v20201120 - https://eclipse.org/jetty/jetty-security)
+ - Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.35.v20201120 - https://eclipse.org/jetty/jetty-server)
+ - Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.35.v20201120 - https://eclipse.org/jetty/jetty-servlet)
+ - Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.35.v20201120 - https://eclipse.org/jetty/jetty-util)
+ - Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.35.v20201120 - https://eclipse.org/jetty/jetty-util-ajax)
+ - Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.35.v20201120 - https://eclipse.org/jetty/jetty-webapp)
+ - Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.35.v20201120 - https://eclipse.org/jetty/jetty-xml)
BSD:
- asm (org.ow2.asm:asm:7.1 - http://asm.ow2.org/)
- asm-analysis (org.ow2.asm:asm-analysis:7.1 - http://asm.ow2.org/)
@@ -48,14 +49,15 @@ Cryptomator uses 46 third-party dependencies under the following licenses:
- asm-tree (org.ow2.asm:asm-tree:7.1 - http://asm.ow2.org/)
- asm-util (org.ow2.asm:asm-util:7.1 - http://asm.ow2.org/)
Eclipse Public License - Version 1.0:
- - Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.33.v20201020 - https://eclipse.org/jetty/jetty-http)
- - Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.33.v20201020 - https://eclipse.org/jetty/jetty-io)
- - Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.33.v20201020 - https://eclipse.org/jetty/jetty-security)
- - Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.33.v20201020 - https://eclipse.org/jetty/jetty-server)
- - Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.33.v20201020 - https://eclipse.org/jetty/jetty-servlet)
- - Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.33.v20201020 - https://eclipse.org/jetty/jetty-util)
- - Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.33.v20201020 - https://eclipse.org/jetty/jetty-webapp)
- - Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.33.v20201020 - https://eclipse.org/jetty/jetty-xml)
+ - Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.35.v20201120 - https://eclipse.org/jetty/jetty-http)
+ - Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.35.v20201120 - https://eclipse.org/jetty/jetty-io)
+ - Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.35.v20201120 - https://eclipse.org/jetty/jetty-security)
+ - Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.35.v20201120 - https://eclipse.org/jetty/jetty-server)
+ - Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.35.v20201120 - https://eclipse.org/jetty/jetty-servlet)
+ - Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.35.v20201120 - https://eclipse.org/jetty/jetty-util)
+ - Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.35.v20201120 - https://eclipse.org/jetty/jetty-util-ajax)
+ - Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.35.v20201120 - https://eclipse.org/jetty/jetty-webapp)
+ - Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.35.v20201120 - https://eclipse.org/jetty/jetty-xml)
Eclipse Public License - v 2.0:
- jnr-posix (com.github.jnr:jnr-posix:3.0.54 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix)
GPLv2:
@@ -68,7 +70,7 @@ Cryptomator uses 46 third-party dependencies under the following licenses:
- javafx-graphics (org.openjfx:javafx-graphics:15 - https://openjdk.java.net/projects/openjfx/javafx-graphics/)
LGPL 2.1:
- jnr-posix (com.github.jnr:jnr-posix:3.0.54 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix)
- - Java Native Access (net.java.dev.jna:jna:5.5.0 - https://github.com/java-native-access/jna)
+ - Java Native Access (net.java.dev.jna:jna:5.6.0 - https://github.com/java-native-access/jna)
- Java Native Access Platform (net.java.dev.jna:jna-platform:5.5.0 - https://github.com/java-native-access/jna)
MIT License:
- java jwt (com.auth0:java-jwt:3.11.0 - https://github.com/auth0/java-jwt)