mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-15 09:11:29 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aef33dc864 | ||
|
|
06d2f2d9e9 | ||
|
|
dad0ad76fb | ||
|
|
99fa8e7c8e | ||
|
|
ab0f175edf | ||
|
|
8cb9728565 | ||
|
|
d91d27f2a4 | ||
|
|
b2a6e038ae | ||
|
|
75f360903c | ||
|
|
79c3137b90 | ||
|
|
d2189d379c | ||
|
|
49aead7323 | ||
|
|
7bd610563f | ||
|
|
5c1a1ad162 | ||
|
|
86906d0049 | ||
|
|
93011dc754 | ||
|
|
b084b651af | ||
|
|
fecf9c0423 | ||
|
|
117fe78a4a | ||
|
|
153d43573a |
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.5.3</version>
|
||||
<version>1.5.4</version>
|
||||
</parent>
|
||||
<artifactId>buildkit</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.5.3</version>
|
||||
<version>1.5.4</version>
|
||||
</parent>
|
||||
<artifactId>commons</artifactId>
|
||||
<name>Cryptomator Commons</name>
|
||||
|
||||
@@ -58,46 +58,22 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
while (in.hasNext()) {
|
||||
String name = in.nextName();
|
||||
switch (name) {
|
||||
case "directories":
|
||||
settings.getDirectories().addAll(readVaultSettingsArray(in));
|
||||
break;
|
||||
case "askedForUpdateCheck":
|
||||
settings.askedForUpdateCheck().set(in.nextBoolean());
|
||||
break;
|
||||
case "checkForUpdatesEnabled":
|
||||
settings.checkForUpdates().set(in.nextBoolean());
|
||||
break;
|
||||
case "startHidden":
|
||||
settings.startHidden().set(in.nextBoolean());
|
||||
break;
|
||||
case "port":
|
||||
settings.port().set(in.nextInt());
|
||||
break;
|
||||
case "numTrayNotifications":
|
||||
settings.numTrayNotifications().set(in.nextInt());
|
||||
break;
|
||||
case "preferredGvfsScheme":
|
||||
settings.preferredGvfsScheme().set(parseWebDavUrlSchemePrefix(in.nextString()));
|
||||
break;
|
||||
case "debugMode":
|
||||
settings.debugMode().set(in.nextBoolean());
|
||||
break;
|
||||
case "preferredVolumeImpl":
|
||||
settings.preferredVolumeImpl().set(parsePreferredVolumeImplName(in.nextString()));
|
||||
break;
|
||||
case "theme":
|
||||
settings.theme().set(parseUiTheme(in.nextString()));
|
||||
break;
|
||||
case "uiOrientation":
|
||||
settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
|
||||
break;
|
||||
case "licenseKey":
|
||||
settings.licenseKey().set(in.nextString());
|
||||
break;
|
||||
default:
|
||||
case "directories" -> settings.getDirectories().addAll(readVaultSettingsArray(in));
|
||||
case "askedForUpdateCheck" -> settings.askedForUpdateCheck().set(in.nextBoolean());
|
||||
case "checkForUpdatesEnabled" -> settings.checkForUpdates().set(in.nextBoolean());
|
||||
case "startHidden" -> settings.startHidden().set(in.nextBoolean());
|
||||
case "port" -> settings.port().set(in.nextInt());
|
||||
case "numTrayNotifications" -> settings.numTrayNotifications().set(in.nextInt());
|
||||
case "preferredGvfsScheme" -> settings.preferredGvfsScheme().set(parseWebDavUrlSchemePrefix(in.nextString()));
|
||||
case "debugMode" -> settings.debugMode().set(in.nextBoolean());
|
||||
case "preferredVolumeImpl" -> settings.preferredVolumeImpl().set(parsePreferredVolumeImplName(in.nextString()));
|
||||
case "theme" -> settings.theme().set(parseUiTheme(in.nextString()));
|
||||
case "uiOrientation" -> settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
|
||||
case "licenseKey" -> settings.licenseKey().set(in.nextString());
|
||||
default -> {
|
||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||
in.skipValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
in.endObject();
|
||||
|
||||
@@ -26,7 +26,6 @@ import java.util.Random;
|
||||
|
||||
/**
|
||||
* The settings specific to a single vault.
|
||||
* TODO: Change the name of individualMountPath and its derivatives to customMountPath
|
||||
*/
|
||||
public class VaultSettings {
|
||||
|
||||
@@ -36,6 +35,7 @@ public class VaultSettings {
|
||||
public static final boolean DEFAULT_USES_READONLY_MODE = false;
|
||||
public static final String DEFAULT_MOUNT_FLAGS = "";
|
||||
public static final int DEFAULT_FILENAME_LENGTH_LIMIT = -1;
|
||||
public static final WhenUnlocked DEFAULT_ACTION_AFTER_UNLOCK = WhenUnlocked.ASK;
|
||||
|
||||
private static final Random RNG = new Random();
|
||||
|
||||
@@ -45,11 +45,12 @@ public class VaultSettings {
|
||||
private final StringProperty winDriveLetter = new SimpleStringProperty();
|
||||
private final BooleanProperty unlockAfterStartup = new SimpleBooleanProperty(DEFAULT_UNLOCK_AFTER_STARTUP);
|
||||
private final BooleanProperty revealAfterMount = new SimpleBooleanProperty(DEFAULT_REAVEAL_AFTER_MOUNT);
|
||||
private final BooleanProperty usesIndividualMountPath = new SimpleBooleanProperty(DEFAULT_USES_INDIVIDUAL_MOUNTPATH);
|
||||
private final StringProperty individualMountPath = new SimpleStringProperty();
|
||||
private final BooleanProperty useCustomMountPath = new SimpleBooleanProperty(DEFAULT_USES_INDIVIDUAL_MOUNTPATH);
|
||||
private final StringProperty customMountPath = new SimpleStringProperty();
|
||||
private final BooleanProperty usesReadOnlyMode = new SimpleBooleanProperty(DEFAULT_USES_READONLY_MODE);
|
||||
private final StringProperty mountFlags = new SimpleStringProperty(DEFAULT_MOUNT_FLAGS);
|
||||
private final IntegerProperty filenameLengthLimit = new SimpleIntegerProperty(DEFAULT_FILENAME_LENGTH_LIMIT);
|
||||
private final ObjectProperty<WhenUnlocked> actionAfterUnlock = new SimpleObjectProperty<>(DEFAULT_ACTION_AFTER_UNLOCK);
|
||||
|
||||
public VaultSettings(String id) {
|
||||
this.id = Objects.requireNonNull(id);
|
||||
@@ -58,7 +59,7 @@ public class VaultSettings {
|
||||
}
|
||||
|
||||
Observable[] observables() {
|
||||
return new Observable[]{path, mountName, winDriveLetter, unlockAfterStartup, revealAfterMount, usesIndividualMountPath, individualMountPath, usesReadOnlyMode, mountFlags, filenameLengthLimit};
|
||||
return new Observable[]{path, mountName, winDriveLetter, unlockAfterStartup, revealAfterMount, useCustomMountPath, customMountPath, usesReadOnlyMode, mountFlags, filenameLengthLimit, actionAfterUnlock};
|
||||
}
|
||||
|
||||
private void deriveMountNameFromPath(Path path) {
|
||||
@@ -122,17 +123,17 @@ public class VaultSettings {
|
||||
return revealAfterMount;
|
||||
}
|
||||
|
||||
public BooleanProperty usesIndividualMountPath() {
|
||||
return usesIndividualMountPath;
|
||||
public BooleanProperty useCustomMountPath() {
|
||||
return useCustomMountPath;
|
||||
}
|
||||
|
||||
public StringProperty individualMountPath() {
|
||||
return individualMountPath;
|
||||
public StringProperty customMountPath() {
|
||||
return customMountPath;
|
||||
}
|
||||
|
||||
public Optional<String> getIndividualMountPath() {
|
||||
if (usesIndividualMountPath.get()) {
|
||||
return Optional.ofNullable(Strings.emptyToNull(individualMountPath.get()));
|
||||
public Optional<String> getCustomMountPath() {
|
||||
if (useCustomMountPath.get()) {
|
||||
return Optional.ofNullable(Strings.emptyToNull(customMountPath.get()));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
@@ -150,6 +151,14 @@ public class VaultSettings {
|
||||
return filenameLengthLimit;
|
||||
}
|
||||
|
||||
public ObjectProperty<WhenUnlocked> actionAfterUnlock() {
|
||||
return actionAfterUnlock;
|
||||
}
|
||||
|
||||
public WhenUnlocked getActionAfterUnlock() {
|
||||
return actionAfterUnlock.get();
|
||||
}
|
||||
|
||||
/* Hashcode/Equals */
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,11 +25,12 @@ class VaultSettingsJsonAdapter {
|
||||
out.name("winDriveLetter").value(value.winDriveLetter().get());
|
||||
out.name("unlockAfterStartup").value(value.unlockAfterStartup().get());
|
||||
out.name("revealAfterMount").value(value.revealAfterMount().get());
|
||||
out.name("usesIndividualMountPath").value(value.usesIndividualMountPath().get());
|
||||
out.name("individualMountPath").value(value.individualMountPath().get());
|
||||
out.name("useCustomMountPath").value(value.useCustomMountPath().get());
|
||||
out.name("customMountPath").value(value.customMountPath().get());
|
||||
out.name("usesReadOnlyMode").value(value.usesReadOnlyMode().get());
|
||||
out.name("mountFlags").value(value.mountFlags().get());
|
||||
out.name("filenameLengthLimit").value(value.filenameLengthLimit().get());
|
||||
out.name("actionAfterUnlock").value(value.actionAfterUnlock().get().name());
|
||||
out.endObject();
|
||||
}
|
||||
|
||||
@@ -37,56 +38,36 @@ class VaultSettingsJsonAdapter {
|
||||
String id = null;
|
||||
String path = null;
|
||||
String mountName = null;
|
||||
String individualMountPath = null;
|
||||
String customMountPath = null;
|
||||
String winDriveLetter = null;
|
||||
boolean unlockAfterStartup = VaultSettings.DEFAULT_UNLOCK_AFTER_STARTUP;
|
||||
boolean revealAfterMount = VaultSettings.DEFAULT_REAVEAL_AFTER_MOUNT;
|
||||
boolean usesIndividualMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH;
|
||||
boolean useCustomMountPath = VaultSettings.DEFAULT_USES_INDIVIDUAL_MOUNTPATH;
|
||||
boolean usesReadOnlyMode = VaultSettings.DEFAULT_USES_READONLY_MODE;
|
||||
String mountFlags = VaultSettings.DEFAULT_MOUNT_FLAGS;
|
||||
int filenameLengthLimit = VaultSettings.DEFAULT_FILENAME_LENGTH_LIMIT;
|
||||
WhenUnlocked actionAfterUnlock = VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
|
||||
|
||||
in.beginObject();
|
||||
while (in.hasNext()) {
|
||||
String name = in.nextName();
|
||||
switch (name) {
|
||||
case "id":
|
||||
id = in.nextString();
|
||||
break;
|
||||
case "path":
|
||||
path = in.nextString();
|
||||
break;
|
||||
case "mountName":
|
||||
mountName = in.nextString();
|
||||
break;
|
||||
case "winDriveLetter":
|
||||
winDriveLetter = in.nextString();
|
||||
break;
|
||||
case "unlockAfterStartup":
|
||||
unlockAfterStartup = in.nextBoolean();
|
||||
break;
|
||||
case "revealAfterMount":
|
||||
revealAfterMount = in.nextBoolean();
|
||||
break;
|
||||
case "usesIndividualMountPath":
|
||||
usesIndividualMountPath = in.nextBoolean();
|
||||
break;
|
||||
case "individualMountPath":
|
||||
individualMountPath = in.nextString();
|
||||
break;
|
||||
case "usesReadOnlyMode":
|
||||
usesReadOnlyMode = in.nextBoolean();
|
||||
break;
|
||||
case "mountFlags":
|
||||
mountFlags = in.nextString();
|
||||
break;
|
||||
case "filenameLengthLimit":
|
||||
filenameLengthLimit = in.nextInt();
|
||||
break;
|
||||
default:
|
||||
case "id" -> id = in.nextString();
|
||||
case "path" -> path = in.nextString();
|
||||
case "mountName" -> mountName = in.nextString();
|
||||
case "winDriveLetter" -> winDriveLetter = in.nextString();
|
||||
case "unlockAfterStartup" -> unlockAfterStartup = in.nextBoolean();
|
||||
case "revealAfterMount" -> revealAfterMount = in.nextBoolean();
|
||||
case "usesIndividualMountPath", "useCustomMountPath" -> useCustomMountPath = in.nextBoolean();
|
||||
case "individualMountPath", "customMountPath" -> customMountPath = in.nextString();
|
||||
case "usesReadOnlyMode" -> usesReadOnlyMode = in.nextBoolean();
|
||||
case "mountFlags" -> mountFlags = in.nextString();
|
||||
case "filenameLengthLimit" -> filenameLengthLimit = in.nextInt();
|
||||
case "actionAfterUnlock" -> actionAfterUnlock = parseActionAfterUnlock(in.nextString());
|
||||
default -> {
|
||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||
in.skipValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
in.endObject();
|
||||
@@ -97,12 +78,22 @@ class VaultSettingsJsonAdapter {
|
||||
vaultSettings.winDriveLetter().set(winDriveLetter);
|
||||
vaultSettings.unlockAfterStartup().set(unlockAfterStartup);
|
||||
vaultSettings.revealAfterMount().set(revealAfterMount);
|
||||
vaultSettings.usesIndividualMountPath().set(usesIndividualMountPath);
|
||||
vaultSettings.individualMountPath().set(individualMountPath);
|
||||
vaultSettings.useCustomMountPath().set(useCustomMountPath);
|
||||
vaultSettings.customMountPath().set(customMountPath);
|
||||
vaultSettings.usesReadOnlyMode().set(usesReadOnlyMode);
|
||||
vaultSettings.mountFlags().set(mountFlags);
|
||||
vaultSettings.filenameLengthLimit().set(filenameLengthLimit);
|
||||
vaultSettings.actionAfterUnlock().set(actionAfterUnlock);
|
||||
return vaultSettings;
|
||||
}
|
||||
|
||||
private WhenUnlocked parseActionAfterUnlock(String actionAfterUnlockName) {
|
||||
try {
|
||||
return WhenUnlocked.valueOf(actionAfterUnlockName.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.warn("Invalid action after unlock {}. Defaulting to {}.", actionAfterUnlockName, VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK);
|
||||
return VaultSettings.DEFAULT_ACTION_AFTER_UNLOCK;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public enum VolumeImpl {
|
||||
WEBDAV("WebDAV"),
|
||||
FUSE("FUSE"),
|
||||
@@ -17,18 +15,4 @@ public enum VolumeImpl {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a VolumeImpl by display name.
|
||||
*
|
||||
* @param displayName Display name of the VolumeImpl
|
||||
* @return VolumeImpl with the given <code>displayName</code>.
|
||||
* @throws IllegalArgumentException if not volumeImpl with the given <code>displayName</code> was found.
|
||||
*/
|
||||
public static VolumeImpl forDisplayName(String displayName) throws IllegalArgumentException {
|
||||
return Arrays.stream(values()) //
|
||||
.filter(impl -> impl.displayName.equals(displayName)) //
|
||||
.findAny() //
|
||||
.orElseThrow(IllegalArgumentException::new);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public enum WebDavUrlScheme {
|
||||
DAV("dav", "dav:// (Gnome, Nautilus, ...)"),
|
||||
WEBDAV("webdav", "webdav:// (KDE, Dolphin, ...)");
|
||||
@@ -20,18 +18,4 @@ public enum WebDavUrlScheme {
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a WebDavUrlScheme by prefix.
|
||||
*
|
||||
* @param prefix Prefix of the WebDavUrlScheme
|
||||
* @return WebDavUrlScheme with the given <code>prefix</code>.
|
||||
* @throws IllegalArgumentException if not WebDavUrlScheme with the given <code>prefix</code> was found.
|
||||
*/
|
||||
public static WebDavUrlScheme forPrefix(String prefix) throws IllegalArgumentException {
|
||||
return Arrays.stream(values()) //
|
||||
.filter(impl -> impl.prefix.equals(prefix)) //
|
||||
.findAny() //
|
||||
.orElseThrow(IllegalArgumentException::new);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.cryptomator.common.settings;
|
||||
|
||||
public enum WhenUnlocked {
|
||||
IGNORE("vaultOptions.general.actionAfterUnlock.ignore"),
|
||||
REVEAL("vaultOptions.general.actionAfterUnlock.reveal"),
|
||||
ASK("vaultOptions.general.actionAfterUnlock.ask");
|
||||
|
||||
private String displayName;
|
||||
|
||||
WhenUnlocked(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ public class DokanyVolume implements Volume {
|
||||
try {
|
||||
this.mount = mountFactory.mount(fs.getPath("/"), mountPoint, mountName, FS_TYPE_NAME, mountFlags.strip());
|
||||
} catch (MountFailedException e) {
|
||||
if (vaultSettings.getIndividualMountPath().isPresent()) {
|
||||
if (vaultSettings.getCustomMountPath().isPresent()) {
|
||||
LOG.warn("Failed to mount vault into {}. Is this directory currently accessed by another process (e.g. Windows Explorer)?", mountPoint);
|
||||
}
|
||||
throw new VolumeException("Unable to mount Filesystem", e);
|
||||
@@ -59,7 +59,7 @@ public class DokanyVolume implements Volume {
|
||||
}
|
||||
|
||||
private Path determineMountPoint() throws VolumeException, IOException {
|
||||
Optional<String> optionalCustomMountPoint = vaultSettings.getIndividualMountPath();
|
||||
Optional<String> optionalCustomMountPoint = vaultSettings.getCustomMountPath();
|
||||
if (optionalCustomMountPoint.isPresent()) {
|
||||
Path customMountPoint = Paths.get(optionalCustomMountPoint.get());
|
||||
checkProvidedMountPoint(customMountPoint);
|
||||
|
||||
@@ -45,7 +45,7 @@ public class FuseVolume implements Volume {
|
||||
|
||||
@Override
|
||||
public void mount(CryptoFileSystem fs, String mountFlags) throws IOException, FuseNotSupportedException, VolumeException {
|
||||
Optional<String> optionalCustomMountPoint = vaultSettings.getIndividualMountPath();
|
||||
Optional<String> optionalCustomMountPoint = vaultSettings.getCustomMountPath();
|
||||
if (optionalCustomMountPoint.isPresent()) {
|
||||
Path customMountPoint = Paths.get(optionalCustomMountPoint.get());
|
||||
checkProvidedMountPoint(customMountPoint);
|
||||
|
||||
@@ -121,7 +121,7 @@ public class Vault {
|
||||
}
|
||||
|
||||
public synchronized void unlock(CharSequence passphrase) throws CryptoException, IOException, Volume.VolumeException {
|
||||
if (vaultSettings.usesIndividualMountPath().get() && Strings.isNullOrEmpty(vaultSettings.individualMountPath().get())) {
|
||||
if (vaultSettings.useCustomMountPath().get() && Strings.isNullOrEmpty(vaultSettings.customMountPath().get())) {
|
||||
throw new NotDirectoryException("");
|
||||
}
|
||||
CryptoFileSystem fs = getCryptoFileSystem(passphrase);
|
||||
|
||||
@@ -92,8 +92,27 @@ public class VaultListManager {
|
||||
}
|
||||
return compBuilder.build().vault();
|
||||
}
|
||||
|
||||
public static VaultState redetermineVaultState(Vault vault) {
|
||||
VaultState previousState = vault.getState();
|
||||
return switch (previousState) {
|
||||
case LOCKED, NEEDS_MIGRATION, MISSING -> {
|
||||
try {
|
||||
VaultState determinedState = determineVaultState(vault.getPath());
|
||||
vault.setState(determinedState);
|
||||
yield determinedState;
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Failed to determine vault state for " + vault.getPath(), e);
|
||||
vault.setState(VaultState.ERROR);
|
||||
vault.setLastKnownException(e);
|
||||
yield VaultState.ERROR;
|
||||
}
|
||||
}
|
||||
case ERROR, UNLOCKED, PROCESSING -> previousState;
|
||||
};
|
||||
}
|
||||
|
||||
public static VaultState determineVaultState(Path pathToVault) throws IOException {
|
||||
private static VaultState determineVaultState(Path pathToVault) throws IOException {
|
||||
if (!CryptoFileSystemProvider.containsVault(pathToVault, MASTERKEY_FILENAME)) {
|
||||
return VaultState.MISSING;
|
||||
} else if (Migrators.get().needsMigration(pathToVault, MASTERKEY_FILENAME)) {
|
||||
|
||||
@@ -16,7 +16,6 @@ import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class VaultSettingsJsonAdapterTest {
|
||||
|
||||
@@ -32,7 +31,7 @@ public class VaultSettingsJsonAdapterTest {
|
||||
Assertions.assertEquals(Paths.get("/foo/bar"), vaultSettings.path().get());
|
||||
Assertions.assertEquals("test", vaultSettings.mountName().get());
|
||||
Assertions.assertEquals("X", vaultSettings.winDriveLetter().get());
|
||||
Assertions.assertEquals("/home/test/crypto", vaultSettings.individualMountPath().get());
|
||||
Assertions.assertEquals("/home/test/crypto", vaultSettings.customMountPath().get());
|
||||
Assertions.assertEquals("--foo --bar", vaultSettings.mountFlags().get());
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.5.3</version>
|
||||
<version>1.5.4</version>
|
||||
</parent>
|
||||
<artifactId>keychain</artifactId>
|
||||
<name>System Keychain Access</name>
|
||||
@@ -40,7 +40,6 @@
|
||||
<dependency>
|
||||
<groupId>de.swiesend</groupId>
|
||||
<artifactId>secret-service</artifactId>
|
||||
<version>1.0.0-RC.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Logging -->
|
||||
|
||||
@@ -28,4 +28,11 @@ public interface KeychainAccess {
|
||||
*/
|
||||
void deletePassphrase(String key) throws KeychainAccessException;
|
||||
|
||||
/**
|
||||
* Updates a passphrase with a given key.
|
||||
*
|
||||
* @param key Unique key previously used while {@link #storePassphrase(String, CharSequence) storing a passphrase}.
|
||||
* @param passphrase The secret to be updated in this keychain.
|
||||
*/
|
||||
void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException;
|
||||
}
|
||||
|
||||
@@ -48,4 +48,9 @@ public class LinuxSecretServiceKeychainAccess implements KeychainAccessStrategy
|
||||
public void deletePassphrase(String key) throws KeychainAccessException {
|
||||
delegate.orElseThrow(IllegalStateException::new).deletePassphrase(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
|
||||
delegate.orElseThrow(IllegalStateException::new).changePassphrase(key, passphrase);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import java.util.Map;
|
||||
|
||||
class LinuxSecretServiceKeychainAccessImpl implements KeychainAccessStrategy {
|
||||
|
||||
private final String LABEL_FOR_SECRET_IN_KEYRING = "Cryptomator";
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
try (@SuppressWarnings("unused") SimpleCollection keyring = new SimpleCollection()) {
|
||||
@@ -24,7 +26,7 @@ class LinuxSecretServiceKeychainAccessImpl implements KeychainAccessStrategy {
|
||||
try (SimpleCollection keyring = new SimpleCollection()) {
|
||||
List<String> list = keyring.getItems(createAttributes(key));
|
||||
if (list == null) {
|
||||
keyring.createItem("Cryptomator", passphrase, createAttributes(key));
|
||||
keyring.createItem(LABEL_FOR_SECRET_IN_KEYRING, passphrase, createAttributes(key));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new KeychainAccessException(e);
|
||||
@@ -57,6 +59,18 @@ class LinuxSecretServiceKeychainAccessImpl implements KeychainAccessStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
|
||||
try (SimpleCollection keyring = new SimpleCollection()) {
|
||||
List<String> list = keyring.getItems(createAttributes(key));
|
||||
if (list != null) {
|
||||
keyring.updateItem(list.get(0), LABEL_FOR_SECRET_IN_KEYRING, passphrase, createAttributes(key));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new KeychainAccessException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> createAttributes(String key) {
|
||||
Map<String, String> attributes = new HashMap();
|
||||
attributes.put("Vault", key);
|
||||
|
||||
@@ -46,4 +46,9 @@ class MacSystemKeychainAccess implements KeychainAccessStrategy {
|
||||
keychain().deletePassword(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
|
||||
storePassphrase(key, passphrase);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -112,6 +112,11 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
|
||||
saveKeychainEntries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changePassphrase(String key, CharSequence passphrase) throws KeychainAccessException {
|
||||
storePassphrase(key, passphrase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return SystemUtils.IS_OS_WINDOWS && winFunctions.isPresent() && !keychainPaths.isEmpty();
|
||||
|
||||
@@ -31,6 +31,12 @@ class MapKeychainAccess implements KeychainAccessStrategy {
|
||||
map.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changePassphrase(String key, CharSequence passphrase) {
|
||||
map.get(key);
|
||||
storePassphrase(key, passphrase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return true;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.5.3</version>
|
||||
<version>1.5.4</version>
|
||||
</parent>
|
||||
<artifactId>launcher</artifactId>
|
||||
<name>Cryptomator Launcher</name>
|
||||
|
||||
12
main/pom.xml
12
main/pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.5.3</version>
|
||||
<version>1.5.4</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Cryptomator</name>
|
||||
|
||||
@@ -27,12 +27,13 @@
|
||||
<cryptomator.cryptofs.version>1.9.10</cryptomator.cryptofs.version>
|
||||
<cryptomator.jni.version>2.2.2</cryptomator.jni.version>
|
||||
<cryptomator.fuse.version>1.2.3</cryptomator.fuse.version>
|
||||
<cryptomator.dokany.version>1.1.14</cryptomator.dokany.version>
|
||||
<cryptomator.dokany.version>1.1.15</cryptomator.dokany.version>
|
||||
<cryptomator.webdav.version>1.0.11</cryptomator.webdav.version>
|
||||
|
||||
<!-- 3rd party dependencies -->
|
||||
<javafx.version>14</javafx.version>
|
||||
<commons-lang3.version>3.9</commons-lang3.version>
|
||||
<secret-service.version>1.0.0</secret-service.version>
|
||||
<jwt.version>3.10.2</jwt.version>
|
||||
<easybind.version>1.0.3</easybind.version>
|
||||
<guava.version>28.2-jre</guava.version>
|
||||
@@ -160,6 +161,13 @@
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>${commons-lang3.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Linux System Keychain -->
|
||||
<dependency>
|
||||
<groupId>de.swiesend</groupId>
|
||||
<artifactId>secret-service</artifactId>
|
||||
<version>${secret-service.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JWT -->
|
||||
<dependency>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.5.3</version>
|
||||
<version>1.5.4</version>
|
||||
</parent>
|
||||
<artifactId>ui</artifactId>
|
||||
<name>Cryptomator GUI</name>
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.NewPasswordController;
|
||||
import org.cryptomator.ui.common.PasswordStrengthUtil;
|
||||
import org.cryptomator.ui.common.StageFactory;
|
||||
import org.cryptomator.ui.mainwindow.MainWindow;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyDisplayController;
|
||||
|
||||
@@ -51,13 +52,12 @@ public abstract class AddVaultModule {
|
||||
@Provides
|
||||
@AddVaultWizardWindow
|
||||
@AddVaultWizardScoped
|
||||
static Stage provideStage(@MainWindow Stage owner, ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons) {
|
||||
Stage stage = new Stage();
|
||||
static Stage provideStage(StageFactory factory, @MainWindow Stage owner, ResourceBundle resourceBundle) {
|
||||
Stage stage = factory.create();
|
||||
stage.setTitle(resourceBundle.getString("addvaultwizard.title"));
|
||||
stage.setResizable(false);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(owner);
|
||||
stage.getIcons().addAll(windowIcons);
|
||||
return stage;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ public class AddVaultSuccessController implements FxController {
|
||||
@FXML
|
||||
public void unlockAndClose() {
|
||||
close();
|
||||
fxApplication.showUnlockWindow(vault.get());
|
||||
fxApplication.startUnlockWorkflow(vault.get());
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
||||
@@ -2,32 +2,28 @@ package org.cryptomator.ui.changepassword;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
import org.cryptomator.keychain.KeychainAccess;
|
||||
import org.cryptomator.keychain.KeychainAccessException;
|
||||
import org.cryptomator.ui.common.Animations;
|
||||
import org.cryptomator.ui.common.ErrorComponent;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.controls.FontAwesome5IconView;
|
||||
import org.cryptomator.ui.controls.NiceSecurePasswordField;
|
||||
import org.cryptomator.ui.common.PasswordStrengthUtil;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.io.IOException;
|
||||
import java.util.ResourceBundle;
|
||||
import java.nio.CharBuffer;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.cryptomator.common.Constants.MASTERKEY_FILENAME;
|
||||
|
||||
@@ -40,17 +36,19 @@ public class ChangePasswordController implements FxController {
|
||||
private final Vault vault;
|
||||
private final ObjectProperty<CharSequence> newPassword;
|
||||
private final ErrorComponent.Builder errorComponent;
|
||||
private final Optional<KeychainAccess> keychain;
|
||||
|
||||
public NiceSecurePasswordField oldPasswordField;
|
||||
public CheckBox finalConfirmationCheckbox;
|
||||
public Button finishButton;
|
||||
|
||||
@Inject
|
||||
public ChangePasswordController(@ChangePasswordWindow Stage window, @ChangePasswordWindow Vault vault, @Named("newPassword") ObjectProperty<CharSequence> newPassword, ErrorComponent.Builder errorComponent) {
|
||||
public ChangePasswordController(@ChangePasswordWindow Stage window, @ChangePasswordWindow Vault vault, @Named("newPassword") ObjectProperty<CharSequence> newPassword, ErrorComponent.Builder errorComponent, Optional<KeychainAccess> keychain) {
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
this.newPassword = newPassword;
|
||||
this.errorComponent = errorComponent;
|
||||
this.keychain = keychain;
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -69,8 +67,9 @@ public class ChangePasswordController implements FxController {
|
||||
public void finish() {
|
||||
try {
|
||||
CryptoFileSystemProvider.changePassphrase(vault.getPath(), MASTERKEY_FILENAME, oldPasswordField.getCharacters(), newPassword.get());
|
||||
LOG.info("Successful changed password for {}", vault.getDisplayableName());
|
||||
LOG.info("Successfully changed password for {}", vault.getDisplayableName());
|
||||
window.close();
|
||||
updatePasswordInSystemkeychain();
|
||||
} catch (IOException e) {
|
||||
LOG.error("IO error occured during password change. Unable to perform operation.", e);
|
||||
errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
|
||||
@@ -81,6 +80,17 @@ public class ChangePasswordController implements FxController {
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePasswordInSystemkeychain() {
|
||||
if (keychain.isPresent()) {
|
||||
try {
|
||||
keychain.get().changePassphrase(vault.getId(), CharBuffer.wrap(newPassword.get()));
|
||||
LOG.info("Successfully updated password in system keychain for {}", vault.getDisplayableName());
|
||||
} catch (KeychainAccessException e) {
|
||||
LOG.error("Failed to update password in system keychain.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public Vault getVault() {
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.NewPasswordController;
|
||||
import org.cryptomator.ui.common.PasswordStrengthUtil;
|
||||
import org.cryptomator.ui.common.StageFactory;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
@@ -46,13 +47,12 @@ abstract class ChangePasswordModule {
|
||||
@Provides
|
||||
@ChangePasswordWindow
|
||||
@ChangePasswordScoped
|
||||
static Stage provideStage(@Named("changePasswordOwner") Stage owner, ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons) {
|
||||
Stage stage = new Stage();
|
||||
static Stage provideStage(StageFactory factory, @Named("changePasswordOwner") Stage owner, ResourceBundle resourceBundle) {
|
||||
Stage stage = factory.create();
|
||||
stage.setTitle(resourceBundle.getString("changepassword.title"));
|
||||
stage.setResizable(false);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(owner);
|
||||
stage.getIcons().addAll(windowIcons);
|
||||
return stage;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.cryptomator.ui.common;
|
||||
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class StageFactory {
|
||||
|
||||
private final Consumer<Stage> initializer;
|
||||
|
||||
public StageFactory(Consumer<Stage> initializer) {
|
||||
this.initializer = initializer;
|
||||
}
|
||||
|
||||
public Stage create() {
|
||||
return create(StageStyle.DECORATED);
|
||||
}
|
||||
|
||||
public Stage create(StageStyle stageStyle) {
|
||||
Stage stage = new Stage(stageStyle);
|
||||
initializer.accept(stage);
|
||||
return stage;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.cryptomator.ui.common;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class UserInteractionLock<E extends Enum> {
|
||||
|
||||
private final Lock lock = new ReentrantLock();
|
||||
private final Condition condition = lock.newCondition();
|
||||
private final BooleanProperty awaitingInteraction = new SimpleBooleanProperty();
|
||||
private volatile E state;
|
||||
|
||||
public UserInteractionLock(E initialValue) {
|
||||
state = initialValue;
|
||||
}
|
||||
|
||||
public void interacted(E result) {
|
||||
assert Platform.isFxApplicationThread();
|
||||
lock.lock();
|
||||
try {
|
||||
state = result;
|
||||
awaitingInteraction.set(false);
|
||||
condition.signal();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public E awaitInteraction() throws InterruptedException {
|
||||
assert !Platform.isFxApplicationThread();
|
||||
lock.lock();
|
||||
try {
|
||||
Platform.runLater(() -> awaitingInteraction.set(true));
|
||||
condition.await();
|
||||
return state;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public ReadOnlyBooleanProperty awaitingInteraction() {
|
||||
return awaitingInteraction;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -52,62 +52,6 @@ public class VaultService {
|
||||
return task;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to unlock all given vaults in a background thread using passwords stored in the system keychain.
|
||||
*
|
||||
* @param vaults The vaults to unlock
|
||||
* @implNote No-op if no system keychain is present
|
||||
*/
|
||||
public void attemptAutoUnlock(Collection<Vault> vaults) {
|
||||
if (!keychain.isPresent()) {
|
||||
LOG.debug("No system keychain found. Unable to auto unlock without saved passwords.");
|
||||
} else {
|
||||
List<Task<Vault>> unlockTasks = vaults.stream().map(v -> createAutoUnlockTask(v, keychain.get())).collect(Collectors.toList());
|
||||
Task<Collection<Vault>> runSequentiallyTask = new RunSequentiallyTask(unlockTasks);
|
||||
executorService.execute(runSequentiallyTask);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates but doesn't start an auto-unlock task.
|
||||
*
|
||||
* @param vault The vault to unlock
|
||||
* @param keychainAccess The system keychain holding the passphrase for the vault
|
||||
* @return The task
|
||||
*/
|
||||
public Task<Vault> createAutoUnlockTask(Vault vault, KeychainAccess keychainAccess) {
|
||||
Task<Vault> task = new AutoUnlockVaultTask(vault, keychainAccess);
|
||||
task.setOnSucceeded(evt -> LOG.info("Auto-unlocked {}", vault.getDisplayableName()));
|
||||
task.setOnFailed(evt -> LOG.error("Failed to auto-unlock " + vault.getDisplayableName(), evt.getSource().getException()));
|
||||
return task;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlocks a vault in a background thread
|
||||
*
|
||||
* @param vault The vault to unlock
|
||||
* @param passphrase The password to use - wipe this param asap
|
||||
* @implNote A copy of the passphrase will be made, which is wiped as soon as the task ran.
|
||||
*/
|
||||
public void unlock(Vault vault, CharSequence passphrase) {
|
||||
executorService.execute(createUnlockTask(vault, passphrase));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates but doesn't start an unlock task.
|
||||
*
|
||||
* @param vault The vault to unlock
|
||||
* @param passphrase The password to use - wipe this param asap
|
||||
* @return The task
|
||||
* @implNote A copy of the passphrase will be made, which is wiped as soon as the task ran.
|
||||
*/
|
||||
public Task<Vault> createUnlockTask(Vault vault, CharSequence passphrase) {
|
||||
Task<Vault> task = new UnlockVaultTask(vault, passphrase);
|
||||
task.setOnSucceeded(evt -> LOG.info("Unlocked {}", vault.getDisplayableName()));
|
||||
task.setOnFailed(evt -> LOG.error("Failed to unlock " + vault.getDisplayableName(), evt.getSource().getException()));
|
||||
return task;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks a vault in a background thread.
|
||||
*
|
||||
@@ -209,116 +153,6 @@ public class VaultService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A task that runs a list of tasks in their given order
|
||||
*/
|
||||
private static class RunSequentiallyTask extends Task<Collection<Vault>> {
|
||||
|
||||
private final List<Task<Vault>> tasks;
|
||||
|
||||
public RunSequentiallyTask(List<Task<Vault>> tasks) {
|
||||
this.tasks = List.copyOf(tasks);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Vault> call() throws ExecutionException, InterruptedException {
|
||||
List<Vault> completed = new ArrayList<>();
|
||||
for (Task<Vault> task : tasks) {
|
||||
task.run();
|
||||
Vault done = task.get();
|
||||
completed.add(done);
|
||||
}
|
||||
return completed;
|
||||
}
|
||||
}
|
||||
|
||||
private static class AutoUnlockVaultTask extends Task<Vault> {
|
||||
|
||||
private final Vault vault;
|
||||
private final KeychainAccess keychain;
|
||||
|
||||
public AutoUnlockVaultTask(Vault vault, KeychainAccess keychain) {
|
||||
this.vault = vault;
|
||||
this.keychain = keychain;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Vault call() throws Exception {
|
||||
char[] storedPw = null;
|
||||
try {
|
||||
storedPw = keychain.loadPassphrase(vault.getId());
|
||||
if (storedPw == null) {
|
||||
throw new InvalidPassphraseException();
|
||||
}
|
||||
vault.unlock(CharBuffer.wrap(storedPw));
|
||||
} finally {
|
||||
if (storedPw != null) {
|
||||
Arrays.fill(storedPw, ' ');
|
||||
}
|
||||
}
|
||||
return vault;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void scheduled() {
|
||||
vault.setState(VaultState.PROCESSING);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
vault.setState(VaultState.UNLOCKED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void failed() {
|
||||
vault.setState(VaultState.LOCKED);
|
||||
}
|
||||
}
|
||||
|
||||
private static class UnlockVaultTask extends Task<Vault> {
|
||||
|
||||
private final Vault vault;
|
||||
private final CharBuffer passphrase;
|
||||
|
||||
/**
|
||||
* @param vault The vault to unlock
|
||||
* @param passphrase The password to use - wipe this param asap
|
||||
* @implNote A copy of the passphrase will be made, which is wiped as soon as the task ran.
|
||||
*/
|
||||
public UnlockVaultTask(Vault vault, CharSequence passphrase) {
|
||||
this.vault = vault;
|
||||
this.passphrase = CharBuffer.allocate(passphrase.length());
|
||||
for (int i = 0; i < passphrase.length(); i++) {
|
||||
this.passphrase.put(i, passphrase.charAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Vault call() throws Exception {
|
||||
try {
|
||||
vault.unlock(passphrase);
|
||||
} finally {
|
||||
Arrays.fill(passphrase.array(), ' ');
|
||||
}
|
||||
return vault;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void scheduled() {
|
||||
vault.setState(VaultState.PROCESSING);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
vault.setState(VaultState.UNLOCKED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void failed() {
|
||||
vault.setState(VaultState.LOCKED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A task that locks a vault
|
||||
*/
|
||||
|
||||
@@ -94,8 +94,8 @@ public class NiceSecurePasswordField extends StackPane {
|
||||
passwordField.setPassword(password);
|
||||
}
|
||||
|
||||
public void swipe() {
|
||||
passwordField.swipe();
|
||||
public void wipe() {
|
||||
passwordField.wipe();
|
||||
}
|
||||
|
||||
public void selectAll() {
|
||||
|
||||
@@ -40,7 +40,7 @@ import java.util.Arrays;
|
||||
*/
|
||||
public class SecurePasswordField extends TextField {
|
||||
|
||||
private static final char SWIPE_CHAR = ' ';
|
||||
private static final char WIPE_CHAR = ' ';
|
||||
private static final int INITIAL_BUFFER_SIZE = 50;
|
||||
private static final int GROW_BUFFER_SIZE = 50;
|
||||
private static final String DEFAULT_PLACEHOLDER = "●";
|
||||
@@ -103,7 +103,7 @@ public class SecurePasswordField extends TextField {
|
||||
if (e.getCode() == KeyCode.CAPS) {
|
||||
updateCapsLocked();
|
||||
} else if (SHORTCUT_BACKSPACE.match(e)) {
|
||||
swipe();
|
||||
wipe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ public class SecurePasswordField extends TextField {
|
||||
if (length > content.length) {
|
||||
char[] newContent = new char[length + GROW_BUFFER_SIZE];
|
||||
System.arraycopy(content, 0, newContent, 0, content.length);
|
||||
swipe(content);
|
||||
wipe(content);
|
||||
this.content = newContent;
|
||||
}
|
||||
}
|
||||
@@ -201,7 +201,7 @@ public class SecurePasswordField extends TextField {
|
||||
* @implNote The CharSequence will not copy the backing char[].
|
||||
* Therefore any mutation to the SecurePasswordField's content will mutate or eventually swipe the returned CharSequence.
|
||||
* @implSpec The CharSequence is usually in <a href="https://www.unicode.org/glossary/#normalization_form_c">NFC</a> representation (unless NFD-encoded char[] is set via {@link #setPassword(char[])}).
|
||||
* @see #swipe()
|
||||
* @see #wipe()
|
||||
*/
|
||||
@Override
|
||||
public CharSequence getCharacters() {
|
||||
@@ -220,7 +220,7 @@ public class SecurePasswordField extends TextField {
|
||||
buf[i] = password.charAt(i);
|
||||
}
|
||||
setPassword(buf);
|
||||
Arrays.fill(buf, SWIPE_CHAR);
|
||||
Arrays.fill(buf, WIPE_CHAR);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -231,7 +231,7 @@ public class SecurePasswordField extends TextField {
|
||||
* @param password
|
||||
*/
|
||||
public void setPassword(char[] password) {
|
||||
swipe();
|
||||
wipe();
|
||||
content = Arrays.copyOf(password, password.length);
|
||||
length = password.length;
|
||||
|
||||
@@ -242,14 +242,14 @@ public class SecurePasswordField extends TextField {
|
||||
/**
|
||||
* Destroys the stored password by overriding each character with a different character.
|
||||
*/
|
||||
public void swipe() {
|
||||
swipe(content);
|
||||
public void wipe() {
|
||||
wipe(content);
|
||||
length = 0;
|
||||
setText(null);
|
||||
}
|
||||
|
||||
private void swipe(char[] buffer) {
|
||||
Arrays.fill(buffer, SWIPE_CHAR);
|
||||
private void wipe(char[] buffer) {
|
||||
Arrays.fill(buffer, WIPE_CHAR);
|
||||
}
|
||||
|
||||
/* Observable Properties */
|
||||
|
||||
@@ -17,6 +17,7 @@ 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 javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
@@ -37,13 +38,12 @@ abstract class ForgetPasswordModule {
|
||||
@Provides
|
||||
@ForgetPasswordWindow
|
||||
@ForgetPasswordScoped
|
||||
static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons, @Named("forgetPasswordOwner") Stage owner) {
|
||||
Stage stage = new Stage();
|
||||
static Stage provideStage(StageFactory factory, ResourceBundle resourceBundle, @Named("forgetPasswordOwner") Stage owner) {
|
||||
Stage stage = factory.create();
|
||||
stage.setTitle(resourceBundle.getString("forgetPassword.title"));
|
||||
stage.setResizable(false);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(owner);
|
||||
stage.getIcons().addAll(windowIcons);
|
||||
return stage;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import java.awt.desktop.QuitResponse;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -38,24 +39,24 @@ public class FxApplication extends Application {
|
||||
private final Settings settings;
|
||||
private final Lazy<MainWindowComponent> mainWindow;
|
||||
private final Lazy<PreferencesComponent> preferencesWindow;
|
||||
private final UnlockComponent.Builder unlockWindowBuilder;
|
||||
private final QuitComponent.Builder quitWindowBuilder;
|
||||
private final Provider<UnlockComponent.Builder> unlockWindowBuilderProvider;
|
||||
private final Provider<QuitComponent.Builder> quitWindowBuilderProvider;
|
||||
private final Optional<MacFunctions> macFunctions;
|
||||
private final VaultService vaultService;
|
||||
private final LicenseHolder licenseHolder;
|
||||
private final ObservableSet<Stage> visibleStages = FXCollections.observableSet();
|
||||
private final BooleanBinding hasVisibleStages = Bindings.isNotEmpty(visibleStages);
|
||||
private final BooleanBinding hasVisibleStages;
|
||||
|
||||
@Inject
|
||||
FxApplication(Settings settings, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, UnlockComponent.Builder unlockWindowBuilder, QuitComponent.Builder quitWindowBuilder, Optional<MacFunctions> macFunctions, VaultService vaultService, LicenseHolder licenseHolder) {
|
||||
FxApplication(Settings settings, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, Provider<UnlockComponent.Builder> unlockWindowBuilderProvider, Provider<QuitComponent.Builder> quitWindowBuilderProvider, Optional<MacFunctions> macFunctions, VaultService vaultService, LicenseHolder licenseHolder, ObservableSet<Stage> visibleStages) {
|
||||
this.settings = settings;
|
||||
this.mainWindow = mainWindow;
|
||||
this.preferencesWindow = preferencesWindow;
|
||||
this.unlockWindowBuilder = unlockWindowBuilder;
|
||||
this.quitWindowBuilder = quitWindowBuilder;
|
||||
this.unlockWindowBuilderProvider = unlockWindowBuilderProvider;
|
||||
this.quitWindowBuilderProvider = quitWindowBuilderProvider;
|
||||
this.macFunctions = macFunctions;
|
||||
this.vaultService = vaultService;
|
||||
this.licenseHolder = licenseHolder;
|
||||
this.hasVisibleStages = Bindings.isNotEmpty(visibleStages);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
@@ -73,11 +74,6 @@ public class FxApplication extends Application {
|
||||
throw new UnsupportedOperationException("Use start() instead.");
|
||||
}
|
||||
|
||||
private void addVisibleStage(Stage stage) {
|
||||
visibleStages.add(stage);
|
||||
stage.setOnHidden(evt -> visibleStages.remove(stage));
|
||||
}
|
||||
|
||||
private void hasVisibleStagesChanged(@SuppressWarnings("unused") ObservableValue<? extends Boolean> observableValue, @SuppressWarnings("unused") boolean oldValue, boolean newValue) {
|
||||
if (newValue) {
|
||||
macFunctions.map(MacFunctions::uiState).ifPresent(MacApplicationUiState::transformToForegroundApplication);
|
||||
@@ -88,32 +84,28 @@ public class FxApplication extends Application {
|
||||
|
||||
public void showPreferencesWindow(SelectedPreferencesTab selectedTab) {
|
||||
Platform.runLater(() -> {
|
||||
Stage stage = preferencesWindow.get().showPreferencesWindow(selectedTab);
|
||||
addVisibleStage(stage);
|
||||
preferencesWindow.get().showPreferencesWindow(selectedTab);
|
||||
LOG.debug("Showing Preferences");
|
||||
});
|
||||
}
|
||||
|
||||
public void showMainWindow() {
|
||||
Platform.runLater(() -> {
|
||||
Stage stage = mainWindow.get().showMainWindow();
|
||||
addVisibleStage(stage);
|
||||
mainWindow.get().showMainWindow();
|
||||
LOG.debug("Showing MainWindow");
|
||||
});
|
||||
}
|
||||
|
||||
public void showUnlockWindow(Vault vault) {
|
||||
public void startUnlockWorkflow(Vault vault) {
|
||||
Platform.runLater(() -> {
|
||||
Stage stage = unlockWindowBuilder.vault(vault).build().showUnlockWindow();
|
||||
addVisibleStage(stage);
|
||||
unlockWindowBuilderProvider.get().vault(vault).build().startUnlockWorkflow();
|
||||
LOG.debug("Showing UnlockWindow for {}", vault.getDisplayableName());
|
||||
});
|
||||
}
|
||||
|
||||
public void showQuitWindow(QuitResponse response) {
|
||||
Platform.runLater(() -> {
|
||||
Stage stage = quitWindowBuilder.quitResponse(response).build().showQuitWindow();
|
||||
addVisibleStage(stage);
|
||||
quitWindowBuilderProvider.get().quitResponse(response).build().showQuitWindow();
|
||||
LOG.debug("Showing QuitWindow");
|
||||
});
|
||||
}
|
||||
@@ -129,15 +121,14 @@ public class FxApplication extends Application {
|
||||
private void loadSelectedStyleSheet(UiTheme desiredTheme) {
|
||||
UiTheme theme = licenseHolder.isValidLicense() ? desiredTheme : UiTheme.LIGHT;
|
||||
switch (theme) {
|
||||
case DARK:
|
||||
case DARK -> {
|
||||
Application.setUserAgentStylesheet(getClass().getResource("/css/dark_theme.css").toString());
|
||||
macFunctions.map(MacFunctions::uiAppearance).ifPresent(JniException.ignore(MacApplicationUiAppearance::setToDarkAqua));
|
||||
break;
|
||||
case LIGHT:
|
||||
default:
|
||||
}
|
||||
case LIGHT -> {
|
||||
Application.setUserAgentStylesheet(getClass().getResource("/css/light_theme.css").toString());
|
||||
macFunctions.map(MacFunctions::uiAppearance).ifPresent(JniException.ignore(MacApplicationUiAppearance::setToAqua));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,10 +11,14 @@ import dagger.Provides;
|
||||
import javafx.application.Application;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableSet;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.ErrorComponent;
|
||||
import org.cryptomator.ui.common.StageFactory;
|
||||
import org.cryptomator.ui.mainwindow.MainWindowComponent;
|
||||
import org.cryptomator.ui.preferences.PreferencesComponent;
|
||||
import org.cryptomator.ui.quit.QuitComponent;
|
||||
@@ -32,8 +36,8 @@ abstract class FxApplicationModule {
|
||||
|
||||
@Provides
|
||||
@FxApplicationScoped
|
||||
static ObjectProperty<Vault> provideSelectedVault() {
|
||||
return new SimpleObjectProperty<>();
|
||||
static ObservableSet<Stage> provideVisibleStages() {
|
||||
return FXCollections.observableSet();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -43,7 +47,6 @@ abstract class FxApplicationModule {
|
||||
if (SystemUtils.IS_OS_MAC) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
try {
|
||||
return List.of( //
|
||||
createImageFromResource("/window_icon_32.png"), //
|
||||
@@ -53,6 +56,21 @@ abstract class FxApplicationModule {
|
||||
throw new UncheckedIOException("Failed to load embedded resource.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxApplicationScoped
|
||||
static StageFactory provideStageFactory(@Named("windowIcons") List<Image> windowIcons, ObservableSet<Stage> visibleStages) {
|
||||
return new StageFactory(stage -> {
|
||||
stage.getIcons().addAll(windowIcons);
|
||||
stage.showingProperty().addListener((observableValue, wasShowing, isShowing) -> {
|
||||
if (isShowing) {
|
||||
visibleStages.add(stage);
|
||||
} else {
|
||||
visibleStages.remove(stage);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static Image createImageFromResource(String resourceName) throws IOException {
|
||||
try (InputStream in = FxApplicationModule.class.getResourceAsStream(resourceName)) {
|
||||
|
||||
@@ -52,19 +52,13 @@ class AppLaunchEventHandler {
|
||||
|
||||
private void handleLaunchEvent(boolean hasTrayIcon, AppLaunchEvent event) {
|
||||
switch (event.getType()) {
|
||||
case REVEAL_APP:
|
||||
fxApplicationStarter.get(hasTrayIcon).thenAccept(FxApplication::showMainWindow);
|
||||
break;
|
||||
case OPEN_FILE:
|
||||
fxApplicationStarter.get(hasTrayIcon).thenRun(() -> {
|
||||
case REVEAL_APP -> fxApplicationStarter.get(hasTrayIcon).thenAccept(FxApplication::showMainWindow);
|
||||
case OPEN_FILE -> fxApplicationStarter.get(hasTrayIcon).thenRun(() -> {
|
||||
Platform.runLater(() -> {
|
||||
event.getPathsToOpen().forEach(this::addVault);
|
||||
});
|
||||
});
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unsupported event type: {}", event.getType());
|
||||
break;
|
||||
default -> LOG.warn("Unsupported event type: {}", event.getType());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,11 @@ public class UiLauncher {
|
||||
// auto unlock
|
||||
Collection<Vault> vaultsWithAutoUnlockEnabled = vaults.filtered(v -> v.getVaultSettings().unlockAfterStartup().get());
|
||||
if (!vaultsWithAutoUnlockEnabled.isEmpty()) {
|
||||
fxApplicationStarter.get(hasTrayIcon).thenAccept(app -> app.getVaultService().attemptAutoUnlock(vaultsWithAutoUnlockEnabled));
|
||||
fxApplicationStarter.get(hasTrayIcon).thenAccept(app -> {
|
||||
for (Vault vault : vaultsWithAutoUnlockEnabled){
|
||||
app.startUnlockWorkflow(vault);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
launchEventHandler.startHandlingLaunchEvents(hasTrayIcon);
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
package org.cryptomator.ui.mainwindow;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.input.DragEvent;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Stage;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultListManager;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
|
||||
@@ -28,15 +33,19 @@ public class MainWindowController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MainWindowController.class);
|
||||
|
||||
private final Stage window;
|
||||
private final VaultListManager vaultListManager;
|
||||
private final ReadOnlyObjectProperty<Vault> selectedVault;
|
||||
private final WrongFileAlertComponent.Builder wrongFileAlert;
|
||||
private final BooleanProperty draggingOver = new SimpleBooleanProperty();
|
||||
private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
|
||||
public StackPane root;
|
||||
|
||||
@Inject
|
||||
public MainWindowController(VaultListManager vaultListManager, WrongFileAlertComponent.Builder wrongFileAlert) {
|
||||
public MainWindowController(@MainWindow Stage window, VaultListManager vaultListManager, ObjectProperty<Vault> selectedVault, WrongFileAlertComponent.Builder wrongFileAlert) {
|
||||
this.window = window;
|
||||
this.vaultListManager = vaultListManager;
|
||||
this.selectedVault = selectedVault;
|
||||
this.wrongFileAlert = wrongFileAlert;
|
||||
}
|
||||
|
||||
@@ -50,6 +59,14 @@ public class MainWindowController implements FxController {
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
root.getStyleClass().add("os-windows");
|
||||
}
|
||||
window.focusedProperty().addListener(this::mainWindowFocusChanged);
|
||||
}
|
||||
|
||||
private void mainWindowFocusChanged(Observable observable) {
|
||||
var v = selectedVault.get();
|
||||
if (v != null) {
|
||||
VaultListManager.redetermineVaultState(v);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDragEvent(DragEvent event) {
|
||||
|
||||
@@ -4,16 +4,21 @@ import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
|
||||
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.fxapp.FxApplicationScoped;
|
||||
import org.cryptomator.ui.migration.MigrationComponent;
|
||||
import org.cryptomator.ui.removevault.RemoveVaultComponent;
|
||||
import org.cryptomator.ui.vaultoptions.VaultOptionsComponent;
|
||||
@@ -28,6 +33,12 @@ import java.util.ResourceBundle;
|
||||
@Module(subcomponents = {AddVaultWizardComponent.class, MigrationComponent.class, RemoveVaultComponent.class, VaultOptionsComponent.class, WrongFileAlertComponent.class})
|
||||
abstract class MainWindowModule {
|
||||
|
||||
@Provides
|
||||
@MainWindowScoped
|
||||
static ObjectProperty<Vault> provideSelectedVault() {
|
||||
return new SimpleObjectProperty<>();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@MainWindow
|
||||
@MainWindowScoped
|
||||
@@ -38,22 +49,21 @@ abstract class MainWindowModule {
|
||||
@Provides
|
||||
@MainWindow
|
||||
@MainWindowScoped
|
||||
static Stage provideStage(@Named("windowIcons") List<Image> windowIcons) {
|
||||
Stage stage = new Stage(StageStyle.UNDECORATED);
|
||||
static Stage provideStage(StageFactory factory) {
|
||||
Stage stage = factory.create(StageStyle.UNDECORATED);
|
||||
// TODO: min/max values chosen arbitrarily. We might wanna take a look at the user's resolution...
|
||||
stage.setMinWidth(650);
|
||||
stage.setMinHeight(440);
|
||||
stage.setMaxWidth(1000);
|
||||
stage.setMaxHeight(700);
|
||||
stage.setTitle("Cryptomator");
|
||||
stage.getIcons().addAll(windowIcons);
|
||||
return stage;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.MAIN_WINDOW)
|
||||
@MainWindowScoped
|
||||
static Scene provideMainScene(@MainWindow FXMLLoaderFactory fxmlLoaders, MainWindowController mainWindowController, VaultListController vaultListController) {
|
||||
static Scene provideMainScene(@MainWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene("/fxml/main_window.fxml");
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ public class VaultDetailLockedController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void unlock() {
|
||||
application.showUnlockWindow(vault.get());
|
||||
application.startUnlockWorkflow(vault.get());
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
||||
@@ -64,21 +64,7 @@ public class VaultListController implements FxController {
|
||||
if (newValue == null) {
|
||||
return;
|
||||
}
|
||||
VaultState reportedState = newValue.getState();
|
||||
switch (reportedState) {
|
||||
case LOCKED, NEEDS_MIGRATION, MISSING:
|
||||
try {
|
||||
VaultState determinedState = VaultListManager.determineVaultState(newValue.getPath());
|
||||
newValue.setState(determinedState);
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Failed to determine vault state for " + newValue.getPath(), e);
|
||||
newValue.setState(VaultState.ERROR);
|
||||
newValue.setLastKnownException(e);
|
||||
}
|
||||
break;
|
||||
case ERROR, UNLOCKED, PROCESSING:
|
||||
break; // no-op
|
||||
}
|
||||
VaultListManager.redetermineVaultState(newValue);
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
||||
@@ -17,6 +17,7 @@ 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.mainwindow.MainWindow;
|
||||
|
||||
import javax.inject.Named;
|
||||
@@ -38,13 +39,12 @@ abstract class MigrationModule {
|
||||
@Provides
|
||||
@MigrationWindow
|
||||
@MigrationScoped
|
||||
static Stage provideStage(@MainWindow Stage owner, ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons) {
|
||||
Stage stage = new Stage();
|
||||
static Stage provideStage(StageFactory factory, @MainWindow Stage owner, ResourceBundle resourceBundle) {
|
||||
Stage stage = factory.create();
|
||||
stage.setTitle(resourceBundle.getString("migration.title"));
|
||||
stage.setResizable(false);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(owner);
|
||||
stage.getIcons().addAll(windowIcons);
|
||||
return stage;
|
||||
}
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ public class MigrationRunController implements FxController {
|
||||
} else {
|
||||
LOG.info("Migration of '{}' succeeded.", vault.getDisplayableName());
|
||||
vault.setState(VaultState.LOCKED);
|
||||
passwordField.swipe();
|
||||
passwordField.wipe();
|
||||
window.setScene(successScene.get());
|
||||
}
|
||||
}).onError(InvalidPassphraseException.class, e -> {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package org.cryptomator.ui.migration;
|
||||
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
@@ -28,7 +25,7 @@ public class MigrationSuccessController implements FxController {
|
||||
@FXML
|
||||
public void unlockAndClose() {
|
||||
close();
|
||||
fxApplication.showUnlockWindow(vault);
|
||||
fxApplication.startUnlockWorkflow(vault);
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
||||
@@ -12,9 +12,11 @@ import javafx.scene.control.RadioButton;
|
||||
import javafx.scene.control.Toggle;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.util.StringConverter;
|
||||
import javafx.application.Application;
|
||||
import org.cryptomator.common.LicenseHolder;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.UiTheme;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -37,6 +39,8 @@ public class GeneralPreferencesController implements FxController {
|
||||
private final LicenseHolder licenseHolder;
|
||||
private final ExecutorService executor;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final Application application;
|
||||
private final Environment environment;
|
||||
public ChoiceBox<UiTheme> themeChoiceBox;
|
||||
public CheckBox startHiddenCheckbox;
|
||||
public CheckBox debugModeCheckbox;
|
||||
@@ -46,7 +50,7 @@ public class GeneralPreferencesController implements FxController {
|
||||
public RadioButton nodeOrientationRtl;
|
||||
|
||||
@Inject
|
||||
GeneralPreferencesController(Settings settings, @Named("trayMenuSupported") boolean trayMenuSupported, Optional<AutoStartStrategy> autoStartStrategy, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, LicenseHolder licenseHolder, ExecutorService executor, ResourceBundle resourceBundle) {
|
||||
GeneralPreferencesController(Settings settings, @Named("trayMenuSupported") boolean trayMenuSupported, Optional<AutoStartStrategy> autoStartStrategy, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, LicenseHolder licenseHolder, ExecutorService executor, ResourceBundle resourceBundle, Application application, Environment environment) {
|
||||
this.settings = settings;
|
||||
this.trayMenuSupported = trayMenuSupported;
|
||||
this.autoStartStrategy = autoStartStrategy;
|
||||
@@ -54,6 +58,8 @@ public class GeneralPreferencesController implements FxController {
|
||||
this.licenseHolder = licenseHolder;
|
||||
this.executor = executor;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.application = application;
|
||||
this.environment = environment;
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -115,6 +121,11 @@ public class GeneralPreferencesController implements FxController {
|
||||
selectedTabProperty.set(SelectedPreferencesTab.DONATION_KEY);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void showLogfileDirectory(){
|
||||
environment.getLogDir().ifPresent(logDirPath -> application.getHostServices().showDocument(logDirPath.toUri().toString()));
|
||||
}
|
||||
|
||||
/* Helper classes */
|
||||
|
||||
private static class UiThemeConverter extends StringConverter<UiTheme> {
|
||||
|
||||
@@ -15,6 +15,7 @@ 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 javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
@@ -41,11 +42,10 @@ abstract class PreferencesModule {
|
||||
@Provides
|
||||
@PreferencesWindow
|
||||
@PreferencesScoped
|
||||
static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons) {
|
||||
Stage stage = new Stage();
|
||||
static Stage provideStage(StageFactory factory, ResourceBundle resourceBundle) {
|
||||
Stage stage = factory.create();
|
||||
stage.setTitle(resourceBundle.getString("preferences.title"));
|
||||
stage.setResizable(false);
|
||||
stage.getIcons().addAll(windowIcons);
|
||||
return stage;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ 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 javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
@@ -37,12 +38,11 @@ abstract class QuitModule {
|
||||
@Provides
|
||||
@QuitWindow
|
||||
@QuitScoped
|
||||
static Stage provideStage(@Named("windowIcons") List<Image> windowIcons) {
|
||||
Stage stage = new Stage();
|
||||
static Stage provideStage(StageFactory factory) {
|
||||
Stage stage = factory.create();
|
||||
stage.setMinWidth(300);
|
||||
stage.setMinHeight(100);
|
||||
stage.initModality(Modality.APPLICATION_MODAL);
|
||||
stage.getIcons().addAll(windowIcons);
|
||||
return stage;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.NewPasswordController;
|
||||
import org.cryptomator.ui.common.PasswordStrengthUtil;
|
||||
import org.cryptomator.ui.common.StageFactory;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
@@ -41,13 +42,12 @@ abstract class RecoveryKeyModule {
|
||||
@Provides
|
||||
@RecoveryKeyWindow
|
||||
@RecoveryKeyScoped
|
||||
static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons, @Named("keyRecoveryOwner") Stage owner) {
|
||||
Stage stage = new Stage();
|
||||
static Stage provideStage(StageFactory factory, ResourceBundle resourceBundle, @Named("keyRecoveryOwner") Stage owner) {
|
||||
Stage stage = factory.create();
|
||||
stage.setTitle(resourceBundle.getString("recoveryKey.title"));
|
||||
stage.setResizable(false);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(owner);
|
||||
stage.getIcons().addAll(windowIcons);
|
||||
return stage;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ 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.mainwindow.MainWindow;
|
||||
|
||||
import javax.inject.Named;
|
||||
@@ -38,13 +39,12 @@ abstract class RemoveVaultModule {
|
||||
@Provides
|
||||
@RemoveVaultWindow
|
||||
@RemoveVaultScoped
|
||||
static Stage provideStage(@MainWindow Stage owner, ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons) {
|
||||
Stage stage = new Stage();
|
||||
static Stage provideStage(StageFactory factory, @MainWindow Stage owner, ResourceBundle resourceBundle) {
|
||||
Stage stage = factory.create();
|
||||
stage.setTitle(resourceBundle.getString("removeVault.title"));
|
||||
stage.setResizable(false);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(owner);
|
||||
stage.getIcons().addAll(windowIcons);
|
||||
return stage;
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ class TrayMenuController {
|
||||
}
|
||||
|
||||
private void unlockVault(Vault vault) {
|
||||
fxApplicationStarter.get(true).thenAccept(app -> app.showUnlockWindow(vault));
|
||||
fxApplicationStarter.get(true).thenAccept(app -> app.startUnlockWorkflow(vault));
|
||||
}
|
||||
|
||||
private void lockVault(Vault vault) {
|
||||
|
||||
@@ -14,21 +14,23 @@ import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
@UnlockScoped
|
||||
@Subcomponent(modules = {UnlockModule.class})
|
||||
public interface UnlockComponent {
|
||||
|
||||
@UnlockWindow
|
||||
Stage window();
|
||||
ExecutorService defaultExecutorService();
|
||||
|
||||
@FxmlScene(FxmlFile.UNLOCK)
|
||||
Lazy<Scene> scene();
|
||||
|
||||
default Stage showUnlockWindow() {
|
||||
Stage stage = window();
|
||||
stage.setScene(scene().get());
|
||||
stage.show();
|
||||
return stage;
|
||||
UnlockWorkflow unlockWorkflow();
|
||||
|
||||
default Future<Boolean> startUnlockWorkflow() {
|
||||
UnlockWorkflow workflow = unlockWorkflow();
|
||||
defaultExecutorService().submit(workflow);
|
||||
return workflow;
|
||||
}
|
||||
|
||||
@Subcomponent.Builder
|
||||
|
||||
@@ -1,39 +1,30 @@
|
||||
package org.cryptomator.ui.unlock;
|
||||
|
||||
import dagger.Lazy;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
import org.cryptomator.keychain.KeychainAccess;
|
||||
import org.cryptomator.keychain.KeychainAccessException;
|
||||
import org.cryptomator.ui.common.Animations;
|
||||
import org.cryptomator.ui.common.ErrorComponent;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.VaultService;
|
||||
import org.cryptomator.ui.common.UserInteractionLock;
|
||||
import org.cryptomator.ui.controls.NiceSecurePasswordField;
|
||||
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.nio.file.DirectoryNotEmptyException;
|
||||
import java.nio.file.NotDirectoryException;
|
||||
import javax.inject.Named;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@UnlockScoped
|
||||
public class UnlockController implements FxController {
|
||||
@@ -42,124 +33,70 @@ public class UnlockController implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
private final Vault vault;
|
||||
private final ExecutorService executor;
|
||||
private final ObjectBinding<ContentDisplay> unlockButtonState;
|
||||
private final Optional<KeychainAccess> keychainAccess;
|
||||
private final VaultService vaultService;
|
||||
private final Lazy<Scene> successScene;
|
||||
private final Lazy<Scene> invalidMountPointScene;
|
||||
private final ErrorComponent.Builder errorComponent;
|
||||
private final AtomicReference<char[]> password;
|
||||
private final AtomicBoolean savePassword;
|
||||
private final Optional<char[]> savedPassword;
|
||||
private final UserInteractionLock<UnlockModule.PasswordEntry> passwordEntryLock;
|
||||
private final ForgetPasswordComponent.Builder forgetPassword;
|
||||
private final Optional<KeychainAccess> keychainAccess;
|
||||
private final ObjectBinding<ContentDisplay> unlockButtonContentDisplay;
|
||||
private final BooleanBinding userInteractionDisabled;
|
||||
private final BooleanProperty unlockButtonDisabled;
|
||||
public NiceSecurePasswordField passwordField;
|
||||
public CheckBox savePassword;
|
||||
public CheckBox savePasswordCheckbox;
|
||||
|
||||
@Inject
|
||||
public UnlockController(@UnlockWindow Stage window, @UnlockWindow Vault vault, ExecutorService executor, Optional<KeychainAccess> keychainAccess, VaultService vaultService, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, ErrorComponent.Builder errorComponent, ForgetPasswordComponent.Builder forgetPassword) {
|
||||
public UnlockController(@UnlockWindow Stage window, @UnlockWindow Vault vault, AtomicReference<char[]> password, @Named("savePassword") AtomicBoolean savePassword, @Named("savedPassword") Optional<char[]> savedPassword, UserInteractionLock<UnlockModule.PasswordEntry> passwordEntryLock, ForgetPasswordComponent.Builder forgetPassword, Optional<KeychainAccess> keychainAccess) {
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
this.executor = executor;
|
||||
this.unlockButtonState = Bindings.createObjectBinding(this::getUnlockButtonState, vault.stateProperty());
|
||||
this.keychainAccess = keychainAccess;
|
||||
this.vaultService = vaultService;
|
||||
this.successScene = successScene;
|
||||
this.invalidMountPointScene = invalidMountPointScene;
|
||||
this.errorComponent = errorComponent;
|
||||
this.password = password;
|
||||
this.savePassword = savePassword;
|
||||
this.savedPassword = savedPassword;
|
||||
this.passwordEntryLock = passwordEntryLock;
|
||||
this.forgetPassword = forgetPassword;
|
||||
this.keychainAccess = keychainAccess;
|
||||
this.unlockButtonContentDisplay = Bindings.createObjectBinding(this::getUnlockButtonContentDisplay, passwordEntryLock.awaitingInteraction());
|
||||
this.userInteractionDisabled = passwordEntryLock.awaitingInteraction().not();
|
||||
this.unlockButtonDisabled = new SimpleBooleanProperty();
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
if (keychainAccess.isPresent()) {
|
||||
loadStoredPassword();
|
||||
} else {
|
||||
savePassword.setSelected(false);
|
||||
savePasswordCheckbox.setSelected(savedPassword.isPresent());
|
||||
if (password.get() != null) {
|
||||
passwordField.setPassword(password.get());
|
||||
}
|
||||
unlockButtonDisabled.bind(vault.stateProperty().isNotEqualTo(VaultState.LOCKED).or(passwordField.textProperty().isEmpty()));
|
||||
unlockButtonDisabled.bind(userInteractionDisabled.or(passwordField.textProperty().isEmpty()));
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void cancel() {
|
||||
LOG.debug("Unlock canceled by user.");
|
||||
window.close();
|
||||
passwordEntryLock.interacted(UnlockModule.PasswordEntry.CANCELED);
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void unlock() {
|
||||
LOG.trace("UnlockController.unlock()");
|
||||
CharSequence password = passwordField.getCharacters();
|
||||
|
||||
Task<Vault> task = vaultService.createUnlockTask(vault, password);
|
||||
passwordField.setDisable(true);
|
||||
task.setOnSucceeded(event -> {
|
||||
passwordField.setDisable(false);
|
||||
if (keychainAccess.isPresent() && savePassword.isSelected()) {
|
||||
try {
|
||||
keychainAccess.get().storePassphrase(vault.getId(), password);
|
||||
} catch (KeychainAccessException e) {
|
||||
LOG.error("Failed to store passphrase in system keychain.", e);
|
||||
}
|
||||
}
|
||||
passwordField.swipe();
|
||||
LOG.info("Unlock of '{}' succeeded.", vault.getDisplayableName());
|
||||
window.setScene(successScene.get());
|
||||
});
|
||||
task.setOnFailed(event -> {
|
||||
passwordField.setDisable(false);
|
||||
if (task.getException() instanceof InvalidPassphraseException) {
|
||||
Animations.createShakeWindowAnimation(window).play();
|
||||
passwordField.selectAll();
|
||||
passwordField.requestFocus();
|
||||
} else if (task.getException() instanceof NotDirectoryException || task.getException() instanceof DirectoryNotEmptyException) {
|
||||
LOG.error("Unlock failed. Mount point not an empty directory: {}", task.getException().getMessage());
|
||||
window.setScene(invalidMountPointScene.get());
|
||||
} else {
|
||||
LOG.error("Unlock failed for technical reasons.", task.getException());
|
||||
errorComponent.cause(task.getException()).window(window).returnToScene(window.getScene()).build().showErrorScene();
|
||||
}
|
||||
});
|
||||
executor.execute(task);
|
||||
CharSequence pwFieldContents = passwordField.getCharacters();
|
||||
char[] newPw = new char[pwFieldContents.length()];
|
||||
for (int i = 0; i < pwFieldContents.length(); i++) {
|
||||
newPw[i] = pwFieldContents.charAt(i);
|
||||
}
|
||||
char[] oldPw = password.getAndSet(newPw);
|
||||
if (oldPw != null) {
|
||||
Arrays.fill(oldPw, ' ');
|
||||
}
|
||||
passwordEntryLock.interacted(UnlockModule.PasswordEntry.PASSWORD_ENTERED);
|
||||
}
|
||||
|
||||
/* Save Password */
|
||||
|
||||
@FXML
|
||||
private void didClickSavePasswordCheckbox() {
|
||||
if (!savePassword.isSelected() && hasStoredPassword()) {
|
||||
forgetPassword.vault(vault).owner(window).build().showForgetPassword().thenAccept(forgotten -> savePassword.setSelected(!forgotten));
|
||||
}
|
||||
}
|
||||
|
||||
private void loadStoredPassword() {
|
||||
assert keychainAccess.isPresent();
|
||||
char[] storedPw = null;
|
||||
try {
|
||||
storedPw = keychainAccess.get().loadPassphrase(vault.getId());
|
||||
if (storedPw != null) {
|
||||
savePassword.setSelected(true);
|
||||
passwordField.setPassword(storedPw);
|
||||
passwordField.selectRange(storedPw.length, storedPw.length);
|
||||
}
|
||||
} catch (KeychainAccessException e) {
|
||||
LOG.error("Failed to load entry from system keychain.", e);
|
||||
} finally {
|
||||
if (storedPw != null) {
|
||||
Arrays.fill(storedPw, ' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasStoredPassword() {
|
||||
char[] storedPw = null;
|
||||
try {
|
||||
storedPw = keychainAccess.get().loadPassphrase(vault.getId());
|
||||
return storedPw != null;
|
||||
} catch (KeychainAccessException e) {
|
||||
return false;
|
||||
} finally {
|
||||
if (storedPw != null) {
|
||||
Arrays.fill(storedPw, ' ');
|
||||
}
|
||||
savePassword.set(savePasswordCheckbox.isSelected());
|
||||
if (!savePasswordCheckbox.isSelected() && savedPassword.isPresent()) {
|
||||
forgetPassword.vault(vault).owner(window).build().showForgetPassword().thenAccept(forgotten -> savePasswordCheckbox.setSelected(!forgotten));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,15 +106,20 @@ public class UnlockController implements FxController {
|
||||
return vault;
|
||||
}
|
||||
|
||||
public ObjectBinding<ContentDisplay> unlockButtonStateProperty() {
|
||||
return unlockButtonState;
|
||||
public ObjectBinding<ContentDisplay> unlockButtonContentDisplayProperty() {
|
||||
return unlockButtonContentDisplay;
|
||||
}
|
||||
|
||||
public ContentDisplay getUnlockButtonState() {
|
||||
return switch (vault.getState()) {
|
||||
case PROCESSING -> ContentDisplay.LEFT;
|
||||
default -> ContentDisplay.TEXT_ONLY;
|
||||
};
|
||||
public ContentDisplay getUnlockButtonContentDisplay() {
|
||||
return passwordEntryLock.awaitingInteraction().get() ? ContentDisplay.TEXT_ONLY : ContentDisplay.LEFT;
|
||||
}
|
||||
|
||||
public BooleanBinding userInteractionDisabledProperty() {
|
||||
return userInteractionDisabled;
|
||||
}
|
||||
|
||||
public boolean isUserInteractionDisabled() {
|
||||
return userInteractionDisabled.get();
|
||||
}
|
||||
|
||||
public ReadOnlyBooleanProperty unlockButtonDisabledProperty() {
|
||||
|
||||
@@ -33,7 +33,7 @@ public class UnlockInvalidMountPointController implements FxController {
|
||||
/* Getter/Setter */
|
||||
|
||||
public String getMountPoint() {
|
||||
return vault.getVaultSettings().getIndividualMountPath().orElse("AUTO");
|
||||
return vault.getVaultSettings().getCustomMountPath().orElse("AUTO");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,23 +9,73 @@ import javafx.scene.image.Image;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.keychain.KeychainAccess;
|
||||
import org.cryptomator.keychain.KeychainAccessException;
|
||||
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 org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import java.nio.CharBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
@Module(subcomponents = {ForgetPasswordComponent.class})
|
||||
abstract class UnlockModule {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UnlockModule.class);
|
||||
|
||||
public enum PasswordEntry {PASSWORD_ENTERED, CANCELED}
|
||||
|
||||
@Provides
|
||||
@UnlockScoped
|
||||
static UserInteractionLock<PasswordEntry> providePasswordEntryLock() {
|
||||
return new UserInteractionLock<>(null);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("savedPassword")
|
||||
@UnlockScoped
|
||||
static Optional<char[]> provideStoredPassword(Optional<KeychainAccess> keychainAccess, @UnlockWindow Vault vault) {
|
||||
return keychainAccess.map(k -> {
|
||||
try {
|
||||
return k.loadPassphrase(vault.getId());
|
||||
} catch (KeychainAccessException e) {
|
||||
LOG.error("Failed to load entry from system keychain.", e);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Provides
|
||||
@UnlockScoped
|
||||
static AtomicReference<char[]> providePassword(@Named("savedPassword") Optional<char[]> storedPassword) {
|
||||
return new AtomicReference(storedPassword.orElse(null));
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("savePassword")
|
||||
@UnlockScoped
|
||||
static AtomicBoolean provideSavePasswordFlag(@Named("savedPassword") Optional<char[]> storedPassword) {
|
||||
return new AtomicBoolean(storedPassword.isPresent());
|
||||
}
|
||||
|
||||
@Provides
|
||||
@UnlockWindow
|
||||
@UnlockScoped
|
||||
@@ -36,12 +86,11 @@ abstract class UnlockModule {
|
||||
@Provides
|
||||
@UnlockWindow
|
||||
@UnlockScoped
|
||||
static Stage provideStage(@UnlockWindow Vault vault, @Named("windowIcons") List<Image> windowIcons) {
|
||||
Stage stage = new Stage();
|
||||
static Stage provideStage(StageFactory factory, @UnlockWindow Vault vault) {
|
||||
Stage stage = factory.create();
|
||||
stage.setTitle(vault.getDisplayableName());
|
||||
stage.setResizable(false);
|
||||
stage.initModality(Modality.APPLICATION_MODAL);
|
||||
stage.getIcons().addAll(windowIcons);
|
||||
return stage;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,10 @@ import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.settings.WhenUnlocked;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.VaultService;
|
||||
@@ -29,6 +31,8 @@ public class UnlockSuccessController implements FxController {
|
||||
private final VaultService vaultService;
|
||||
private final ObjectProperty<ContentDisplay> revealButtonState;
|
||||
private final BooleanProperty revealButtonDisabled;
|
||||
|
||||
public CheckBox rememberChoiceCheckbox;
|
||||
|
||||
@Inject
|
||||
public UnlockSuccessController(@UnlockWindow Stage window, @UnlockWindow Vault vault, ExecutorService executor, VaultService vaultService) {
|
||||
@@ -44,6 +48,9 @@ public class UnlockSuccessController implements FxController {
|
||||
public void close() {
|
||||
LOG.trace("UnlockSuccessController.close()");
|
||||
window.close();
|
||||
if (rememberChoiceCheckbox.isSelected()) {
|
||||
vault.getVaultSettings().actionAfterUnlock().setValue(WhenUnlocked.IGNORE);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -64,6 +71,9 @@ public class UnlockSuccessController implements FxController {
|
||||
revealButtonDisabled.set(false);
|
||||
});
|
||||
executor.execute(revealTask);
|
||||
if (rememberChoiceCheckbox.isSelected()) {
|
||||
vault.getVaultSettings().actionAfterUnlock().setValue(WhenUnlocked.REVEAL);
|
||||
}
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
package org.cryptomator.ui.unlock;
|
||||
|
||||
import dagger.Lazy;
|
||||
import javafx.application.Platform;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.common.vaults.Volume;
|
||||
import org.cryptomator.cryptolib.api.CryptoException;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
import org.cryptomator.keychain.KeychainAccess;
|
||||
import org.cryptomator.keychain.KeychainAccessException;
|
||||
import org.cryptomator.ui.common.Animations;
|
||||
import org.cryptomator.ui.common.ErrorComponent;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.UserInteractionLock;
|
||||
import org.cryptomator.ui.common.VaultService;
|
||||
import org.cryptomator.ui.unlock.UnlockModule.PasswordEntry;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.io.IOException;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.file.DirectoryNotEmptyException;
|
||||
import java.nio.file.FileSystemException;
|
||||
import java.nio.file.NotDirectoryException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* A multi-step task that consists of background activities as well as user interaction.
|
||||
* <p>
|
||||
* This class runs the unlock process and controls when to display which UI.
|
||||
*/
|
||||
@UnlockScoped
|
||||
public class UnlockWorkflow extends Task<Boolean> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UnlockWorkflow.class);
|
||||
|
||||
private final Stage window;
|
||||
private final Vault vault;
|
||||
private final VaultService vaultService;
|
||||
private final AtomicReference<char[]> password;
|
||||
private final AtomicBoolean savePassword;
|
||||
private final Optional<char[]> savedPassword;
|
||||
private final UserInteractionLock<PasswordEntry> passwordEntryLock;
|
||||
private final Optional<KeychainAccess> keychain;
|
||||
private final Lazy<Scene> unlockScene;
|
||||
private final Lazy<Scene> successScene;
|
||||
private final Lazy<Scene> invalidMountPointScene;
|
||||
private final ErrorComponent.Builder errorComponent;
|
||||
|
||||
@Inject
|
||||
UnlockWorkflow(@UnlockWindow Stage window, @UnlockWindow Vault vault, VaultService vaultService, AtomicReference<char[]> password, @Named("savePassword") AtomicBoolean savePassword, @Named("savedPassword") Optional<char[]> savedPassword, UserInteractionLock<PasswordEntry> passwordEntryLock, Optional<KeychainAccess> keychain, @FxmlScene(FxmlFile.UNLOCK) Lazy<Scene> unlockScene, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, ErrorComponent.Builder errorComponent) {
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
this.vaultService = vaultService;
|
||||
this.password = password;
|
||||
this.savePassword = savePassword;
|
||||
this.savedPassword = savedPassword;
|
||||
this.passwordEntryLock = passwordEntryLock;
|
||||
this.keychain = keychain;
|
||||
this.unlockScene = unlockScene;
|
||||
this.successScene = successScene;
|
||||
this.invalidMountPointScene = invalidMountPointScene;
|
||||
this.errorComponent = errorComponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean call() throws InterruptedException, IOException, Volume.VolumeException {
|
||||
try {
|
||||
if (attemptUnlock()) {
|
||||
handleSuccess();
|
||||
return true;
|
||||
} else {
|
||||
cancel(false); // set Tasks state to cancelled
|
||||
return false;
|
||||
}
|
||||
} catch (NotDirectoryException | DirectoryNotEmptyException e) {
|
||||
handleInvalidMountPoint(e);
|
||||
throw e; // rethrow to trigger correct exception handling in Task
|
||||
} catch (CryptoException | Volume.VolumeException | IOException e) {
|
||||
handleGenericError(e);
|
||||
throw e; // rethrow to trigger correct exception handling in Task
|
||||
} finally {
|
||||
wipePassword(password.get());
|
||||
wipePassword(savedPassword.orElse(null));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean attemptUnlock() throws InterruptedException, IOException, Volume.VolumeException {
|
||||
boolean proceed = password.get() != null || askForPassword(false) == PasswordEntry.PASSWORD_ENTERED;
|
||||
while (proceed) {
|
||||
try {
|
||||
vault.unlock(CharBuffer.wrap(password.get()));
|
||||
return true;
|
||||
} catch (InvalidPassphraseException e) {
|
||||
proceed = askForPassword(true) == PasswordEntry.PASSWORD_ENTERED;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private PasswordEntry askForPassword(boolean animateShake) throws InterruptedException {
|
||||
Platform.runLater(() -> {
|
||||
window.setScene(unlockScene.get());
|
||||
window.show();
|
||||
if (animateShake) {
|
||||
Animations.createShakeWindowAnimation(window).play();
|
||||
}
|
||||
});
|
||||
return passwordEntryLock.awaitInteraction();
|
||||
}
|
||||
|
||||
private void handleSuccess() {
|
||||
LOG.info("Unlock of '{}' succeeded.", vault.getDisplayableName());
|
||||
if (savePassword.get()) {
|
||||
savePasswordToSystemkeychain();
|
||||
}
|
||||
switch (vault.getVaultSettings().actionAfterUnlock().get()) {
|
||||
case ASK -> Platform.runLater(() -> {
|
||||
window.setScene(successScene.get());
|
||||
window.show();
|
||||
});
|
||||
case REVEAL -> {
|
||||
Platform.runLater(window::close);
|
||||
vaultService.reveal(vault);
|
||||
}
|
||||
case IGNORE -> Platform.runLater(window::close);
|
||||
}
|
||||
}
|
||||
|
||||
private void savePasswordToSystemkeychain() {
|
||||
if (keychain.isPresent()) {
|
||||
try {
|
||||
keychain.get().storePassphrase(vault.getId(), CharBuffer.wrap(password.get()));
|
||||
} catch (KeychainAccessException e) {
|
||||
LOG.error("Failed to store passphrase in system keychain.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleInvalidMountPoint(FileSystemException e) {
|
||||
LOG.error("Unlock failed. Mount point not an empty directory: {}", e.getMessage());
|
||||
Platform.runLater(() -> {
|
||||
window.setScene(invalidMountPointScene.get());
|
||||
});
|
||||
}
|
||||
|
||||
private void handleGenericError(Exception e) {
|
||||
LOG.error("Unlock failed for technical reasons.", e);
|
||||
Platform.runLater(() -> {
|
||||
errorComponent.cause(e).window(window).returnToScene(window.getScene()).build().showErrorScene();
|
||||
});
|
||||
}
|
||||
|
||||
private void wipePassword(char[] pw) {
|
||||
if (pw != null) {
|
||||
Arrays.fill(pw, ' ');
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void scheduled() {
|
||||
vault.setState(VaultState.PROCESSING);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void succeeded() {
|
||||
vault.setState(VaultState.UNLOCKED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void failed() {
|
||||
vault.setState(VaultState.LOCKED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cancelled() {
|
||||
vault.setState(VaultState.LOCKED);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,24 +2,56 @@ package org.cryptomator.ui.vaultoptions;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ChoiceBox;
|
||||
import javafx.util.StringConverter;
|
||||
import org.cryptomator.common.settings.UiTheme;
|
||||
import org.cryptomator.common.settings.WhenUnlocked;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@VaultOptionsScoped
|
||||
public class GeneralVaultOptionsController implements FxController {
|
||||
|
||||
private final Vault vault;
|
||||
private final ResourceBundle resourceBundle;
|
||||
|
||||
public CheckBox unlockOnStartupCheckbox;
|
||||
public ChoiceBox<WhenUnlocked> actionAfterUnlockChoiceBox;
|
||||
|
||||
@Inject
|
||||
GeneralVaultOptionsController(@VaultOptionsWindow Vault vault) {
|
||||
GeneralVaultOptionsController(@VaultOptionsWindow Vault vault, ResourceBundle resourceBundle) {
|
||||
this.vault = vault;
|
||||
this.resourceBundle = resourceBundle;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
unlockOnStartupCheckbox.selectedProperty().bindBidirectional(vault.getVaultSettings().unlockAfterStartup());
|
||||
actionAfterUnlockChoiceBox.getItems().addAll(WhenUnlocked.values());
|
||||
actionAfterUnlockChoiceBox.valueProperty().bindBidirectional(vault.getVaultSettings().actionAfterUnlock());
|
||||
actionAfterUnlockChoiceBox.setConverter(new WhenUnlockedConverter(resourceBundle));
|
||||
}
|
||||
|
||||
private static class WhenUnlockedConverter extends StringConverter<WhenUnlocked> {
|
||||
|
||||
private final ResourceBundle resourceBundle;
|
||||
|
||||
public WhenUnlockedConverter(ResourceBundle resourceBundle) {
|
||||
this.resourceBundle = resourceBundle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(WhenUnlocked obj) {
|
||||
return resourceBundle.getString(obj.getDisplayName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public WhenUnlocked fromString(String string) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ public class MountOptionsController implements FxController {
|
||||
driveLetterSelection.setConverter(new WinDriveLetterLabelConverter(windowsDriveLetters, resourceBundle));
|
||||
driveLetterSelection.setValue(vault.getVaultSettings().winDriveLetter().get());
|
||||
|
||||
if (vault.getVaultSettings().usesIndividualMountPath().get()) {
|
||||
if (vault.getVaultSettings().useCustomMountPath().get()) {
|
||||
mountPoint.selectToggle(mountPointCustomDir);
|
||||
} else if (!Strings.isNullOrEmpty(vault.getVaultSettings().winDriveLetter().get())) {
|
||||
mountPoint.selectToggle(mountPointWinDriveLetter);
|
||||
@@ -93,7 +93,7 @@ public class MountOptionsController implements FxController {
|
||||
mountPoint.selectToggle(mountPointAuto);
|
||||
}
|
||||
|
||||
vault.getVaultSettings().usesIndividualMountPath().bind(mountPoint.selectedToggleProperty().isEqualTo(mountPointCustomDir));
|
||||
vault.getVaultSettings().useCustomMountPath().bind(mountPoint.selectedToggleProperty().isEqualTo(mountPointCustomDir));
|
||||
vault.getVaultSettings().winDriveLetter().bind( //
|
||||
Bindings.when(mountPoint.selectedToggleProperty().isEqualTo(mountPointWinDriveLetter)) //
|
||||
.then(driveLetterSelection.getSelectionModel().selectedItemProperty()) //
|
||||
@@ -126,14 +126,14 @@ public class MountOptionsController implements FxController {
|
||||
}
|
||||
File file = directoryChooser.showDialog(window);
|
||||
if (file != null) {
|
||||
vault.getVaultSettings().individualMountPath().set(file.getAbsolutePath());
|
||||
vault.getVaultSettings().customMountPath().set(file.getAbsolutePath());
|
||||
} else {
|
||||
vault.getVaultSettings().individualMountPath().set(null);
|
||||
vault.getVaultSettings().customMountPath().set(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleMountPoint(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
|
||||
if (mountPointCustomDir.equals(newValue) && Strings.isNullOrEmpty(vault.getVaultSettings().individualMountPath().get())) {
|
||||
if (mountPointCustomDir.equals(newValue) && Strings.isNullOrEmpty(vault.getVaultSettings().customMountPath().get())) {
|
||||
chooseCustomMountPoint();
|
||||
}
|
||||
}
|
||||
@@ -186,11 +186,11 @@ public class MountOptionsController implements FxController {
|
||||
}
|
||||
|
||||
public StringProperty customMountPathProperty() {
|
||||
return vault.getVaultSettings().individualMountPath();
|
||||
return vault.getVaultSettings().customMountPath();
|
||||
}
|
||||
|
||||
public String getCustomMountPath() {
|
||||
return vault.getVaultSettings().individualMountPath().get();
|
||||
return vault.getVaultSettings().customMountPath().get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ 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.mainwindow.MainWindow;
|
||||
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
|
||||
|
||||
@@ -38,15 +39,14 @@ abstract class VaultOptionsModule {
|
||||
@Provides
|
||||
@VaultOptionsWindow
|
||||
@VaultOptionsScoped
|
||||
static Stage provideStage(@MainWindow Stage owner, @VaultOptionsWindow Vault vault, ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons) {
|
||||
Stage stage = new Stage();
|
||||
static Stage provideStage(StageFactory factory, @MainWindow Stage owner, @VaultOptionsWindow Vault vault) {
|
||||
Stage stage = factory.create();
|
||||
stage.setTitle(vault.getDisplayableName());
|
||||
stage.setResizable(true);
|
||||
stage.setMinWidth(400);
|
||||
stage.setMinHeight(300);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.initOwner(owner);
|
||||
stage.getIcons().addAll(windowIcons);
|
||||
return stage;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ 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.mainwindow.MainWindow;
|
||||
|
||||
import javax.inject.Named;
|
||||
@@ -35,13 +36,12 @@ abstract class WrongFileAlertModule {
|
||||
@Provides
|
||||
@WrongFileAlertWindow
|
||||
@WrongFileAlertScoped
|
||||
static Stage provideStage(@MainWindow Stage mainWindow, ResourceBundle resourceBundle, @Named("windowIcons") List<Image> windowIcons) {
|
||||
Stage stage = new Stage();
|
||||
static Stage provideStage(StageFactory factory, @MainWindow Stage mainWindow, ResourceBundle resourceBundle) {
|
||||
Stage stage = factory.create();
|
||||
stage.setTitle(resourceBundle.getString("wrongFileAlert.title"));
|
||||
stage.setResizable(false);
|
||||
stage.initOwner(mainWindow);
|
||||
stage.initModality(Modality.WINDOW_MODAL);
|
||||
stage.getIcons().addAll(windowIcons);
|
||||
return stage;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,10 @@
|
||||
|
||||
<CheckBox fx:id="startHiddenCheckbox" text="%preferences.general.startHidden" visible="${controller.trayMenuSupported}" managed="${controller.trayMenuSupported}"/>
|
||||
|
||||
<CheckBox fx:id="debugModeCheckbox" text="%preferences.general.debugLogging"/>
|
||||
<HBox spacing="6" alignment="CENTER_LEFT">
|
||||
<CheckBox fx:id="debugModeCheckbox" text="%preferences.general.debugLogging"/>
|
||||
<Hyperlink styleClass="hyperlink-underline" text="%preferences.general.debugDirectory" onAction="#showLogfileDirectory"/>
|
||||
</HBox>
|
||||
|
||||
<CheckBox fx:id="autoStartCheckbox" text="%preferences.general.autoStart" visible="${controller.autoStartSupported}" managed="${controller.autoStartSupported}" onAction="#toggleAutoStart"/>
|
||||
</children>
|
||||
|
||||
@@ -21,15 +21,15 @@
|
||||
<children>
|
||||
<VBox spacing="6">
|
||||
<FormattedLabel format="%unlock.passwordPrompt" arg1="${controller.vault.displayableName}" wrapText="true"/>
|
||||
<NiceSecurePasswordField fx:id="passwordField"/>
|
||||
<CheckBox fx:id="savePassword" text="%unlock.savePassword" onAction="#didClickSavePasswordCheckbox" disable="${controller.vault.processing}" visible="${controller.keychainAccessAvailable}"/>
|
||||
<NiceSecurePasswordField fx:id="passwordField" disable="${controller.userInteractionDisabled}"/>
|
||||
<CheckBox fx:id="savePasswordCheckbox" text="%unlock.savePassword" onAction="#didClickSavePasswordCheckbox" disable="${controller.userInteractionDisabled}" visible="${controller.keychainAccessAvailable}"/>
|
||||
</VBox>
|
||||
|
||||
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
|
||||
<buttons>
|
||||
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#cancel" disable="${controller.vault.processing}"/>
|
||||
<Button text="%unlock.unlockBtn" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#unlock" contentDisplay="${controller.unlockButtonState}" disable="${controller.unlockButtonDisabled}">
|
||||
<Button text="%generic.button.cancel" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#cancel" disable="${controller.userInteractionDisabled}"/>
|
||||
<Button text="%unlock.unlockBtn" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#unlock" contentDisplay="${controller.unlockButtonContentDisplay}" disable="${controller.unlockButtonDisabled}">
|
||||
<graphic>
|
||||
<ProgressIndicator progress="-1" prefWidth="12" prefHeight="12"/>
|
||||
</graphic>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<?import javafx.scene.shape.Circle?>
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import org.cryptomator.ui.controls.FormattedLabel?>
|
||||
<?import javafx.scene.control.CheckBox?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.unlock.UnlockSuccessController"
|
||||
@@ -26,7 +27,10 @@
|
||||
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
||||
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="CHECK" glyphSize="24"/>
|
||||
</StackPane>
|
||||
<FormattedLabel format="%unlock.success.message" arg1="${controller.vault.displayableName}" wrapText="true" HBox.hgrow="ALWAYS"/>
|
||||
<VBox spacing="6">
|
||||
<FormattedLabel format="%unlock.success.message" arg1="${controller.vault.displayableName}" wrapText="true" HBox.hgrow="ALWAYS"/>
|
||||
<CheckBox text="%unlock.success.rememberChoice" fx:id="rememberChoiceCheckbox"/>
|
||||
</VBox>
|
||||
</HBox>
|
||||
|
||||
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.CheckBox?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.control.ChoiceBox?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.vaultoptions.GeneralVaultOptionsController"
|
||||
@@ -12,5 +15,10 @@
|
||||
</padding>
|
||||
<children>
|
||||
<CheckBox text="%vaultOptions.general.unlockAfterStartup" fx:id="unlockOnStartupCheckbox"/>
|
||||
|
||||
<HBox spacing="6" alignment="CENTER_LEFT">
|
||||
<Label text="%vaultOptions.general.actionAfterUnlock"/>
|
||||
<ChoiceBox fx:id="actionAfterUnlockChoiceBox"/>
|
||||
</HBox>
|
||||
</children>
|
||||
</VBox>
|
||||
|
||||
@@ -96,6 +96,7 @@ unlock.savePassword=Save Password
|
||||
unlock.unlockBtn=Unlock
|
||||
## Success
|
||||
unlock.success.message=Unlocked "%s" successfully! Your vault is now accessible.
|
||||
unlock.success.rememberChoice=Remember choice, don't show this again
|
||||
unlock.success.revealBtn=Reveal Vault
|
||||
## Invalid Mount Point
|
||||
unlock.error.invalidMountPoint=Mount point is not an empty directory: %s
|
||||
@@ -134,6 +135,7 @@ preferences.general.theme.dark=Dark
|
||||
preferences.general.unlockThemes=Unlock dark mode
|
||||
preferences.general.startHidden=Hide window when starting Cryptomator
|
||||
preferences.general.debugLogging=Enable debug logging
|
||||
preferences.general.debugDirectory=Reveal log files
|
||||
preferences.general.autoStart=Launch Cryptomator on system start
|
||||
preferences.general.interfaceOrientation=Interface Orientation
|
||||
preferences.general.interfaceOrientation.ltr=Left to Right
|
||||
@@ -206,6 +208,10 @@ wrongFileAlert.link=For further assistance, visit
|
||||
## General
|
||||
vaultOptions.general=General
|
||||
vaultOptions.general.unlockAfterStartup=Unlock vault when starting Cryptomator
|
||||
vaultOptions.general.actionAfterUnlock=After successful unlock
|
||||
vaultOptions.general.actionAfterUnlock.ignore=Do nothing
|
||||
vaultOptions.general.actionAfterUnlock.reveal=Reveal Drive
|
||||
vaultOptions.general.actionAfterUnlock.ask=Ask
|
||||
## Mount
|
||||
vaultOptions.mount=Mounting
|
||||
vaultOptions.mount.readonly=Read-Only
|
||||
|
||||
@@ -95,6 +95,7 @@ unlock.savePassword=احفظ كلمة المرور
|
||||
unlock.unlockBtn=افتح
|
||||
## Success
|
||||
unlock.success.message=تم فتح المخزن "%s" بنجاح! يمكنك الوصول إليه الآن.
|
||||
unlock.success.rememberChoice=تذكر اختياري ولا تظهر هذا مرة أخرى
|
||||
unlock.success.revealBtn=افتح الحافظة
|
||||
## Invalid Mount Point
|
||||
unlock.error.invalidMountPoint=نقطة الوصول ليست مجلد فارغ: %s
|
||||
@@ -128,6 +129,8 @@ preferences.title=تفضيلات
|
||||
## General
|
||||
preferences.general=عام
|
||||
preferences.general.theme=الشكل والمظهر
|
||||
preferences.general.theme.light=فاتح (أبيض)
|
||||
preferences.general.theme.dark=مظلم (أسود)
|
||||
preferences.general.unlockThemes=تفعيل الوضع المظلم
|
||||
preferences.general.startHidden=إخفاء النافذة عند بدء تشغيل Cryptomator
|
||||
preferences.general.debugLogging=تمكين سجلات التصحيح
|
||||
@@ -203,6 +206,10 @@ wrongFileAlert.link=لمزيد من المساعدة، قم بزيارة
|
||||
## General
|
||||
vaultOptions.general=عام
|
||||
vaultOptions.general.unlockAfterStartup=فتح قفل المخزن عند بدء تشغيل Cryptomator
|
||||
vaultOptions.general.actionAfterUnlock=بعد فتح القفل بنجاح
|
||||
vaultOptions.general.actionAfterUnlock.ignore=لا تفعل شيئاً
|
||||
vaultOptions.general.actionAfterUnlock.reveal=اظهار القرص
|
||||
vaultOptions.general.actionAfterUnlock.ask=اسأل
|
||||
## Mount
|
||||
vaultOptions.mount=القرص الوهمي
|
||||
vaultOptions.mount.readonly=للقراءة فقط
|
||||
|
||||
@@ -95,6 +95,7 @@ unlock.savePassword=Recorda la contrasenya
|
||||
unlock.unlockBtn=Desbloqueja
|
||||
## Success
|
||||
unlock.success.message="%s" s'ha desbloquejat correctament! Ja es pot accedir a la caixa forta.
|
||||
unlock.success.rememberChoice=Recorda l'elecció. No ho tornis a mostrar.
|
||||
unlock.success.revealBtn=Mostra la caixa forta
|
||||
## Invalid Mount Point
|
||||
unlock.error.invalidMountPoint=El punt de muntatge no és un directori buit: %s
|
||||
@@ -133,6 +134,7 @@ preferences.general.theme.dark=Fosc
|
||||
preferences.general.unlockThemes=Desbloqueja el tema fosc
|
||||
preferences.general.startHidden=Amaga la finestra al iniciar 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.interfaceOrientation=Orientació de la interfície
|
||||
preferences.general.interfaceOrientation.ltr=Esquerra a dreta
|
||||
@@ -205,6 +207,10 @@ wrongFileAlert.link=Per rebre assistència, visiteu
|
||||
## General
|
||||
vaultOptions.general=General
|
||||
vaultOptions.general.unlockAfterStartup=Desbloqueja la caixa forta al iniciar Cryptomator
|
||||
vaultOptions.general.actionAfterUnlock=Després d'un desbloqueig correcte
|
||||
vaultOptions.general.actionAfterUnlock.ignore=No facis res
|
||||
vaultOptions.general.actionAfterUnlock.reveal=Mostra la unitat
|
||||
vaultOptions.general.actionAfterUnlock.ask=Pregunta
|
||||
## Mount
|
||||
vaultOptions.mount=Muntatge
|
||||
vaultOptions.mount.readonly=Només lectura
|
||||
|
||||
@@ -95,6 +95,7 @@ unlock.savePassword=Uložit heslo
|
||||
unlock.unlockBtn=Odemknout
|
||||
## Success
|
||||
unlock.success.message=Trezor "%s" byl úspěšně odemčen a nyní je dostupný.
|
||||
unlock.success.rememberChoice=Pamatovat si volbu, nezobrazovat to znovu
|
||||
unlock.success.revealBtn=Zobrazit trezor
|
||||
## Invalid Mount Point
|
||||
unlock.error.invalidMountPoint=Připojovací bod není prázdný adresář: %s
|
||||
@@ -107,6 +108,7 @@ migration.start.confirm=Ano, můj trezor je plně synchronizován.
|
||||
## Run
|
||||
migration.run.enterPassword=Zadejte heslo pro "%s"
|
||||
migration.run.startMigrationBtn=Migrovat trezor
|
||||
migration.run.progressHint=Může to chvíli trvat…
|
||||
## Sucess
|
||||
migration.success.nextStepsInstructions=Migrace "%s" byla úspěšná.\nNyní můžete svůj trezor odemknout.
|
||||
migration.success.unlockNow=Odemknout nyní
|
||||
@@ -118,15 +120,21 @@ migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Souborový syst
|
||||
migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=Souborový systém neumožňuje čtení.
|
||||
migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=Souborový systém neumožňuje zápis.
|
||||
## Impossible
|
||||
migration.impossible.heading=Trezor není možné zmigrovat
|
||||
migration.impossible.reason=Trezor nemůže být automaticky zmigrován, protože jeho umístění v úložišti nebo přístupový bod není kompatibilní.
|
||||
migration.impossible.moreInfo=Trezor může být stále otevřen starší verzí. Pro pokyny, jak ručně migrovat trezor, navštivte
|
||||
|
||||
# Preferences
|
||||
preferences.title=Nastavení
|
||||
## General
|
||||
preferences.general=Obecné
|
||||
preferences.general.theme=Vzhled
|
||||
preferences.general.theme.light=Světlý
|
||||
preferences.general.theme.dark=Tmavý
|
||||
preferences.general.unlockThemes=Odemknout tmavý režim
|
||||
preferences.general.startHidden=Skrýt okno Cryptomatoru při spuštění
|
||||
preferences.general.debugLogging=Ladicí režim
|
||||
preferences.general.debugDirectory=Ukázat logovací soubory
|
||||
preferences.general.autoStart=Spustit Cryptomator při spuštění systému
|
||||
preferences.general.interfaceOrientation=Orientace prostředí
|
||||
preferences.general.interfaceOrientation.ltr=Zleva doprava
|
||||
@@ -199,6 +207,10 @@ wrongFileAlert.link=Pro další informace navštivte
|
||||
## General
|
||||
vaultOptions.general=Obecné
|
||||
vaultOptions.general.unlockAfterStartup=Odemknout trezor při spuštění Cryptomator
|
||||
vaultOptions.general.actionAfterUnlock=Po úspěšném odemčení
|
||||
vaultOptions.general.actionAfterUnlock.ignore=Nedělat nic
|
||||
vaultOptions.general.actionAfterUnlock.reveal=Zobrazit jednotku
|
||||
vaultOptions.general.actionAfterUnlock.ask=Zeptat se
|
||||
## Mount
|
||||
vaultOptions.mount=Připojení
|
||||
vaultOptions.mount.readonly=Pouze pro čtení
|
||||
|
||||
@@ -95,6 +95,7 @@ unlock.savePassword=Passwort speichern
|
||||
unlock.unlockBtn=Entsperren
|
||||
## Success
|
||||
unlock.success.message=„%s“ erfolgreich entsperrt! Nun kannst du auf deinen Tresor zugreifen.
|
||||
unlock.success.rememberChoice=Auswahl speichern und nicht mehr anzeigen
|
||||
unlock.success.revealBtn=Tresor anzeigen
|
||||
## Invalid Mount Point
|
||||
unlock.error.invalidMountPoint=Einhängepunkt ist kein leeres Verzeichnis: %s
|
||||
@@ -121,7 +122,7 @@ migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=Das Dateisyste
|
||||
## Impossible
|
||||
migration.impossible.heading=Tresor kann nicht migriert werden
|
||||
migration.impossible.reason=Der Tresor kann nicht automatisch migriert werden, da sein Speicherort oder Zugangspunkt nicht kompatibel ist.
|
||||
migration.impossible.moreInfo=Der Tresor kann immer noch mit einer älteren Version geöffnet werden. Eine Anleitung zum manuellen Migrieren eines Tresors findest du unter
|
||||
migration.impossible.moreInfo=Der Tresor kann auch jetzt noch mit einer älteren Version geöffnet werden. Eine Anleitung zum manuellen Migrieren eines Tresors findest du unter
|
||||
|
||||
# Preferences
|
||||
preferences.title=Einstellungen
|
||||
@@ -133,6 +134,7 @@ preferences.general.theme.dark=Dunkel
|
||||
preferences.general.unlockThemes=Dunklen Modus freischalten
|
||||
preferences.general.startHidden=Cryptomator im Hintergrund starten
|
||||
preferences.general.debugLogging=Diagnoseprotokoll aktivieren
|
||||
preferences.general.debugDirectory=Protokolldateien anzeigen
|
||||
preferences.general.autoStart=Cryptomator beim Systemstart starten
|
||||
preferences.general.interfaceOrientation=Oberflächenausrichtung
|
||||
preferences.general.interfaceOrientation.ltr=Von links nach rechts
|
||||
@@ -205,6 +207,10 @@ wrongFileAlert.link=Für weitere Unterstützung besuche
|
||||
## General
|
||||
vaultOptions.general=Allgemein
|
||||
vaultOptions.general.unlockAfterStartup=Tresor beim Start von Cryptomator entsperren
|
||||
vaultOptions.general.actionAfterUnlock=Nach erfolgreichem Entsperren
|
||||
vaultOptions.general.actionAfterUnlock.ignore=Nichts tun
|
||||
vaultOptions.general.actionAfterUnlock.reveal=Laufwerk anzeigen
|
||||
vaultOptions.general.actionAfterUnlock.ask=Nachfragen
|
||||
## Mount
|
||||
vaultOptions.mount=Laufwerk
|
||||
vaultOptions.mount.readonly=Schreibgeschützt
|
||||
|
||||
@@ -95,6 +95,7 @@ unlock.savePassword=Guardar contraseña
|
||||
unlock.unlockBtn=Desbloquear
|
||||
## Success
|
||||
unlock.success.message=¡"%s" se desbloqueó con éxito! La bóveda ya es accesible.
|
||||
unlock.success.rememberChoice=Recordar opción y no mostrar de nuevo
|
||||
unlock.success.revealBtn=Revelar bóveda
|
||||
## Invalid Mount Point
|
||||
unlock.error.invalidMountPoint=El punto de montaje no es un directorio vacío: %s
|
||||
@@ -128,6 +129,8 @@ preferences.title=Preferencias
|
||||
## General
|
||||
preferences.general=General
|
||||
preferences.general.theme=Apariencia
|
||||
preferences.general.theme.light=Claro
|
||||
preferences.general.theme.dark=Oscuro
|
||||
preferences.general.unlockThemes=Desbloquear el modo oscuro
|
||||
preferences.general.startHidden=Ocultar ventana al iniciar Cryptomator
|
||||
preferences.general.debugLogging=Habilitar registro de depuración
|
||||
@@ -203,6 +206,10 @@ wrongFileAlert.link=Para más ayuda, visite
|
||||
## General
|
||||
vaultOptions.general=General
|
||||
vaultOptions.general.unlockAfterStartup=Desbloquear bóveda al iniciar Cryptomator
|
||||
vaultOptions.general.actionAfterUnlock=Después de desbloquear exitosamente
|
||||
vaultOptions.general.actionAfterUnlock.ignore=No hacer nada
|
||||
vaultOptions.general.actionAfterUnlock.reveal=Revelar unidad
|
||||
vaultOptions.general.actionAfterUnlock.ask=Preguntar
|
||||
## Mount
|
||||
vaultOptions.mount=Montaje
|
||||
vaultOptions.mount.readonly=Sólo lectura
|
||||
|
||||
@@ -95,6 +95,7 @@ unlock.savePassword=Enregistrer le mot de passe
|
||||
unlock.unlockBtn=Déverrouiller
|
||||
## Success
|
||||
unlock.success.message=“%s” déverrouillé ! Le contenu de votre coffre est maintenant accessible.
|
||||
unlock.success.rememberChoice=Se souvenir de mon choix et ne plus me demander
|
||||
unlock.success.revealBtn=Révéler le coffre
|
||||
## Invalid Mount Point
|
||||
unlock.error.invalidMountPoint=Le point de montage n'est pas un répertoire vide : %s
|
||||
@@ -107,7 +108,7 @@ migration.start.confirm=Ce coffre est bien synchronisé
|
||||
## Run
|
||||
migration.run.enterPassword=Entrez le mot de passe pour %s
|
||||
migration.run.startMigrationBtn=Migrer le coffre
|
||||
migration.run.progressHint=Veuillez patienter, cela peut prendre du temps…
|
||||
migration.run.progressHint=Ceci peut prendre un certain temps…
|
||||
## Sucess
|
||||
migration.success.nextStepsInstructions=“%s” migré.\nVous pouvez à présent déverrouiller ce coffre.
|
||||
migration.success.unlockNow=Déverouiller
|
||||
@@ -119,9 +120,9 @@ migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Ce système de f
|
||||
migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=Le système de fichiers ne permet pas d'être lu.
|
||||
migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=Le système de fichiers ne permet pas l'écriture.
|
||||
## Impossible
|
||||
migration.impossible.heading=Impossible de migrer le coffre
|
||||
migration.impossible.reason=Le coffre ne peut pas être migré automatiquement car son emplacement de stockage ou son point d'accès n'est pas compatible.
|
||||
migration.impossible.moreInfo=Le coffre peut toujours être ouvert avec une version plus ancienne. Pour obtenir des instructions sur la façon de migrer manuellement un coffre, visitez
|
||||
migration.impossible.heading=Impossible de migrer le coffre-fort
|
||||
migration.impossible.reason=Le coffre-fort ne peut pas être migré automatiquement car son emplacement de stockage ou son point d'accès n'est pas compatible.
|
||||
migration.impossible.moreInfo=Le coffre-fort peut encore être ouvert avec une version plus ancienne. Pour obtenir des instructions sur la façon de migrer manuellement un coffre-fort, visitez
|
||||
|
||||
# Preferences
|
||||
preferences.title=Préférences
|
||||
@@ -133,6 +134,7 @@ preferences.general.theme.dark=Sombre
|
||||
preferences.general.unlockThemes=Débloquer le mode nuit
|
||||
preferences.general.startHidden=Démarrer Cryptomator en mode caché
|
||||
preferences.general.debugLogging=Activer les logs debug
|
||||
preferences.general.debugDirectory=Afficher le relevé
|
||||
preferences.general.autoStart=Lancer Cryptomator au démarrage du système
|
||||
preferences.general.interfaceOrientation=Orientation de l'interface
|
||||
preferences.general.interfaceOrientation.ltr=De gauche à droite
|
||||
@@ -205,6 +207,10 @@ wrongFileAlert.link=Pour toute aide supplémentaire, visitez
|
||||
## General
|
||||
vaultOptions.general=Général
|
||||
vaultOptions.general.unlockAfterStartup=Déverrouiller le coffre au démarrage
|
||||
vaultOptions.general.actionAfterUnlock=Après un déverrouillage réussi
|
||||
vaultOptions.general.actionAfterUnlock.ignore=Ne rien faire
|
||||
vaultOptions.general.actionAfterUnlock.reveal=Afficher le disque
|
||||
vaultOptions.general.actionAfterUnlock.ask=Demander
|
||||
## Mount
|
||||
vaultOptions.mount=Montage
|
||||
vaultOptions.mount.readonly=Lecture seule
|
||||
|
||||
@@ -92,7 +92,7 @@ main.vaultDetail.migrateButton=वाउल्ट को अपग्रेड
|
||||
|
||||
# Wrong File Alert
|
||||
wrongFileAlert.instruction.1=अपना वाउल्ट खोलें।
|
||||
wrongFileAlert.link=और मदद लिए, यह जाएं
|
||||
wrongFileAlert.link=और मदद के लिए, यह जाएं
|
||||
|
||||
# Vault Options
|
||||
## General
|
||||
|
||||
@@ -95,6 +95,7 @@ unlock.savePassword=Salva password
|
||||
unlock.unlockBtn=Sblocca
|
||||
## Success
|
||||
unlock.success.message=Sbloccato "%s" con successo! La tua cassaforte è ora accessibile.
|
||||
unlock.success.rememberChoice=Ricorda la scelta, non mostrare ancora
|
||||
unlock.success.revealBtn=Rivela Cassaforte
|
||||
## Invalid Mount Point
|
||||
unlock.error.invalidMountPoint=Il punto di mount non è una cartella vuota: %s
|
||||
@@ -128,9 +129,12 @@ preferences.title=Impostazioni
|
||||
## General
|
||||
preferences.general=Generali
|
||||
preferences.general.theme=Aspetto
|
||||
preferences.general.theme.light=Chiaro
|
||||
preferences.general.theme.dark=Scuro
|
||||
preferences.general.unlockThemes=Sblocca modalità scura
|
||||
preferences.general.startHidden=Nascondi la finestra all'avvio di Cryptomator
|
||||
preferences.general.debugLogging=Abilita i registri di debug
|
||||
preferences.general.debugDirectory=Mostra file log
|
||||
preferences.general.autoStart=Avvia Cryptomator all'avvio del sistema
|
||||
preferences.general.interfaceOrientation=Orientamento Interfaccia
|
||||
preferences.general.interfaceOrientation.ltr=Da Sinistra a Destra
|
||||
@@ -203,6 +207,10 @@ wrongFileAlert.link=Per ulteriore assistenza, visita
|
||||
## General
|
||||
vaultOptions.general=Generali
|
||||
vaultOptions.general.unlockAfterStartup=Sblocca vault all'avvio di Cryptomator
|
||||
vaultOptions.general.actionAfterUnlock=Dopo aver sbloccato con successo
|
||||
vaultOptions.general.actionAfterUnlock.ignore=Non fare nulla
|
||||
vaultOptions.general.actionAfterUnlock.reveal=Visualizza disco
|
||||
vaultOptions.general.actionAfterUnlock.ask=Chiedi
|
||||
## Mount
|
||||
vaultOptions.mount=Montaggio
|
||||
vaultOptions.mount.readonly=Sola lettura
|
||||
|
||||
@@ -95,6 +95,7 @@ unlock.savePassword=パスワードを保存
|
||||
unlock.unlockBtn=解錠
|
||||
## Success
|
||||
unlock.success.message="%s" の解錠に成功しました! 金庫にアクセス可能です。
|
||||
unlock.success.rememberChoice=選択を記憶させて、再度表示しない
|
||||
unlock.success.revealBtn=金庫を表示
|
||||
## Invalid Mount Point
|
||||
unlock.error.invalidMountPoint=マウントポイントが空のディレクトリではありません: %s
|
||||
@@ -107,6 +108,7 @@ migration.start.confirm=はい。同期が完了しています
|
||||
## Run
|
||||
migration.run.enterPassword="%s" のパスワードを入力してください
|
||||
migration.run.startMigrationBtn=金庫を移行
|
||||
migration.run.progressHint=時間がかかる場合があります...
|
||||
## Sucess
|
||||
migration.success.nextStepsInstructions="%s" の移行が成功しました。\n金庫を解錠できるようになりました。
|
||||
migration.success.unlockNow=今すぐ解錠
|
||||
@@ -118,17 +120,21 @@ migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=ファイルシ
|
||||
migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=ファイルシステムによって読み込みが許可されていません。
|
||||
migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=ファイルシステムによって書き込みが許可されていません。
|
||||
## Impossible
|
||||
migration.impossible.heading=金庫の移行に失敗しました
|
||||
migration.impossible.reason=ストレージ場所またはアクセスポイントに互換性がないため、金庫を自動的に移行できません。
|
||||
migration.impossible.moreInfo=金庫を古いバージョンで開くことは可能です。手動による金庫の移行方法については次をご覧ください:
|
||||
|
||||
# Preferences
|
||||
preferences.title=設定
|
||||
## General
|
||||
preferences.general=基本設定
|
||||
preferences.general.theme=外見 & 操作性
|
||||
preferences.general.theme.light=明るい
|
||||
preferences.general.theme.dark=暗い
|
||||
preferences.general.theme.light=ライト
|
||||
preferences.general.theme.dark=ダーク
|
||||
preferences.general.unlockThemes=ダークモードの解錠
|
||||
preferences.general.startHidden=Cryptomator を開始したときウィンドウを隠す
|
||||
preferences.general.debugLogging=ログを有効にする
|
||||
preferences.general.debugDirectory=ログ ファイルを表示
|
||||
preferences.general.autoStart=システム開始時にCryptomatorを起動する
|
||||
preferences.general.interfaceOrientation=インターフェイスの向き
|
||||
preferences.general.interfaceOrientation.ltr=左から右
|
||||
@@ -201,6 +207,10 @@ wrongFileAlert.link=より詳細な手順については、次のページをご
|
||||
## General
|
||||
vaultOptions.general=基本設定
|
||||
vaultOptions.general.unlockAfterStartup=Cryptomatorの起動時に金庫を解錠する
|
||||
vaultOptions.general.actionAfterUnlock=解錠に成功したあと
|
||||
vaultOptions.general.actionAfterUnlock.ignore=何もしない
|
||||
vaultOptions.general.actionAfterUnlock.reveal=ドライブを表示
|
||||
vaultOptions.general.actionAfterUnlock.ask=尋ねる
|
||||
## Mount
|
||||
vaultOptions.mount=マウント
|
||||
vaultOptions.mount.readonly=読み取り専用
|
||||
|
||||
@@ -95,6 +95,7 @@ unlock.savePassword=비밀번호 저장
|
||||
unlock.unlockBtn=잠금해제
|
||||
## Success
|
||||
unlock.success.message="%s"의 잠금해제가 성공적으로 수행되었습니다! 이제 이 Vault의 접근이 가능합니다.
|
||||
unlock.success.rememberChoice=선택 기억함, 다시 묻지 않음
|
||||
unlock.success.revealBtn=Vault 표시
|
||||
## Invalid Mount Point
|
||||
unlock.error.invalidMountPoint=구성지점의 디렉터리가 비어있지 않습니다: %s
|
||||
@@ -205,6 +206,10 @@ wrongFileAlert.link=추후 지원을 위해, 다음을 방문하십시요
|
||||
## General
|
||||
vaultOptions.general=일반
|
||||
vaultOptions.general.unlockAfterStartup=Cryptomator를 시작할 때 Vault 잠금 해제
|
||||
vaultOptions.general.actionAfterUnlock=성공적으로 잠금해제 후
|
||||
vaultOptions.general.actionAfterUnlock.ignore=아무 것도 하지 않음
|
||||
vaultOptions.general.actionAfterUnlock.reveal=드라이브 표시
|
||||
vaultOptions.general.actionAfterUnlock.ask=요청
|
||||
## Mount
|
||||
vaultOptions.mount=드라이브 구성
|
||||
vaultOptions.mount.readonly=읽기 전용
|
||||
|
||||
@@ -124,6 +124,8 @@ preferences.title=Iestatījumi
|
||||
## General
|
||||
preferences.general=Vispārēji
|
||||
preferences.general.theme=Izskats
|
||||
preferences.general.theme.light=Gaišs
|
||||
preferences.general.theme.dark=Tumšs
|
||||
preferences.general.unlockThemes=Iespējot tumšo režīmu
|
||||
preferences.general.startHidden=Paslēpt logu, kad startē Cryptomator
|
||||
preferences.general.debugLogging=Iespējot atkļūdošanas žurnalēšanu
|
||||
@@ -199,6 +201,7 @@ wrongFileAlert.link=Lai iegūtu turpmāku palīdzību, apmeklējiet
|
||||
## General
|
||||
vaultOptions.general=Vispārēji
|
||||
vaultOptions.general.unlockAfterStartup=Atslēgt glabātuvi startējot Cryptomator
|
||||
vaultOptions.general.actionAfterUnlock.reveal=Atklāt disku
|
||||
## Mount
|
||||
vaultOptions.mount=Montē
|
||||
vaultOptions.mount.readonly=Tikai lasīt
|
||||
|
||||
@@ -95,6 +95,7 @@ unlock.savePassword=Lagre passord
|
||||
unlock.unlockBtn=Lås opp
|
||||
## Success
|
||||
unlock.success.message=Vellykket opplåsning av "%s"! Hvelvet ditt er nå tilgjengelig.
|
||||
unlock.success.rememberChoice=Husk valget - ikke vis dette igjen
|
||||
unlock.success.revealBtn=Gjør hvelvet synlig
|
||||
## Invalid Mount Point
|
||||
unlock.error.invalidMountPoint=Monteringspunktet er ikke en tom mappe: %s
|
||||
@@ -107,6 +108,7 @@ migration.start.confirm=Ja, hvelvet mitt er fullstendig synkronisert
|
||||
## Run
|
||||
migration.run.enterPassword=Skriv inn passordet for "%s"
|
||||
migration.run.startMigrationBtn=Overfør hvelv
|
||||
migration.run.progressHint=Dette kan ta litt tid…
|
||||
## Sucess
|
||||
migration.success.nextStepsInstructions=Vellykket overføring av "%s".\nDu kan nå låse opp hvelvet ditt.
|
||||
migration.success.unlockNow=Lås opp nå
|
||||
@@ -118,12 +120,17 @@ migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Filsystemet stø
|
||||
migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=Filsystemet tillater ikke lesing.
|
||||
migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=Filsystemet tillater ikke å bli skrevet på.
|
||||
## Impossible
|
||||
migration.impossible.heading=Kunne ikke overføre hvelvet
|
||||
migration.impossible.reason=Hvelvet kan ikke overføres automatisk fordi lagringsstedet eller tilgangspunkt ikke er kompatibelt.
|
||||
migration.impossible.moreInfo=Hvelvet kan fortsatt åpnes hvis du bruker en eldre versjon. For instruksjoner om hvordan man overfører et hvelv, besøk
|
||||
|
||||
# Preferences
|
||||
preferences.title=Innstillinger
|
||||
## General
|
||||
preferences.general=Generelt
|
||||
preferences.general.theme=Grafisk utseende
|
||||
preferences.general.theme.light=Lys
|
||||
preferences.general.theme.dark=Mørk
|
||||
preferences.general.unlockThemes=Lås opp mørk modus
|
||||
preferences.general.startHidden=Skjul vinduet når du starter Cryptomator
|
||||
preferences.general.debugLogging=Aktiver loggføring av feilsøk
|
||||
@@ -199,6 +206,10 @@ wrongFileAlert.link=For ytterligere hjelp, besøk
|
||||
## General
|
||||
vaultOptions.general=Generelt
|
||||
vaultOptions.general.unlockAfterStartup=Lås opp hvelvet når du starter Cryptomator
|
||||
vaultOptions.general.actionAfterUnlock=Etter vellykket opplåsing
|
||||
vaultOptions.general.actionAfterUnlock.ignore=Ikke gjør noe
|
||||
vaultOptions.general.actionAfterUnlock.reveal=Gjør enheten synlig
|
||||
vaultOptions.general.actionAfterUnlock.ask=Spør
|
||||
## Mount
|
||||
vaultOptions.mount=Montering
|
||||
vaultOptions.mount.readonly=Skrivebeskyttet
|
||||
|
||||
@@ -197,6 +197,7 @@ wrongFileAlert.link=Voor verdere ondersteuning, bezoek
|
||||
## General
|
||||
vaultOptions.general=Algemeen
|
||||
vaultOptions.general.unlockAfterStartup=Ontgrendel kluis bij het starten van Cryptomator
|
||||
vaultOptions.general.actionAfterUnlock.reveal=Toon Schijf
|
||||
## Mount
|
||||
vaultOptions.mount=Aankoppelen
|
||||
vaultOptions.mount.readonly=Alleen-Lezen
|
||||
|
||||
@@ -95,6 +95,7 @@ unlock.savePassword=Lagre passordet
|
||||
unlock.unlockBtn=Låse opp
|
||||
## Success
|
||||
unlock.success.message=Vellykka opplåsning av "%s"! Kvelven din er no tilgjengeleg.
|
||||
unlock.success.rememberChoice=Hugs valet - ikkje vis dette igjen
|
||||
unlock.success.revealBtn=Gjer kvelven synleg
|
||||
## Invalid Mount Point
|
||||
unlock.error.invalidMountPoint=Monteringspunktet er ikkje ei tom mappe: %s
|
||||
@@ -107,6 +108,7 @@ migration.start.confirm=Ja, kvelven min er fullstendig synkronisert
|
||||
## Run
|
||||
migration.run.enterPassword=Skriv inn passordet for "%s"
|
||||
migration.run.startMigrationBtn=Migrer kvelv
|
||||
migration.run.progressHint=Dette kan ta litt tid…
|
||||
## Sucess
|
||||
migration.success.nextStepsInstructions=Vellykka migrering av "%s". Du kan no låsa opp kvelven din.
|
||||
migration.success.unlockNow=Lås opp no
|
||||
@@ -118,12 +120,17 @@ migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Filsystemet stø
|
||||
migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=Filsystemet tillèt ikkje å bli lese.
|
||||
migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=Filsystemet tillèt ikkje å bli skrive på.
|
||||
## Impossible
|
||||
migration.impossible.heading=Kunne ikkje overføra kvelven
|
||||
migration.impossible.reason=Kvelven kan ikkje overførast automatisk fordi lagringsstaden eller tilgangspunkt ikkje er kompatibelt.
|
||||
migration.impossible.moreInfo=Kvelven kan framleis opnast viss du bruker ein eldre versjon. For instruksjonar om korleis ein overfører ein kvelv, besøk
|
||||
|
||||
# Preferences
|
||||
preferences.title=Innstillingar
|
||||
## General
|
||||
preferences.general=Generelt
|
||||
preferences.general.theme=Grafisk utsjånad
|
||||
preferences.general.theme.light=Lys
|
||||
preferences.general.theme.dark=Mørk
|
||||
preferences.general.unlockThemes=Lås opp mørk modus
|
||||
preferences.general.startHidden=Skjul vindauget når du startar Cryptomator
|
||||
preferences.general.debugLogging=Aktivar protokollføring av feilsøk
|
||||
@@ -199,6 +206,10 @@ wrongFileAlert.link=For ytterlegare hjelp, besøk
|
||||
## General
|
||||
vaultOptions.general=Generelt
|
||||
vaultOptions.general.unlockAfterStartup=Lås opp kvelven når du startar Cryptomator
|
||||
vaultOptions.general.actionAfterUnlock=Etter vellykka opplåsing
|
||||
vaultOptions.general.actionAfterUnlock.ignore=Ikkje gjer noko
|
||||
vaultOptions.general.actionAfterUnlock.reveal=Gjer eininga synleg
|
||||
vaultOptions.general.actionAfterUnlock.ask=Spør
|
||||
## Mount
|
||||
vaultOptions.mount=Montering
|
||||
vaultOptions.mount.readonly=Skriveverna
|
||||
|
||||
@@ -95,6 +95,7 @@ unlock.savePassword=Salvar Senha
|
||||
unlock.unlockBtn=Desbloquear
|
||||
## Success
|
||||
unlock.success.message="%s" foi desbloqueado com sucesso! Seu cofre agora está acessível.
|
||||
unlock.success.rememberChoice=Lembrar opção escolhida, não mostrar isto novamente
|
||||
unlock.success.revealBtn=Revelar Cofre
|
||||
## Invalid Mount Point
|
||||
unlock.error.invalidMountPoint=O ponto de montagem não é um diretório vazio: %s
|
||||
@@ -205,6 +206,10 @@ wrongFileAlert.link=Para obter assistência, visite
|
||||
## General
|
||||
vaultOptions.general=Geral
|
||||
vaultOptions.general.unlockAfterStartup=Desbloqueie o cofre ao iniciar o Cryptomator
|
||||
vaultOptions.general.actionAfterUnlock=Após desbloquear com sucesso
|
||||
vaultOptions.general.actionAfterUnlock.ignore=Não fazer nada
|
||||
vaultOptions.general.actionAfterUnlock.reveal=Revelar Volume
|
||||
vaultOptions.general.actionAfterUnlock.ask=Perguntar
|
||||
## Mount
|
||||
vaultOptions.mount=Montagem
|
||||
vaultOptions.mount.readonly=Somente Leitura
|
||||
|
||||
@@ -14,12 +14,12 @@ generic.button.next=Далее
|
||||
generic.button.print=Распечатать
|
||||
## Error
|
||||
generic.error.title=Произошла непредвиденная ошибка
|
||||
generic.error.instruction=Этого не должно было произойти. Создайте отчёт об ошибке ниже и опишите, какие шаги к ней привели.
|
||||
generic.error.instruction=Этого не должно было произойти. Пожалуйста, создайте отчет об ошибке ниже и опишите, какие шаги к ней привели.
|
||||
|
||||
# Tray Menu
|
||||
traymenu.showMainWindow=Показать
|
||||
traymenu.showPreferencesWindow=Настройки
|
||||
traymenu.lockAllVaults=Заблокировать всё
|
||||
traymenu.lockAllVaults=Заблокировать все
|
||||
traymenu.quitApplication=Выход
|
||||
traymenu.vault.unlock=Разблокировать
|
||||
traymenu.vault.lock=Заблокировать
|
||||
@@ -29,33 +29,33 @@ traymenu.vault.reveal=Показать
|
||||
addvaultwizard.title=Добавить хранилище
|
||||
## Welcome
|
||||
addvaultwizard.welcome.newButton=Создать хранилище
|
||||
addvaultwizard.welcome.existingButton=Открыть имеющееся хранилище
|
||||
addvaultwizard.welcome.existingButton=Открыть существующее хранилище
|
||||
## New
|
||||
### Name
|
||||
addvaultwizard.new.nameInstruction=Выберите имя для хранилища
|
||||
addvaultwizard.new.namePrompt=Имя хранилища
|
||||
addvaultwizard.new.namePrompt=Название хранилища
|
||||
### Location
|
||||
addvaultwizard.new.locationInstruction=Где Cryptomator должен хранить зашифрованные файлы вашего хранилища?
|
||||
addvaultwizard.new.locationLabel=Место хранения
|
||||
addvaultwizard.new.locationLabel=Расположение хранилища
|
||||
addvaultwizard.new.locationPrompt=…
|
||||
addvaultwizard.new.directoryPickerLabel=Своё расположение
|
||||
addvaultwizard.new.directoryPickerButton=Выбрать…
|
||||
addvaultwizard.new.directoryPickerTitle=Выберите каталог
|
||||
addvaultwizard.new.fileAlreadyExists=Хранилище не может быть создано по этому пути, потому что некоторые объекты уже существуют.
|
||||
addvaultwizard.new.invalidName=Неверное имя хранилища. Укажите корректное имя каталога.
|
||||
addvaultwizard.new.invalidName=Недопустимое название хранилища. Пожалуйста, выберите корректное название каталога.
|
||||
### Password
|
||||
addvaultwizard.new.createVaultBtn=Создать хранилище
|
||||
addvaultwizard.new.generateRecoveryKeyChoice=Вы не сможете получить доступ к своим данным без пароля. Хотите создать ключ для восстановления на случай потери пароля?
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.yes=Да, лучше предостеречься, чем потом жалеть
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.yes=Да, пожалуй, лучше так, чем потом сожалеть
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.no=Нет, спасибо, я не потеряю свой пароль
|
||||
### Information
|
||||
addvault.new.readme.storageLocation.fileName=ВАЖНО.rtf
|
||||
addvault.new.readme.storageLocation.1=⚠️ ФАЙЛЫ ХРАНИЛИЩА ⚠️
|
||||
addvault.new.readme.storageLocation.2=Это место, где находится ваше хранилище.
|
||||
addvault.new.readme.storageLocation.2=Это место расположения вашего хранилища.
|
||||
addvault.new.readme.storageLocation.3=НЕТ
|
||||
addvault.new.readme.storageLocation.4=• изменяйте любые файлы в этой папке или
|
||||
addvault.new.readme.storageLocation.5=• добавляйте в эту папку любые файлы для шифрования.
|
||||
addvault.new.readme.storageLocation.6=Чтобы зашифровать файлы и просмотреть содержимое хранилища, сделайте следующее:
|
||||
addvault.new.readme.storageLocation.6=Если вы хотите зашифровать файлы и просмотреть содержимое хранилища, сделайте следующее:
|
||||
addvault.new.readme.storageLocation.7=1. Добавьте это хранилище в Cryptomator.
|
||||
addvault.new.readme.storageLocation.8=2. Разблокируйте хранилище в Cryptomator.
|
||||
addvault.new.readme.storageLocation.9=3. Откройте место доступа, нажав кнопку "Показать".
|
||||
@@ -63,19 +63,19 @@ addvault.new.readme.storageLocation.10=Если вам нужна помощь,
|
||||
addvault.new.readme.accessLocation.fileName=ПРИВЕТСТВИЕ.rtf
|
||||
addvault.new.readme.accessLocation.1=🔐️ ЗАШИФРОВАННЫЙ ТОМ 🔐️
|
||||
addvault.new.readme.accessLocation.2=Это место доступа к вашему хранилищу.
|
||||
addvault.new.readme.accessLocation.3=Любые файлы, добавленные в этот том, будут зашифрованы Cryptomator. Вы можете работать с ним как с любым другим диском/папкой. Здесь отображается только расшифрованное содержимое тома, ваши файлы остаются зашифрованными на жёстком диске постоянно.
|
||||
addvault.new.readme.accessLocation.3=Любые файлы, добавленные в этот том, будут зашифрованы Cryptomator. Вы можете работать с ним, как с любым другим диском/папкой. Здесь отображается только расшифрованное содержимое тома, ваши файлы остаются зашифрованными на жестком диске постоянно.
|
||||
addvault.new.readme.accessLocation.4=Этот файл можно удалить.
|
||||
## Existing
|
||||
addvaultwizard.existing.instruction=Выберите файл "masterkey.cryptomator" из имеющегося хранилища.
|
||||
addvaultwizard.existing.instruction=Выберите файл "masterkey.cryptomator" от существующего хранилища.
|
||||
addvaultwizard.existing.chooseBtn=Выбрать…
|
||||
addvaultwizard.existing.filePickerTitle=Выберите файл мастер-ключа
|
||||
addvaultwizard.existing.filePickerTitle=Выберите файл MasterKey
|
||||
## Success
|
||||
addvaultwizard.success.nextStepsInstructions=Добавлено хранилище "%s".\nДля добавления данных или доступа к содержимому нужно разблокировать хранилище. Его можно разблокировать и позже.
|
||||
addvaultwizard.success.unlockNow=Разблокировать
|
||||
|
||||
# Remove Vault
|
||||
removeVault.title=Удалить хранилище
|
||||
removeVault.information=Cryptomator просто забудет это хранилище. Позже вы можете добавить его снова. Зашифрованные файлы не будут удалены с жёсткого диска.
|
||||
removeVault.information=Cryptomator просто забудет это хранилище. Вы можете добавить его снова позже. Зашифрованные файлы не будут удалены с жесткого диска.
|
||||
removeVault.confirmBtn=Удалить хранилище
|
||||
|
||||
# Change Password
|
||||
@@ -95,6 +95,7 @@ unlock.savePassword=Сохранить пароль
|
||||
unlock.unlockBtn=Разблокировать
|
||||
## Success
|
||||
unlock.success.message=Разблокировка "%s" успешно выполнена! Доступ в хранилище открыт.
|
||||
unlock.success.rememberChoice=Запомнить выбор и больше не спрашивать
|
||||
unlock.success.revealBtn=Показать хранилище
|
||||
## Invalid Mount Point
|
||||
unlock.error.invalidMountPoint=Точка монтирования - не пустая папка: %s
|
||||
@@ -102,18 +103,18 @@ unlock.error.invalidMountPoint=Точка монтирования - не пус
|
||||
# Migration
|
||||
migration.title=Обновить хранилище
|
||||
## Start
|
||||
migration.start.prompt=Хранилище "%s" нужно преобразовать в более новый формат. Прежде чем продолжить, убедитесь, что нет отложенной синхронизации, которая может повлиять на хранилище.
|
||||
migration.start.prompt=Ваше хранилище "%s" должно быть обновлено до более нового формата. Перед тем, как продолжить, убедитесь в отсутствии отложенной синхронизации, которая может повлиять на хранилище.
|
||||
migration.start.confirm=Да, моё хранилище полностью синхронизировано
|
||||
## Run
|
||||
migration.run.enterPassword=Введите пароль для "%s"
|
||||
migration.run.startMigrationBtn=Перенести хранилище
|
||||
migration.run.progressHint=Это может занять некоторое время…
|
||||
## Sucess
|
||||
migration.success.nextStepsInstructions=Перенос "%s" успешно выполнен.\nТеперь можно разблокировать хранилище.
|
||||
migration.success.nextStepsInstructions="%s" перенесено успешно.\nТеперь вы можете разблокировать хранилище.
|
||||
migration.success.unlockNow=Разблокировать
|
||||
## Missing file system capabilities
|
||||
migration.error.missingFileSystemCapabilities.title=Неподдерживаемая файловая система
|
||||
migration.error.missingFileSystemCapabilities.description=Миграция не была запущена, так как хранилище находится в ненадлежащей файловой системе.
|
||||
migration.error.missingFileSystemCapabilities.description=Миграция не была запущена, так как ваше хранилище расположено в неподходящей файловой системе.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_FILENAMES=Файловая система не поддерживает длинные имена файлов.
|
||||
migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=Файловая система не поддерживает длинные пути.
|
||||
migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=Файловая система не разрешает чтение.
|
||||
@@ -127,14 +128,15 @@ migration.impossible.moreInfo=Хранилище по-прежнему можн
|
||||
preferences.title=Настройки
|
||||
## General
|
||||
preferences.general=Общие
|
||||
preferences.general.theme=Оформление
|
||||
preferences.general.theme=Внешний вид
|
||||
preferences.general.theme.light=Светлая
|
||||
preferences.general.theme.dark=Темная
|
||||
preferences.general.unlockThemes=Разблокировать тёмный режим
|
||||
preferences.general.theme.dark=Тёмная
|
||||
preferences.general.unlockThemes=Разблокировать темный режим
|
||||
preferences.general.startHidden=Скрывать окно при запуске Cryptomator
|
||||
preferences.general.debugLogging=Вести журнал отладки
|
||||
preferences.general.debugLogging=Включить ведение журнала отладки
|
||||
preferences.general.debugDirectory=Показать файлы журнала
|
||||
preferences.general.autoStart=Запускать Cryptomator при старте системы
|
||||
preferences.general.interfaceOrientation=Интерфейс
|
||||
preferences.general.interfaceOrientation=Ориентация интерфейса
|
||||
preferences.general.interfaceOrientation.ltr=Слева направо
|
||||
preferences.general.interfaceOrientation.rtl=Справа налево
|
||||
## Volume
|
||||
@@ -146,7 +148,7 @@ preferences.volume.webdav.scheme=Схема WebDAV
|
||||
preferences.updates=Обновления
|
||||
preferences.updates.currentVersion=Текущая версия: %s
|
||||
preferences.updates.autoUpdateCheck=Автоматически проверять наличие обновлений
|
||||
preferences.updates.checkNowBtn=Проверить
|
||||
preferences.updates.checkNowBtn=Проверить сейчас
|
||||
preferences.updates.updateAvailable=Доступно обновление до версии %s.
|
||||
## Donation Key
|
||||
preferences.donationKey=Пожертвование
|
||||
@@ -165,19 +167,19 @@ main.donationKeyMissing.tooltip=Мы будем рады финансовой п
|
||||
main.dropZone.dropVault=Добавить это хранилище
|
||||
main.dropZone.unknownDragboardContent=Если вы хотите добавить хранилище, перетащите его в это окно
|
||||
## Vault List
|
||||
main.vaultlist.emptyList.onboardingInstruction=Нажмите здесь, чтобы добавить хранилище
|
||||
main.vaultlist.emptyList.onboardingInstruction=Нажмите здесь для добавления хранилища
|
||||
main.vaultlist.contextMenu.remove=Удалить хранилище
|
||||
main.vaultlist.addVaultBtn=Добавить хранилище
|
||||
## Vault Detail
|
||||
### Welcome
|
||||
main.vaultDetail.welcomeOnboarding=Благодарим за выбор Cryptomator для защиты ваших файлов. Если требуется помощь, ознакомьтесь с документацией по началу работы:
|
||||
main.vaultDetail.welcomeOnboarding=Спасибо, что выбрали Cryptomator для защиты ваших файлов. Если вам нужна помощь, ознакомьтесь с нашими руководствами по началу работы:
|
||||
### Locked
|
||||
main.vaultDetail.lockedStatus=ЗАБЛОКИРОВАНО
|
||||
main.vaultDetail.unlockBtn=Разблокировать
|
||||
main.vaultDetail.optionsBtn=Параметры хранилища
|
||||
### Unlocked
|
||||
main.vaultDetail.unlockedStatus=РАЗБЛОКИРОВАНО
|
||||
main.vaultDetail.accessLocation=Содержимое хранилища доступно здесь:
|
||||
main.vaultDetail.accessLocation=Содержимое вашего хранилища доступно здесь:
|
||||
main.vaultDetail.revealBtn=Показать диск
|
||||
main.vaultDetail.lockBtn=Заблокировать
|
||||
main.vaultDetail.bytesPerSecondRead=чтение:
|
||||
@@ -194,9 +196,9 @@ main.vaultDetail.migratePrompt=Чтобы получить доступ к хр
|
||||
# Wrong File Alert
|
||||
wrongFileAlert.title=Как шифровать файлы
|
||||
wrongFileAlert.header.title=Вы пытались зашифровать эти файлы?
|
||||
wrongFileAlert.header.lead=Для этого Cryptomator создаёт том в системном диспетчере файлов.
|
||||
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=Если нужна помощь, посетите
|
||||
@@ -205,31 +207,35 @@ wrongFileAlert.link=Если нужна помощь, посетите
|
||||
## General
|
||||
vaultOptions.general=Общие
|
||||
vaultOptions.general.unlockAfterStartup=Разблокировать хранилище при запуске Cryptomator
|
||||
vaultOptions.general.actionAfterUnlock=После успешной разблокировки
|
||||
vaultOptions.general.actionAfterUnlock.ignore=Ничего не делать
|
||||
vaultOptions.general.actionAfterUnlock.reveal=Показать диск
|
||||
vaultOptions.general.actionAfterUnlock.ask=Спрашивать
|
||||
## Mount
|
||||
vaultOptions.mount=Монтирование
|
||||
vaultOptions.mount.readonly=Только чтение
|
||||
vaultOptions.mount.driveName=Имя диска
|
||||
vaultOptions.mount.customMountFlags=Свои флаги монтирования
|
||||
vaultOptions.mount.customMountFlags=Пользовательские флаги монтирования
|
||||
vaultOptions.mount.winDriveLetterOccupied=занято
|
||||
vaultOptions.mount.mountPoint=Точка монтирования
|
||||
vaultOptions.mount.mountPoint.auto=Автоматически выбирать подходящую
|
||||
vaultOptions.mount.mountPoint.driveLetter=Использовать назначенную букву диска
|
||||
vaultOptions.mount.mountPoint.custom=Свой путь
|
||||
vaultOptions.mount.mountPoint.directoryPickerButton=Выбрать…
|
||||
vaultOptions.mount.mountPoint.directoryPickerTitle=Выберите пустую папку
|
||||
vaultOptions.mount.mountPoint.directoryPickerTitle=Выберите пустой каталог
|
||||
## Master Key
|
||||
vaultOptions.masterkey=Пароль
|
||||
vaultOptions.masterkey.changePasswordBtn=Изменить пароль
|
||||
vaultOptions.masterkey.recoveryKeyExpanation=Ключ восстановления - это единственный способ восстановить доступ к хранилищу при утере пароля.
|
||||
vaultOptions.masterkey.recoveryKeyExpanation=Ключ восстановления - это единственный способ восстановить доступ к хранилищу в случае утери пароля.
|
||||
vaultOptions.masterkey.showRecoveryKeyBtn=Показать ключ восстановления
|
||||
vaultOptions.masterkey.recoverPasswordBtn=Восстановить пароль
|
||||
|
||||
# Recovery Key
|
||||
recoveryKey.title=Ключ восстановления
|
||||
recoveryKey.enterPassword.prompt=Введите пароль, чтобы показать ключ для "%s":
|
||||
recoveryKey.enterPassword.prompt=Введите пароль для отображения ключа восстановления для "%s":
|
||||
recoveryKey.display.message=Следующий ключ восстановления можно использовать для восстановления доступа к "%s":
|
||||
recoveryKey.display.StorageHints=Храните его в надёжном месте, например:\n • в диспетчере паролей\n • на флеш-накопителе USB\n • распечатанным на бумаге
|
||||
recoveryKey.recover.prompt=Введите ключ восстановления для "%s":
|
||||
recoveryKey.display.StorageHints=Храните его в надежном месте, например:\n • в менеджере паролей\n • на флэш-накопителе USB\n • распечатайте на бумаге
|
||||
recoveryKey.recover.prompt=Введите ваш ключ восстановления для "%s":
|
||||
recoveryKey.recover.validKey=Это действительный ключ восстановления
|
||||
recoveryKey.printout.heading=Ключ восстановления Cryptomator\n"%s"\n
|
||||
|
||||
@@ -246,5 +252,5 @@ passwordStrength.messageLabel.3=Сильный
|
||||
passwordStrength.messageLabel.4=Очень сильный
|
||||
|
||||
# Quit
|
||||
quit.prompt=Выйти из приложения? Есть незаблокированные хранилища.
|
||||
quit.prompt=Выйти из приложения? Есть разблокированные хранилища.
|
||||
quit.lockAndQuit=Заблокировать и выйти
|
||||
|
||||
@@ -95,6 +95,7 @@ unlock.savePassword=Spara lösenord
|
||||
unlock.unlockBtn=Lås upp
|
||||
## Success
|
||||
unlock.success.message="%s" upplåst! Ditt valv är nu åtkomligt.
|
||||
unlock.success.rememberChoice=Kom ihåg valet, visa inte detta igen
|
||||
unlock.success.revealBtn=Visa valv
|
||||
## Invalid Mount Point
|
||||
unlock.error.invalidMountPoint=Monteringspunkten är inte en tom katalog: %s
|
||||
@@ -128,6 +129,8 @@ preferences.title=Inställningar
|
||||
## General
|
||||
preferences.general=Allmänt
|
||||
preferences.general.theme=Utseende
|
||||
preferences.general.theme.light=Ljust
|
||||
preferences.general.theme.dark=Mörkt
|
||||
preferences.general.unlockThemes=Lås upp mörkt läge
|
||||
preferences.general.startHidden=Dölj fönster när Cryptomator startar
|
||||
preferences.general.debugLogging=Aktivera loggning för felsökning
|
||||
@@ -203,6 +206,10 @@ wrongFileAlert.link=För ytterligare hjälp, besök
|
||||
## General
|
||||
vaultOptions.general=Allmänt
|
||||
vaultOptions.general.unlockAfterStartup=Lås upp valvet när Cryptomator startas
|
||||
vaultOptions.general.actionAfterUnlock=Efter lyckad upplåsning
|
||||
vaultOptions.general.actionAfterUnlock.ignore=Gör ingenting
|
||||
vaultOptions.general.actionAfterUnlock.reveal=Visa enhet
|
||||
vaultOptions.general.actionAfterUnlock.ask=Fråga
|
||||
## Mount
|
||||
vaultOptions.mount=Montering
|
||||
vaultOptions.mount.readonly=Skrivskyddad
|
||||
|
||||
@@ -95,6 +95,7 @@ unlock.savePassword=Şifreyi Kaydet
|
||||
unlock.unlockBtn=Kilidi Aç
|
||||
## Success
|
||||
unlock.success.message="%s" 'nin kilidi başarıyla açıldı! Kasanız şimdi erişilebilir durumda.
|
||||
unlock.success.rememberChoice=Seçimi hatırla, bunu bir daha gösterme
|
||||
unlock.success.revealBtn=Kasayı Göster
|
||||
## Invalid Mount Point
|
||||
unlock.error.invalidMountPoint=Bağlantı noktası boş bir dizin değil: %s
|
||||
@@ -205,6 +206,10 @@ wrongFileAlert.link=Daha fazla yardım için, ziyaret edin:
|
||||
## General
|
||||
vaultOptions.general=Genel
|
||||
vaultOptions.general.unlockAfterStartup=Cryptomator başlatıldığında kasayı aç
|
||||
vaultOptions.general.actionAfterUnlock=Başarılı kilit açmanın ardından
|
||||
vaultOptions.general.actionAfterUnlock.ignore=Hiçbir şey yapma
|
||||
vaultOptions.general.actionAfterUnlock.reveal=Sürücüyü Göster
|
||||
vaultOptions.general.actionAfterUnlock.ask=Sor
|
||||
## Mount
|
||||
vaultOptions.mount=Bağlantı
|
||||
vaultOptions.mount.readonly=Salt-Okunur
|
||||
|
||||
@@ -95,6 +95,7 @@ unlock.savePassword=保存密码
|
||||
unlock.unlockBtn=解锁
|
||||
## Success
|
||||
unlock.success.message=已成功解锁 "%s" !您的保险库现在可以访问。
|
||||
unlock.success.rememberChoice=记住该选择,不要再显示
|
||||
unlock.success.revealBtn=显示保险库
|
||||
## Invalid Mount Point
|
||||
unlock.error.invalidMountPoint=挂载点不是一个空目录: %s
|
||||
@@ -107,6 +108,7 @@ migration.start.confirm=是的,我的保险库已完全同步
|
||||
## Run
|
||||
migration.run.enterPassword=输入 "%s" 的密码
|
||||
migration.run.startMigrationBtn=迁移保险库
|
||||
migration.run.progressHint=这可能需要一些时间…
|
||||
## Sucess
|
||||
migration.success.nextStepsInstructions=已成功迁移 "%s"\n您现在可以解锁您的保险库。
|
||||
migration.success.unlockNow=立即解锁
|
||||
@@ -118,12 +120,17 @@ migration.error.missingFileSystemCapabilities.reason.LONG_PATHS=此文件系统
|
||||
migration.error.missingFileSystemCapabilities.reason.READ_ACCESS=文件系统不允许读取访问
|
||||
migration.error.missingFileSystemCapabilities.reason.WRITE_ACCESS=文件系统不允许写入
|
||||
## Impossible
|
||||
migration.impossible.heading=无法迁移保险库
|
||||
migration.impossible.reason=此保险库不能自动迁移,因为它的存储位置或接入点不兼容
|
||||
migration.impossible.moreInfo=此保险库仍可使用旧版本来打开。关于如何手动迁移保险库的操作指南,请访问
|
||||
|
||||
# Preferences
|
||||
preferences.title=首选项
|
||||
## General
|
||||
preferences.general=常规
|
||||
preferences.general.theme=界面外观
|
||||
preferences.general.theme.light=亮色
|
||||
preferences.general.theme.dark=暗色
|
||||
preferences.general.unlockThemes=解锁暗黑模式
|
||||
preferences.general.startHidden=最小化启动 Cryptomator 到系统托盘
|
||||
preferences.general.debugLogging=启用调试日志
|
||||
@@ -199,6 +206,10 @@ wrongFileAlert.link=如需进一步帮助,请访问
|
||||
## General
|
||||
vaultOptions.general=常规
|
||||
vaultOptions.general.unlockAfterStartup=启动 Cryptomator 时解锁保险库 (此功能需先勾选保存密码)
|
||||
vaultOptions.general.actionAfterUnlock=解锁成功后的操作
|
||||
vaultOptions.general.actionAfterUnlock.ignore=什么也不做
|
||||
vaultOptions.general.actionAfterUnlock.reveal=显示驱动器
|
||||
vaultOptions.general.actionAfterUnlock.ask=询问
|
||||
## Mount
|
||||
vaultOptions.mount=挂载
|
||||
vaultOptions.mount.readonly=只读
|
||||
|
||||
@@ -95,6 +95,7 @@ unlock.savePassword=儲存密碼
|
||||
unlock.unlockBtn=解鎖
|
||||
## Success
|
||||
unlock.success.message=成功解鎖 "%s"!您現在可以存取您的加密檔案庫。
|
||||
unlock.success.rememberChoice=記得這個決定,不要再顯示
|
||||
unlock.success.revealBtn=顯示加密檔案庫
|
||||
## Invalid Mount Point
|
||||
unlock.error.invalidMountPoint=掛載點不是空資料夾:%s
|
||||
@@ -128,6 +129,8 @@ preferences.title=偏好
|
||||
## General
|
||||
preferences.general=一般
|
||||
preferences.general.theme=外觀
|
||||
preferences.general.theme.light=亮色
|
||||
preferences.general.theme.dark=暗色
|
||||
preferences.general.unlockThemes=解鎖暗色模式
|
||||
preferences.general.startHidden=啟動 Cryptomator 時隱藏視窗
|
||||
preferences.general.debugLogging=啟用除錯日誌
|
||||
@@ -203,6 +206,10 @@ wrongFileAlert.link=如需進一步協助協助,請參照
|
||||
## General
|
||||
vaultOptions.general=一般
|
||||
vaultOptions.general.unlockAfterStartup=啟動 Cryptomator 時解鎖加密檔案庫
|
||||
vaultOptions.general.actionAfterUnlock=成功解鎖後
|
||||
vaultOptions.general.actionAfterUnlock.ignore=無動作
|
||||
vaultOptions.general.actionAfterUnlock.reveal=顯示磁碟
|
||||
vaultOptions.general.actionAfterUnlock.ask=詢問
|
||||
## Mount
|
||||
vaultOptions.mount=掛載
|
||||
vaultOptions.mount.readonly=唯讀
|
||||
|
||||
@@ -11,13 +11,15 @@ 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 50 third-party dependencies under the following licenses:
|
||||
Cryptomator uses 52 third-party dependencies under the following licenses:
|
||||
Apache License v2.0:
|
||||
- HKDF-RFC5869 (at.favre.lib:hkdf:1.0.2 - https://github.com/patrickfav/hkdf)
|
||||
- HKDF-RFC5869 (at.favre.lib:hkdf:1.1.0 - https://github.com/patrickfav/hkdf)
|
||||
- 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)
|
||||
- jnr-constants (com.github.jnr:jnr-constants:0.9.15 - http://github.com/jnr/jnr-constants)
|
||||
- jnr-enxio (com.github.jnr:jnr-enxio:0.21 - http://github.com/jnr/jnr-enxio)
|
||||
- jnr-ffi (com.github.jnr:jnr-ffi:2.1.12 - http://github.com/jnr/jnr-ffi)
|
||||
- jnr-unixsocket (com.github.jnr:jnr-unixsocket:0.23 - http://github.com/jnr/jnr-unixsocket)
|
||||
- FindBugs-jsr305 (com.google.code.findbugs:jsr305:3.0.2 - http://findbugs.sourceforge.net/)
|
||||
- Gson (com.google.code.gson:gson:2.8.6 - https://github.com/google/gson/gson)
|
||||
- Dagger (com.google.dagger:dagger:2.27 - https://github.com/google/dagger)
|
||||
@@ -68,17 +70,17 @@ Cryptomator uses 50 third-party dependencies under the following licenses:
|
||||
- javafx-fxml (org.openjfx:javafx-fxml:14 - https://openjdk.java.net/projects/openjfx/javafx-fxml/)
|
||||
- javafx-graphics (org.openjfx:javafx-graphics:14 - https://openjdk.java.net/projects/openjfx/javafx-graphics/)
|
||||
LGPL 2.1:
|
||||
- dbus-java (com.github.hypfvieh:dbus-java:3.0.2 - https://github.com/hypfvieh/dbus-java/dbus-java)
|
||||
- dbus-java (com.github.hypfvieh:dbus-java:3.2.1 - https://github.com/hypfvieh/dbus-java/dbus-java)
|
||||
- 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.1.0 - https://github.com/java-native-access/jna)
|
||||
- Java Native Access Platform (net.java.dev.jna:jna-platform:5.1.0 - https://github.com/java-native-access/jna)
|
||||
MIT License:
|
||||
- java jwt (com.auth0:java-jwt:3.10.2 - https://github.com/auth0/java-jwt)
|
||||
- java-utils (com.github.hypfvieh:java-utils:1.0.5 - https://github.com/hypfvieh/java-utils)
|
||||
- java-utils (com.github.hypfvieh:java-utils:1.0.6 - https://github.com/hypfvieh/java-utils)
|
||||
- jnr-x86asm (com.github.jnr:jnr-x86asm:1.0.2 - http://github.com/jnr/jnr-x86asm)
|
||||
- jnr-fuse (com.github.serceman:jnr-fuse:0.5.4 - no url defined)
|
||||
- zxcvbn4j (com.nulab-inc:zxcvbn:1.3.0 - https://github.com/nulab/zxcvbn4j)
|
||||
- secret-service (de.swiesend:secret-service:1.0.0-RC.3 - https://github.com/swiesend/secret-service)
|
||||
- secret-service (de.swiesend:secret-service:1.0.0 - https://github.com/swiesend/secret-service)
|
||||
- Checker Qual (org.checkerframework:checker-qual:2.10.0 - https://checkerframework.org)
|
||||
- SLF4J API Module (org.slf4j:slf4j-api:1.7.30 - http://www.slf4j.org)
|
||||
The BSD 2-Clause License:
|
||||
|
||||
@@ -152,7 +152,7 @@ class SecurePasswordFieldTest {
|
||||
|
||||
CharSequence result1 = pwField.getCharacters();
|
||||
Assertions.assertEquals("topSecret", result1.toString());
|
||||
pwField.swipe();
|
||||
pwField.wipe();
|
||||
CharSequence result2 = pwField.getCharacters();
|
||||
Assertions.assertEquals(" ", result1.toString());
|
||||
Assertions.assertEquals("", result2.toString());
|
||||
|
||||
Reference in New Issue
Block a user