mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-09-22 16:04:28 +00:00
Compare commits
68
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6e680e32d | ||
|
|
d60a024c63 | ||
|
|
cc1b536656 | ||
|
|
85e773abef | ||
|
|
4690bc56aa | ||
|
|
292c2676fd | ||
|
|
f7c6604c7c | ||
|
|
22310eb957 | ||
|
|
6d17d1298b | ||
|
|
e90a680620 | ||
|
|
35d0ccfe89 | ||
|
|
62cc454367 | ||
|
|
8bb277fe56 | ||
|
|
67485b76af | ||
|
|
270e60d7cf | ||
|
|
11ffec862e | ||
|
|
d37e0dd5e1 | ||
|
|
960c3665e0 | ||
|
|
3396c79c38 | ||
|
|
cc1517f1b8 | ||
|
|
4f3ff55ae7 | ||
|
|
f51362e95e | ||
|
|
415dfece58 | ||
|
|
3bdb5353da | ||
|
|
6ddf1b59ef | ||
|
|
ef88f11c4f | ||
|
|
ec800c5439 | ||
|
|
a2f3f5d254 | ||
|
|
f475f70adf | ||
|
|
93c3da66da | ||
|
|
9b6145ce3f | ||
|
|
148eed172a | ||
|
|
c97f146964 | ||
|
|
a1034f5663 | ||
|
|
938b351f33 | ||
|
|
d870ecbcdb | ||
|
|
5dff2126c4 | ||
|
|
41a6cc15e8 | ||
|
|
f21b30c009 | ||
|
|
d69b63acc3 | ||
|
|
f95b2baad5 | ||
|
|
1a4d1fffb3 | ||
|
|
ac536ba125 | ||
|
|
362b225d66 | ||
|
|
7d0bdc1a63 | ||
|
|
94e3b21e44 | ||
|
|
23f89c1dc9 | ||
|
|
ce9c2f2c7a | ||
|
|
44e97ab046 | ||
|
|
5d0a1fd49f | ||
|
|
ffc1d8dc1e | ||
|
|
54fca93bba | ||
|
|
c281687910 | ||
|
|
6229d7abbe | ||
|
|
01644eddb6 | ||
|
|
6e30df3796 | ||
|
|
f87fa319ff | ||
|
|
9b019726bb | ||
|
|
cceafb76ed | ||
|
|
39e2994c69 | ||
|
|
1ddfcc3219 | ||
|
|
687f11596e | ||
|
|
b445f614fb | ||
|
|
c6fc1d93a0 | ||
|
|
28d58922e3 | ||
|
|
b884ea7ddc | ||
|
|
1717e20b61 | ||
|
|
6222f96b01 |
+3
-1
@@ -1,12 +1,14 @@
|
|||||||
# Number of days of inactivity before an issue becomes stale
|
# Number of days of inactivity before an issue becomes stale
|
||||||
daysUntilStale: 90
|
daysUntilStale: 180
|
||||||
# Number of days of inactivity before a stale issue is closed
|
# Number of days of inactivity before a stale issue is closed
|
||||||
daysUntilClose: 30
|
daysUntilClose: 30
|
||||||
# Issues with these labels will never be considered stale
|
# Issues with these labels will never be considered stale
|
||||||
exemptLabels:
|
exemptLabels:
|
||||||
- type:security-issue # never close automatically
|
- type:security-issue # never close automatically
|
||||||
|
- type:feature-request # never close automatically
|
||||||
- state:awaiting-response # handled by different bot
|
- state:awaiting-response # handled by different bot
|
||||||
- state:blocked
|
- state:blocked
|
||||||
|
- state:confirmed
|
||||||
# Set to true to ignore issues in a milestone (defaults to false)
|
# Set to true to ignore issues in a milestone (defaults to false)
|
||||||
exemptMilestones: true
|
exemptMilestones: true
|
||||||
# Label to use when marking an issue as stale
|
# Label to use when marking an issue as stale
|
||||||
|
|||||||
Generated
+2
-2
@@ -31,10 +31,10 @@
|
|||||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar" />
|
||||||
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-metadata-jvm/0.1.0/kotlinx-metadata-jvm-0.1.0.jar" />
|
<entry name="$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-metadata-jvm/0.1.0/kotlinx-metadata-jvm-0.1.0.jar" />
|
||||||
</processorPath>
|
</processorPath>
|
||||||
<module name="commons" />
|
|
||||||
<module name="keychain" />
|
<module name="keychain" />
|
||||||
<module name="ui" />
|
|
||||||
<module name="launcher" />
|
<module name="launcher" />
|
||||||
|
<module name="commons" />
|
||||||
|
<module name="ui" />
|
||||||
</profile>
|
</profile>
|
||||||
</annotationProcessing>
|
</annotationProcessing>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
Generated
+8
-6
@@ -1,11 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="Encoding" addBOMForNewFiles="with NO BOM">
|
<component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
|
||||||
<file url="file://$PROJECT_DIR$/main" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/main" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/main/buildkit" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/main/commons/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/main/commons" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/main/keychain/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/main/keychain" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/main/keychain/src/main/resources" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/main/launcher" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/main/launcher/src/main/java" charset="UTF-8" />
|
||||||
<file url="file://$PROJECT_DIR$/main/ui" charset="UTF-8" />
|
<file url="file://$PROJECT_DIR$/main/ui/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/main/ui/src/main/resources" charset="UTF-8" />
|
||||||
|
<file url="PROJECT" charset="UTF-8" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.5.0-beta1</version>
|
<version>1.5.0-beta2</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>buildkit</artifactId>
|
<artifactId>buildkit</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.5.0-beta1</version>
|
<version>1.5.0-beta2</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>commons</artifactId>
|
<artifactId>commons</artifactId>
|
||||||
<name>Cryptomator Commons</name>
|
<name>Cryptomator Commons</name>
|
||||||
@@ -48,6 +48,12 @@
|
|||||||
<artifactId>easybind</artifactId>
|
<artifactId>easybind</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- JWT -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.auth0</groupId>
|
||||||
|
<artifactId>java-jwt</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Google -->
|
<!-- Google -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.guava</groupId>
|
<groupId>com.google.guava</groupId>
|
||||||
|
|||||||
@@ -28,13 +28,23 @@ import java.util.concurrent.ExecutorService;
|
|||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
@Module(subcomponents = {VaultComponent.class})
|
@Module(subcomponents = {VaultComponent.class})
|
||||||
public abstract class CommonsModule {
|
public abstract class CommonsModule {
|
||||||
|
|
||||||
private static final int NUM_SCHEDULER_THREADS = 4;
|
private static final int NUM_SCHEDULER_THREADS = 4;
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@Named("licensePublicKey")
|
||||||
|
static String provideLicensePublicKey() {
|
||||||
|
// in PEM format without the dash-escaped begin/end lines
|
||||||
|
return "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQB7NfnqiZbg2KTmoflmZ71PbXru7oW" //
|
||||||
|
+ "fmnV2yv3eDjlDfGruBrqz9TtXBZV/eYWt31xu1osIqaT12lKBvZ511aaAkIBeOEV" //
|
||||||
|
+ "gwcBIlJr6kUw7NKzeJt7r2rrsOyQoOG2nWc/Of/NBqA3mIZRHk5Aq1YupFdD26QE" //
|
||||||
|
+ "r0DzRyj4ixPIt38CQB8=";
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
@Named("SemVer")
|
@Named("SemVer")
|
||||||
@@ -56,7 +66,7 @@ public abstract class CommonsModule {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
static ScheduledExecutorService provideScheduledExecutorService(@Named("shutdownTaskScheduler") Consumer<Runnable> shutdownTaskScheduler) {
|
static ScheduledExecutorService provideScheduledExecutorService(ShutdownHook shutdownHook) {
|
||||||
final AtomicInteger threadNumber = new AtomicInteger(1);
|
final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||||
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(NUM_SCHEDULER_THREADS, r -> {
|
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(NUM_SCHEDULER_THREADS, r -> {
|
||||||
Thread t = new Thread(r);
|
Thread t = new Thread(r);
|
||||||
@@ -64,7 +74,7 @@ public abstract class CommonsModule {
|
|||||||
t.setDaemon(true);
|
t.setDaemon(true);
|
||||||
return t;
|
return t;
|
||||||
});
|
});
|
||||||
shutdownTaskScheduler.accept(executorService::shutdown);
|
shutdownHook.runOnShutdown(executorService::shutdown);
|
||||||
return executorService;
|
return executorService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ public class Environment {
|
|||||||
private static final Path RELATIVE_HOME_DIR = Paths.get("~");
|
private static final Path RELATIVE_HOME_DIR = Paths.get("~");
|
||||||
private static final Path ABSOLUTE_HOME_DIR = Paths.get(USER_HOME);
|
private static final Path ABSOLUTE_HOME_DIR = Paths.get(USER_HOME);
|
||||||
private static final char PATH_LIST_SEP = ':';
|
private static final char PATH_LIST_SEP = ':';
|
||||||
|
private static final int DEFAULT_MIN_PW_LENGTH = 8;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public Environment() {
|
public Environment() {
|
||||||
@@ -37,6 +38,7 @@ public class Environment {
|
|||||||
LOG.debug("cryptomator.keychainPath: {}", System.getProperty("cryptomator.keychainPath"));
|
LOG.debug("cryptomator.keychainPath: {}", System.getProperty("cryptomator.keychainPath"));
|
||||||
LOG.debug("cryptomator.logDir: {}", System.getProperty("cryptomator.logDir"));
|
LOG.debug("cryptomator.logDir: {}", System.getProperty("cryptomator.logDir"));
|
||||||
LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir"));
|
LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir"));
|
||||||
|
LOG.debug("cryptomator.minPwLength: {}", System.getProperty("cryptomator.minPwLength"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean useCustomLogbackConfig() {
|
public boolean useCustomLogbackConfig() {
|
||||||
@@ -63,6 +65,19 @@ public class Environment {
|
|||||||
return getPath("cryptomator.mountPointsDir").map(this::replaceHomeDir);
|
return getPath("cryptomator.mountPointsDir").map(this::replaceHomeDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getMinPwLength() {
|
||||||
|
return getInt("cryptomator.minPwLength", DEFAULT_MIN_PW_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getInt(String propertyName, int defaultValue) {
|
||||||
|
String value = System.getProperty(propertyName);
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(value);
|
||||||
|
} catch (NumberFormatException e) { // includes "null" values
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Optional<Path> getPath(String propertyName) {
|
private Optional<Path> getPath(String propertyName) {
|
||||||
String value = System.getProperty(propertyName);
|
String value = System.getProperty(propertyName);
|
||||||
return Optional.ofNullable(value).map(Paths::get);
|
return Optional.ofNullable(value).map(Paths::get);
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package org.cryptomator.common;
|
||||||
|
|
||||||
|
import com.auth0.jwt.JWT;
|
||||||
|
import com.auth0.jwt.algorithms.Algorithm;
|
||||||
|
import com.auth0.jwt.exceptions.JWTVerificationException;
|
||||||
|
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||||
|
import com.auth0.jwt.interfaces.JWTVerifier;
|
||||||
|
import com.google.common.io.BaseEncoding;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.interfaces.ECPublicKey;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class LicenseChecker {
|
||||||
|
|
||||||
|
private final JWTVerifier verifier;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public LicenseChecker(@Named("licensePublicKey") String pemEncodedPublicKey) {
|
||||||
|
Algorithm algorithm = Algorithm.ECDSA512(decodePublicKey(pemEncodedPublicKey), null);
|
||||||
|
this.verifier = JWT.require(algorithm).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ECPublicKey decodePublicKey(String pemEncodedPublicKey) {
|
||||||
|
try {
|
||||||
|
byte[] keyBytes = BaseEncoding.base64().decode(pemEncodedPublicKey);
|
||||||
|
PublicKey key = KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(keyBytes));
|
||||||
|
if (key instanceof ECPublicKey) {
|
||||||
|
return (ECPublicKey) key;
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Key not an EC public key.");
|
||||||
|
}
|
||||||
|
} catch (InvalidKeySpecException e) {
|
||||||
|
throw new IllegalArgumentException("Invalid license public key", e);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<DecodedJWT> check(String licenseKey) {
|
||||||
|
try {
|
||||||
|
return Optional.of(verifier.verify(licenseKey));
|
||||||
|
} catch (JWTVerificationException exception) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package org.cryptomator.common;
|
||||||
|
|
||||||
|
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.binding.BooleanBinding;
|
||||||
|
import javafx.beans.binding.StringBinding;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import org.cryptomator.common.settings.Settings;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class LicenseHolder {
|
||||||
|
|
||||||
|
private final Settings settings;
|
||||||
|
private final LicenseChecker licenseChecker;
|
||||||
|
private final ObjectProperty<DecodedJWT> validJwtClaims;
|
||||||
|
private final StringBinding licenseSubject;
|
||||||
|
private final BooleanBinding validLicenseProperty;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public LicenseHolder(LicenseChecker licenseChecker, Settings settings) {
|
||||||
|
this.settings = settings;
|
||||||
|
this.licenseChecker = licenseChecker;
|
||||||
|
this.validJwtClaims = new SimpleObjectProperty<>();
|
||||||
|
this.licenseSubject = Bindings.createStringBinding(this::getLicenseSubject, validJwtClaims);
|
||||||
|
this.validLicenseProperty = validJwtClaims.isNotNull();
|
||||||
|
|
||||||
|
Optional<DecodedJWT> claims = licenseChecker.check(settings.licenseKey().get());
|
||||||
|
validJwtClaims.set(claims.orElse(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean validateAndStoreLicense(String licenseKey) {
|
||||||
|
Optional<DecodedJWT> claims = licenseChecker.check(licenseKey);
|
||||||
|
validJwtClaims.set(claims.orElse(null));
|
||||||
|
if (claims.isPresent()) {
|
||||||
|
settings.licenseKey().set(licenseKey);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Observable Properties */
|
||||||
|
|
||||||
|
public Optional<String> getLicenseKey() {
|
||||||
|
DecodedJWT claims = validJwtClaims.get();
|
||||||
|
if (claims != null) {
|
||||||
|
return Optional.of(claims.getToken());
|
||||||
|
} else {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringBinding licenseSubjectProperty() {
|
||||||
|
return licenseSubject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLicenseSubject() {
|
||||||
|
DecodedJWT claims = validJwtClaims.get();
|
||||||
|
if (claims != null) {
|
||||||
|
return claims.getSubject();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanBinding validLicenseProperty() {
|
||||||
|
return validLicenseProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValidLicense() {
|
||||||
|
return validLicenseProperty.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.cryptomator.common;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
public final class Optionals {
|
|
||||||
|
|
||||||
private Optionals() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a function that is equivalent to the input function but immediately gets the value of the returned optional when invoked.
|
|
||||||
*
|
|
||||||
* @param <T> the type of the input to the function
|
|
||||||
* @param <R> the type of the result of the function
|
|
||||||
* @param function An {@code Optional}-bearing input function {@code Function<Foo, Optional<Bar>>}
|
|
||||||
* @return A {@code Function<Foo, Bar>}, that may throw a NoSuchElementException, if the original function returns an empty optional.
|
|
||||||
*/
|
|
||||||
public static <T, R> Function<T, R> unwrap(Function<T, Optional<R>> function) {
|
|
||||||
return t -> function.apply(t).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the accompanying LICENSE file.
|
||||||
|
*******************************************************************************/
|
||||||
|
package org.cryptomator.common;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.Runnables;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.PriorityBlockingQueue;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class ShutdownHook extends Thread {
|
||||||
|
|
||||||
|
private static final int PRIO_VERY_LAST = Integer.MIN_VALUE;
|
||||||
|
public static final int PRIO_LAST = PRIO_VERY_LAST + 1;
|
||||||
|
public static final int PRIO_DEFAULT = 0;
|
||||||
|
public static final int PRIO_FIRST = Integer.MAX_VALUE;
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(ShutdownHook.class);
|
||||||
|
private static final OrderedTask POISON = new OrderedTask(PRIO_VERY_LAST, Runnables.doNothing());
|
||||||
|
private final Queue<OrderedTask> tasks = new PriorityBlockingQueue<>();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ShutdownHook() {
|
||||||
|
super(null, null, "ShutdownTasks", 0);
|
||||||
|
Runtime.getRuntime().addShutdownHook(this);
|
||||||
|
LOG.debug("Registered shutdown hook.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
LOG.debug("Running graceful shutdown tasks...");
|
||||||
|
tasks.add(POISON);
|
||||||
|
Runnable task;
|
||||||
|
while ((task = tasks.remove()) != POISON) {
|
||||||
|
try {
|
||||||
|
task.run();
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
LOG.error("Exception while shutting down.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedules a task to be run during shutdown with default order
|
||||||
|
*
|
||||||
|
* @param task The task to be scheduled
|
||||||
|
*/
|
||||||
|
public void runOnShutdown(Runnable task) {
|
||||||
|
runOnShutdown(PRIO_DEFAULT, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedules a task to be run with the given priority
|
||||||
|
*
|
||||||
|
* @param priority Tasks with high priority will be run before task with lower priority
|
||||||
|
* @param task The task to be scheduled
|
||||||
|
*/
|
||||||
|
public void runOnShutdown(int priority, Runnable task) {
|
||||||
|
tasks.add(new OrderedTask(priority, task));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class OrderedTask implements Comparable<OrderedTask>, Runnable {
|
||||||
|
|
||||||
|
private final int priority;
|
||||||
|
private final Runnable task;
|
||||||
|
|
||||||
|
public OrderedTask(int priority, Runnable task) {
|
||||||
|
this.priority = priority;
|
||||||
|
this.task = task;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(OrderedTask other) {
|
||||||
|
// overflow-safe signum impl:
|
||||||
|
if (this.priority > other.priority) {
|
||||||
|
return -1; // higher prio -> this before other
|
||||||
|
} else if (this.priority < other.priority) {
|
||||||
|
return +1; // lower prio -> other before this
|
||||||
|
} else {
|
||||||
|
return 0; // same prio
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
task.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -15,6 +15,8 @@ import javafx.beans.property.ObjectProperty;
|
|||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.beans.property.SimpleIntegerProperty;
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.geometry.NodeOrientation;
|
import javafx.geometry.NodeOrientation;
|
||||||
@@ -35,6 +37,7 @@ public class Settings {
|
|||||||
public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = System.getProperty("os.name").toLowerCase().contains("windows") ? VolumeImpl.DOKANY : VolumeImpl.FUSE;
|
public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = System.getProperty("os.name").toLowerCase().contains("windows") ? VolumeImpl.DOKANY : VolumeImpl.FUSE;
|
||||||
public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
|
public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT;
|
||||||
public static final NodeOrientation DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT;
|
public static final NodeOrientation DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT;
|
||||||
|
private static final String DEFAULT_LICENSE_KEY = "";
|
||||||
|
|
||||||
private final ObservableList<VaultSettings> directories = FXCollections.observableArrayList(VaultSettings::observables);
|
private final ObservableList<VaultSettings> directories = FXCollections.observableArrayList(VaultSettings::observables);
|
||||||
private final BooleanProperty askedForUpdateCheck = new SimpleBooleanProperty(DEFAULT_ASKED_FOR_UPDATE_CHECK);
|
private final BooleanProperty askedForUpdateCheck = new SimpleBooleanProperty(DEFAULT_ASKED_FOR_UPDATE_CHECK);
|
||||||
@@ -47,6 +50,7 @@ public class Settings {
|
|||||||
private final ObjectProperty<VolumeImpl> preferredVolumeImpl = new SimpleObjectProperty<>(DEFAULT_PREFERRED_VOLUME_IMPL);
|
private final ObjectProperty<VolumeImpl> preferredVolumeImpl = new SimpleObjectProperty<>(DEFAULT_PREFERRED_VOLUME_IMPL);
|
||||||
private final ObjectProperty<UiTheme> theme = new SimpleObjectProperty<>(DEFAULT_THEME);
|
private final ObjectProperty<UiTheme> theme = new SimpleObjectProperty<>(DEFAULT_THEME);
|
||||||
private final ObjectProperty<NodeOrientation> userInterfaceOrientation = new SimpleObjectProperty<>(DEFAULT_USER_INTERFACE_ORIENTATION);
|
private final ObjectProperty<NodeOrientation> userInterfaceOrientation = new SimpleObjectProperty<>(DEFAULT_USER_INTERFACE_ORIENTATION);
|
||||||
|
private final StringProperty licenseKey = new SimpleStringProperty(DEFAULT_LICENSE_KEY);
|
||||||
|
|
||||||
private Consumer<Settings> saveCmd;
|
private Consumer<Settings> saveCmd;
|
||||||
|
|
||||||
@@ -65,6 +69,7 @@ public class Settings {
|
|||||||
preferredVolumeImpl.addListener(this::somethingChanged);
|
preferredVolumeImpl.addListener(this::somethingChanged);
|
||||||
theme.addListener(this::somethingChanged);
|
theme.addListener(this::somethingChanged);
|
||||||
userInterfaceOrientation.addListener(this::somethingChanged);
|
userInterfaceOrientation.addListener(this::somethingChanged);
|
||||||
|
licenseKey.addListener(this::somethingChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSaveCmd(Consumer<Settings> saveCmd) {
|
void setSaveCmd(Consumer<Settings> saveCmd) {
|
||||||
@@ -126,4 +131,8 @@ public class Settings {
|
|||||||
public ObjectProperty<NodeOrientation> userInterfaceOrientation() {
|
public ObjectProperty<NodeOrientation> userInterfaceOrientation() {
|
||||||
return userInterfaceOrientation;
|
return userInterfaceOrientation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public StringProperty licenseKey() {
|
||||||
|
return licenseKey;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
out.name("preferredVolumeImpl").value(value.preferredVolumeImpl().get().name());
|
out.name("preferredVolumeImpl").value(value.preferredVolumeImpl().get().name());
|
||||||
out.name("theme").value(value.theme().get().name());
|
out.name("theme").value(value.theme().get().name());
|
||||||
out.name("uiOrientation").value(value.userInterfaceOrientation().get().name());
|
out.name("uiOrientation").value(value.userInterfaceOrientation().get().name());
|
||||||
|
out.name("licenseKey").value(value.licenseKey().get());
|
||||||
out.endObject();
|
out.endObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +91,9 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
|||||||
case "uiOrientation":
|
case "uiOrientation":
|
||||||
settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
|
settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
|
||||||
break;
|
break;
|
||||||
|
case "licenseKey":
|
||||||
|
settings.licenseKey().set(in.nextString());
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||||
in.skipValue();
|
in.skipValue();
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import org.cryptomator.cryptofs.migration.Migrators;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.nio.file.NoSuchFileException;
|
import java.nio.file.NoSuchFileException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -67,7 +68,13 @@ public class VaultListManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Optional<Vault> get(Path vaultPath) {
|
private Optional<Vault> get(Path vaultPath) {
|
||||||
return vaultList.stream().filter(v -> v.getPath().equals(vaultPath)).findAny();
|
return vaultList.stream().filter(v -> {
|
||||||
|
try {
|
||||||
|
return Files.isSameFile(vaultPath, v.getPath());
|
||||||
|
} catch (IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}).findAny();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vault create(VaultSettings vaultSettings) {
|
private Vault create(VaultSettings vaultSettings) {
|
||||||
@@ -76,7 +83,7 @@ public class VaultListManager {
|
|||||||
return comp.vault();
|
return comp.vault();
|
||||||
}
|
}
|
||||||
|
|
||||||
private VaultState determineVaultState(Path pathToVault) {
|
public static VaultState determineVaultState(Path pathToVault) {
|
||||||
try {
|
try {
|
||||||
if (!CryptoFileSystemProvider.containsVault(pathToVault, MASTERKEY_FILENAME)) {
|
if (!CryptoFileSystemProvider.containsVault(pathToVault, MASTERKEY_FILENAME)) {
|
||||||
return VaultState.MISSING;
|
return VaultState.MISSING;
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package org.cryptomator.common;
|
||||||
|
|
||||||
|
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||||
|
import org.cryptomator.common.LicenseChecker;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
class LicenseCheckerTest {
|
||||||
|
|
||||||
|
private static final String PUBLIC_KEY = "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ" //
|
||||||
|
+ "PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47" //
|
||||||
|
+ "6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM" //
|
||||||
|
+ "Al8G7CqwoJOsW7Kddns=";
|
||||||
|
|
||||||
|
private LicenseChecker licenseChecker;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setup() {
|
||||||
|
licenseChecker = new LicenseChecker(PUBLIC_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCheckValidLicense() {
|
||||||
|
String license = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.AQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
|
||||||
|
|
||||||
|
Optional<DecodedJWT> decoded = licenseChecker.check(license);
|
||||||
|
|
||||||
|
Assertions.assertTrue(decoded.isPresent());
|
||||||
|
Assertions.assertEquals("cryptobot@example.com", decoded.get().getSubject());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCheckInvalidLicenseHeader() {
|
||||||
|
String license = "EyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.AQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
|
||||||
|
|
||||||
|
Optional<DecodedJWT> decoded = licenseChecker.check(license);
|
||||||
|
|
||||||
|
Assertions.assertFalse(decoded.isPresent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCheckInvalidLicensePayload() {
|
||||||
|
String license = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.EyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.AQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
|
||||||
|
|
||||||
|
Optional<DecodedJWT> decoded = licenseChecker.check(license);
|
||||||
|
|
||||||
|
Assertions.assertFalse(decoded.isPresent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCheckInvalidLicenseSignature() {
|
||||||
|
String license = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.aQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj";
|
||||||
|
|
||||||
|
Optional<DecodedJWT> decoded = licenseChecker.check(license);
|
||||||
|
|
||||||
|
Assertions.assertFalse(decoded.isPresent());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.5.0-beta1</version>
|
<version>1.5.0-beta2</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>keychain</artifactId>
|
<artifactId>keychain</artifactId>
|
||||||
<name>System Keychain Access</name>
|
<name>System Keychain Access</name>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.5.0-beta1</version>
|
<version>1.5.0-beta2</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>launcher</artifactId>
|
<artifactId>launcher</artifactId>
|
||||||
<name>Cryptomator Launcher</name>
|
<name>Cryptomator Launcher</name>
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
* Copyright (c) 2017 Skymatic UG (haftungsbeschränkt).
|
|
||||||
* All rights reserved. This program and the accompanying materials
|
|
||||||
* are made available under the terms of the accompanying LICENSE file.
|
|
||||||
*******************************************************************************/
|
|
||||||
package org.cryptomator.launcher;
|
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ConcurrentMap;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class CleanShutdownPerformer extends Thread {
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(CleanShutdownPerformer.class);
|
|
||||||
private final ConcurrentMap<Runnable, Boolean> tasks = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
CleanShutdownPerformer() {
|
|
||||||
super(null, null, "ShutdownTasks", 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
LOG.debug("Running graceful shutdown tasks...");
|
|
||||||
tasks.keySet().forEach(r -> {
|
|
||||||
try {
|
|
||||||
r.run();
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
LOG.error("Exception while shutting down.", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
tasks.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void scheduleShutdownTask(Runnable task) {
|
|
||||||
tasks.put(task, Boolean.TRUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
void registerShutdownHook() {
|
|
||||||
Runtime.getRuntime().addShutdownHook(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -32,17 +32,15 @@ public class Cryptomator {
|
|||||||
private final IpcFactory ipcFactory;
|
private final IpcFactory ipcFactory;
|
||||||
private final Optional<String> applicationVersion;
|
private final Optional<String> applicationVersion;
|
||||||
private final CountDownLatch shutdownLatch;
|
private final CountDownLatch shutdownLatch;
|
||||||
private final CleanShutdownPerformer shutdownPerformer;
|
|
||||||
private final UiLauncher uiLauncher;
|
private final UiLauncher uiLauncher;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, IpcFactory ipcFactory, @Named("applicationVersion") Optional<String> applicationVersion, @Named("shutdownLatch") CountDownLatch shutdownLatch, CleanShutdownPerformer shutdownPerformer, UiLauncher uiLauncher) {
|
Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, IpcFactory ipcFactory, @Named("applicationVersion") Optional<String> applicationVersion, @Named("shutdownLatch") CountDownLatch shutdownLatch, UiLauncher uiLauncher) {
|
||||||
this.logConfig = logConfig;
|
this.logConfig = logConfig;
|
||||||
this.debugMode = debugMode;
|
this.debugMode = debugMode;
|
||||||
this.ipcFactory = ipcFactory;
|
this.ipcFactory = ipcFactory;
|
||||||
this.applicationVersion = applicationVersion;
|
this.applicationVersion = applicationVersion;
|
||||||
this.shutdownLatch = shutdownLatch;
|
this.shutdownLatch = shutdownLatch;
|
||||||
this.shutdownPerformer = shutdownPerformer;
|
|
||||||
this.uiLauncher = uiLauncher;
|
this.uiLauncher = uiLauncher;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +88,6 @@ public class Cryptomator {
|
|||||||
*/
|
*/
|
||||||
private int runGuiApplication() {
|
private int runGuiApplication() {
|
||||||
try {
|
try {
|
||||||
shutdownPerformer.registerShutdownHook();
|
|
||||||
uiLauncher.launch();
|
uiLauncher.launch();
|
||||||
shutdownLatch.await();
|
shutdownLatch.await();
|
||||||
LOG.info("UI shut down");
|
LOG.info("UI shut down");
|
||||||
|
|||||||
@@ -7,18 +7,10 @@ import javax.inject.Named;
|
|||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
class CryptomatorModule {
|
class CryptomatorModule {
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
@Named("shutdownTaskScheduler")
|
|
||||||
Consumer<Runnable> provideShutdownTaskScheduler(CleanShutdownPerformer shutdownPerformer) {
|
|
||||||
return shutdownPerformer::scheduleShutdownTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
@Named("shutdownLatch")
|
@Named("shutdownLatch")
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ import ch.qos.logback.classic.Logger;
|
|||||||
import ch.qos.logback.classic.LoggerContext;
|
import ch.qos.logback.classic.LoggerContext;
|
||||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||||
import ch.qos.logback.core.Appender;
|
import ch.qos.logback.core.Appender;
|
||||||
import ch.qos.logback.core.hook.DelayingShutdownHook;
|
|
||||||
import ch.qos.logback.core.util.Duration;
|
|
||||||
import org.cryptomator.common.Environment;
|
import org.cryptomator.common.Environment;
|
||||||
|
import org.cryptomator.common.ShutdownHook;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
@@ -16,26 +15,27 @@ import java.util.Map;
|
|||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class LoggerConfiguration {
|
public class LoggerConfiguration {
|
||||||
|
|
||||||
private static final double SHUTDOWN_DELAY_MS = 100;
|
|
||||||
|
|
||||||
private final LoggerContext context;
|
private final LoggerContext context;
|
||||||
private final Environment environment;
|
private final Environment environment;
|
||||||
private final Appender<ILoggingEvent> stdout;
|
private final Appender<ILoggingEvent> stdout;
|
||||||
private final Appender<ILoggingEvent> upgrade;
|
private final Appender<ILoggingEvent> upgrade;
|
||||||
private final Appender<ILoggingEvent> file;
|
private final Appender<ILoggingEvent> file;
|
||||||
|
private final ShutdownHook shutdownHook;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
LoggerConfiguration(LoggerContext context, //
|
LoggerConfiguration(LoggerContext context, //
|
||||||
Environment environment, //
|
Environment environment, //
|
||||||
@Named("stdoutAppender") Appender<ILoggingEvent> stdout, //
|
@Named("stdoutAppender") Appender<ILoggingEvent> stdout, //
|
||||||
@Named("upgradeAppender") Appender<ILoggingEvent> upgrade, //
|
@Named("upgradeAppender") Appender<ILoggingEvent> upgrade, //
|
||||||
@Named("fileAppender") Appender<ILoggingEvent> file) {
|
@Named("fileAppender") Appender<ILoggingEvent> file, //
|
||||||
|
ShutdownHook shutdownHook) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.environment = environment;
|
this.environment = environment;
|
||||||
this.stdout = stdout;
|
this.stdout = stdout;
|
||||||
this.upgrade = upgrade;
|
this.upgrade = upgrade;
|
||||||
this.file = file;
|
this.file = file;
|
||||||
|
this.shutdownHook = shutdownHook;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init() {
|
public void init() {
|
||||||
@@ -62,9 +62,7 @@ public class LoggerConfiguration {
|
|||||||
upgrades.setAdditive(false);
|
upgrades.setAdditive(false);
|
||||||
|
|
||||||
// add shutdown hook
|
// add shutdown hook
|
||||||
DelayingShutdownHook shutdownHook = new DelayingShutdownHook();
|
shutdownHook.runOnShutdown(ShutdownHook.PRIO_LAST, context::stop);
|
||||||
shutdownHook.setContext(context);
|
|
||||||
shutdownHook.setDelay(Duration.buildByMilliseconds(SHUTDOWN_DELAY_MS));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+15
-9
@@ -3,7 +3,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.5.0-beta1</version>
|
<version>1.5.0-beta2</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>Cryptomator</name>
|
<name>Cryptomator</name>
|
||||||
|
|
||||||
@@ -23,26 +23,25 @@
|
|||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
|
||||||
<!-- dependency versions -->
|
<!-- cryptomator dependencies -->
|
||||||
<cryptomator.cryptofs.version>1.9.0-rc2</cryptomator.cryptofs.version>
|
<cryptomator.cryptofs.version>1.9.0-rc2</cryptomator.cryptofs.version>
|
||||||
<cryptomator.jni.version>2.2.1</cryptomator.jni.version>
|
<cryptomator.jni.version>2.2.2</cryptomator.jni.version>
|
||||||
<cryptomator.fuse.version>1.2.1</cryptomator.fuse.version>
|
<cryptomator.fuse.version>1.2.2</cryptomator.fuse.version>
|
||||||
<cryptomator.dokany.version>1.1.11</cryptomator.dokany.version>
|
<cryptomator.dokany.version>1.1.12</cryptomator.dokany.version>
|
||||||
<cryptomator.webdav.version>1.0.10</cryptomator.webdav.version>
|
<cryptomator.webdav.version>1.0.10</cryptomator.webdav.version>
|
||||||
|
|
||||||
|
<!-- 3rd party dependencies -->
|
||||||
<javafx.version>13.0.1</javafx.version>
|
<javafx.version>13.0.1</javafx.version>
|
||||||
|
|
||||||
<commons-lang3.version>3.9</commons-lang3.version>
|
<commons-lang3.version>3.9</commons-lang3.version>
|
||||||
|
<jwt.version>3.8.3</jwt.version>
|
||||||
<easybind.version>1.0.3</easybind.version>
|
<easybind.version>1.0.3</easybind.version>
|
||||||
|
|
||||||
<guava.version>28.1-jre</guava.version>
|
<guava.version>28.1-jre</guava.version>
|
||||||
<dagger.version>2.25.2</dagger.version>
|
<dagger.version>2.25.2</dagger.version>
|
||||||
<gson.version>2.8.6</gson.version>
|
<gson.version>2.8.6</gson.version>
|
||||||
|
|
||||||
<slf4j.version>1.7.29</slf4j.version>
|
<slf4j.version>1.7.29</slf4j.version>
|
||||||
<logback.version>1.2.3</logback.version>
|
<logback.version>1.2.3</logback.version>
|
||||||
|
|
||||||
|
<!-- test dependencies -->
|
||||||
<junit.jupiter.version>5.5.2</junit.jupiter.version>
|
<junit.jupiter.version>5.5.2</junit.jupiter.version>
|
||||||
<mockito.version>3.1.0</mockito.version>
|
<mockito.version>3.1.0</mockito.version>
|
||||||
<hamcrest.version>2.2</hamcrest.version>
|
<hamcrest.version>2.2</hamcrest.version>
|
||||||
@@ -156,6 +155,13 @@
|
|||||||
<artifactId>commons-lang3</artifactId>
|
<artifactId>commons-lang3</artifactId>
|
||||||
<version>${commons-lang3.version}</version>
|
<version>${commons-lang3.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- JWT -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.auth0</groupId>
|
||||||
|
<artifactId>java-jwt</artifactId>
|
||||||
|
<version>${jwt.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- EasyBind -->
|
<!-- EasyBind -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
+3
-3
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.cryptomator</groupId>
|
<groupId>org.cryptomator</groupId>
|
||||||
<artifactId>main</artifactId>
|
<artifactId>main</artifactId>
|
||||||
<version>1.5.0-beta1</version>
|
<version>1.5.0-beta2</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>ui</artifactId>
|
<artifactId>ui</artifactId>
|
||||||
<name>Cryptomator GUI</name>
|
<name>Cryptomator GUI</name>
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.fxmisc.easybind</groupId>
|
<groupId>org.fxmisc.easybind</groupId>
|
||||||
<artifactId>easybind</artifactId>
|
<artifactId>easybind</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Google -->
|
<!-- Google -->
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.nulab-inc</groupId>
|
<groupId>com.nulab-inc</groupId>
|
||||||
<artifactId>zxcvbn</artifactId>
|
<artifactId>zxcvbn</artifactId>
|
||||||
<version>1.2.7</version>
|
<version>1.3.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Logging -->
|
<!-- Logging -->
|
||||||
|
|||||||
+2
-2
@@ -15,14 +15,14 @@ import javax.inject.Inject;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
@AddVaultWizardScoped
|
@AddVaultWizardScoped
|
||||||
public class AddVaultFailureExisitingController implements FxController {
|
public class AddVaultFailureExistingController implements FxController {
|
||||||
|
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final Lazy<Scene> previousScene;
|
private final Lazy<Scene> previousScene;
|
||||||
private final StringBinding vaultName;
|
private final StringBinding vaultName;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
AddVaultFailureExisitingController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_EXISTING) Lazy<Scene> previousScene, ObjectProperty<Path> pathOfFailedVault){
|
AddVaultFailureExistingController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_EXISTING) Lazy<Scene> previousScene, ObjectProperty<Path> pathOfFailedVault){
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.previousScene = previousScene;
|
this.previousScene = previousScene;
|
||||||
this.vaultName = Bindings.createStringBinding(() -> pathOfFailedVault.get().getFileName().toString(),pathOfFailedVault);
|
this.vaultName = Bindings.createStringBinding(() -> pathOfFailedVault.get().getFileName().toString(),pathOfFailedVault);
|
||||||
@@ -19,7 +19,10 @@ import org.cryptomator.ui.common.FxController;
|
|||||||
import org.cryptomator.ui.common.FxControllerKey;
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
import org.cryptomator.ui.common.FxmlScene;
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
|
import org.cryptomator.ui.common.NewPasswordController;
|
||||||
|
import org.cryptomator.ui.common.PasswordStrengthUtil;
|
||||||
import org.cryptomator.ui.mainwindow.MainWindow;
|
import org.cryptomator.ui.mainwindow.MainWindow;
|
||||||
|
import org.cryptomator.ui.recoverykey.RecoveryKeyDisplayController;
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Provider;
|
import javax.inject.Provider;
|
||||||
@@ -31,6 +34,13 @@ import java.util.ResourceBundle;
|
|||||||
@Module
|
@Module
|
||||||
public abstract class AddVaultModule {
|
public abstract class AddVaultModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@AddVaultWizardScoped
|
||||||
|
@Named("newPassword")
|
||||||
|
static ObjectProperty<CharSequence> provideNewPasswordProperty() {
|
||||||
|
return new SimpleObjectProperty<>("");
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@AddVaultWizardWindow
|
@AddVaultWizardWindow
|
||||||
@AddVaultWizardScoped
|
@AddVaultWizardScoped
|
||||||
@@ -150,8 +160,8 @@ public abstract class AddVaultModule {
|
|||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FxControllerKey(AddVaultFailureExisitingController.class)
|
@FxControllerKey(AddVaultFailureExistingController.class)
|
||||||
abstract FxController bindAddVaultFailureExistingController(AddVaultFailureExisitingController controller);
|
abstract FxController bindAddVaultFailureExistingController(AddVaultFailureExistingController controller);
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@@ -168,13 +178,27 @@ public abstract class AddVaultModule {
|
|||||||
@FxControllerKey(CreateNewVaultPasswordController.class)
|
@FxControllerKey(CreateNewVaultPasswordController.class)
|
||||||
abstract FxController bindCreateNewVaultPasswordController(CreateNewVaultPasswordController controller);
|
abstract FxController bindCreateNewVaultPasswordController(CreateNewVaultPasswordController controller);
|
||||||
|
|
||||||
@Binds
|
@Provides
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FxControllerKey(AddVaultSuccessController.class)
|
@FxControllerKey(NewPasswordController.class)
|
||||||
abstract FxController bindAddVaultSuccessController(AddVaultSuccessController controller);
|
static FxController provideNewPasswordController(ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater, @Named("newPassword") ObjectProperty<CharSequence> password) {
|
||||||
|
return new NewPasswordController(resourceBundle, strengthRater, password);
|
||||||
|
}
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FxControllerKey(CreateNewVaultRecoveryKeyController.class)
|
@FxControllerKey(CreateNewVaultRecoveryKeyController.class)
|
||||||
abstract FxController bindCreateNewVaultRecoveryKeyController(CreateNewVaultRecoveryKeyController controller);
|
abstract FxController bindCreateNewVaultRecoveryKeyController(CreateNewVaultRecoveryKeyController controller);
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@IntoMap
|
||||||
|
@FxControllerKey(RecoveryKeyDisplayController.class)
|
||||||
|
static FxController provideRecoveryKeyDisplayController(@AddVaultWizardWindow Stage window, @Named("vaultName") StringProperty vaultName, @Named("recoveryKey") StringProperty recoveryKey) {
|
||||||
|
return new RecoveryKeyDisplayController(window, vaultName.get(), recoveryKey.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FxControllerKey(AddVaultSuccessController.class)
|
||||||
|
abstract FxController bindAddVaultSuccessController(AddVaultSuccessController controller);
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-1
@@ -52,8 +52,10 @@ public class CreateNewVaultLocationController implements FxController {
|
|||||||
|
|
||||||
private Path customVaultPath = DEFAULT_CUSTOM_VAULT_PATH;
|
private Path customVaultPath = DEFAULT_CUSTOM_VAULT_PATH;
|
||||||
public ToggleGroup predefinedLocationToggler;
|
public ToggleGroup predefinedLocationToggler;
|
||||||
|
public RadioButton iclouddriveRadioButton;
|
||||||
public RadioButton dropboxRadioButton;
|
public RadioButton dropboxRadioButton;
|
||||||
public RadioButton gdriveRadioButton;
|
public RadioButton gdriveRadioButton;
|
||||||
|
public RadioButton onedriveRadioButton;
|
||||||
public RadioButton customRadioButton;
|
public RadioButton customRadioButton;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -91,10 +93,14 @@ public class CreateNewVaultLocationController implements FxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void togglePredefinedLocation(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
|
private void togglePredefinedLocation(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
|
||||||
if (dropboxRadioButton.equals(newValue)) {
|
if (iclouddriveRadioButton.equals(newValue)) {
|
||||||
|
vaultPath.set(locationPresets.getIclouddriveLocation().resolve(vaultName.get()));
|
||||||
|
} else if (dropboxRadioButton.equals(newValue)) {
|
||||||
vaultPath.set(locationPresets.getDropboxLocation().resolve(vaultName.get()));
|
vaultPath.set(locationPresets.getDropboxLocation().resolve(vaultName.get()));
|
||||||
} else if (gdriveRadioButton.equals(newValue)) {
|
} else if (gdriveRadioButton.equals(newValue)) {
|
||||||
vaultPath.set(locationPresets.getGdriveLocation().resolve(vaultName.get()));
|
vaultPath.set(locationPresets.getGdriveLocation().resolve(vaultName.get()));
|
||||||
|
} else if (onedriveRadioButton.equals(newValue)) {
|
||||||
|
vaultPath.set(locationPresets.getOnedriveLocation().resolve(vaultName.get()));
|
||||||
} else if (customRadioButton.equals(newValue)) {
|
} else if (customRadioButton.equals(newValue)) {
|
||||||
vaultPath.set(customVaultPath.resolve(vaultName.get()));
|
vaultPath.set(customVaultPath.resolve(vaultName.get()));
|
||||||
}
|
}
|
||||||
|
|||||||
+47
-52
@@ -5,16 +5,14 @@ import javafx.beans.binding.Bindings;
|
|||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.binding.BooleanBinding;
|
||||||
import javafx.beans.binding.ObjectBinding;
|
import javafx.beans.binding.ObjectBinding;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
import javafx.beans.property.IntegerProperty;
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.beans.property.SimpleIntegerProperty;
|
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.control.ContentDisplay;
|
import javafx.scene.control.ContentDisplay;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Toggle;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.control.ToggleGroup;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultListManager;
|
import org.cryptomator.common.vaults.VaultListManager;
|
||||||
@@ -24,11 +22,7 @@ import org.cryptomator.ui.common.FxController;
|
|||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
import org.cryptomator.ui.common.FxmlScene;
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
import org.cryptomator.ui.common.Tasks;
|
import org.cryptomator.ui.common.Tasks;
|
||||||
import org.cryptomator.ui.controls.FontAwesome5IconView;
|
|
||||||
import org.cryptomator.ui.controls.NiceSecurePasswordField;
|
|
||||||
import org.cryptomator.ui.common.PasswordStrengthUtil;
|
|
||||||
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
|
import org.cryptomator.ui.recoverykey.RecoveryKeyFactory;
|
||||||
import org.fxmisc.easybind.EasyBind;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -58,6 +52,7 @@ public class CreateNewVaultPasswordController implements FxController {
|
|||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final Lazy<Scene> chooseLocationScene;
|
private final Lazy<Scene> chooseLocationScene;
|
||||||
private final Lazy<Scene> recoveryKeyScene;
|
private final Lazy<Scene> recoveryKeyScene;
|
||||||
|
private final Lazy<Scene> successScene;
|
||||||
private final ExecutorService executor;
|
private final ExecutorService executor;
|
||||||
private final RecoveryKeyFactory recoveryKeyFactory;
|
private final RecoveryKeyFactory recoveryKeyFactory;
|
||||||
private final StringProperty vaultNameProperty;
|
private final StringProperty vaultNameProperty;
|
||||||
@@ -66,26 +61,22 @@ public class CreateNewVaultPasswordController implements FxController {
|
|||||||
private final StringProperty recoveryKeyProperty;
|
private final StringProperty recoveryKeyProperty;
|
||||||
private final VaultListManager vaultListManager;
|
private final VaultListManager vaultListManager;
|
||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
private final PasswordStrengthUtil strengthRater;
|
private final ObjectProperty<CharSequence> password;
|
||||||
private final ReadmeGenerator readmeGenerator;
|
private final ReadmeGenerator readmeGenerator;
|
||||||
private final IntegerProperty passwordStrength;
|
|
||||||
private final BooleanProperty processing;
|
private final BooleanProperty processing;
|
||||||
private final BooleanProperty readyToCreateVault;
|
private final BooleanProperty readyToCreateVault;
|
||||||
private final ObjectBinding<ContentDisplay> createVaultButtonState;
|
private final ObjectBinding<ContentDisplay> createVaultButtonState;
|
||||||
|
|
||||||
public NiceSecurePasswordField passwordField;
|
public ToggleGroup recoveryKeyChoice;
|
||||||
public NiceSecurePasswordField reenterField;
|
public Toggle showRecoveryKey;
|
||||||
public Label passwordStrengthLabel;
|
public Toggle skipRecoveryKey;
|
||||||
public HBox passwordMatchBox;
|
|
||||||
public FontAwesome5IconView checkmark;
|
|
||||||
public FontAwesome5IconView cross;
|
|
||||||
public Label passwordMatchLabel;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
CreateNewVaultPasswordController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY) Lazy<Scene> recoveryKeyScene, ExecutorService executor, RecoveryKeyFactory recoveryKeyFactory, @Named("vaultName") StringProperty vaultName, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, @Named("recoveryKey") StringProperty recoveryKey, VaultListManager vaultListManager, ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater, ReadmeGenerator readmeGenerator) {
|
CreateNewVaultPasswordController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy<Scene> chooseLocationScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY) Lazy<Scene> recoveryKeyScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, ExecutorService executor, RecoveryKeyFactory recoveryKeyFactory, @Named("vaultName") StringProperty vaultName, ObjectProperty<Path> vaultPath, @AddVaultWizardWindow ObjectProperty<Vault> vault, @Named("recoveryKey") StringProperty recoveryKey, VaultListManager vaultListManager, ResourceBundle resourceBundle, @Named("newPassword") ObjectProperty<CharSequence> password, ReadmeGenerator readmeGenerator) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.chooseLocationScene = chooseLocationScene;
|
this.chooseLocationScene = chooseLocationScene;
|
||||||
this.recoveryKeyScene = recoveryKeyScene;
|
this.recoveryKeyScene = recoveryKeyScene;
|
||||||
|
this.successScene = successScene;
|
||||||
this.executor = executor;
|
this.executor = executor;
|
||||||
this.recoveryKeyFactory = recoveryKeyFactory;
|
this.recoveryKeyFactory = recoveryKeyFactory;
|
||||||
this.vaultNameProperty = vaultName;
|
this.vaultNameProperty = vaultName;
|
||||||
@@ -94,9 +85,8 @@ public class CreateNewVaultPasswordController implements FxController {
|
|||||||
this.recoveryKeyProperty = recoveryKey;
|
this.recoveryKeyProperty = recoveryKey;
|
||||||
this.vaultListManager = vaultListManager;
|
this.vaultListManager = vaultListManager;
|
||||||
this.resourceBundle = resourceBundle;
|
this.resourceBundle = resourceBundle;
|
||||||
this.strengthRater = strengthRater;
|
this.password = password;
|
||||||
this.readmeGenerator = readmeGenerator;
|
this.readmeGenerator = readmeGenerator;
|
||||||
this.passwordStrength = new SimpleIntegerProperty(-1);
|
|
||||||
this.processing = new SimpleBooleanProperty();
|
this.processing = new SimpleBooleanProperty();
|
||||||
this.readyToCreateVault = new SimpleBooleanProperty();
|
this.readyToCreateVault = new SimpleBooleanProperty();
|
||||||
this.createVaultButtonState = Bindings.createObjectBinding(this::getCreateVaultButtonState, processing);
|
this.createVaultButtonState = Bindings.createObjectBinding(this::getCreateVaultButtonState, processing);
|
||||||
@@ -104,22 +94,8 @@ public class CreateNewVaultPasswordController implements FxController {
|
|||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
//binds the actual strength value to the rating of the password util
|
BooleanBinding isValidNewPassword = Bindings.createBooleanBinding(() -> password.get() != null && password.get().length() > 0, password);
|
||||||
passwordStrength.bind(Bindings.createIntegerBinding(() -> strengthRater.computeRate(passwordField.getCharacters().toString()), passwordField.textProperty()));
|
readyToCreateVault.bind(isValidNewPassword.and(recoveryKeyChoice.selectedToggleProperty().isNotNull()).and(processing.not()));
|
||||||
//binding indicating if the passwords not match
|
|
||||||
BooleanBinding passwordsMatch = Bindings.createBooleanBinding(() -> CharSequence.compare(passwordField.getCharacters(), reenterField.getCharacters()) == 0, passwordField.textProperty(), reenterField.textProperty());
|
|
||||||
BooleanBinding reenterFieldNotEmpty = reenterField.textProperty().isNotEmpty();
|
|
||||||
readyToCreateVault.bind(reenterFieldNotEmpty.and(passwordsMatch).and(processing.not()));
|
|
||||||
//make match indicator invisible when passwords do not match or one is empty
|
|
||||||
passwordMatchBox.visibleProperty().bind(reenterFieldNotEmpty);
|
|
||||||
checkmark.visibleProperty().bind(passwordsMatch.and(reenterFieldNotEmpty));
|
|
||||||
checkmark.managedProperty().bind(checkmark.visibleProperty());
|
|
||||||
cross.visibleProperty().bind(passwordsMatch.not().and(reenterFieldNotEmpty));
|
|
||||||
cross.managedProperty().bind(cross.visibleProperty());
|
|
||||||
passwordMatchLabel.textProperty().bind(Bindings.when(passwordsMatch.and(reenterFieldNotEmpty)).then(resourceBundle.getString("addvaultwizard.new.passwordsMatch")).otherwise(resourceBundle.getString("addvaultwizard.new.passwordsDoNotMatch")));
|
|
||||||
|
|
||||||
//bindsings for the password strength indicator
|
|
||||||
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -130,7 +106,7 @@ public class CreateNewVaultPasswordController implements FxController {
|
|||||||
@FXML
|
@FXML
|
||||||
public void next() {
|
public void next() {
|
||||||
Path pathToVault = vaultPathProperty.get();
|
Path pathToVault = vaultPathProperty.get();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Files.createDirectory(pathToVault);
|
Files.createDirectory(pathToVault);
|
||||||
} catch (FileAlreadyExistsException e) {
|
} catch (FileAlreadyExistsException e) {
|
||||||
@@ -141,12 +117,41 @@ public class CreateNewVaultPasswordController implements FxController {
|
|||||||
LOG.error("", e);
|
LOG.error("", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showRecoveryKey.equals(recoveryKeyChoice.getSelectedToggle())) {
|
||||||
|
showRecoveryKeyScene();
|
||||||
|
} else if (skipRecoveryKey.equals(recoveryKeyChoice.getSelectedToggle())) {
|
||||||
|
showSuccessScene();
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Unexpected toggle state");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showRecoveryKeyScene() {
|
||||||
|
Path pathToVault = vaultPathProperty.get();
|
||||||
processing.set(true);
|
processing.set(true);
|
||||||
Tasks.create(() -> {
|
Tasks.create(() -> {
|
||||||
initializeVault(pathToVault, passwordField.getCharacters());
|
initializeVault(pathToVault, password.get());
|
||||||
return recoveryKeyFactory.createRecoveryKey(pathToVault, passwordField.getCharacters());
|
return recoveryKeyFactory.createRecoveryKey(pathToVault, password.get());
|
||||||
}).onSuccess(recoveryKey -> {
|
}).onSuccess(recoveryKey -> {
|
||||||
initializationSucceeded(pathToVault, recoveryKey);
|
initializationSucceeded(pathToVault);
|
||||||
|
recoveryKeyProperty.set(recoveryKey);
|
||||||
|
window.setScene(recoveryKeyScene.get());
|
||||||
|
}).onError(IOException.class, e -> {
|
||||||
|
// TODO show generic error screen
|
||||||
|
LOG.error("", e);
|
||||||
|
}).andFinally(() -> {
|
||||||
|
processing.set(false);
|
||||||
|
}).runOnce(executor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showSuccessScene() {
|
||||||
|
Path pathToVault = vaultPathProperty.get();
|
||||||
|
processing.set(true);
|
||||||
|
Tasks.create(() -> {
|
||||||
|
initializeVault(pathToVault, password.get());
|
||||||
|
}).onSuccess(() -> {
|
||||||
|
initializationSucceeded(pathToVault);
|
||||||
|
window.setScene(successScene.get());
|
||||||
}).onError(IOException.class, e -> {
|
}).onError(IOException.class, e -> {
|
||||||
// TODO show generic error screen
|
// TODO show generic error screen
|
||||||
LOG.error("", e);
|
LOG.error("", e);
|
||||||
@@ -175,13 +180,11 @@ public class CreateNewVaultPasswordController implements FxController {
|
|||||||
}
|
}
|
||||||
LOG.info("Created vault at {}", path);
|
LOG.info("Created vault at {}", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializationSucceeded(Path pathToVault, String recoveryKey) {
|
private void initializationSucceeded(Path pathToVault) {
|
||||||
try {
|
try {
|
||||||
Vault newVault = vaultListManager.add(pathToVault);
|
Vault newVault = vaultListManager.add(pathToVault);
|
||||||
vaultProperty.set(newVault);
|
vaultProperty.set(newVault);
|
||||||
recoveryKeyProperty.set(recoveryKey);
|
|
||||||
window.setScene(recoveryKeyScene.get());
|
|
||||||
} catch (NoSuchFileException e) {
|
} catch (NoSuchFileException e) {
|
||||||
throw new UncheckedIOException(e);
|
throw new UncheckedIOException(e);
|
||||||
}
|
}
|
||||||
@@ -197,14 +200,6 @@ public class CreateNewVaultPasswordController implements FxController {
|
|||||||
return vaultNameProperty;
|
return vaultNameProperty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IntegerProperty passwordStrengthProperty() {
|
|
||||||
return passwordStrength;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPasswordStrength() {
|
|
||||||
return passwordStrength.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public BooleanProperty readyToCreateVaultProperty() {
|
public BooleanProperty readyToCreateVaultProperty() {
|
||||||
return readyToCreateVault;
|
return readyToCreateVault;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-15
@@ -1,7 +1,6 @@
|
|||||||
package org.cryptomator.ui.addvaultwizard;
|
package org.cryptomator.ui.addvaultwizard;
|
||||||
|
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
import javafx.beans.property.StringProperty;
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
@@ -10,33 +9,20 @@ import org.cryptomator.ui.common.FxmlFile;
|
|||||||
import org.cryptomator.ui.common.FxmlScene;
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
|
||||||
|
|
||||||
public class CreateNewVaultRecoveryKeyController implements FxController {
|
public class CreateNewVaultRecoveryKeyController implements FxController {
|
||||||
|
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final Lazy<Scene> successScene;
|
private final Lazy<Scene> successScene;
|
||||||
private final StringProperty recoveryKeyProperty;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
CreateNewVaultRecoveryKeyController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene, @Named("recoveryKey")StringProperty recoveryKey) {
|
CreateNewVaultRecoveryKeyController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy<Scene> successScene) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.successScene = successScene;
|
this.successScene = successScene;
|
||||||
this.recoveryKeyProperty = recoveryKey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void next() {
|
public void next() {
|
||||||
window.setScene(successScene.get());
|
window.setScene(successScene.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Getter/Setter */
|
|
||||||
|
|
||||||
public String getRecoveryKey() {
|
|
||||||
return recoveryKeyProperty.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public StringProperty recoveryKeyProperty() {
|
|
||||||
return recoveryKeyProperty;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,20 +13,30 @@ import java.nio.file.Paths;
|
|||||||
public class LocationPresets {
|
public class LocationPresets {
|
||||||
|
|
||||||
private static final String USER_HOME = System.getProperty("user.home");
|
private static final String USER_HOME = System.getProperty("user.home");
|
||||||
|
private static final String[] ICLOUDDRIVE_LOCATIONS = {"~/Library/Mobile Documents/iCloud~com~setolabs~Cryptomator/Documents"};
|
||||||
private static final String[] DROPBOX_LOCATIONS = {"~/Dropbox"};
|
private static final String[] DROPBOX_LOCATIONS = {"~/Dropbox"};
|
||||||
private static final String[] GDRIVE_LOCATIONS = {"~/Google Drive"};
|
private static final String[] GDRIVE_LOCATIONS = {"~/Google Drive"};
|
||||||
|
private static final String[] ONEDRIVE_LOCATIONS = {"~/OneDrive"};
|
||||||
|
|
||||||
|
private final ReadOnlyObjectProperty<Path> iclouddriveLocation;
|
||||||
private final ReadOnlyObjectProperty<Path> dropboxLocation;
|
private final ReadOnlyObjectProperty<Path> dropboxLocation;
|
||||||
private final ReadOnlyObjectProperty<Path> gdriveLocation;
|
private final ReadOnlyObjectProperty<Path> gdriveLocation;
|
||||||
|
private final ReadOnlyObjectProperty<Path> onedriveLocation;
|
||||||
|
private final BooleanBinding foundIclouddrive;
|
||||||
private final BooleanBinding foundDropbox;
|
private final BooleanBinding foundDropbox;
|
||||||
private final BooleanBinding foundGdrive;
|
private final BooleanBinding foundGdrive;
|
||||||
|
private final BooleanBinding foundOnedrive;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public LocationPresets() {
|
public LocationPresets() {
|
||||||
|
this.iclouddriveLocation = new SimpleObjectProperty<>(existingWritablePath(ICLOUDDRIVE_LOCATIONS));
|
||||||
this.dropboxLocation = new SimpleObjectProperty<>(existingWritablePath(DROPBOX_LOCATIONS));
|
this.dropboxLocation = new SimpleObjectProperty<>(existingWritablePath(DROPBOX_LOCATIONS));
|
||||||
this.gdriveLocation = new SimpleObjectProperty<>(existingWritablePath(GDRIVE_LOCATIONS));
|
this.gdriveLocation = new SimpleObjectProperty<>(existingWritablePath(GDRIVE_LOCATIONS));
|
||||||
|
this.onedriveLocation = new SimpleObjectProperty<>(existingWritablePath(ONEDRIVE_LOCATIONS));
|
||||||
|
this.foundIclouddrive = iclouddriveLocation.isNotNull();
|
||||||
this.foundDropbox = dropboxLocation.isNotNull();
|
this.foundDropbox = dropboxLocation.isNotNull();
|
||||||
this.foundGdrive = gdriveLocation.isNotNull();
|
this.foundGdrive = gdriveLocation.isNotNull();
|
||||||
|
this.foundOnedrive = onedriveLocation.isNotNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Path existingWritablePath(String... candidates) {
|
private static Path existingWritablePath(String... candidates) {
|
||||||
@@ -49,6 +59,22 @@ public class LocationPresets {
|
|||||||
|
|
||||||
/* Observables */
|
/* Observables */
|
||||||
|
|
||||||
|
public ReadOnlyObjectProperty<Path> iclouddriveLocationProperty() {
|
||||||
|
return iclouddriveLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getIclouddriveLocation() {
|
||||||
|
return iclouddriveLocation.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanBinding foundIclouddriveProperty() {
|
||||||
|
return foundIclouddrive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFoundIclouddrive() {
|
||||||
|
return foundIclouddrive.get();
|
||||||
|
}
|
||||||
|
|
||||||
public ReadOnlyObjectProperty<Path> dropboxLocationProperty() {
|
public ReadOnlyObjectProperty<Path> dropboxLocationProperty() {
|
||||||
return dropboxLocation;
|
return dropboxLocation;
|
||||||
}
|
}
|
||||||
@@ -73,7 +99,7 @@ public class LocationPresets {
|
|||||||
return gdriveLocation.get();
|
return gdriveLocation.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BooleanBinding froundGdriveProperty() {
|
public BooleanBinding foundGdriveProperty() {
|
||||||
return foundGdrive;
|
return foundGdrive;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,4 +107,20 @@ public class LocationPresets {
|
|||||||
return foundGdrive.get();
|
return foundGdrive.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ReadOnlyObjectProperty<Path> onedriveLocationProperty() {
|
||||||
|
return onedriveLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getOnedriveLocation() {
|
||||||
|
return onedriveLocation.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanBinding foundOnedriveProperty() {
|
||||||
|
return foundOnedrive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFoundOnedrive() {
|
||||||
|
return foundOnedrive.get();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-40
@@ -3,6 +3,7 @@ package org.cryptomator.ui.changepassword;
|
|||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.binding.BooleanBinding;
|
||||||
import javafx.beans.property.IntegerProperty;
|
import javafx.beans.property.IntegerProperty;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleIntegerProperty;
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
@@ -22,6 +23,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
@@ -33,47 +35,24 @@ public class ChangePasswordController implements FxController {
|
|||||||
|
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final Vault vault;
|
private final Vault vault;
|
||||||
private final ResourceBundle resourceBundle;
|
private final ObjectProperty<CharSequence> newPassword;
|
||||||
private final PasswordStrengthUtil strengthRater;
|
|
||||||
private final IntegerProperty passwordStrength;
|
|
||||||
|
|
||||||
public NiceSecurePasswordField oldPasswordField;
|
public NiceSecurePasswordField oldPasswordField;
|
||||||
public NiceSecurePasswordField newPasswordField;
|
|
||||||
public NiceSecurePasswordField reenterPasswordField;
|
|
||||||
public Label passwordStrengthLabel;
|
|
||||||
public HBox passwordMatchBox;
|
|
||||||
public FontAwesome5IconView checkmark;
|
|
||||||
public FontAwesome5IconView cross;
|
|
||||||
public Label passwordMatchLabel;
|
|
||||||
public CheckBox finalConfirmationCheckbox;
|
public CheckBox finalConfirmationCheckbox;
|
||||||
public Button finishButton;
|
public Button finishButton;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ChangePasswordController(@ChangePasswordWindow Stage window, @ChangePasswordWindow Vault vault, ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater) {
|
public ChangePasswordController(@ChangePasswordWindow Stage window, @ChangePasswordWindow Vault vault, @Named("newPassword") ObjectProperty<CharSequence> newPassword) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
this.resourceBundle = resourceBundle;
|
this.newPassword = newPassword;
|
||||||
this.strengthRater = strengthRater;
|
|
||||||
this.passwordStrength = new SimpleIntegerProperty(-1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
//binds the actual strength value to the rating of the password util
|
BooleanBinding hasNotConfirmedCheckbox = finalConfirmationCheckbox.selectedProperty().not();
|
||||||
passwordStrength.bind(Bindings.createIntegerBinding(() -> strengthRater.computeRate(newPasswordField.getCharacters().toString()), newPasswordField.textProperty()));
|
BooleanBinding isInvalidNewPassword = Bindings.createBooleanBinding(() -> newPassword.get() == null || newPassword.get().length() == 0, newPassword);
|
||||||
//binding indicating if the passwords not match
|
finishButton.disableProperty().bind(hasNotConfirmedCheckbox.or(isInvalidNewPassword));
|
||||||
BooleanBinding passwordsMatch = Bindings.createBooleanBinding(() -> CharSequence.compare(newPasswordField.getCharacters(), reenterPasswordField.getCharacters()) == 0, newPasswordField.textProperty(), reenterPasswordField.textProperty());
|
|
||||||
BooleanBinding reenterFieldNotEmpty = reenterPasswordField.textProperty().isNotEmpty();
|
|
||||||
//disable the finish button when passwords do not match or one is empty
|
|
||||||
finishButton.disableProperty().bind(reenterFieldNotEmpty.not().or(passwordsMatch.not()).or(finalConfirmationCheckbox.selectedProperty().not()));
|
|
||||||
//make match indicator invisible when passwords do not match or one is empty
|
|
||||||
passwordMatchBox.visibleProperty().bind(reenterFieldNotEmpty);
|
|
||||||
checkmark.visibleProperty().bind(passwordsMatch.and(reenterFieldNotEmpty));
|
|
||||||
checkmark.managedProperty().bind(checkmark.visibleProperty());
|
|
||||||
cross.visibleProperty().bind(passwordsMatch.not().and(reenterFieldNotEmpty));
|
|
||||||
cross.managedProperty().bind(cross.visibleProperty());
|
|
||||||
passwordMatchLabel.textProperty().bind(Bindings.when(passwordsMatch.and(reenterFieldNotEmpty)).then(resourceBundle.getString("changepassword.passwordsMatch")).otherwise(resourceBundle.getString("changepassword.passwordsDoNotMatch")));
|
|
||||||
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -84,15 +63,15 @@ public class ChangePasswordController implements FxController {
|
|||||||
@FXML
|
@FXML
|
||||||
public void finish() {
|
public void finish() {
|
||||||
try {
|
try {
|
||||||
CryptoFileSystemProvider.changePassphrase(vault.getPath(), MASTERKEY_FILENAME, oldPasswordField.getCharacters(), newPasswordField.getCharacters());
|
CryptoFileSystemProvider.changePassphrase(vault.getPath(), MASTERKEY_FILENAME, oldPasswordField.getCharacters(), newPassword.get());
|
||||||
LOG.info("Successful changed password for {}", vault.getDisplayableName());
|
LOG.info("Successful changed password for {}", vault.getDisplayableName());
|
||||||
window.close();
|
window.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
//TODO
|
// TODO show generic error screen
|
||||||
LOG.error("IO error occured during password change. Unable to perform operation.", e);
|
LOG.error("IO error occured during password change. Unable to perform operation.", e);
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} catch (InvalidPassphraseException e) {
|
} catch (InvalidPassphraseException e) {
|
||||||
//TODO
|
// TODO shake
|
||||||
LOG.info("Wrong old password.");
|
LOG.info("Wrong old password.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,12 +81,5 @@ public class ChangePasswordController implements FxController {
|
|||||||
public Vault getVault() {
|
public Vault getVault() {
|
||||||
return vault;
|
return vault;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IntegerProperty passwordStrengthProperty() {
|
|
||||||
return passwordStrength;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPasswordStrength() {
|
|
||||||
return passwordStrength.get();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import dagger.Binds;
|
|||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import dagger.multibindings.IntoMap;
|
import dagger.multibindings.IntoMap;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.stage.Modality;
|
import javafx.stage.Modality;
|
||||||
@@ -14,15 +16,25 @@ import org.cryptomator.ui.common.FxController;
|
|||||||
import org.cryptomator.ui.common.FxControllerKey;
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
import org.cryptomator.ui.common.FxmlScene;
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
|
import org.cryptomator.ui.common.NewPasswordController;
|
||||||
|
import org.cryptomator.ui.common.PasswordStrengthUtil;
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import javax.inject.Provider;
|
import javax.inject.Provider;
|
||||||
|
import java.nio.CharBuffer;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
abstract class ChangePasswordModule {
|
abstract class ChangePasswordModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@ChangePasswordScoped
|
||||||
|
@Named("newPassword")
|
||||||
|
static ObjectProperty<CharSequence> provideNewPasswordProperty() {
|
||||||
|
return new SimpleObjectProperty<>("");
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@ChangePasswordWindow
|
@ChangePasswordWindow
|
||||||
@@ -58,5 +70,12 @@ abstract class ChangePasswordModule {
|
|||||||
@IntoMap
|
@IntoMap
|
||||||
@FxControllerKey(ChangePasswordController.class)
|
@FxControllerKey(ChangePasswordController.class)
|
||||||
abstract FxController bindUnlockController(ChangePasswordController controller);
|
abstract FxController bindUnlockController(ChangePasswordController controller);
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@IntoMap
|
||||||
|
@FxControllerKey(NewPasswordController.class)
|
||||||
|
static FxController provideNewPasswordController(ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater, @Named("newPassword") ObjectProperty<CharSequence> password) {
|
||||||
|
return new NewPasswordController(resourceBundle, strengthRater, password);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,9 +18,11 @@ public enum FxmlFile {
|
|||||||
PREFERENCES("/fxml/preferences.fxml"), //
|
PREFERENCES("/fxml/preferences.fxml"), //
|
||||||
QUIT("/fxml/quit.fxml"), //
|
QUIT("/fxml/quit.fxml"), //
|
||||||
RECOVERYKEY_CREATE("/fxml/recoverykey_create.fxml"), //
|
RECOVERYKEY_CREATE("/fxml/recoverykey_create.fxml"), //
|
||||||
RECOVERYKEY_DISPLAY("/fxml/recoverykey_display.fxml"), //
|
RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), //
|
||||||
REMOVE_VAULT("/fxml/remove_vault.fxml"), //
|
REMOVE_VAULT("/fxml/remove_vault.fxml"), //
|
||||||
UNLOCK("/fxml/unlock.fxml"),
|
UNLOCK("/fxml/unlock.fxml"),
|
||||||
|
UNLOCK_GENERIC_ERROR("/fxml/unlock_generic_error.fxml"), //
|
||||||
|
UNLOCK_INVALID_MOUNT_POINT("/fxml/unlock_invalid_mount_point.fxml"), //
|
||||||
UNLOCK_SUCCESS("/fxml/unlock_success.fxml"), //
|
UNLOCK_SUCCESS("/fxml/unlock_success.fxml"), //
|
||||||
VAULT_OPTIONS("/fxml/vault_options.fxml"), //
|
VAULT_OPTIONS("/fxml/vault_options.fxml"), //
|
||||||
WRONGFILEALERT("/fxml/wrongfilealert.fxml");
|
WRONGFILEALERT("/fxml/wrongfilealert.fxml");
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package org.cryptomator.ui.common;
|
||||||
|
|
||||||
|
import javafx.beans.Observable;
|
||||||
|
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.Label;
|
||||||
|
import org.cryptomator.ui.controls.FontAwesome5IconView;
|
||||||
|
import org.cryptomator.ui.controls.NiceSecurePasswordField;
|
||||||
|
import org.fxmisc.easybind.EasyBind;
|
||||||
|
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
public class NewPasswordController implements FxController {
|
||||||
|
|
||||||
|
private final ResourceBundle resourceBundle;
|
||||||
|
private final PasswordStrengthUtil strengthRater;
|
||||||
|
private final ObjectProperty<CharSequence> password;
|
||||||
|
private final IntegerProperty passwordStrength = new SimpleIntegerProperty(-1);
|
||||||
|
|
||||||
|
public NiceSecurePasswordField passwordField;
|
||||||
|
public NiceSecurePasswordField reenterField;
|
||||||
|
public Label passwordStrengthLabel;
|
||||||
|
public Label passwordMatchLabel;
|
||||||
|
public FontAwesome5IconView checkmark;
|
||||||
|
public FontAwesome5IconView cross;
|
||||||
|
|
||||||
|
public NewPasswordController(ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater, ObjectProperty<CharSequence> password) {
|
||||||
|
this.resourceBundle = resourceBundle;
|
||||||
|
this.strengthRater = strengthRater;
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void initialize() {
|
||||||
|
BooleanBinding passwordsMatch = Bindings.createBooleanBinding(this::hasSamePasswordInBothFields, passwordField.textProperty(), reenterField.textProperty());
|
||||||
|
BooleanBinding reenterFieldNotEmpty = reenterField.textProperty().isNotEmpty();
|
||||||
|
passwordStrength.bind(Bindings.createIntegerBinding(() -> strengthRater.computeRate(passwordField.getCharacters()), passwordField.textProperty()));
|
||||||
|
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
|
||||||
|
|
||||||
|
passwordMatchLabel.visibleProperty().bind(reenterFieldNotEmpty);
|
||||||
|
passwordMatchLabel.graphicProperty().bind(Bindings.when(passwordsMatch.and(reenterFieldNotEmpty)).then(checkmark).otherwise(cross));
|
||||||
|
passwordMatchLabel.textProperty().bind(Bindings.when(passwordsMatch.and(reenterFieldNotEmpty)).then(resourceBundle.getString("newPassword.passwordsMatch")).otherwise(resourceBundle.getString("newPassword.passwordsDoNotMatch")));
|
||||||
|
|
||||||
|
passwordField.textProperty().addListener(this::passwordsDidChange);
|
||||||
|
reenterField.textProperty().addListener(this::passwordsDidChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void passwordsDidChange(@SuppressWarnings("unused") Observable observable) {
|
||||||
|
if (hasSamePasswordInBothFields() && strengthRater.fulfillsMinimumRequirements(passwordField.getCharacters())) {
|
||||||
|
password.set(passwordField.getCharacters());
|
||||||
|
} else {
|
||||||
|
password.set("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasSamePasswordInBothFields() {
|
||||||
|
return CharSequence.compare(passwordField.getCharacters(), reenterField.getCharacters()) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Getter/Setter */
|
||||||
|
|
||||||
|
public IntegerProperty passwordStrengthProperty() {
|
||||||
|
return passwordStrength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPasswordStrength() {
|
||||||
|
return passwordStrength.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -8,12 +8,11 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui.common;
|
package org.cryptomator.ui.common;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import com.nulabinc.zxcvbn.Zxcvbn;
|
import com.nulabinc.zxcvbn.Zxcvbn;
|
||||||
|
import org.cryptomator.common.Environment;
|
||||||
import org.cryptomator.ui.fxapp.FxApplicationScoped;
|
import org.cryptomator.ui.fxapp.FxApplicationScoped;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
@@ -22,30 +21,37 @@ public class PasswordStrengthUtil {
|
|||||||
|
|
||||||
private static final int PW_TRUNC_LEN = 100; // truncate very long passwords, since zxcvbn memory and runtime depends vastly on the length
|
private static final int PW_TRUNC_LEN = 100; // truncate very long passwords, since zxcvbn memory and runtime depends vastly on the length
|
||||||
private static final String RESSOURCE_PREFIX = "passwordStrength.messageLabel.";
|
private static final String RESSOURCE_PREFIX = "passwordStrength.messageLabel.";
|
||||||
|
private static final List<String> SANITIZED_INPUTS = List.of("cryptomator");
|
||||||
|
|
||||||
private final Zxcvbn zxcvbn;
|
|
||||||
private final List<String> sanitizedInputs;
|
|
||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
|
private final int minPwLength;
|
||||||
|
private final Zxcvbn zxcvbn;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public PasswordStrengthUtil(ResourceBundle resourceBundle) {
|
public PasswordStrengthUtil(ResourceBundle resourceBundle, Environment environment) {
|
||||||
this.resourceBundle = resourceBundle;
|
this.resourceBundle = resourceBundle;
|
||||||
|
this.minPwLength = environment.getMinPwLength();
|
||||||
this.zxcvbn = new Zxcvbn();
|
this.zxcvbn = new Zxcvbn();
|
||||||
this.sanitizedInputs = List.of("cryptomator");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int computeRate(String password) {
|
public boolean fulfillsMinimumRequirements(CharSequence password) {
|
||||||
if (Strings.isNullOrEmpty(password)) {
|
return password.length() >= minPwLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int computeRate(CharSequence password) {
|
||||||
|
if (password == null || password.length() < minPwLength) {
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
int numCharsToRate = Math.min(PW_TRUNC_LEN, password.length());
|
int numCharsToRate = Math.min(PW_TRUNC_LEN, password.length());
|
||||||
return zxcvbn.measure(password.substring(0, numCharsToRate), sanitizedInputs).getScore();
|
return zxcvbn.measure(password.subSequence(0, numCharsToRate), SANITIZED_INPUTS).getScore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getStrengthDescription(Number score) {
|
public String getStrengthDescription(Number score) {
|
||||||
if (resourceBundle.containsKey(RESSOURCE_PREFIX + score.intValue())) {
|
if (score.intValue() == -1) {
|
||||||
return resourceBundle.getString("passwordStrength.messageLabel." + score.intValue());
|
return String.format(resourceBundle.getString(RESSOURCE_PREFIX + "tooShort"), minPwLength);
|
||||||
|
} else if (resourceBundle.containsKey(RESSOURCE_PREFIX + score.intValue())) {
|
||||||
|
return resourceBundle.getString(RESSOURCE_PREFIX + score.intValue());
|
||||||
} else {
|
} else {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package org.cryptomator.ui.common;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class StackTraceController implements FxController {
|
||||||
|
|
||||||
|
private final String stackTrace;
|
||||||
|
|
||||||
|
public StackTraceController(Exception cause) {
|
||||||
|
this.stackTrace = provideStackTrace(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String provideStackTrace(Exception cause) {
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
cause.printStackTrace(new PrintStream(baos));
|
||||||
|
return baos.toString(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Getter/Setter */
|
||||||
|
|
||||||
|
public String getStackTrace() {
|
||||||
|
return stackTrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
package org.cryptomator.ui.common;
|
||||||
|
|
||||||
|
import javafx.concurrent.Task;
|
||||||
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.common.vaults.VaultState;
|
||||||
|
import org.cryptomator.common.vaults.Volume;
|
||||||
|
import org.cryptomator.ui.fxapp.FxApplicationScoped;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@FxApplicationScoped
|
||||||
|
public class VaultService {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(VaultService.class);
|
||||||
|
|
||||||
|
private final ExecutorService executorService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public VaultService(ExecutorService executorService) {
|
||||||
|
this.executorService = executorService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reveal(Vault vault) {
|
||||||
|
executorService.execute(createRevealTask(vault));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates but doesn't start a reveal task.
|
||||||
|
*
|
||||||
|
* @param vault The vault to reveal
|
||||||
|
*/
|
||||||
|
public Task<Vault> createRevealTask(Vault vault) {
|
||||||
|
Task<Vault> task = new RevealVaultTask(vault);
|
||||||
|
task.setOnSucceeded(evt -> LOG.info("Revealed {}", vault.getDisplayableName()));
|
||||||
|
task.setOnFailed(evt -> LOG.error("Failed to reveal " + vault.getDisplayableName(), evt.getSource().getException()));
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locks a vault in a background thread.
|
||||||
|
*
|
||||||
|
* @param vault The vault to lock
|
||||||
|
* @param forced Whether to attempt a forced lock
|
||||||
|
*/
|
||||||
|
public void lock(Vault vault, boolean forced) {
|
||||||
|
executorService.execute(createLockTask(vault, forced));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates but doesn't start a lock task.
|
||||||
|
*
|
||||||
|
* @param vault The vault to lock
|
||||||
|
* @param forced Whether to attempt a forced lock
|
||||||
|
*/
|
||||||
|
public Task<Vault> createLockTask(Vault vault, boolean forced) {
|
||||||
|
Task<Vault> task = new LockVaultTask(vault, forced);
|
||||||
|
task.setOnSucceeded(evt -> LOG.info("Locked {}", vault.getDisplayableName()));
|
||||||
|
task.setOnFailed(evt -> LOG.error("Failed to lock " + vault.getDisplayableName(), evt.getSource().getException()));
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locks all given vaults in a background thread.
|
||||||
|
*
|
||||||
|
* @param vaults The vaults to lock
|
||||||
|
* @param forced Whether to attempt a forced lock
|
||||||
|
*/
|
||||||
|
public void lockAll(Collection<Vault> vaults, boolean forced) {
|
||||||
|
executorService.execute(createLockAllTask(vaults, forced));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates but doesn't start a lock-all task.
|
||||||
|
*
|
||||||
|
* @param vaults The list of vaults to be locked
|
||||||
|
* @param forced Whether to attempt a forced lock
|
||||||
|
* @return Meta-Task that waits until all vaults are locked or fails after the first failure of a subtask
|
||||||
|
*/
|
||||||
|
public Task<Collection<Vault>> createLockAllTask(Collection<Vault> vaults, boolean forced) {
|
||||||
|
List<Task<Vault>> lockTasks = vaults.stream().map(v -> new LockVaultTask(v, forced)).collect(Collectors.toUnmodifiableList());
|
||||||
|
lockTasks.forEach(executorService::execute);
|
||||||
|
Task<Collection<Vault>> task = new WaitForTasksTask(lockTasks);
|
||||||
|
String vaultNames = vaults.stream().map(Vault::getDisplayableName).collect(Collectors.joining(", "));
|
||||||
|
task.setOnSucceeded(evt -> LOG.info("Locked {}", vaultNames));
|
||||||
|
task.setOnFailed(evt -> LOG.error("Failed to lock vaults " + vaultNames, evt.getSource().getException()));
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RevealVaultTask extends Task<Vault> {
|
||||||
|
|
||||||
|
private final Vault vault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param vault The vault to lock
|
||||||
|
*/
|
||||||
|
public RevealVaultTask(Vault vault) {
|
||||||
|
this.vault = vault;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Vault call() throws Volume.VolumeException {
|
||||||
|
vault.reveal();
|
||||||
|
return vault;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A task that waits for completion of multiple other tasks
|
||||||
|
*/
|
||||||
|
private static class WaitForTasksTask extends Task<Collection<Vault>> {
|
||||||
|
|
||||||
|
private final Collection<Task<Vault>> startedTasks;
|
||||||
|
|
||||||
|
public WaitForTasksTask(Collection<Task<Vault>> tasks) {
|
||||||
|
this.startedTasks = List.copyOf(tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Collection<Vault> call() throws Exception {
|
||||||
|
Iterator<Task<Vault>> remainingTasks = startedTasks.iterator();
|
||||||
|
Collection<Vault> completed = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
// wait for all tasks:
|
||||||
|
while (remainingTasks.hasNext()) {
|
||||||
|
Vault lockedVault = remainingTasks.next().get();
|
||||||
|
completed.add(lockedVault);
|
||||||
|
}
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
// cancel all remaining:
|
||||||
|
while (remainingTasks.hasNext()) {
|
||||||
|
remainingTasks.next().cancel(true);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
return List.copyOf(completed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A task that locks a vault
|
||||||
|
*/
|
||||||
|
private static class LockVaultTask extends Task<Vault> {
|
||||||
|
|
||||||
|
private final Vault vault;
|
||||||
|
private final boolean forced;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param vault The vault to lock
|
||||||
|
* @param forced Whether to attempt a forced lock
|
||||||
|
*/
|
||||||
|
public LockVaultTask(Vault vault, boolean forced) {
|
||||||
|
this.vault = vault;
|
||||||
|
this.forced = forced;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Vault call() throws Volume.VolumeException {
|
||||||
|
vault.lock(forced);
|
||||||
|
return vault;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void scheduled() {
|
||||||
|
vault.setState(VaultState.PROCESSING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void succeeded() {
|
||||||
|
vault.setState(VaultState.LOCKED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void failed() {
|
||||||
|
vault.setState(VaultState.UNLOCKED);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -9,23 +9,29 @@ public enum FontAwesome5Icon {
|
|||||||
CHECK("\uF00C"), //
|
CHECK("\uF00C"), //
|
||||||
COG("\uF013"), //
|
COG("\uF013"), //
|
||||||
COGS("\uF085"), //
|
COGS("\uF085"), //
|
||||||
|
COPY("\uF0C5"), //
|
||||||
EXCLAMATION("\uF12A"),
|
EXCLAMATION("\uF12A"),
|
||||||
|
EXCLAMATION_CIRCLE("\uF06A"), //
|
||||||
EXCLAMATION_TRIANGLE("\uF071"), //
|
EXCLAMATION_TRIANGLE("\uF071"), //
|
||||||
EYE("\uF06E"), //
|
EYE("\uF06E"), //
|
||||||
EYE_SLASH("\uF070"), //
|
EYE_SLASH("\uF070"), //
|
||||||
FILE_IMPORT("\uF56F"), //
|
FILE_IMPORT("\uF56F"), //
|
||||||
FOLDER_OPEN("\uF07C"), //
|
FOLDER_OPEN("\uF07C"), //
|
||||||
|
HAND_HOLDING_HEART("\uF4BE"), //
|
||||||
|
HEART("\uF004"), //
|
||||||
HDD("\uF0A0"), //
|
HDD("\uF0A0"), //
|
||||||
KEY("\uF084"), //
|
KEY("\uF084"), //
|
||||||
|
LINK("\uF0C1"), //
|
||||||
LOCK_ALT("\uF30D"), //
|
LOCK_ALT("\uF30D"), //
|
||||||
LOCK_OPEN_ALT("\uF3C2"), //
|
LOCK_OPEN_ALT("\uF3C2"), //
|
||||||
MINUS("\uF068"), //
|
|
||||||
PLUS("\uF067"), //
|
PLUS("\uF067"), //
|
||||||
|
PRINT("\uF02F"), //
|
||||||
QUESTION("\uF128"), //
|
QUESTION("\uF128"), //
|
||||||
SPARKLES("\uF890"), //
|
SPARKLES("\uF890"), //
|
||||||
SPINNER("\uF110"), //
|
SPINNER("\uF110"), //
|
||||||
SYNC("\uF021"), //
|
SYNC("\uF021"), //
|
||||||
TIMES("\uF00D"), //
|
TIMES("\uF00D"), //
|
||||||
|
USER_CROWN("\uF6A4"), //
|
||||||
WRENCH("\uF0AD"), //
|
WRENCH("\uF0AD"), //
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ public class NiceSecurePasswordField extends StackPane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void swipe() {
|
public void swipe() {
|
||||||
passwordField.swipe();;
|
passwordField.swipe();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void selectAll() {
|
public void selectAll() {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import javafx.beans.value.ObservableValue;
|
|||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableSet;
|
import javafx.collections.ObservableSet;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import org.cryptomator.common.LicenseHolder;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.UiTheme;
|
import org.cryptomator.common.settings.UiTheme;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
@@ -16,8 +17,10 @@ import org.cryptomator.jni.JniException;
|
|||||||
import org.cryptomator.jni.MacApplicationUiAppearance;
|
import org.cryptomator.jni.MacApplicationUiAppearance;
|
||||||
import org.cryptomator.jni.MacApplicationUiState;
|
import org.cryptomator.jni.MacApplicationUiState;
|
||||||
import org.cryptomator.jni.MacFunctions;
|
import org.cryptomator.jni.MacFunctions;
|
||||||
|
import org.cryptomator.ui.common.VaultService;
|
||||||
import org.cryptomator.ui.mainwindow.MainWindowComponent;
|
import org.cryptomator.ui.mainwindow.MainWindowComponent;
|
||||||
import org.cryptomator.ui.preferences.PreferencesComponent;
|
import org.cryptomator.ui.preferences.PreferencesComponent;
|
||||||
|
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
||||||
import org.cryptomator.ui.quit.QuitComponent;
|
import org.cryptomator.ui.quit.QuitComponent;
|
||||||
import org.cryptomator.ui.unlock.UnlockComponent;
|
import org.cryptomator.ui.unlock.UnlockComponent;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -38,17 +41,21 @@ public class FxApplication extends Application {
|
|||||||
private final UnlockComponent.Builder unlockWindowBuilder;
|
private final UnlockComponent.Builder unlockWindowBuilder;
|
||||||
private final QuitComponent.Builder quitWindowBuilder;
|
private final QuitComponent.Builder quitWindowBuilder;
|
||||||
private final Optional<MacFunctions> macFunctions;
|
private final Optional<MacFunctions> macFunctions;
|
||||||
|
private final VaultService vaultService;
|
||||||
|
private final LicenseHolder licenseHolder;
|
||||||
private final ObservableSet<Stage> visibleStages = FXCollections.observableSet();
|
private final ObservableSet<Stage> visibleStages = FXCollections.observableSet();
|
||||||
private final BooleanBinding hasVisibleStages = Bindings.isNotEmpty(visibleStages);
|
private final BooleanBinding hasVisibleStages = Bindings.isNotEmpty(visibleStages);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FxApplication(Settings settings, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, UnlockComponent.Builder unlockWindowBuilder, QuitComponent.Builder quitWindowBuilder, Optional<MacFunctions> macFunctions) {
|
FxApplication(Settings settings, Lazy<MainWindowComponent> mainWindow, Lazy<PreferencesComponent> preferencesWindow, UnlockComponent.Builder unlockWindowBuilder, QuitComponent.Builder quitWindowBuilder, Optional<MacFunctions> macFunctions, VaultService vaultService, LicenseHolder licenseHolder) {
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.mainWindow = mainWindow;
|
this.mainWindow = mainWindow;
|
||||||
this.preferencesWindow = preferencesWindow;
|
this.preferencesWindow = preferencesWindow;
|
||||||
this.unlockWindowBuilder = unlockWindowBuilder;
|
this.unlockWindowBuilder = unlockWindowBuilder;
|
||||||
this.quitWindowBuilder = quitWindowBuilder;
|
this.quitWindowBuilder = quitWindowBuilder;
|
||||||
this.macFunctions = macFunctions;
|
this.macFunctions = macFunctions;
|
||||||
|
this.vaultService = vaultService;
|
||||||
|
this.licenseHolder = licenseHolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
@@ -79,9 +86,9 @@ public class FxApplication extends Application {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void showPreferencesWindow() {
|
public void showPreferencesWindow(SelectedPreferencesTab selectedTab) {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
Stage stage = preferencesWindow.get().showPreferencesWindow();
|
Stage stage = preferencesWindow.get().showPreferencesWindow(selectedTab);
|
||||||
addVisibleStage(stage);
|
addVisibleStage(stage);
|
||||||
LOG.debug("Showing Preferences");
|
LOG.debug("Showing Preferences");
|
||||||
});
|
});
|
||||||
@@ -111,11 +118,16 @@ public class FxApplication extends Application {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public VaultService getVaultService() {
|
||||||
|
return vaultService;
|
||||||
|
}
|
||||||
|
|
||||||
private void themeChanged(@SuppressWarnings("unused") ObservableValue<? extends UiTheme> observable, @SuppressWarnings("unused") UiTheme oldValue, UiTheme newValue) {
|
private void themeChanged(@SuppressWarnings("unused") ObservableValue<? extends UiTheme> observable, @SuppressWarnings("unused") UiTheme oldValue, UiTheme newValue) {
|
||||||
loadSelectedStyleSheet(newValue);
|
loadSelectedStyleSheet(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadSelectedStyleSheet(UiTheme theme) {
|
private void loadSelectedStyleSheet(UiTheme desiredTheme) {
|
||||||
|
UiTheme theme = licenseHolder.isValidLicense() ? desiredTheme : UiTheme.LIGHT;
|
||||||
switch (theme) {
|
switch (theme) {
|
||||||
// case CUSTOM:
|
// case CUSTOM:
|
||||||
// // TODO
|
// // TODO
|
||||||
|
|||||||
@@ -5,8 +5,10 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.cryptomator.ui.fxapp;
|
package org.cryptomator.ui.fxapp;
|
||||||
|
|
||||||
|
import dagger.Binds;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
|
import javafx.application.Application;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
@@ -44,6 +46,9 @@ abstract class FxApplicationModule {
|
|||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract Application bindApplication(FxApplication application);
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
static MainWindowComponent provideMainWindowComponent(MainWindowComponent.Builder builder) {
|
static MainWindowComponent provideMainWindowComponent(MainWindowComponent.Builder builder) {
|
||||||
|
|||||||
@@ -1,30 +1,24 @@
|
|||||||
package org.cryptomator.ui.mainwindow;
|
package org.cryptomator.ui.mainwindow;
|
||||||
|
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.input.DragEvent;
|
||||||
import javafx.scene.input.TransferMode;
|
import javafx.scene.input.TransferMode;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.layout.Pane;
|
|
||||||
import javafx.scene.layout.Region;
|
|
||||||
import javafx.scene.layout.VBox;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
import org.cryptomator.common.vaults.Vault;
|
|
||||||
import org.cryptomator.common.vaults.VaultListManager;
|
import org.cryptomator.common.vaults.VaultListManager;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.fxapp.FxApplication;
|
|
||||||
import org.cryptomator.ui.fxapp.UpdateChecker;
|
|
||||||
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
|
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.nio.file.NoSuchFileException;
|
import java.nio.file.NoSuchFileException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Collection;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
@MainWindowScoped
|
@MainWindowScoped
|
||||||
public class MainWindowController implements FxController {
|
public class MainWindowController implements FxController {
|
||||||
@@ -32,27 +26,14 @@ public class MainWindowController implements FxController {
|
|||||||
private static final Logger LOG = LoggerFactory.getLogger(MainWindowController.class);
|
private static final Logger LOG = LoggerFactory.getLogger(MainWindowController.class);
|
||||||
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; // TODO: deduplicate constant declared in multiple classes
|
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; // TODO: deduplicate constant declared in multiple classes
|
||||||
|
|
||||||
private final Stage window;
|
|
||||||
private final FxApplication application;
|
|
||||||
private final boolean minimizeToSysTray;
|
|
||||||
private final UpdateChecker updateChecker;
|
|
||||||
private final BooleanBinding updateAvailable;
|
|
||||||
private final VaultListManager vaultListManager;
|
private final VaultListManager vaultListManager;
|
||||||
private final WrongFileAlertComponent.Builder wrongFileAlert;
|
private final WrongFileAlertComponent.Builder wrongFileAlert;
|
||||||
public HBox titleBar;
|
private final BooleanProperty draggingOver = new SimpleBooleanProperty();
|
||||||
public VBox root;
|
private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
|
||||||
public Pane dragAndDropIndicator;
|
public StackPane root;
|
||||||
public Region resizer;
|
|
||||||
private double xOffset;
|
|
||||||
private double yOffset;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public MainWindowController(@MainWindow Stage window, FxApplication application, @Named("trayMenuSupported") boolean minimizeToSysTray, UpdateChecker updateChecker, VaultListManager vaultListManager, WrongFileAlertComponent.Builder wrongFileAlert) {
|
public MainWindowController(VaultListManager vaultListManager, WrongFileAlertComponent.Builder wrongFileAlert) {
|
||||||
this.window = window;
|
|
||||||
this.application = application;
|
|
||||||
this.minimizeToSysTray = minimizeToSysTray;
|
|
||||||
this.updateChecker = updateChecker;
|
|
||||||
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
|
|
||||||
this.vaultListManager = vaultListManager;
|
this.vaultListManager = vaultListManager;
|
||||||
this.wrongFileAlert = wrongFileAlert;
|
this.wrongFileAlert = wrongFileAlert;
|
||||||
}
|
}
|
||||||
@@ -60,77 +41,70 @@ public class MainWindowController implements FxController {
|
|||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
LOG.debug("init MainWindowController");
|
LOG.debug("init MainWindowController");
|
||||||
titleBar.setOnMousePressed(event -> {
|
root.setOnDragEntered(this::handleDragEvent);
|
||||||
xOffset = event.getSceneX();
|
root.setOnDragOver(this::handleDragEvent);
|
||||||
yOffset = event.getSceneY();
|
root.setOnDragDropped(this::handleDragEvent);
|
||||||
});
|
root.setOnDragExited(this::handleDragEvent);
|
||||||
titleBar.setOnMouseDragged(event -> {
|
|
||||||
window.setX(event.getScreenX() - xOffset);
|
|
||||||
window.setY(event.getScreenY() - yOffset);
|
|
||||||
});
|
|
||||||
resizer.setOnMouseDragged(event -> {
|
|
||||||
// we know for a fact that window is borderless. i.e. the scene starts at 0/0 of the window.
|
|
||||||
window.setWidth(event.getSceneX());
|
|
||||||
window.setHeight(event.getSceneY());
|
|
||||||
});
|
|
||||||
updateChecker.automaticallyCheckForUpdatesIfEnabled();
|
|
||||||
dragAndDropIndicator.setVisible(false);
|
|
||||||
root.setOnDragOver(event -> {
|
|
||||||
if (event.getGestureSource() != root && event.getDragboard().hasFiles()) {
|
|
||||||
/* allow for both copying and moving, whatever user chooses */
|
|
||||||
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
|
|
||||||
dragAndDropIndicator.setVisible(true);
|
|
||||||
}
|
|
||||||
event.consume();
|
|
||||||
});
|
|
||||||
root.setOnDragExited(event -> dragAndDropIndicator.setVisible(false));
|
|
||||||
root.setOnDragDropped(event -> {
|
|
||||||
if (event.getGestureSource() != root && event.getDragboard().hasFiles()) {
|
|
||||||
/* allow for both copying and moving, whatever user chooses */
|
|
||||||
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
|
|
||||||
Collection<Vault> vaultPaths = event.getDragboard().getFiles().stream().map(File::toPath).flatMap(this::addVault).collect(Collectors.toSet());
|
|
||||||
if (vaultPaths.isEmpty()) {
|
|
||||||
wrongFileAlert.build().showWrongFileAlertWindow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
event.consume();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream<Vault> addVault(Path pathToVault) {
|
private void handleDragEvent(DragEvent event) {
|
||||||
|
if (DragEvent.DRAG_ENTERED.equals(event.getEventType()) && event.getGestureSource() == null) {
|
||||||
|
draggingOver.set(true);
|
||||||
|
} else if (DragEvent.DRAG_OVER.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
||||||
|
event.acceptTransferModes(TransferMode.ANY);
|
||||||
|
draggingVaultOver.set(event.getDragboard().getFiles().stream().map(File::toPath).anyMatch(this::containsVault));
|
||||||
|
} else if (DragEvent.DRAG_DROPPED.equals(event.getEventType()) && event.getGestureSource() == null && event.getDragboard().hasFiles()) {
|
||||||
|
Set<Path> vaultPaths = event.getDragboard().getFiles().stream().map(File::toPath).filter(this::containsVault).collect(Collectors.toSet());
|
||||||
|
if (vaultPaths.isEmpty()) {
|
||||||
|
wrongFileAlert.build().showWrongFileAlertWindow();
|
||||||
|
} else {
|
||||||
|
vaultPaths.forEach(this::addVault);
|
||||||
|
}
|
||||||
|
event.setDropCompleted(!vaultPaths.isEmpty());
|
||||||
|
event.consume();
|
||||||
|
} else if (DragEvent.DRAG_EXITED.equals(event.getEventType())) {
|
||||||
|
draggingOver.set(false);
|
||||||
|
draggingVaultOver.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean containsVault(Path path) {
|
||||||
|
if (path.getFileName().toString().equals(MASTERKEY_FILENAME)) {
|
||||||
|
return true;
|
||||||
|
} else if (Files.isDirectory(path) && Files.exists(path.resolve(MASTERKEY_FILENAME))) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addVault(Path pathToVault) {
|
||||||
try {
|
try {
|
||||||
if (pathToVault.getFileName().toString().equals(MASTERKEY_FILENAME)) {
|
if (pathToVault.getFileName().toString().equals(MASTERKEY_FILENAME)) {
|
||||||
return Stream.of(vaultListManager.add(pathToVault.getParent()));
|
vaultListManager.add(pathToVault.getParent());
|
||||||
} else {
|
} else {
|
||||||
return Stream.of(vaultListManager.add(pathToVault));
|
vaultListManager.add(pathToVault);
|
||||||
}
|
}
|
||||||
} catch (NoSuchFileException e) {
|
} catch (NoSuchFileException e) {
|
||||||
LOG.debug("Not a vault: {}", pathToVault);
|
LOG.debug("Not a vault: {}", pathToVault);
|
||||||
}
|
}
|
||||||
return Stream.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
public void close() {
|
|
||||||
if (minimizeToSysTray) {
|
|
||||||
window.close();
|
|
||||||
} else {
|
|
||||||
window.setIconified(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
public void showPreferences() {
|
|
||||||
application.showPreferencesWindow();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Getter/Setter */
|
/* Getter/Setter */
|
||||||
|
|
||||||
public BooleanBinding updateAvailableProperty() {
|
public BooleanProperty draggingOverProperty() {
|
||||||
return updateAvailable;
|
return draggingOver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUpdateAvailable() {
|
public boolean isDraggingOver() {
|
||||||
return updateAvailable.get();
|
return draggingOver.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanProperty draggingVaultOverProperty() {
|
||||||
|
return draggingVaultOver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDraggingVaultOver() {
|
||||||
|
return draggingVaultOver.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,16 @@ abstract class MainWindowModule {
|
|||||||
@FxControllerKey(MainWindowController.class)
|
@FxControllerKey(MainWindowController.class)
|
||||||
abstract FxController bindMainWindowController(MainWindowController controller);
|
abstract FxController bindMainWindowController(MainWindowController controller);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FxControllerKey(MainWindowTitleController.class)
|
||||||
|
abstract FxController bindMainWindowTitleController(MainWindowTitleController controller);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FxControllerKey(ResizeController.class)
|
||||||
|
abstract FxController bindResizeController(ResizeController controller);
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FxControllerKey(VaultListController.class)
|
@FxControllerKey(VaultListController.class)
|
||||||
|
|||||||
@@ -17,22 +17,22 @@ public class MainWindowSceneFactory extends DefaultSceneFactory {
|
|||||||
|
|
||||||
protected static final KeyCodeCombination SHORTCUT_N = new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN);
|
protected static final KeyCodeCombination SHORTCUT_N = new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN);
|
||||||
|
|
||||||
private final Lazy<MainWindowController> mainWindowController;
|
private final Lazy<MainWindowTitleController> mainWindowTitleController;
|
||||||
private final Lazy<VaultListController> vaultListController;
|
private final Lazy<VaultListController> vaultListController;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public MainWindowSceneFactory(Settings settings, Lazy<MainWindowController> mainWindowController, Lazy<VaultListController> vaultListController) {
|
public MainWindowSceneFactory(Settings settings, Lazy<MainWindowTitleController> mainWindowTitleController, Lazy<VaultListController> vaultListController) {
|
||||||
super(settings);
|
super(settings);
|
||||||
this.mainWindowController = mainWindowController;
|
this.mainWindowTitleController = mainWindowTitleController;
|
||||||
this.vaultListController = vaultListController;
|
this.vaultListController = vaultListController;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setupDefaultAccelerators(Scene scene, Stage stage) {
|
protected void setupDefaultAccelerators(Scene scene, Stage stage) {
|
||||||
if (SystemUtils.IS_OS_WINDOWS) {
|
if (SystemUtils.IS_OS_WINDOWS) {
|
||||||
scene.getAccelerators().put(ALT_F4, mainWindowController.get()::close);
|
scene.getAccelerators().put(ALT_F4, mainWindowTitleController.get()::close);
|
||||||
} else {
|
} else {
|
||||||
scene.getAccelerators().put(SHORTCUT_W, mainWindowController.get()::close);
|
scene.getAccelerators().put(SHORTCUT_W, mainWindowTitleController.get()::close);
|
||||||
}
|
}
|
||||||
scene.getAccelerators().put(SHORTCUT_N, vaultListController.get()::didClickAddVault);
|
scene.getAccelerators().put(SHORTCUT_N, vaultListController.get()::didClickAddVault);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package org.cryptomator.ui.mainwindow;
|
||||||
|
|
||||||
|
import javafx.beans.binding.BooleanBinding;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import org.cryptomator.common.LicenseHolder;
|
||||||
|
import org.cryptomator.common.vaults.VaultListManager;
|
||||||
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
import org.cryptomator.ui.fxapp.FxApplication;
|
||||||
|
import org.cryptomator.ui.fxapp.UpdateChecker;
|
||||||
|
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
||||||
|
import org.cryptomator.ui.wrongfilealert.WrongFileAlertComponent;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
|
||||||
|
@MainWindowScoped
|
||||||
|
public class MainWindowTitleController implements FxController {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(MainWindowTitleController.class);
|
||||||
|
|
||||||
|
public HBox titleBar;
|
||||||
|
|
||||||
|
private final Stage window;
|
||||||
|
private final FxApplication application;
|
||||||
|
private final boolean minimizeToSysTray;
|
||||||
|
private final UpdateChecker updateChecker;
|
||||||
|
private final BooleanBinding updateAvailable;
|
||||||
|
private final LicenseHolder licenseHolder;
|
||||||
|
|
||||||
|
private double xOffset;
|
||||||
|
private double yOffset;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MainWindowTitleController(@MainWindow Stage window, FxApplication application, @Named("trayMenuSupported") boolean minimizeToSysTray, UpdateChecker updateChecker, LicenseHolder licenseHolder) {
|
||||||
|
this.window = window;
|
||||||
|
this.application = application;
|
||||||
|
this.minimizeToSysTray = minimizeToSysTray;
|
||||||
|
this.updateChecker = updateChecker;
|
||||||
|
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
|
||||||
|
this.licenseHolder = licenseHolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void initialize() {
|
||||||
|
LOG.debug("init MainWindowTitleController");
|
||||||
|
updateChecker.automaticallyCheckForUpdatesIfEnabled();
|
||||||
|
titleBar.setOnMousePressed(event -> {
|
||||||
|
xOffset = event.getSceneX();
|
||||||
|
yOffset = event.getSceneY();
|
||||||
|
});
|
||||||
|
titleBar.setOnMouseDragged(event -> {
|
||||||
|
window.setX(event.getScreenX() - xOffset);
|
||||||
|
window.setY(event.getScreenY() - yOffset);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void close() {
|
||||||
|
if (minimizeToSysTray) {
|
||||||
|
window.close();
|
||||||
|
} else {
|
||||||
|
window.setIconified(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void showPreferences() {
|
||||||
|
application.showPreferencesWindow(SelectedPreferencesTab.ANY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void showDonationKeyPreferences() {
|
||||||
|
application.showPreferencesWindow(SelectedPreferencesTab.DONATION_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Getter/Setter */
|
||||||
|
|
||||||
|
public LicenseHolder getLicenseHolder() {
|
||||||
|
return licenseHolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BooleanBinding updateAvailableProperty() {
|
||||||
|
return updateAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUpdateAvailable() {
|
||||||
|
return updateAvailable.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package org.cryptomator.ui.mainwindow;
|
||||||
|
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
@MainWindow
|
||||||
|
public class ResizeController implements FxController {
|
||||||
|
|
||||||
|
private final Stage window;
|
||||||
|
|
||||||
|
public Region tlResizer;
|
||||||
|
public Region trResizer;
|
||||||
|
public Region blResizer;
|
||||||
|
public Region brResizer;
|
||||||
|
|
||||||
|
private double origX, origY, origW, origH;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ResizeController(@MainWindow Stage window) {
|
||||||
|
this.window = window;
|
||||||
|
// TODO inject settings and save current position and size
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void initialize() {
|
||||||
|
tlResizer.setOnMousePressed(this::startResize);
|
||||||
|
trResizer.setOnMousePressed(this::startResize);
|
||||||
|
blResizer.setOnMousePressed(this::startResize);
|
||||||
|
brResizer.setOnMousePressed(this::startResize);
|
||||||
|
tlResizer.setOnMouseDragged(this::resizeTopLeft);
|
||||||
|
trResizer.setOnMouseDragged(this::resizeTopRight);
|
||||||
|
blResizer.setOnMouseDragged(this::resizeBottomLeft);
|
||||||
|
brResizer.setOnMouseDragged(this::resizeBottomRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startResize(MouseEvent evt) {
|
||||||
|
origX = window.getX();
|
||||||
|
origY = window.getY();
|
||||||
|
origW = window.getWidth();
|
||||||
|
origH = window.getHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resizeTopLeft(MouseEvent evt) {
|
||||||
|
resizeTop(evt);
|
||||||
|
resizeLeft(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resizeTopRight(MouseEvent evt) {
|
||||||
|
resizeTop(evt);
|
||||||
|
resizeRight(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resizeBottomLeft(MouseEvent evt) {
|
||||||
|
resizeBottom(evt);
|
||||||
|
resizeLeft(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resizeBottomRight(MouseEvent evt) {
|
||||||
|
resizeBottom(evt);
|
||||||
|
resizeRight(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resizeTop(MouseEvent evt) {
|
||||||
|
double newY = evt.getScreenY();
|
||||||
|
double dy = newY - origY;
|
||||||
|
double newH = origH - dy;
|
||||||
|
if (newH < window.getMaxHeight() && newH > window.getMinHeight()) {
|
||||||
|
window.setY(newY);
|
||||||
|
window.setHeight(newH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resizeLeft(MouseEvent evt) {
|
||||||
|
double newX = evt.getScreenX();
|
||||||
|
double dx = newX - origX;
|
||||||
|
double newW = origW - dx;
|
||||||
|
if (newW < window.getMaxWidth() && newW > window.getMinWidth()) {
|
||||||
|
window.setX(newX);
|
||||||
|
window.setWidth(newW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resizeBottom(MouseEvent evt) {
|
||||||
|
double newH = evt.getSceneY();
|
||||||
|
if (newH < window.getMaxHeight() && newH > window.getMinHeight()) {
|
||||||
|
window.setHeight(newH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resizeRight(MouseEvent evt) {
|
||||||
|
double newW = evt.getSceneX();
|
||||||
|
if (newW < window.getMaxWidth() && newW > window.getMinWidth()) {
|
||||||
|
window.setWidth(newW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+7
-28
@@ -4,53 +4,32 @@ import javafx.beans.property.ObjectProperty;
|
|||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultState;
|
|
||||||
import org.cryptomator.common.vaults.Volume;
|
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.Tasks;
|
import org.cryptomator.ui.common.VaultService;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
|
|
||||||
@MainWindowScoped
|
@MainWindowScoped
|
||||||
public class VaultDetailUnlockedController implements FxController {
|
public class VaultDetailUnlockedController implements FxController {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(VaultDetailUnlockedController.class);
|
|
||||||
|
|
||||||
private final ReadOnlyObjectProperty<Vault> vault;
|
private final ReadOnlyObjectProperty<Vault> vault;
|
||||||
private final ExecutorService executor;
|
private final VaultService vaultService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public VaultDetailUnlockedController(ObjectProperty<Vault> vault, ExecutorService executor) {
|
public VaultDetailUnlockedController(ObjectProperty<Vault> vault, VaultService vaultService) {
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
this.executor = executor;
|
this.vaultService = vaultService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void revealAccessLocation() {
|
public void revealAccessLocation() {
|
||||||
try {
|
vaultService.reveal(vault.get());
|
||||||
vault.get().reveal();
|
|
||||||
} catch (Volume.VolumeException e) {
|
|
||||||
LOG.error("Failed to reveal vault.", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void lock() {
|
public void lock() {
|
||||||
Vault v = vault.get();
|
vaultService.lock(vault.get(), false);
|
||||||
v.setState(VaultState.PROCESSING);
|
// TODO count lock attempts, and allow forced lock
|
||||||
Tasks.create(() -> {
|
|
||||||
v.lock(false);
|
|
||||||
}).onSuccess(() -> {
|
|
||||||
LOG.trace("Regular unmount succeeded.");
|
|
||||||
v.setState(VaultState.LOCKED);
|
|
||||||
}).onError(Exception.class, e -> {
|
|
||||||
v.setState(VaultState.UNLOCKED);
|
|
||||||
LOG.error("Regular unmount failed.", e);
|
|
||||||
// TODO
|
|
||||||
}).runOnce(executor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Getter/Setter */
|
/* Getter/Setter */
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
package org.cryptomator.ui.mainwindow;
|
package org.cryptomator.ui.mainwindow;
|
||||||
|
|
||||||
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.binding.BooleanBinding;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.collections.ListChangeListener;
|
import javafx.collections.ListChangeListener;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.ListView;
|
import javafx.scene.control.ListView;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.common.vaults.VaultListManager;
|
||||||
|
import org.cryptomator.common.vaults.VaultState;
|
||||||
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
|
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.removevault.RemoveVaultComponent;
|
import org.cryptomator.ui.removevault.RemoveVaultComponent;
|
||||||
@@ -23,7 +27,6 @@ public class VaultListController implements FxController {
|
|||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(VaultListController.class);
|
private static final Logger LOG = LoggerFactory.getLogger(VaultListController.class);
|
||||||
|
|
||||||
private final Stage window;
|
|
||||||
private final ObservableList<Vault> vaults;
|
private final ObservableList<Vault> vaults;
|
||||||
private final ObjectProperty<Vault> selectedVault;
|
private final ObjectProperty<Vault> selectedVault;
|
||||||
private final VaultListCellFactory cellFactory;
|
private final VaultListCellFactory cellFactory;
|
||||||
@@ -34,8 +37,7 @@ public class VaultListController implements FxController {
|
|||||||
public ListView<Vault> vaultList;
|
public ListView<Vault> vaultList;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
VaultListController(@MainWindow Stage window, ObservableList<Vault> vaults, ObjectProperty<Vault> selectedVault, VaultListCellFactory cellFactory, AddVaultWizardComponent.Builder addVaultWizard, RemoveVaultComponent.Builder removeVault) {
|
VaultListController(ObservableList<Vault> vaults, ObjectProperty<Vault> selectedVault, VaultListCellFactory cellFactory, AddVaultWizardComponent.Builder addVaultWizard, RemoveVaultComponent.Builder removeVault) {
|
||||||
this.window = window;
|
|
||||||
this.vaults = vaults;
|
this.vaults = vaults;
|
||||||
this.selectedVault = selectedVault;
|
this.selectedVault = selectedVault;
|
||||||
this.cellFactory = cellFactory;
|
this.cellFactory = cellFactory;
|
||||||
@@ -43,6 +45,7 @@ public class VaultListController implements FxController {
|
|||||||
this.removeVault = removeVault;
|
this.removeVault = removeVault;
|
||||||
this.noVaultSelected = selectedVault.isNull();
|
this.noVaultSelected = selectedVault.isNull();
|
||||||
this.emptyVaultList = Bindings.isEmpty(vaults);
|
this.emptyVaultList = Bindings.isEmpty(vaults);
|
||||||
|
selectedVault.addListener(this::selectedVaultDidChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
@@ -59,6 +62,23 @@ public class VaultListController implements FxController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void selectedVaultDidChange(@SuppressWarnings("unused") ObservableValue<? extends Vault> observableValue, @SuppressWarnings("unused") Vault oldValue, Vault newValue) {
|
||||||
|
VaultState reportedState = newValue.getState();
|
||||||
|
switch (reportedState) {
|
||||||
|
case LOCKED:
|
||||||
|
case NEEDS_MIGRATION:
|
||||||
|
case MISSING:
|
||||||
|
VaultState determinedState = VaultListManager.determineVaultState(newValue.getPath());
|
||||||
|
newValue.setState(determinedState);
|
||||||
|
break;
|
||||||
|
case ERROR:
|
||||||
|
case UNLOCKED:
|
||||||
|
case PROCESSING:
|
||||||
|
default:
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void didClickAddVault() {
|
public void didClickAddVault() {
|
||||||
addVaultWizard.build().showAddVaultWizard();
|
addVaultWizard.build().showAddVaultWizard();
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package org.cryptomator.ui.preferences;
|
||||||
|
|
||||||
|
import org.cryptomator.jni.MacFunctions;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.CompletionStage;
|
||||||
|
|
||||||
|
class AutoStartMacStrategy implements AutoStartStrategy {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(AutoStartMacStrategy.class);
|
||||||
|
|
||||||
|
private final MacFunctions macFunctions;
|
||||||
|
|
||||||
|
public AutoStartMacStrategy(MacFunctions macFunctions) {
|
||||||
|
this.macFunctions = macFunctions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletionStage<Boolean> isAutoStartEnabled() {
|
||||||
|
boolean enabled = macFunctions.launchServices().isLoginItemEnabled();
|
||||||
|
return CompletableFuture.completedFuture(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enableAutoStart() throws TogglingAutoStartFailedException {
|
||||||
|
if (macFunctions.launchServices().enableLoginItem()) {
|
||||||
|
LOG.debug("Added login item.");
|
||||||
|
} else {
|
||||||
|
throw new TogglingAutoStartFailedException("Failed to add login item.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disableAutoStart() throws TogglingAutoStartFailedException {
|
||||||
|
if (macFunctions.launchServices().disableLoginItem()) {
|
||||||
|
LOG.debug("Removed login item.");
|
||||||
|
} else {
|
||||||
|
throw new TogglingAutoStartFailedException("Failed to remove login item.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package org.cryptomator.ui.preferences;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import org.cryptomator.jni.MacFunctions;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
abstract class AutoStartModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@PreferencesScoped
|
||||||
|
public static Optional<AutoStartStrategy> provideAutoStartStrategy(Optional<MacFunctions> macFunctions) {
|
||||||
|
if (SystemUtils.IS_OS_MAC_OSX && macFunctions.isPresent()) {
|
||||||
|
return Optional.of(new AutoStartMacStrategy(macFunctions.get()));
|
||||||
|
} else if (SystemUtils.IS_OS_WINDOWS) {
|
||||||
|
Optional<String> exeName = ProcessHandle.current().info().command();
|
||||||
|
return exeName.map(AutoStartWinStrategy::new);
|
||||||
|
} else {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package org.cryptomator.ui.preferences;
|
||||||
|
|
||||||
|
import java.util.concurrent.CompletionStage;
|
||||||
|
|
||||||
|
public interface AutoStartStrategy {
|
||||||
|
|
||||||
|
CompletionStage<Boolean> isAutoStartEnabled();
|
||||||
|
|
||||||
|
void enableAutoStart() throws TogglingAutoStartFailedException;
|
||||||
|
|
||||||
|
void disableAutoStart() throws TogglingAutoStartFailedException;
|
||||||
|
|
||||||
|
class TogglingAutoStartFailedException extends Exception {
|
||||||
|
|
||||||
|
public TogglingAutoStartFailedException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TogglingAutoStartFailedException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package org.cryptomator.ui.preferences;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.CompletionStage;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
class AutoStartWinStrategy implements AutoStartStrategy {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(AutoStartWinStrategy.class);
|
||||||
|
private static final String HKCU_AUTOSTART_KEY = "\"HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\"";
|
||||||
|
private static final String AUTOSTART_VALUE = "Cryptomator";
|
||||||
|
private final String exePath;
|
||||||
|
|
||||||
|
public AutoStartWinStrategy(String exePath) {
|
||||||
|
this.exePath = exePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompletionStage<Boolean> isAutoStartEnabled() {
|
||||||
|
ProcessBuilder regQuery = new ProcessBuilder("reg", "query", HKCU_AUTOSTART_KEY, //
|
||||||
|
"/v", AUTOSTART_VALUE);
|
||||||
|
try {
|
||||||
|
Process proc = regQuery.start();
|
||||||
|
return proc.onExit().thenApply(p -> p.exitValue() == 0);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.warn("Failed to query {} from registry key {}", AUTOSTART_VALUE, HKCU_AUTOSTART_KEY);
|
||||||
|
return CompletableFuture.completedFuture(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void enableAutoStart() throws TogglingAutoStartFailedException {
|
||||||
|
ProcessBuilder regAdd = new ProcessBuilder("reg", "add", HKCU_AUTOSTART_KEY, //
|
||||||
|
"/v", AUTOSTART_VALUE, //
|
||||||
|
"/t", "REG_SZ", //
|
||||||
|
"/d", "\"" + exePath + "\"", //
|
||||||
|
"/f");
|
||||||
|
String command = regAdd.command().stream().collect(Collectors.joining(" "));
|
||||||
|
try {
|
||||||
|
Process proc = regAdd.start();
|
||||||
|
boolean finishedInTime = waitForProcess(proc, 5, TimeUnit.SECONDS);
|
||||||
|
if (finishedInTime) {
|
||||||
|
LOG.debug("Added {} to registry key {}.", AUTOSTART_VALUE, HKCU_AUTOSTART_KEY);
|
||||||
|
} else {
|
||||||
|
throw new TogglingAutoStartFailedException("Adding registry value failed.");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new TogglingAutoStartFailedException("Adding registry value failed. " + command, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disableAutoStart() throws TogglingAutoStartFailedException {
|
||||||
|
ProcessBuilder regRemove = new ProcessBuilder("reg", "delete", HKCU_AUTOSTART_KEY, //
|
||||||
|
"/v", AUTOSTART_VALUE, //
|
||||||
|
"/f");
|
||||||
|
String command = regRemove.command().stream().collect(Collectors.joining(" "));
|
||||||
|
try {
|
||||||
|
Process proc = regRemove.start();
|
||||||
|
boolean finishedInTime = waitForProcess(proc, 5, TimeUnit.SECONDS);
|
||||||
|
if (finishedInTime) {
|
||||||
|
LOG.debug("Removed {} from registry key {}.", AUTOSTART_VALUE, HKCU_AUTOSTART_KEY);
|
||||||
|
} else {
|
||||||
|
throw new TogglingAutoStartFailedException("Removing registry value failed.");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new TogglingAutoStartFailedException("Removing registry value failed. " + command, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean waitForProcess(Process proc, int timeout, TimeUnit timeUnit) {
|
||||||
|
boolean finishedInTime = false;
|
||||||
|
try {
|
||||||
|
finishedInTime = proc.waitFor(timeout, timeUnit);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOG.error("Timeout while reading registry", e);
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
} finally {
|
||||||
|
if (!finishedInTime) {
|
||||||
|
proc.destroyForcibly();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return finishedInTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
package org.cryptomator.ui.preferences;
|
||||||
|
|
||||||
|
import javafx.application.Application;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.TextArea;
|
||||||
|
import org.cryptomator.common.LicenseHolder;
|
||||||
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
@PreferencesScoped
|
||||||
|
public class DonationKeyPreferencesController implements FxController {
|
||||||
|
|
||||||
|
private static final String DONATION_URI = "https://store.cryptomator.org/desktop";
|
||||||
|
|
||||||
|
private final Application application;
|
||||||
|
private final LicenseHolder licenseHolder;
|
||||||
|
public TextArea donationKeyField;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
DonationKeyPreferencesController(Application application, LicenseHolder licenseHolder) {
|
||||||
|
this.application = application;
|
||||||
|
this.licenseHolder = licenseHolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void initialize() {
|
||||||
|
donationKeyField.setText(licenseHolder.getLicenseKey().orElse(null));
|
||||||
|
donationKeyField.textProperty().addListener(this::registrationKeyChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registrationKeyChanged(@SuppressWarnings("unused") ObservableValue<? extends String> observable, @SuppressWarnings("unused") String oldValue, String newValue) {
|
||||||
|
licenseHolder.validateAndStoreLicense(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void getDonationKey() {
|
||||||
|
application.getHostServices().showDocument(DONATION_URI);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LicenseHolder getLicenseHolder() {
|
||||||
|
return licenseHolder;
|
||||||
|
}
|
||||||
|
}
|
||||||
+69
-3
@@ -1,6 +1,9 @@
|
|||||||
package org.cryptomator.ui.preferences;
|
package org.cryptomator.ui.preferences;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.concurrent.Task;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
import javafx.geometry.NodeOrientation;
|
import javafx.geometry.NodeOrientation;
|
||||||
import javafx.scene.control.CheckBox;
|
import javafx.scene.control.CheckBox;
|
||||||
import javafx.scene.control.ChoiceBox;
|
import javafx.scene.control.ChoiceBox;
|
||||||
@@ -8,6 +11,7 @@ import javafx.scene.control.RadioButton;
|
|||||||
import javafx.scene.control.Toggle;
|
import javafx.scene.control.Toggle;
|
||||||
import javafx.scene.control.ToggleGroup;
|
import javafx.scene.control.ToggleGroup;
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
|
import org.cryptomator.common.LicenseHolder;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.settings.UiTheme;
|
import org.cryptomator.common.settings.UiTheme;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
@@ -15,25 +19,38 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
|
||||||
@PreferencesScoped
|
@PreferencesScoped
|
||||||
public class GeneralPreferencesController implements FxController {
|
public class GeneralPreferencesController implements FxController {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(GeneralPreferencesController.class);
|
private static final Logger LOG = LoggerFactory.getLogger(GeneralPreferencesController.class);
|
||||||
|
|
||||||
private final Settings settings;
|
private final Settings settings;
|
||||||
|
private final boolean trayMenuSupported;
|
||||||
|
private final Optional<AutoStartStrategy> autoStartStrategy;
|
||||||
|
private final LicenseHolder licenseHolder;
|
||||||
|
private final ExecutorService executor;
|
||||||
public ChoiceBox<UiTheme> themeChoiceBox;
|
public ChoiceBox<UiTheme> themeChoiceBox;
|
||||||
public CheckBox startHiddenCheckbox;
|
public CheckBox startHiddenCheckbox;
|
||||||
public CheckBox debugModeCheckbox;
|
public CheckBox debugModeCheckbox;
|
||||||
|
public CheckBox autoStartCheckbox;
|
||||||
public ToggleGroup nodeOrientation;
|
public ToggleGroup nodeOrientation;
|
||||||
public RadioButton nodeOrientationLtr;
|
public RadioButton nodeOrientationLtr;
|
||||||
public RadioButton nodeOrientationRtl;
|
public RadioButton nodeOrientationRtl;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
GeneralPreferencesController(Settings settings) {
|
GeneralPreferencesController(Settings settings, @Named("trayMenuSupported") boolean trayMenuSupported, Optional<AutoStartStrategy> autoStartStrategy, LicenseHolder licenseHolder, ExecutorService executor) {
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
|
this.trayMenuSupported = trayMenuSupported;
|
||||||
|
this.autoStartStrategy = autoStartStrategy;
|
||||||
|
this.licenseHolder = licenseHolder;
|
||||||
|
this.executor = executor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
themeChoiceBox.getItems().addAll(UiTheme.values());
|
themeChoiceBox.getItems().addAll(UiTheme.values());
|
||||||
themeChoiceBox.valueProperty().bindBidirectional(settings.theme());
|
themeChoiceBox.valueProperty().bindBidirectional(settings.theme());
|
||||||
@@ -43,9 +60,23 @@ public class GeneralPreferencesController implements FxController {
|
|||||||
|
|
||||||
debugModeCheckbox.selectedProperty().bindBidirectional(settings.debugMode());
|
debugModeCheckbox.selectedProperty().bindBidirectional(settings.debugMode());
|
||||||
|
|
||||||
nodeOrientation.selectedToggleProperty().addListener(this::toggleNodeOrientation);
|
autoStartStrategy.ifPresent(autoStart -> {
|
||||||
|
autoStart.isAutoStartEnabled().thenAccept(enabled -> {
|
||||||
|
Platform.runLater(() -> autoStartCheckbox.setSelected(enabled));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
nodeOrientationLtr.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.LEFT_TO_RIGHT);
|
nodeOrientationLtr.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.LEFT_TO_RIGHT);
|
||||||
nodeOrientationRtl.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.RIGHT_TO_LEFT);
|
nodeOrientationRtl.setSelected(settings.userInterfaceOrientation().get() == NodeOrientation.RIGHT_TO_LEFT);
|
||||||
|
nodeOrientation.selectedToggleProperty().addListener(this::toggleNodeOrientation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTrayMenuSupported() {
|
||||||
|
return this.trayMenuSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAutoStartSupported() {
|
||||||
|
return autoStartStrategy.isPresent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toggleNodeOrientation(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
|
private void toggleNodeOrientation(@SuppressWarnings("unused") ObservableValue<? extends Toggle> observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) {
|
||||||
@@ -58,6 +89,20 @@ public class GeneralPreferencesController implements FxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void toggleAutoStart() {
|
||||||
|
autoStartStrategy.ifPresent(autoStart -> {
|
||||||
|
boolean enableAutoStart = autoStartCheckbox.isSelected();
|
||||||
|
Task<Void> toggleTask = new ToggleAutoStartTask(autoStart, enableAutoStart);
|
||||||
|
toggleTask.setOnFailed(evt -> autoStartCheckbox.setSelected(!enableAutoStart)); // restore previous state
|
||||||
|
executor.execute(toggleTask);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public LicenseHolder getLicenseHolder() {
|
||||||
|
return licenseHolder;
|
||||||
|
}
|
||||||
|
|
||||||
/* Helper classes */
|
/* Helper classes */
|
||||||
|
|
||||||
private static class UiThemeConverter extends StringConverter<UiTheme> {
|
private static class UiThemeConverter extends StringConverter<UiTheme> {
|
||||||
@@ -73,4 +118,25 @@ public class GeneralPreferencesController implements FxController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class ToggleAutoStartTask extends Task<Void> {
|
||||||
|
|
||||||
|
private final AutoStartStrategy autoStart;
|
||||||
|
private final boolean enable;
|
||||||
|
|
||||||
|
public ToggleAutoStartTask(AutoStartStrategy autoStart, boolean enable) {
|
||||||
|
this.autoStart = autoStart;
|
||||||
|
this.enable = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void call() throws Exception {
|
||||||
|
if (enable) {
|
||||||
|
autoStart.enableAutoStart();
|
||||||
|
} else {
|
||||||
|
autoStart.disableAutoStart();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ package org.cryptomator.ui.preferences;
|
|||||||
|
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
import dagger.Subcomponent;
|
import dagger.Subcomponent;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
import org.cryptomator.ui.common.FxmlScene;
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
|
|
||||||
@@ -23,7 +23,10 @@ public interface PreferencesComponent {
|
|||||||
@FxmlScene(FxmlFile.PREFERENCES)
|
@FxmlScene(FxmlFile.PREFERENCES)
|
||||||
Lazy<Scene> scene();
|
Lazy<Scene> scene();
|
||||||
|
|
||||||
default Stage showPreferencesWindow() {
|
ObjectProperty<SelectedPreferencesTab> selectedTabProperty();
|
||||||
|
|
||||||
|
default Stage showPreferencesWindow(SelectedPreferencesTab selectedTab) {
|
||||||
|
selectedTabProperty().set(selectedTab);
|
||||||
Stage stage = window();
|
Stage stage = window();
|
||||||
stage.setScene(scene().get());
|
stage.setScene(scene().get());
|
||||||
stage.show();
|
stage.show();
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package org.cryptomator.ui.preferences;
|
package org.cryptomator.ui.preferences;
|
||||||
|
|
||||||
|
import javafx.beans.Observable;
|
||||||
import javafx.beans.binding.BooleanBinding;
|
import javafx.beans.binding.BooleanBinding;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Tab;
|
import javafx.scene.control.Tab;
|
||||||
import javafx.scene.control.TabPane;
|
import javafx.scene.control.TabPane;
|
||||||
@@ -15,26 +18,50 @@ import javax.inject.Inject;
|
|||||||
public class PreferencesController implements FxController {
|
public class PreferencesController implements FxController {
|
||||||
|
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
|
private final ObjectProperty<SelectedPreferencesTab> selectedTabProperty;
|
||||||
private final BooleanBinding updateAvailable;
|
private final BooleanBinding updateAvailable;
|
||||||
public TabPane tabPane;
|
public TabPane tabPane;
|
||||||
public Tab generalTab;
|
public Tab generalTab;
|
||||||
|
public Tab volumeTab;
|
||||||
public Tab updatesTab;
|
public Tab updatesTab;
|
||||||
|
public Tab donationKeyTab;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public PreferencesController(@PreferencesWindow Stage window, UpdateChecker updateChecker) {
|
public PreferencesController(@PreferencesWindow Stage window, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, UpdateChecker updateChecker) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
|
this.selectedTabProperty = selectedTabProperty;
|
||||||
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
|
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
window.setOnShowing(this::windowWillAppear);
|
window.setOnShowing(this::windowWillAppear);
|
||||||
|
selectedTabProperty.addListener(observable -> this.selectChosenTab());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void windowWillAppear(@SuppressWarnings("unused") WindowEvent windowEvent) {
|
private void selectChosenTab() {
|
||||||
if (updateAvailable.get()) {
|
Tab toBeSelected = getTabToSelect(selectedTabProperty.get());
|
||||||
tabPane.getSelectionModel().select(updatesTab);
|
tabPane.getSelectionModel().select(toBeSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Tab getTabToSelect(SelectedPreferencesTab selectedTab) {
|
||||||
|
switch (selectedTab) {
|
||||||
|
case UPDATES:
|
||||||
|
return updatesTab;
|
||||||
|
case VOLUME:
|
||||||
|
return volumeTab;
|
||||||
|
case DONATION_KEY:
|
||||||
|
return donationKeyTab;
|
||||||
|
case GENERAL:
|
||||||
|
return generalTab;
|
||||||
|
case ANY:
|
||||||
|
default:
|
||||||
|
return updateAvailable.get() ? updatesTab : generalTab;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void windowWillAppear(@SuppressWarnings("unused") WindowEvent windowEvent) {
|
||||||
|
selectChosenTab();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import dagger.Binds;
|
|||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import dagger.multibindings.IntoMap;
|
import dagger.multibindings.IntoMap;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
@@ -20,9 +22,15 @@ import java.util.Map;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
@Module
|
@Module(includes = {AutoStartModule.class})
|
||||||
abstract class PreferencesModule {
|
abstract class PreferencesModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@PreferencesScoped
|
||||||
|
static ObjectProperty<SelectedPreferencesTab> provideSelectedTabProperty() {
|
||||||
|
return new SimpleObjectProperty<>(SelectedPreferencesTab.ANY);
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@PreferencesWindow
|
@PreferencesWindow
|
||||||
@PreferencesScoped
|
@PreferencesScoped
|
||||||
@@ -70,4 +78,9 @@ abstract class PreferencesModule {
|
|||||||
@FxControllerKey(VolumePreferencesController.class)
|
@FxControllerKey(VolumePreferencesController.class)
|
||||||
abstract FxController bindVolumePreferencesController(VolumePreferencesController controller);
|
abstract FxController bindVolumePreferencesController(VolumePreferencesController controller);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FxControllerKey(DonationKeyPreferencesController.class)
|
||||||
|
abstract FxController bindDonationKeyPreferencesController(DonationKeyPreferencesController controller);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package org.cryptomator.ui.preferences;
|
||||||
|
|
||||||
|
public enum SelectedPreferencesTab {
|
||||||
|
/**
|
||||||
|
* Let the controller decide which tab to show.
|
||||||
|
*/
|
||||||
|
ANY,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show general tab
|
||||||
|
*/
|
||||||
|
GENERAL,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show volume tab
|
||||||
|
*/
|
||||||
|
VOLUME,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show updates tab
|
||||||
|
*/
|
||||||
|
UPDATES,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show donation key tab
|
||||||
|
*/
|
||||||
|
DONATION_KEY,
|
||||||
|
}
|
||||||
@@ -1,26 +1,22 @@
|
|||||||
package org.cryptomator.ui.quit;
|
package org.cryptomator.ui.quit;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.concurrent.ScheduledService;
|
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.ContentDisplay;
|
import javafx.scene.control.ContentDisplay;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultState;
|
|
||||||
import org.cryptomator.common.vaults.Volume;
|
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
import org.cryptomator.ui.common.VaultService;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import java.awt.desktop.QuitResponse;
|
import java.awt.desktop.QuitResponse;
|
||||||
import java.util.Iterator;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@QuitScoped
|
@QuitScoped
|
||||||
public class QuitController implements FxController {
|
public class QuitController implements FxController {
|
||||||
@@ -30,15 +26,17 @@ public class QuitController implements FxController {
|
|||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final QuitResponse response;
|
private final QuitResponse response;
|
||||||
private final ObservableList<Vault> unlockedVaults;
|
private final ObservableList<Vault> unlockedVaults;
|
||||||
private final ExecutorService executor;
|
private final ExecutorService executorService;
|
||||||
|
private final VaultService vaultService;
|
||||||
public Button lockAndQuitButton;
|
public Button lockAndQuitButton;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
QuitController(@QuitWindow Stage window, QuitResponse response, ObservableList<Vault> vaults, ExecutorService executor) {
|
QuitController(@QuitWindow Stage window, QuitResponse response, ObservableList<Vault> vaults, ExecutorService executorService, VaultService vaultService) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.response = response;
|
this.response = response;
|
||||||
this.unlockedVaults = vaults.filtered(Vault::isUnlocked);
|
this.unlockedVaults = vaults.filtered(Vault::isUnlocked);
|
||||||
this.executor = executor;
|
this.executorService = executorService;
|
||||||
|
this.vaultService = vaultService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -52,84 +50,24 @@ public class QuitController implements FxController {
|
|||||||
public void lockAndQuit() {
|
public void lockAndQuit() {
|
||||||
lockAndQuitButton.setDisable(true);
|
lockAndQuitButton.setDisable(true);
|
||||||
lockAndQuitButton.setContentDisplay(ContentDisplay.LEFT);
|
lockAndQuitButton.setContentDisplay(ContentDisplay.LEFT);
|
||||||
|
|
||||||
Iterator<Vault> toBeLocked = List.copyOf(unlockedVaults).iterator();
|
Task<Collection<Vault>> lockAllTask = vaultService.createLockAllTask(unlockedVaults, false);
|
||||||
ScheduledService<Void> lockAllService = new LockAllVaultsService(executor, toBeLocked);
|
lockAllTask.setOnSucceeded(evt -> {
|
||||||
lockAllService.setOnSucceeded(evt -> {
|
LOG.info("Locked {}", lockAllTask.getValue().stream().map(Vault::getDisplayableName).collect(Collectors.joining(", ")));
|
||||||
if (!toBeLocked.hasNext()) {
|
if (unlockedVaults.isEmpty()) {
|
||||||
window.close();
|
window.close();
|
||||||
response.performQuit();
|
response.performQuit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
lockAllService.setOnFailed(evt -> {
|
lockAllTask.setOnFailed(evt -> {
|
||||||
|
LOG.warn("Locking failed", lockAllTask.getException());
|
||||||
lockAndQuitButton.setDisable(false);
|
lockAndQuitButton.setDisable(false);
|
||||||
lockAndQuitButton.setContentDisplay(ContentDisplay.TEXT_ONLY);
|
lockAndQuitButton.setContentDisplay(ContentDisplay.TEXT_ONLY);
|
||||||
// TODO: show force lock or force quit scene (and DO NOT cancelQuit() here!)
|
// TODO: show force lock or force quit scene (and DO NOT cancelQuit() here!)
|
||||||
// see https://github.com/cryptomator/cryptomator/blob/1.4.16/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java#L151-L163
|
// see https://github.com/cryptomator/cryptomator/blob/1.4.16/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java#L151-L163
|
||||||
response.cancelQuit();
|
response.cancelQuit();
|
||||||
});
|
});
|
||||||
lockAllService.start();
|
executorService.execute(lockAllTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param vault The vault to lock
|
|
||||||
* @return Task that tries to lock the given vault gracefully.
|
|
||||||
*/
|
|
||||||
private Task<Void> createGracefulLockTask(Vault vault) {
|
|
||||||
Task task = new Task<Void>() {
|
|
||||||
@Override
|
|
||||||
protected Void call() throws Volume.VolumeException {
|
|
||||||
vault.lock(false);
|
|
||||||
LOG.info("Locked {}", vault.getDisplayableName());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
task.setOnScheduled(evt -> {
|
|
||||||
vault.setState(VaultState.PROCESSING);
|
|
||||||
});
|
|
||||||
task.setOnSucceeded(evt -> {
|
|
||||||
vault.setState(VaultState.LOCKED);
|
|
||||||
});
|
|
||||||
task.setOnFailed(evt -> {
|
|
||||||
LOG.warn("Failed to lock vault", vault);
|
|
||||||
});
|
|
||||||
return task;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Task that succeeds immediately
|
|
||||||
*/
|
|
||||||
private Task<Void> createNoopTask() {
|
|
||||||
return new Task<>() {
|
|
||||||
@Override
|
|
||||||
protected Void call() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private class LockAllVaultsService extends ScheduledService<Void> {
|
|
||||||
|
|
||||||
private final Iterator<Vault> vaultsToLock;
|
|
||||||
|
|
||||||
public LockAllVaultsService(Executor executor, Iterator<Vault> vaultsToLock) {
|
|
||||||
this.vaultsToLock = vaultsToLock;
|
|
||||||
setExecutor(executor);
|
|
||||||
setRestartOnFailure(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Task<Void> createTask() {
|
|
||||||
assert Platform.isFxApplicationThread();
|
|
||||||
if (vaultsToLock.hasNext()) {
|
|
||||||
return createGracefulLockTask(vaultsToLock.next());
|
|
||||||
} else {
|
|
||||||
// This should be unreachable code, since vaultsToLock is only accessed on the FX App Thread.
|
|
||||||
// But if quitting the application takes longer for any reason, this service should shut down properly
|
|
||||||
reset();
|
|
||||||
return createNoopTask();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -40,7 +40,7 @@ public class RecoveryKeyCreationController implements FxController {
|
|||||||
public NiceSecurePasswordField passwordField;
|
public NiceSecurePasswordField passwordField;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public RecoveryKeyCreationController(@RecoveryKeyWindow Stage window, @FxmlScene(FxmlFile.RECOVERYKEY_DISPLAY) Lazy<Scene> successScene, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey) {
|
public RecoveryKeyCreationController(@RecoveryKeyWindow Stage window, @FxmlScene(FxmlFile.RECOVERYKEY_SUCCESS) Lazy<Scene> successScene, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.successScene = successScene;
|
this.successScene = successScene;
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
|
|||||||
+67
-20
@@ -1,26 +1,73 @@
|
|||||||
package org.cryptomator.ui.recoverykey;
|
package org.cryptomator.ui.recoverykey;
|
||||||
|
|
||||||
import javafx.beans.property.ReadOnlyStringProperty;
|
|
||||||
import javafx.beans.property.StringProperty;
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.print.PageLayout;
|
||||||
|
import javafx.print.Printer;
|
||||||
|
import javafx.print.PrinterJob;
|
||||||
|
import javafx.scene.input.Clipboard;
|
||||||
|
import javafx.scene.input.ClipboardContent;
|
||||||
|
import javafx.scene.text.Font;
|
||||||
|
import javafx.scene.text.FontSmoothingType;
|
||||||
|
import javafx.scene.text.FontWeight;
|
||||||
|
import javafx.scene.text.Text;
|
||||||
|
import javafx.scene.text.TextFlow;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
@RecoveryKeyScoped
|
|
||||||
public class RecoveryKeyDisplayController implements FxController {
|
public class RecoveryKeyDisplayController implements FxController {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyDisplayController.class);
|
||||||
|
|
||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final Vault vault;
|
private final String vaultName;
|
||||||
private final StringProperty recoveryKeyProperty;
|
private final String recoveryKey;
|
||||||
|
|
||||||
@Inject
|
public RecoveryKeyDisplayController(Stage window, String vaultName, String recoveryKey) {
|
||||||
public RecoveryKeyDisplayController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow StringProperty recoveryKey) {
|
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.vault = vault;
|
this.vaultName = vaultName;
|
||||||
this.recoveryKeyProperty = recoveryKey;
|
this.recoveryKey = recoveryKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void printRecoveryKey() {
|
||||||
|
// TODO localize
|
||||||
|
|
||||||
|
PrinterJob job = PrinterJob.createPrinterJob();
|
||||||
|
if (job != null && job.showPrintDialog(window)) {
|
||||||
|
PageLayout pageLayout = job.getJobSettings().getPageLayout();
|
||||||
|
|
||||||
|
Text heading = new Text("Cryptomator Recovery Key\n" + vaultName + "\n");
|
||||||
|
heading.setFont(Font.font("serif", FontWeight.BOLD, 20));
|
||||||
|
heading.setFontSmoothingType(FontSmoothingType.LCD);
|
||||||
|
|
||||||
|
Text key = new Text(recoveryKey);
|
||||||
|
key.setFont(Font.font("serif", FontWeight.NORMAL, 16));
|
||||||
|
key.setFontSmoothingType(FontSmoothingType.GRAY);
|
||||||
|
|
||||||
|
TextFlow textFlow = new TextFlow();
|
||||||
|
textFlow.setPrefSize(pageLayout.getPrintableWidth(), pageLayout.getPrintableHeight());
|
||||||
|
textFlow.getChildren().addAll(heading, key);
|
||||||
|
textFlow.setLineSpacing(6);
|
||||||
|
|
||||||
|
if (job.printPage(textFlow)) {
|
||||||
|
LOG.info("Recovery key printed.");
|
||||||
|
job.endJob();
|
||||||
|
} else {
|
||||||
|
LOG.warn("Printing recovery key failed.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG.info("Printing recovery key canceled by user.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void copyRecoveryKey() {
|
||||||
|
ClipboardContent clipboardContent = new ClipboardContent();
|
||||||
|
clipboardContent.putString(recoveryKey);
|
||||||
|
Clipboard.getSystemClipboard().setContent(clipboardContent);
|
||||||
|
LOG.info("Recovery key copied to clipboard.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -30,15 +77,15 @@ public class RecoveryKeyDisplayController implements FxController {
|
|||||||
|
|
||||||
/* Getter/Setter */
|
/* Getter/Setter */
|
||||||
|
|
||||||
public Vault getVault() {
|
public boolean isPrinterSupported() {
|
||||||
return vault;
|
return Printer.getDefaultPrinter() != null;
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlyStringProperty recoveryKeyProperty() {
|
|
||||||
return recoveryKeyProperty;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRecoveryKey() {
|
public String getRecoveryKey() {
|
||||||
return recoveryKeyProperty.get();
|
return recoveryKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVaultName() {
|
||||||
|
return vaultName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import javafx.scene.Scene;
|
|||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.stage.Modality;
|
import javafx.stage.Modality;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
@@ -63,10 +64,10 @@ abstract class RecoveryKeyModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.RECOVERYKEY_DISPLAY)
|
@FxmlScene(FxmlFile.RECOVERYKEY_SUCCESS)
|
||||||
@RecoveryKeyScoped
|
@RecoveryKeyScoped
|
||||||
static Scene provideRecoveryKeyDisplayScene(@RecoveryKeyWindow FXMLLoaderFactory fxmlLoaders) {
|
static Scene provideRecoveryKeySuccessScene(@RecoveryKeyWindow FXMLLoaderFactory fxmlLoaders) {
|
||||||
return fxmlLoaders.createScene("/fxml/recoverykey_display.fxml");
|
return fxmlLoaders.createScene("/fxml/recoverykey_success.fxml");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
@@ -76,9 +77,16 @@ abstract class RecoveryKeyModule {
|
|||||||
@FxControllerKey(RecoveryKeyCreationController.class)
|
@FxControllerKey(RecoveryKeyCreationController.class)
|
||||||
abstract FxController bindRecoveryKeyCreationController(RecoveryKeyCreationController controller);
|
abstract FxController bindRecoveryKeyCreationController(RecoveryKeyCreationController controller);
|
||||||
|
|
||||||
@Binds
|
@Provides
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@FxControllerKey(RecoveryKeyDisplayController.class)
|
@FxControllerKey(RecoveryKeyDisplayController.class)
|
||||||
abstract FxController bindRecoveryKeyDisplayController(RecoveryKeyDisplayController controller);
|
static FxController provideRecoveryKeyDisplayController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow StringProperty recoveryKey) {
|
||||||
|
return new RecoveryKeyDisplayController(window, vault.getDisplayableName(), recoveryKey.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FxControllerKey(RecoveryKeySuccessController.class)
|
||||||
|
abstract FxController bindRecoveryKeySuccessController(RecoveryKeySuccessController controller);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
package org.cryptomator.ui.recoverykey;
|
||||||
|
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
@RecoveryKeyScoped
|
||||||
|
public class RecoveryKeySuccessController implements FxController {
|
||||||
|
|
||||||
|
private final Stage window;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public RecoveryKeySuccessController(@RecoveryKeyWindow Stage window) {
|
||||||
|
this.window = window;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void close() {
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,11 +3,14 @@ package org.cryptomator.ui.traymenu;
|
|||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.Observable;
|
import javafx.beans.Observable;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
import org.cryptomator.common.ShutdownHook;
|
||||||
import org.cryptomator.common.settings.Settings;
|
import org.cryptomator.common.settings.Settings;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.common.vaults.VaultState;
|
import org.cryptomator.common.vaults.VaultState;
|
||||||
|
import org.cryptomator.common.vaults.Volume;
|
||||||
import org.cryptomator.ui.fxapp.FxApplication;
|
import org.cryptomator.ui.fxapp.FxApplication;
|
||||||
import org.cryptomator.ui.launcher.FxApplicationStarter;
|
import org.cryptomator.ui.launcher.FxApplicationStarter;
|
||||||
|
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -37,17 +40,17 @@ class TrayMenuController {
|
|||||||
private final ResourceBundle resourceBundle;
|
private final ResourceBundle resourceBundle;
|
||||||
private final FxApplicationStarter fxApplicationStarter;
|
private final FxApplicationStarter fxApplicationStarter;
|
||||||
private final CountDownLatch shutdownLatch;
|
private final CountDownLatch shutdownLatch;
|
||||||
private final Settings settings;
|
private final ShutdownHook shutdownHook;
|
||||||
private final ObservableList<Vault> vaults;
|
private final ObservableList<Vault> vaults;
|
||||||
private final PopupMenu menu;
|
private final PopupMenu menu;
|
||||||
private final AtomicBoolean allowSuddenTermination;
|
private final AtomicBoolean allowSuddenTermination;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
TrayMenuController(ResourceBundle resourceBundle, FxApplicationStarter fxApplicationStarter, @Named("shutdownLatch") CountDownLatch shutdownLatch, Settings settings, ObservableList<Vault> vaults) {
|
TrayMenuController(ResourceBundle resourceBundle, FxApplicationStarter fxApplicationStarter, @Named("shutdownLatch") CountDownLatch shutdownLatch, ShutdownHook shutdownHook, ObservableList<Vault> vaults) {
|
||||||
this.resourceBundle = resourceBundle;
|
this.resourceBundle = resourceBundle;
|
||||||
this.fxApplicationStarter = fxApplicationStarter;
|
this.fxApplicationStarter = fxApplicationStarter;
|
||||||
this.shutdownLatch = shutdownLatch;
|
this.shutdownLatch = shutdownLatch;
|
||||||
this.settings = settings;
|
this.shutdownHook = shutdownHook;
|
||||||
this.vaults = vaults;
|
this.vaults = vaults;
|
||||||
this.menu = new PopupMenu();
|
this.menu = new PopupMenu();
|
||||||
this.allowSuddenTermination = new AtomicBoolean(true);
|
this.allowSuddenTermination = new AtomicBoolean(true);
|
||||||
@@ -71,6 +74,7 @@ class TrayMenuController {
|
|||||||
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_QUIT_HANDLER)) {
|
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_QUIT_HANDLER)) {
|
||||||
Desktop.getDesktop().setQuitHandler(this::handleQuitRequest);
|
Desktop.getDesktop().setQuitHandler(this::handleQuitRequest);
|
||||||
}
|
}
|
||||||
|
shutdownHook.runOnShutdown(this::forceUnmountRemainingVaults);
|
||||||
|
|
||||||
// allow sudden termination
|
// allow sudden termination
|
||||||
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_SUDDEN_TERMINATION)) {
|
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_SUDDEN_TERMINATION)) {
|
||||||
@@ -112,6 +116,11 @@ class TrayMenuController {
|
|||||||
}
|
}
|
||||||
menu.addSeparator();
|
menu.addSeparator();
|
||||||
|
|
||||||
|
MenuItem lockAllItem = new MenuItem(resourceBundle.getString("traymenu.lockAllVaults"));
|
||||||
|
lockAllItem.addActionListener(this::lockAllVaults);
|
||||||
|
lockAllItem.setEnabled(!vaults.filtered(Vault::isUnlocked).isEmpty());
|
||||||
|
menu.add(lockAllItem);
|
||||||
|
|
||||||
MenuItem quitApplicationItem = new MenuItem(resourceBundle.getString("traymenu.quitApplication"));
|
MenuItem quitApplicationItem = new MenuItem(resourceBundle.getString("traymenu.quitApplication"));
|
||||||
quitApplicationItem.addActionListener(this::quitApplication);
|
quitApplicationItem.addActionListener(this::quitApplication);
|
||||||
menu.add(quitApplicationItem);
|
menu.add(quitApplicationItem);
|
||||||
@@ -126,11 +135,11 @@ class TrayMenuController {
|
|||||||
submenu.add(unlockItem);
|
submenu.add(unlockItem);
|
||||||
} else if (vault.isUnlocked()) {
|
} else if (vault.isUnlocked()) {
|
||||||
MenuItem lockItem = new MenuItem(resourceBundle.getString("traymenu.vault.lock"));
|
MenuItem lockItem = new MenuItem(resourceBundle.getString("traymenu.vault.lock"));
|
||||||
lockItem.setEnabled(false); // TODO add action listener
|
lockItem.addActionListener(createActionListenerForVault(vault, this::lockVault));
|
||||||
submenu.add(lockItem);
|
submenu.add(lockItem);
|
||||||
|
|
||||||
MenuItem revealItem = new MenuItem(resourceBundle.getString("traymenu.vault.reveal"));
|
MenuItem revealItem = new MenuItem(resourceBundle.getString("traymenu.vault.reveal"));
|
||||||
revealItem.setEnabled(false); // TODO add action listener
|
revealItem.addActionListener(createActionListenerForVault(vault, this::revealVault));
|
||||||
submenu.add(revealItem);
|
submenu.add(revealItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,12 +154,24 @@ class TrayMenuController {
|
|||||||
fxApplicationStarter.get(true).thenAccept(app -> app.showUnlockWindow(vault));
|
fxApplicationStarter.get(true).thenAccept(app -> app.showUnlockWindow(vault));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void lockVault(Vault vault) {
|
||||||
|
fxApplicationStarter.get(true).thenAccept(app -> app.getVaultService().lock(vault, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void lockAllVaults(ActionEvent actionEvent) {
|
||||||
|
fxApplicationStarter.get(true).thenAccept(app -> app.getVaultService().lockAll(vaults.filtered(Vault::isUnlocked), false));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void revealVault(Vault vault) {
|
||||||
|
fxApplicationStarter.get(true).thenAccept(app -> app.getVaultService().reveal(vault));
|
||||||
|
}
|
||||||
|
|
||||||
void showMainWindow(@SuppressWarnings("unused") ActionEvent actionEvent) {
|
void showMainWindow(@SuppressWarnings("unused") ActionEvent actionEvent) {
|
||||||
fxApplicationStarter.get(true).thenAccept(app -> app.showMainWindow());
|
fxApplicationStarter.get(true).thenAccept(app -> app.showMainWindow());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showPreferencesWindow(@SuppressWarnings("unused") EventObject actionEvent) {
|
private void showPreferencesWindow(@SuppressWarnings("unused") EventObject actionEvent) {
|
||||||
fxApplicationStarter.get(true).thenAccept(FxApplication::showPreferencesWindow);
|
fxApplicationStarter.get(true).thenAccept(app -> app.showPreferencesWindow(SelectedPreferencesTab.ANY));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleQuitRequest(EventObject e, QuitResponse response) {
|
private void handleQuitRequest(EventObject e, QuitResponse response) {
|
||||||
@@ -174,4 +195,16 @@ class TrayMenuController {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void forceUnmountRemainingVaults() {
|
||||||
|
for (Vault vault : vaults) {
|
||||||
|
if (vault.isUnlocked()) {
|
||||||
|
try {
|
||||||
|
vault.lock(true);
|
||||||
|
} catch (Volume.VolumeException e) {
|
||||||
|
LOG.error("Failed to unmount vault " + vault.getPath(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import javafx.animation.Timeline;
|
|||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.binding.ObjectBinding;
|
import javafx.beans.binding.ObjectBinding;
|
||||||
import javafx.beans.property.BooleanProperty;
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.beans.value.WritableValue;
|
import javafx.beans.value.WritableValue;
|
||||||
@@ -32,6 +33,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
import java.nio.file.DirectoryNotEmptyException;
|
import java.nio.file.DirectoryNotEmptyException;
|
||||||
import java.nio.file.NotDirectoryException;
|
import java.nio.file.NotDirectoryException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -49,19 +51,25 @@ public class UnlockController implements FxController {
|
|||||||
private final ObjectBinding<ContentDisplay> unlockButtonState;
|
private final ObjectBinding<ContentDisplay> unlockButtonState;
|
||||||
private final Optional<KeychainAccess> keychainAccess;
|
private final Optional<KeychainAccess> keychainAccess;
|
||||||
private final Lazy<Scene> successScene;
|
private final Lazy<Scene> successScene;
|
||||||
|
private final Lazy<Scene> invalidMountPointScene;
|
||||||
|
private final Lazy<Scene> genericErrorScene;
|
||||||
|
private final ObjectProperty<Exception> genericErrorCause;
|
||||||
private final ForgetPasswordComponent.Builder forgetPassword;
|
private final ForgetPasswordComponent.Builder forgetPassword;
|
||||||
private final BooleanProperty unlockButtonDisabled;
|
private final BooleanProperty unlockButtonDisabled;
|
||||||
public NiceSecurePasswordField passwordField;
|
public NiceSecurePasswordField passwordField;
|
||||||
public CheckBox savePassword;
|
public CheckBox savePassword;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public UnlockController(@UnlockWindow Stage window, @UnlockWindow Vault vault, ExecutorService executor, Optional<KeychainAccess> keychainAccess, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, ForgetPasswordComponent.Builder forgetPassword) {
|
public UnlockController(@UnlockWindow Stage window, @UnlockWindow Vault vault, ExecutorService executor, Optional<KeychainAccess> keychainAccess, @FxmlScene(FxmlFile.UNLOCK_SUCCESS) Lazy<Scene> successScene, @FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT) Lazy<Scene> invalidMountPointScene, @FxmlScene(FxmlFile.UNLOCK_GENERIC_ERROR) Lazy<Scene> genericErrorScene, @Named("genericErrorCause") ObjectProperty<Exception> genericErrorCause, ForgetPasswordComponent.Builder forgetPassword) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
this.executor = executor;
|
this.executor = executor;
|
||||||
this.unlockButtonState = Bindings.createObjectBinding(this::getUnlockButtonState, vault.stateProperty());
|
this.unlockButtonState = Bindings.createObjectBinding(this::getUnlockButtonState, vault.stateProperty());
|
||||||
this.keychainAccess = keychainAccess;
|
this.keychainAccess = keychainAccess;
|
||||||
this.successScene = successScene;
|
this.successScene = successScene;
|
||||||
|
this.invalidMountPointScene = invalidMountPointScene;
|
||||||
|
this.genericErrorScene = genericErrorScene;
|
||||||
|
this.genericErrorCause = genericErrorCause;
|
||||||
this.forgetPassword = forgetPassword;
|
this.forgetPassword = forgetPassword;
|
||||||
this.unlockButtonDisabled = new SimpleBooleanProperty();
|
this.unlockButtonDisabled = new SimpleBooleanProperty();
|
||||||
}
|
}
|
||||||
@@ -100,17 +108,16 @@ public class UnlockController implements FxController {
|
|||||||
shakeWindow();
|
shakeWindow();
|
||||||
passwordField.selectAll();
|
passwordField.selectAll();
|
||||||
passwordField.requestFocus();
|
passwordField.requestFocus();
|
||||||
}).onError(UnsupportedVaultFormatException.class, e -> {
|
|
||||||
// TODO
|
|
||||||
}).onError(NotDirectoryException.class, e -> {
|
}).onError(NotDirectoryException.class, e -> {
|
||||||
LOG.error("Unlock failed. Mount point not a directory: {}", e.getMessage());
|
LOG.error("Unlock failed. Mount point not a directory: {}", e.getMessage());
|
||||||
// TODO
|
window.setScene(invalidMountPointScene.get());
|
||||||
}).onError(DirectoryNotEmptyException.class, e -> {
|
}).onError(DirectoryNotEmptyException.class, e -> {
|
||||||
LOG.error("Unlock failed. Mount point not empty: {}", e.getMessage());
|
LOG.error("Unlock failed. Mount point not empty: {}", e.getMessage());
|
||||||
// TODO
|
window.setScene(invalidMountPointScene.get());
|
||||||
}).onError(Exception.class, e -> { // including RuntimeExceptions
|
}).onError(Exception.class, e -> { // including RuntimeExceptions
|
||||||
LOG.error("Unlock failed for technical reasons.", e);
|
LOG.error("Unlock failed for technical reasons.", e);
|
||||||
// TODO
|
genericErrorCause.set(e);
|
||||||
|
window.setScene(genericErrorScene.get());
|
||||||
}).andFinally(() -> {
|
}).andFinally(() -> {
|
||||||
if (!vault.isUnlocked()) {
|
if (!vault.isUnlocked()) {
|
||||||
vault.setState(VaultState.LOCKED);
|
vault.setState(VaultState.LOCKED);
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package org.cryptomator.ui.unlock;
|
||||||
|
|
||||||
|
import dagger.Lazy;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
@UnlockScoped
|
||||||
|
public class UnlockGenericErrorController implements FxController {
|
||||||
|
|
||||||
|
private final Stage window;
|
||||||
|
private final Lazy<Scene> unlockScene;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
UnlockGenericErrorController(@UnlockWindow Stage window, @FxmlScene(FxmlFile.UNLOCK) Lazy<Scene> unlockScene) {
|
||||||
|
this.window = window;
|
||||||
|
this.unlockScene = unlockScene;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void back() {
|
||||||
|
window.setScene(unlockScene.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
package org.cryptomator.ui.unlock;
|
||||||
|
|
||||||
|
import dagger.Lazy;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import org.cryptomator.common.vaults.Vault;
|
||||||
|
import org.cryptomator.ui.common.FxController;
|
||||||
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
@UnlockScoped
|
||||||
|
public class UnlockInvalidMountPointController implements FxController {
|
||||||
|
|
||||||
|
private final Stage window;
|
||||||
|
private final Lazy<Scene> unlockScene;
|
||||||
|
private final Vault vault;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
UnlockInvalidMountPointController(@UnlockWindow Stage window, @FxmlScene(FxmlFile.UNLOCK) Lazy<Scene> unlockScene, @UnlockWindow Vault vault) {
|
||||||
|
this.window = window;
|
||||||
|
this.unlockScene = unlockScene;
|
||||||
|
this.vault = vault;
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void back() {
|
||||||
|
window.setScene(unlockScene.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Getter/Setter */
|
||||||
|
|
||||||
|
public String getMountPoint() {
|
||||||
|
return vault.getVaultSettings().getIndividualMountPath().orElse("AUTO");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,6 +4,8 @@ import dagger.Binds;
|
|||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import dagger.multibindings.IntoMap;
|
import dagger.multibindings.IntoMap;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.stage.Modality;
|
import javafx.stage.Modality;
|
||||||
@@ -14,6 +16,7 @@ import org.cryptomator.ui.common.FxController;
|
|||||||
import org.cryptomator.ui.common.FxControllerKey;
|
import org.cryptomator.ui.common.FxControllerKey;
|
||||||
import org.cryptomator.ui.common.FxmlFile;
|
import org.cryptomator.ui.common.FxmlFile;
|
||||||
import org.cryptomator.ui.common.FxmlScene;
|
import org.cryptomator.ui.common.FxmlScene;
|
||||||
|
import org.cryptomator.ui.common.StackTraceController;
|
||||||
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
|
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
@@ -44,6 +47,13 @@ abstract class UnlockModule {
|
|||||||
return stage;
|
return stage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Named("genericErrorCause")
|
||||||
|
@UnlockScoped
|
||||||
|
static ObjectProperty<Exception> provideGenericErrorCause() {
|
||||||
|
return new SimpleObjectProperty<>();
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FxmlScene(FxmlFile.UNLOCK)
|
@FxmlScene(FxmlFile.UNLOCK)
|
||||||
@UnlockScoped
|
@UnlockScoped
|
||||||
@@ -58,6 +68,21 @@ abstract class UnlockModule {
|
|||||||
return fxmlLoaders.createScene("/fxml/unlock_success.fxml");
|
return fxmlLoaders.createScene("/fxml/unlock_success.fxml");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@FxmlScene(FxmlFile.UNLOCK_INVALID_MOUNT_POINT)
|
||||||
|
@UnlockScoped
|
||||||
|
static Scene provideInvalidMountPointScene(@UnlockWindow FXMLLoaderFactory fxmlLoaders) {
|
||||||
|
return fxmlLoaders.createScene("/fxml/unlock_invalid_mount_point.fxml");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@FxmlScene(FxmlFile.UNLOCK_GENERIC_ERROR)
|
||||||
|
@UnlockScoped
|
||||||
|
static Scene provideGenericErrorScene(@UnlockWindow FXMLLoaderFactory fxmlLoaders) {
|
||||||
|
return fxmlLoaders.createScene("/fxml/unlock_generic_error.fxml");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
|
|
||||||
@@ -71,5 +96,22 @@ abstract class UnlockModule {
|
|||||||
@FxControllerKey(UnlockSuccessController.class)
|
@FxControllerKey(UnlockSuccessController.class)
|
||||||
abstract FxController bindUnlockSuccessController(UnlockSuccessController controller);
|
abstract FxController bindUnlockSuccessController(UnlockSuccessController controller);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FxControllerKey(UnlockInvalidMountPointController.class)
|
||||||
|
abstract FxController bindUnlockInvalidMountPointController(UnlockInvalidMountPointController controller);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@FxControllerKey(UnlockGenericErrorController.class)
|
||||||
|
abstract FxController bindUnlockGenericErrorController(UnlockGenericErrorController controller);
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@IntoMap
|
||||||
|
@FxControllerKey(StackTraceController.class)
|
||||||
|
static FxController provideStackTraceController(@Named("genericErrorCause") ObjectProperty<Exception> errorCause) {
|
||||||
|
return new StackTraceController(errorCause.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ import javafx.beans.property.ObjectProperty;
|
|||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
import javafx.beans.property.SimpleBooleanProperty;
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.concurrent.Task;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.ContentDisplay;
|
import javafx.scene.control.ContentDisplay;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import org.cryptomator.common.vaults.Vault;
|
import org.cryptomator.common.vaults.Vault;
|
||||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
|
||||||
import org.cryptomator.ui.common.FxController;
|
import org.cryptomator.ui.common.FxController;
|
||||||
import org.cryptomator.ui.common.Tasks;
|
import org.cryptomator.ui.common.VaultService;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -26,14 +26,16 @@ public class UnlockSuccessController implements FxController {
|
|||||||
private final Stage window;
|
private final Stage window;
|
||||||
private final Vault vault;
|
private final Vault vault;
|
||||||
private final ExecutorService executor;
|
private final ExecutorService executor;
|
||||||
|
private final VaultService vaultService;
|
||||||
private final ObjectProperty<ContentDisplay> revealButtonState;
|
private final ObjectProperty<ContentDisplay> revealButtonState;
|
||||||
private final BooleanProperty revealButtonDisabled;
|
private final BooleanProperty revealButtonDisabled;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public UnlockSuccessController(@UnlockWindow Stage window, @UnlockWindow Vault vault, ExecutorService executor) {
|
public UnlockSuccessController(@UnlockWindow Stage window, @UnlockWindow Vault vault, ExecutorService executor, VaultService vaultService) {
|
||||||
this.window = window;
|
this.window = window;
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
this.executor = executor;
|
this.executor = executor;
|
||||||
|
this.vaultService = vaultService;
|
||||||
this.revealButtonState = new SimpleObjectProperty<>(ContentDisplay.TEXT_ONLY);
|
this.revealButtonState = new SimpleObjectProperty<>(ContentDisplay.TEXT_ONLY);
|
||||||
this.revealButtonDisabled = new SimpleBooleanProperty();
|
this.revealButtonDisabled = new SimpleBooleanProperty();
|
||||||
}
|
}
|
||||||
@@ -49,17 +51,19 @@ public class UnlockSuccessController implements FxController {
|
|||||||
LOG.trace("UnlockSuccessController.revealAndClose()");
|
LOG.trace("UnlockSuccessController.revealAndClose()");
|
||||||
revealButtonState.set(ContentDisplay.LEFT);
|
revealButtonState.set(ContentDisplay.LEFT);
|
||||||
revealButtonDisabled.set(true);
|
revealButtonDisabled.set(true);
|
||||||
Tasks.create(() -> {
|
|
||||||
vault.reveal();
|
Task<Vault> revealTask = vaultService.createRevealTask(vault);
|
||||||
}).onSuccess(() -> {
|
revealTask.setOnSucceeded(evt -> {
|
||||||
window.close();
|
|
||||||
}).onError(InvalidPassphraseException.class, e -> {
|
|
||||||
// TODO
|
|
||||||
LOG.warn("Reveal failed.", e);
|
|
||||||
}).andFinally(() -> {
|
|
||||||
revealButtonState.set(ContentDisplay.TEXT_ONLY);
|
revealButtonState.set(ContentDisplay.TEXT_ONLY);
|
||||||
revealButtonDisabled.set(false);
|
revealButtonDisabled.set(false);
|
||||||
}).runOnce(executor);
|
window.close();
|
||||||
|
});
|
||||||
|
revealTask.setOnFailed(evt -> {
|
||||||
|
LOG.warn("Reveal failed.", revealTask.getException());
|
||||||
|
revealButtonState.set(ContentDisplay.TEXT_ONLY);
|
||||||
|
revealButtonDisabled.set(false);
|
||||||
|
});
|
||||||
|
executor.execute(revealTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Getter/Setter */
|
/* Getter/Setter */
|
||||||
|
|||||||
@@ -84,12 +84,7 @@ public class MountOptionsController implements FxController {
|
|||||||
driveLetterSelection.getItems().addAll(windowsDriveLetters.getAllDriveLetters());
|
driveLetterSelection.getItems().addAll(windowsDriveLetters.getAllDriveLetters());
|
||||||
driveLetterSelection.setConverter(new WinDriveLetterLabelConverter(windowsDriveLetters));
|
driveLetterSelection.setConverter(new WinDriveLetterLabelConverter(windowsDriveLetters));
|
||||||
driveLetterSelection.setValue(vault.getVaultSettings().winDriveLetter().get());
|
driveLetterSelection.setValue(vault.getVaultSettings().winDriveLetter().get());
|
||||||
vault.getVaultSettings().usesIndividualMountPath().bind(mountPoint.selectedToggleProperty().isEqualTo(mountPointCustomDir));
|
|
||||||
vault.getVaultSettings().winDriveLetter().bind( //
|
|
||||||
Bindings.when(mountPoint.selectedToggleProperty().isEqualTo(mountPointWinDriveLetter)) //
|
|
||||||
.then(driveLetterSelection.getSelectionModel().selectedItemProperty()) //
|
|
||||||
.otherwise((String) null) //
|
|
||||||
);
|
|
||||||
if (vault.getVaultSettings().usesIndividualMountPath().get()) {
|
if (vault.getVaultSettings().usesIndividualMountPath().get()) {
|
||||||
mountPoint.selectToggle(mountPointCustomDir);
|
mountPoint.selectToggle(mountPointCustomDir);
|
||||||
} else if (!Strings.isNullOrEmpty(vault.getVaultSettings().winDriveLetter().get())) {
|
} else if (!Strings.isNullOrEmpty(vault.getVaultSettings().winDriveLetter().get())) {
|
||||||
@@ -97,6 +92,13 @@ public class MountOptionsController implements FxController {
|
|||||||
} else {
|
} else {
|
||||||
mountPoint.selectToggle(mountPointAuto);
|
mountPoint.selectToggle(mountPointAuto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vault.getVaultSettings().usesIndividualMountPath().bind(mountPoint.selectedToggleProperty().isEqualTo(mountPointCustomDir));
|
||||||
|
vault.getVaultSettings().winDriveLetter().bind( //
|
||||||
|
Bindings.when(mountPoint.selectedToggleProperty().isEqualTo(mountPointWinDriveLetter)) //
|
||||||
|
.then(driveLetterSelection.getSelectionModel().selectedItemProperty()) //
|
||||||
|
.otherwise((String) null) //
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@@ -116,7 +118,7 @@ public class MountOptionsController implements FxController {
|
|||||||
@FXML
|
@FXML
|
||||||
private void chooseCustomMountPoint() {
|
private void chooseCustomMountPoint() {
|
||||||
DirectoryChooser directoryChooser = new DirectoryChooser();
|
DirectoryChooser directoryChooser = new DirectoryChooser();
|
||||||
directoryChooser.setTitle(resourceBundle.getString("vaultOptions.mount.winDirChooser"));
|
directoryChooser.setTitle(resourceBundle.getString("vaultOptions.mount.mountPoint.directoryPickerTitle"));
|
||||||
try {
|
try {
|
||||||
directoryChooser.setInitialDirectory(Path.of(System.getProperty("user.home")).toFile());
|
directoryChooser.setInitialDirectory(Path.of(System.getProperty("user.home")).toFile());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@@ -78,6 +78,7 @@
|
|||||||
SCROLL_BAR_THUMB_NORMAL: GRAY_3;
|
SCROLL_BAR_THUMB_NORMAL: GRAY_3;
|
||||||
SCROLL_BAR_THUMB_HOVER: GRAY_4;
|
SCROLL_BAR_THUMB_HOVER: GRAY_4;
|
||||||
INDICATOR_BG: RED_5;
|
INDICATOR_BG: RED_5;
|
||||||
|
DRAG_N_DROP_INDICATOR_BG: GRAY_3;
|
||||||
PROGRESS_INDICATOR_BEGIN: GRAY_7;
|
PROGRESS_INDICATOR_BEGIN: GRAY_7;
|
||||||
PROGRESS_INDICATOR_END: GRAY_5;
|
PROGRESS_INDICATOR_END: GRAY_5;
|
||||||
PROGRESS_BAR_BG: GRAY_2;
|
PROGRESS_BAR_BG: GRAY_2;
|
||||||
@@ -116,6 +117,10 @@
|
|||||||
-fx-font-size: 0.8em;
|
-fx-font-size: 0.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.label-extra-small {
|
||||||
|
-fx-font-size: 0.64em;
|
||||||
|
}
|
||||||
|
|
||||||
.text-flow > * {
|
.text-flow > * {
|
||||||
-fx-fill: TEXT_FILL;
|
-fx-fill: TEXT_FILL;
|
||||||
}
|
}
|
||||||
@@ -179,20 +184,6 @@
|
|||||||
-fx-fill: CONTROL_WHITE_BG_ARMED;
|
-fx-fill: CONTROL_WHITE_BG_ARMED;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-window .resizer {
|
|
||||||
-fx-background-color: linear-gradient(to bottom right,
|
|
||||||
transparent 50%,
|
|
||||||
CONTROL_BORDER_NORMAL 51%,
|
|
||||||
CONTROL_BORDER_NORMAL 60%,
|
|
||||||
transparent 61%,
|
|
||||||
transparent 70%,
|
|
||||||
CONTROL_BORDER_NORMAL 71%,
|
|
||||||
CONTROL_BORDER_NORMAL 80%,
|
|
||||||
transparent 81%
|
|
||||||
);
|
|
||||||
-fx-cursor: nw_resize;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-window .update-indicator {
|
.main-window .update-indicator {
|
||||||
-fx-background-color: PRIMARY_BG, white, INDICATOR_BG;
|
-fx-background-color: PRIMARY_BG, white, INDICATOR_BG;
|
||||||
-fx-background-insets: 0, 1px, 2px;
|
-fx-background-insets: 0, 1px, 2px;
|
||||||
@@ -201,6 +192,16 @@
|
|||||||
-fx-translate-y: 1px;
|
-fx-translate-y: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.main-window .drag-n-drop-indicator {
|
||||||
|
-fx-border-color: DRAG_N_DROP_INDICATOR_BG;
|
||||||
|
-fx-border-width: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-window .drag-n-drop-indicator .drag-n-drop-header {
|
||||||
|
-fx-background-color: DRAG_N_DROP_INDICATOR_BG;
|
||||||
|
-fx-padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* *
|
* *
|
||||||
* TabPane *
|
* TabPane *
|
||||||
@@ -212,34 +213,36 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tab-pane > .tab-header-area {
|
.tab-pane > .tab-header-area {
|
||||||
-fx-padding: 6px 12px 0 12px;
|
-fx-padding: 0 12px;
|
||||||
-fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG;
|
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||||
-fx-background-insets: 0, 0 0 1px 0;
|
-fx-background-insets: 0, 0 0 3px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-pane > .tab-header-area > .headers-region > .tab {
|
.tab-pane .tab {
|
||||||
-fx-background-color: CONTROL_BORDER_NORMAL, MAIN_BG;
|
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||||
-fx-background-insets: 0 0 1px 0, 1px;
|
-fx-background-insets: 0, 0 0 3px 0;
|
||||||
-fx-background-radius: 4px 4px 0 0;
|
-fx-padding: 6px 12px;
|
||||||
-fx-padding: 0.2em 1em 0.2em 1em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-pane > .tab-header-area > .headers-region > .tab:selected {
|
.tab-pane .tab:selected {
|
||||||
-fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG;
|
-fx-background-color: PRIMARY_BG, CONTROL_PRIMARY_LIGHT_BG_NORMAL;
|
||||||
-fx-background-insets: 0, 1px 1px 0 1px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-pane > .tab-header-area > .headers-region > .tab > .tab-container > .tab-label {
|
.tab-pane .tab .tab-label {
|
||||||
-fx-text-fill: TEXT_FILL;
|
-fx-text-fill: SECONDARY_BG;
|
||||||
-fx-alignment: CENTER;
|
-fx-alignment: CENTER;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-pane > .tab-header-area > .headers-region > .tab .glyph-icon {
|
.tab-pane .tab .glyph-icon {
|
||||||
-fx-fill: SECONDARY_BG;
|
-fx-fill: SECONDARY_BG;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-pane > .tab-header-area > .headers-region > .tab:selected .glyph-icon {
|
.tab-pane .tab:selected .glyph-icon {
|
||||||
-fx-fill: TEXT_FILL;
|
-fx-fill: PRIMARY_BG;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-pane .tab:selected .tab-label {
|
||||||
|
-fx-text-fill: TEXT_FILL_PRIMARY;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@@ -672,7 +675,6 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
.choice-box {
|
.choice-box {
|
||||||
-fx-text-fill: TEXT_FILL;
|
|
||||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||||
-fx-background-insets: 0, 1px;
|
-fx-background-insets: 0, 1px;
|
||||||
-fx-background-radius: 4px;
|
-fx-background-radius: 4px;
|
||||||
@@ -683,6 +685,18 @@
|
|||||||
-fx-background-color: CONTROL_BORDER_FOCUSED, CONTROL_BG_NORMAL;
|
-fx-background-color: CONTROL_BORDER_FOCUSED, CONTROL_BG_NORMAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.choice-box:disabled {
|
||||||
|
-fx-background-color: CONTROL_BORDER_DISABLED, CONTROL_BG_DISABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choice-box > .label {
|
||||||
|
-fx-text-fill: TEXT_FILL;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choice-box:disabled > .label {
|
||||||
|
-fx-text-fill: TEXT_FILL_SECONDARY;
|
||||||
|
}
|
||||||
|
|
||||||
.choice-box > .open-button {
|
.choice-box > .open-button {
|
||||||
-fx-padding: 0 0 0 0.3em;
|
-fx-padding: 0 0 0 0.3em;
|
||||||
}
|
}
|
||||||
@@ -694,6 +708,10 @@
|
|||||||
-fx-shape: "M 0 0 h 7 l -3.5 4 z";
|
-fx-shape: "M 0 0 h 7 l -3.5 4 z";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.choice-box:disabled > .open-button > .arrow {
|
||||||
|
-fx-background-color: transparent, TEXT_FILL_SECONDARY;
|
||||||
|
}
|
||||||
|
|
||||||
.choice-box .context-menu {
|
.choice-box .context-menu {
|
||||||
-fx-translate-x: -1.4em;
|
-fx-translate-x: -1.4em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,7 @@
|
|||||||
SCROLL_BAR_THUMB_NORMAL: GRAY_7;
|
SCROLL_BAR_THUMB_NORMAL: GRAY_7;
|
||||||
SCROLL_BAR_THUMB_HOVER: GRAY_6;
|
SCROLL_BAR_THUMB_HOVER: GRAY_6;
|
||||||
INDICATOR_BG: RED_5;
|
INDICATOR_BG: RED_5;
|
||||||
|
DRAG_N_DROP_INDICATOR_BG: GRAY_5;
|
||||||
PROGRESS_INDICATOR_BEGIN: GRAY_2;
|
PROGRESS_INDICATOR_BEGIN: GRAY_2;
|
||||||
PROGRESS_INDICATOR_END: GRAY_4;
|
PROGRESS_INDICATOR_END: GRAY_4;
|
||||||
PROGRESS_BAR_BG: GRAY_8;
|
PROGRESS_BAR_BG: GRAY_8;
|
||||||
@@ -116,6 +117,10 @@
|
|||||||
-fx-font-size: 0.8em;
|
-fx-font-size: 0.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.label-extra-small {
|
||||||
|
-fx-font-size: 0.64em;
|
||||||
|
}
|
||||||
|
|
||||||
.text-flow > * {
|
.text-flow > * {
|
||||||
-fx-fill: TEXT_FILL;
|
-fx-fill: TEXT_FILL;
|
||||||
}
|
}
|
||||||
@@ -179,20 +184,6 @@
|
|||||||
-fx-fill: CONTROL_WHITE_BG_ARMED;
|
-fx-fill: CONTROL_WHITE_BG_ARMED;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-window .resizer {
|
|
||||||
-fx-background-color: linear-gradient(to bottom right,
|
|
||||||
transparent 50%,
|
|
||||||
CONTROL_BORDER_NORMAL 51%,
|
|
||||||
CONTROL_BORDER_NORMAL 60%,
|
|
||||||
transparent 61%,
|
|
||||||
transparent 70%,
|
|
||||||
CONTROL_BORDER_NORMAL 71%,
|
|
||||||
CONTROL_BORDER_NORMAL 80%,
|
|
||||||
transparent 81%
|
|
||||||
);
|
|
||||||
-fx-cursor: nw_resize;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-window .update-indicator {
|
.main-window .update-indicator {
|
||||||
-fx-background-color: PRIMARY_BG, white, INDICATOR_BG;
|
-fx-background-color: PRIMARY_BG, white, INDICATOR_BG;
|
||||||
-fx-background-insets: 0, 1px, 2px;
|
-fx-background-insets: 0, 1px, 2px;
|
||||||
@@ -201,6 +192,16 @@
|
|||||||
-fx-translate-y: 1px;
|
-fx-translate-y: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.main-window .drag-n-drop-indicator {
|
||||||
|
-fx-border-color: DRAG_N_DROP_INDICATOR_BG;
|
||||||
|
-fx-border-width: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-window .drag-n-drop-indicator .drag-n-drop-header {
|
||||||
|
-fx-background-color: DRAG_N_DROP_INDICATOR_BG;
|
||||||
|
-fx-padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* *
|
* *
|
||||||
* TabPane *
|
* TabPane *
|
||||||
@@ -212,34 +213,36 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tab-pane > .tab-header-area {
|
.tab-pane > .tab-header-area {
|
||||||
-fx-padding: 6px 12px 0 12px;
|
-fx-padding: 0 12px;
|
||||||
-fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG;
|
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||||
-fx-background-insets: 0, 0 0 1px 0;
|
-fx-background-insets: 0, 0 0 3px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-pane > .tab-header-area > .headers-region > .tab {
|
.tab-pane .tab {
|
||||||
-fx-background-color: CONTROL_BORDER_NORMAL, MAIN_BG;
|
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||||
-fx-background-insets: 0 0 1px 0, 1px;
|
-fx-background-insets: 0, 0 0 3px 0;
|
||||||
-fx-background-radius: 4px 4px 0 0;
|
-fx-padding: 6px 12px;
|
||||||
-fx-padding: 0.2em 1em 0.2em 1em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-pane > .tab-header-area > .headers-region > .tab:selected {
|
.tab-pane .tab:selected {
|
||||||
-fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG;
|
-fx-background-color: PRIMARY_BG, CONTROL_PRIMARY_LIGHT_BG_NORMAL;
|
||||||
-fx-background-insets: 0, 1px 1px 0 1px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-pane > .tab-header-area > .headers-region > .tab > .tab-container > .tab-label {
|
.tab-pane .tab .tab-label {
|
||||||
-fx-text-fill: TEXT_FILL;
|
-fx-text-fill: SECONDARY_BG;
|
||||||
-fx-alignment: CENTER;
|
-fx-alignment: CENTER;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-pane > .tab-header-area > .headers-region > .tab .glyph-icon {
|
.tab-pane .tab .glyph-icon {
|
||||||
-fx-fill: SECONDARY_BG;
|
-fx-fill: SECONDARY_BG;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-pane > .tab-header-area > .headers-region > .tab:selected .glyph-icon {
|
.tab-pane .tab:selected .glyph-icon {
|
||||||
-fx-fill: TEXT_FILL;
|
-fx-fill: PRIMARY_BG;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-pane .tab:selected .tab-label {
|
||||||
|
-fx-text-fill: TEXT_FILL_PRIMARY;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@@ -672,7 +675,6 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
.choice-box {
|
.choice-box {
|
||||||
-fx-text-fill: TEXT_FILL;
|
|
||||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||||
-fx-background-insets: 0, 1px;
|
-fx-background-insets: 0, 1px;
|
||||||
-fx-background-radius: 4px;
|
-fx-background-radius: 4px;
|
||||||
@@ -683,6 +685,18 @@
|
|||||||
-fx-background-color: CONTROL_BORDER_FOCUSED, CONTROL_BG_NORMAL;
|
-fx-background-color: CONTROL_BORDER_FOCUSED, CONTROL_BG_NORMAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.choice-box:disabled {
|
||||||
|
-fx-background-color: CONTROL_BORDER_DISABLED, CONTROL_BG_DISABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choice-box > .label {
|
||||||
|
-fx-text-fill: TEXT_FILL;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choice-box:disabled > .label {
|
||||||
|
-fx-text-fill: TEXT_FILL_SECONDARY;
|
||||||
|
}
|
||||||
|
|
||||||
.choice-box > .open-button {
|
.choice-box > .open-button {
|
||||||
-fx-padding: 0 0 0 0.3em;
|
-fx-padding: 0 0 0 0.3em;
|
||||||
}
|
}
|
||||||
@@ -694,6 +708,10 @@
|
|||||||
-fx-shape: "M 0 0 h 7 l -3.5 4 z";
|
-fx-shape: "M 0 0 h 7 l -3.5 4 z";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.choice-box:disabled > .open-button > .arrow {
|
||||||
|
-fx-background-color: transparent, TEXT_FILL_SECONDARY;
|
||||||
|
}
|
||||||
|
|
||||||
.choice-box .context-menu {
|
.choice-box .context-menu {
|
||||||
-fx-translate-x: -1.4em;
|
-fx-translate-x: -1.4em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
<VBox xmlns="http://javafx.com/javafx"
|
<VBox xmlns="http://javafx.com/javafx"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
fx:controller="org.cryptomator.ui.addvaultwizard.ChooseExistingVaultController"
|
fx:controller="org.cryptomator.ui.addvaultwizard.ChooseExistingVaultController"
|
||||||
prefWidth="400"
|
prefWidth="450"
|
||||||
prefHeight="400"
|
prefHeight="450"
|
||||||
spacing="12"
|
spacing="12"
|
||||||
alignment="CENTER">
|
alignment="CENTER">
|
||||||
<padding>
|
<padding>
|
||||||
|
|||||||
@@ -11,9 +11,9 @@
|
|||||||
<?import org.cryptomator.ui.controls.FormattedLabel?>
|
<?import org.cryptomator.ui.controls.FormattedLabel?>
|
||||||
<VBox xmlns="http://javafx.com/javafx"
|
<VBox xmlns="http://javafx.com/javafx"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
fx:controller="org.cryptomator.ui.addvaultwizard.AddVaultFailureExisitingController"
|
fx:controller="org.cryptomator.ui.addvaultwizard.AddVaultFailureExistingController"
|
||||||
minWidth="400"
|
minWidth="450"
|
||||||
maxWidth="400"
|
maxWidth="450"
|
||||||
minHeight="145"
|
minHeight="145"
|
||||||
spacing="12">
|
spacing="12">
|
||||||
<padding>
|
<padding>
|
||||||
|
|||||||
@@ -14,8 +14,8 @@
|
|||||||
<VBox xmlns="http://javafx.com/javafx"
|
<VBox xmlns="http://javafx.com/javafx"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
fx:controller="org.cryptomator.ui.addvaultwizard.CreateNewVaultLocationController"
|
fx:controller="org.cryptomator.ui.addvaultwizard.CreateNewVaultLocationController"
|
||||||
prefWidth="400"
|
prefWidth="450"
|
||||||
prefHeight="400"
|
prefHeight="450"
|
||||||
spacing="12"
|
spacing="12"
|
||||||
alignment="CENTER_LEFT">
|
alignment="CENTER_LEFT">
|
||||||
<fx:define>
|
<fx:define>
|
||||||
@@ -29,8 +29,10 @@
|
|||||||
|
|
||||||
<VBox spacing="6">
|
<VBox spacing="6">
|
||||||
<Label wrapText="true" text="%addvaultwizard.new.locationInstruction"/>
|
<Label wrapText="true" text="%addvaultwizard.new.locationInstruction"/>
|
||||||
|
<RadioButton fx:id="iclouddriveRadioButton" toggleGroup="${predefinedLocationToggler}" text="iCloud Drive" visible="${controller.locationPresets.foundIclouddrive}" managed="${controller.locationPresets.foundIclouddrive}"/>
|
||||||
<RadioButton fx:id="dropboxRadioButton" toggleGroup="${predefinedLocationToggler}" text="Dropbox" visible="${controller.locationPresets.foundDropbox}" managed="${controller.locationPresets.foundDropbox}"/>
|
<RadioButton fx:id="dropboxRadioButton" toggleGroup="${predefinedLocationToggler}" text="Dropbox" visible="${controller.locationPresets.foundDropbox}" managed="${controller.locationPresets.foundDropbox}"/>
|
||||||
<RadioButton fx:id="gdriveRadioButton" toggleGroup="${predefinedLocationToggler}" text="Google Drive" visible="${controller.locationPresets.foundGdrive}" managed="${controller.locationPresets.foundGdrive}"/>
|
<RadioButton fx:id="gdriveRadioButton" toggleGroup="${predefinedLocationToggler}" text="Google Drive" visible="${controller.locationPresets.foundGdrive}" managed="${controller.locationPresets.foundGdrive}"/>
|
||||||
|
<RadioButton fx:id="onedriveRadioButton" toggleGroup="${predefinedLocationToggler}" text="OneDrive" visible="${controller.locationPresets.foundOnedrive}" managed="${controller.locationPresets.foundOnedrive}"/>
|
||||||
<HBox spacing="12" alignment="CENTER_LEFT">
|
<HBox spacing="12" alignment="CENTER_LEFT">
|
||||||
<RadioButton fx:id="customRadioButton" toggleGroup="${predefinedLocationToggler}" text="%addvaultwizard.new.directoryPickerLabel"/>
|
<RadioButton fx:id="customRadioButton" toggleGroup="${predefinedLocationToggler}" text="%addvaultwizard.new.directoryPickerLabel"/>
|
||||||
<Button contentDisplay="LEFT" text="%addvaultwizard.new.directoryPickerButton" onAction="#chooseCustomVaultPath" disable="${controller.usePresetPath}">
|
<Button contentDisplay="LEFT" text="%addvaultwizard.new.directoryPickerButton" onAction="#chooseCustomVaultPath" disable="${controller.usePresetPath}">
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
<VBox xmlns="http://javafx.com/javafx"
|
<VBox xmlns="http://javafx.com/javafx"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
fx:controller="org.cryptomator.ui.addvaultwizard.CreateNewVaultNameController"
|
fx:controller="org.cryptomator.ui.addvaultwizard.CreateNewVaultNameController"
|
||||||
prefWidth="400"
|
prefWidth="450"
|
||||||
prefHeight="400"
|
prefHeight="450"
|
||||||
spacing="12"
|
spacing="12"
|
||||||
alignment="CENTER_LEFT">
|
alignment="CENTER_LEFT">
|
||||||
<padding>
|
<padding>
|
||||||
|
|||||||
@@ -3,42 +3,34 @@
|
|||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.control.Button?>
|
<?import javafx.scene.control.Button?>
|
||||||
<?import javafx.scene.control.ButtonBar?>
|
<?import javafx.scene.control.ButtonBar?>
|
||||||
<?import javafx.scene.control.CheckBox?>
|
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.ProgressIndicator?>
|
<?import javafx.scene.control.ProgressIndicator?>
|
||||||
<?import javafx.scene.layout.HBox?>
|
<?import javafx.scene.control.RadioButton?>
|
||||||
|
<?import javafx.scene.control.ToggleGroup?>
|
||||||
<?import javafx.scene.layout.Region?>
|
<?import javafx.scene.layout.Region?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
|
||||||
<?import org.cryptomator.ui.controls.NiceSecurePasswordField?>
|
|
||||||
<?import org.cryptomator.ui.controls.PasswordStrengthIndicator?>
|
|
||||||
<VBox xmlns="http://javafx.com/javafx"
|
<VBox xmlns="http://javafx.com/javafx"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
fx:controller="org.cryptomator.ui.addvaultwizard.CreateNewVaultPasswordController"
|
fx:controller="org.cryptomator.ui.addvaultwizard.CreateNewVaultPasswordController"
|
||||||
prefWidth="400"
|
prefWidth="450"
|
||||||
prefHeight="400"
|
prefHeight="450"
|
||||||
spacing="12"
|
spacing="12"
|
||||||
alignment="CENTER_LEFT">
|
alignment="CENTER_LEFT">
|
||||||
|
<fx:define>
|
||||||
|
<ToggleGroup fx:id="recoveryKeyChoice"/>
|
||||||
|
</fx:define>
|
||||||
<padding>
|
<padding>
|
||||||
<Insets topRightBottomLeft="24"/>
|
<Insets topRightBottomLeft="24"/>
|
||||||
</padding>
|
</padding>
|
||||||
<children>
|
<children>
|
||||||
<Region VBox.vgrow="ALWAYS"/>
|
<fx:include source="/fxml/new_password.fxml"/>
|
||||||
|
|
||||||
|
<Region VBox.vgrow="ALWAYS"/>
|
||||||
|
|
||||||
<VBox spacing="6">
|
<VBox spacing="6">
|
||||||
<Label text="%addvaultwizard.new.enterPassword" labelFor="$passwordField"/>
|
<Label text="%addvaultwizard.new.generateRecoveryKeyChoice" wrapText="true"/>
|
||||||
<NiceSecurePasswordField fx:id="passwordField"/>
|
<RadioButton fx:id="showRecoveryKey" toggleGroup="${recoveryKeyChoice}" text="%addvaultwizard.new.generateRecoveryKeyChoice.yes" />
|
||||||
<PasswordStrengthIndicator spacing="6" prefHeight="6" strength="${controller.passwordStrength}"/>
|
<RadioButton fx:id="skipRecoveryKey" toggleGroup="${recoveryKeyChoice}" text="%addvaultwizard.new.generateRecoveryKeyChoice.no" />
|
||||||
<Label fx:id="passwordStrengthLabel" styleClass="label-secondary" labelFor="$passwordField" alignment="CENTER_RIGHT" maxWidth="Infinity"/>
|
|
||||||
</VBox>
|
|
||||||
<VBox spacing="6">
|
|
||||||
<Label text="%addvaultwizard.new.reenterPassword" labelFor="$reenterField"/>
|
|
||||||
<NiceSecurePasswordField fx:id="reenterField"/>
|
|
||||||
<HBox fx:id="passwordMatchBox" spacing="6" alignment="CENTER_RIGHT">
|
|
||||||
<FontAwesome5IconView fx:id="checkmark" styleClass="glyph-icon-primary" glyph="CHECK"/>
|
|
||||||
<FontAwesome5IconView fx:id="cross" styleClass="glyph-icon-red" glyph="TIMES"/>
|
|
||||||
<Label fx:id="passwordMatchLabel" styleClass="label-secondary" labelFor="$reenterField"/>
|
|
||||||
</HBox>
|
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|
||||||
<Region VBox.vgrow="ALWAYS"/>
|
<Region VBox.vgrow="ALWAYS"/>
|
||||||
|
|||||||
@@ -3,16 +3,13 @@
|
|||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.control.Button?>
|
<?import javafx.scene.control.Button?>
|
||||||
<?import javafx.scene.control.ButtonBar?>
|
<?import javafx.scene.control.ButtonBar?>
|
||||||
<?import javafx.scene.control.CheckBox?>
|
|
||||||
<?import javafx.scene.control.Label?>
|
|
||||||
<?import javafx.scene.control.TextArea?>
|
|
||||||
<?import javafx.scene.layout.Region?>
|
<?import javafx.scene.layout.Region?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
<VBox xmlns="http://javafx.com/javafx"
|
<VBox xmlns="http://javafx.com/javafx"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
fx:controller="org.cryptomator.ui.addvaultwizard.CreateNewVaultRecoveryKeyController"
|
fx:controller="org.cryptomator.ui.addvaultwizard.CreateNewVaultRecoveryKeyController"
|
||||||
prefWidth="400"
|
prefWidth="450"
|
||||||
prefHeight="400"
|
prefHeight="450"
|
||||||
spacing="12"
|
spacing="12"
|
||||||
alignment="CENTER_LEFT">
|
alignment="CENTER_LEFT">
|
||||||
<padding>
|
<padding>
|
||||||
@@ -21,17 +18,13 @@
|
|||||||
<children>
|
<children>
|
||||||
<Region VBox.vgrow="ALWAYS"/>
|
<Region VBox.vgrow="ALWAYS"/>
|
||||||
|
|
||||||
<Label text="%addvaultwizard.new.recoveryKeyInstruction" wrapText="true"/>
|
<fx:include source="/fxml/recoverykey_display.fxml"/>
|
||||||
|
|
||||||
<TextArea editable="false" text="${controller.recoveryKey}" wrapText="true"/>
|
|
||||||
|
|
||||||
<CheckBox fx:id="finalConfirmationCheckbox" text="%addvaultwizard.new.recoveryKeySavedCheckbox" wrapText="true"/>
|
|
||||||
|
|
||||||
<Region VBox.vgrow="ALWAYS"/>
|
<Region VBox.vgrow="ALWAYS"/>
|
||||||
|
|
||||||
<ButtonBar buttonMinWidth="120" buttonOrder="+X">
|
<ButtonBar buttonMinWidth="120" buttonOrder="+X">
|
||||||
<buttons>
|
<buttons>
|
||||||
<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" onAction="#next" defaultButton="true" disable="${!finalConfirmationCheckbox.selected}"/>
|
<Button text="%generic.button.next" ButtonBar.buttonData="NEXT_FORWARD" onAction="#next" defaultButton="true"/>
|
||||||
</buttons>
|
</buttons>
|
||||||
</ButtonBar>
|
</ButtonBar>
|
||||||
</children>
|
</children>
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
<VBox xmlns="http://javafx.com/javafx"
|
<VBox xmlns="http://javafx.com/javafx"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
fx:controller="org.cryptomator.ui.addvaultwizard.AddVaultSuccessController"
|
fx:controller="org.cryptomator.ui.addvaultwizard.AddVaultSuccessController"
|
||||||
prefWidth="400"
|
prefWidth="450"
|
||||||
prefHeight="400"
|
prefHeight="450"
|
||||||
spacing="12"
|
spacing="12"
|
||||||
alignment="TOP_CENTER">
|
alignment="TOP_CENTER">
|
||||||
<padding>
|
<padding>
|
||||||
@@ -36,8 +36,8 @@
|
|||||||
|
|
||||||
<ButtonBar buttonMinWidth="120" buttonOrder="+IU">
|
<ButtonBar buttonMinWidth="120" buttonOrder="+IU">
|
||||||
<buttons>
|
<buttons>
|
||||||
<Button text="%generic.button.done" ButtonBar.buttonData="FINISH" onAction="#close"/>
|
<Button text="%generic.button.done" ButtonBar.buttonData="FINISH" onAction="#close" defaultButton="${!controller.vault.locked}"/>
|
||||||
<Button text="%addvaultwizard.success.unlockNow" ButtonBar.buttonData="OTHER" onAction="#unlockAndClose" defaultButton="true"/>
|
<Button text="%addvaultwizard.success.unlockNow" ButtonBar.buttonData="OTHER" onAction="#unlockAndClose" defaultButton="${controller.vault.locked}" visible="${controller.vault.locked}"/>
|
||||||
</buttons>
|
</buttons>
|
||||||
</ButtonBar>
|
</ButtonBar>
|
||||||
</children>
|
</children>
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
<VBox xmlns="http://javafx.com/javafx"
|
<VBox xmlns="http://javafx.com/javafx"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
fx:controller="org.cryptomator.ui.addvaultwizard.AddVaultWelcomeController"
|
fx:controller="org.cryptomator.ui.addvaultwizard.AddVaultWelcomeController"
|
||||||
prefWidth="400"
|
prefWidth="450"
|
||||||
prefHeight="400"
|
prefHeight="450"
|
||||||
spacing="12"
|
spacing="12"
|
||||||
alignment="TOP_CENTER">
|
alignment="TOP_CENTER">
|
||||||
<padding>
|
<padding>
|
||||||
|
|||||||
@@ -28,22 +28,9 @@
|
|||||||
</VBox>
|
</VBox>
|
||||||
|
|
||||||
<Region prefHeight="12" VBox.vgrow="NEVER"/>
|
<Region prefHeight="12" VBox.vgrow="NEVER"/>
|
||||||
|
|
||||||
|
<fx:include source="/fxml/new_password.fxml"/>
|
||||||
|
|
||||||
<VBox spacing="6">
|
|
||||||
<Label labelFor="$newPasswordField" text="%changepassword.enterNewPassword"/>
|
|
||||||
<NiceSecurePasswordField fx:id="newPasswordField"/>
|
|
||||||
<PasswordStrengthIndicator prefHeight="6" spacing="6" strength="${controller.passwordStrength}"/>
|
|
||||||
<Label fx:id="passwordStrengthLabel" styleClass="label-secondary" alignment="CENTER_RIGHT" maxWidth="Infinity"/>
|
|
||||||
</VBox>
|
|
||||||
<VBox spacing="6">
|
|
||||||
<Label labelFor="$reenterPasswordField" text="%changepassword.reenterNewPassword"/>
|
|
||||||
<NiceSecurePasswordField fx:id="reenterPasswordField"/>
|
|
||||||
<HBox fx:id="passwordMatchBox" spacing="6" alignment="CENTER_RIGHT">
|
|
||||||
<FontAwesome5IconView fx:id="checkmark" styleClass="glyph-icon-primary" glyph="CHECK"/>
|
|
||||||
<FontAwesome5IconView fx:id="cross" styleClass="glyph-icon-red" glyph="TIMES"/>
|
|
||||||
<Label fx:id="passwordMatchLabel" styleClass="label-secondary" labelFor="$reenterPasswordField"/>
|
|
||||||
</HBox>
|
|
||||||
</VBox>
|
|
||||||
<CheckBox fx:id="finalConfirmationCheckbox" text="%changepassword.finalConfirmation" wrapText="true"/>
|
<CheckBox fx:id="finalConfirmationCheckbox" text="%changepassword.finalConfirmation" wrapText="true"/>
|
||||||
|
|
||||||
<Region VBox.vgrow="ALWAYS"/>
|
<Region VBox.vgrow="ALWAYS"/>
|
||||||
|
|||||||
@@ -9,55 +9,32 @@
|
|||||||
<?import javafx.scene.layout.Region?>
|
<?import javafx.scene.layout.Region?>
|
||||||
<?import javafx.scene.layout.StackPane?>
|
<?import javafx.scene.layout.StackPane?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
<?import javafx.scene.shape.Rectangle?>
|
|
||||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||||
<?import javafx.scene.layout.Pane?>
|
<StackPane xmlns="http://javafx.com/javafx"
|
||||||
<VBox xmlns="http://javafx.com/javafx"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
fx:id="root"
|
||||||
fx:id="root"
|
fx:controller="org.cryptomator.ui.mainwindow.MainWindowController"
|
||||||
fx:controller="org.cryptomator.ui.mainwindow.MainWindowController"
|
styleClass="main-window">
|
||||||
styleClass="main-window">
|
<VBox>
|
||||||
<HBox styleClass="title" fx:id="titleBar" alignment="CENTER" minHeight="50" maxHeight="50" VBox.vgrow="NEVER" spacing="6">
|
<fx:include source="/fxml/main_window_title.fxml" VBox.vgrow="NEVER" />
|
||||||
<padding>
|
<StackPane VBox.vgrow="ALWAYS">
|
||||||
<Insets bottom="6" left="12" right="12" top="6"/>
|
<SplitPane dividerPositions="0.33" orientation="HORIZONTAL">
|
||||||
</padding>
|
<fx:include source="/fxml/vault_list.fxml" SplitPane.resizableWithParent="false"/>
|
||||||
<children>
|
<fx:include source="/fxml/vault_detail.fxml" SplitPane.resizableWithParent="true"/>
|
||||||
<Label text="Cryptomator"/>
|
</SplitPane>
|
||||||
<Region HBox.hgrow="ALWAYS"/>
|
|
||||||
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#showPreferences" focusTraversable="false">
|
<VBox styleClass="drag-n-drop-indicator" visible="${controller.draggingOver}" alignment="TOP_CENTER">
|
||||||
<graphic>
|
<HBox visible="${!controller.draggingVaultOver}" managed="${!controller.draggingVaultOver}" spacing="6" styleClass="drag-n-drop-header" alignment="CENTER" VBox.vgrow="NEVER">
|
||||||
<StackPane>
|
<FontAwesome5IconView glyph="EXCLAMATION_TRIANGLE"/>
|
||||||
<FontAwesome5IconView glyph="COGS"/>
|
<Label text="%main.dropZone.unknownDragboardContent"/>
|
||||||
<Region styleClass="update-indicator" visible="${controller.updateAvailable}" StackPane.alignment="TOP_RIGHT" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity"/>
|
</HBox>
|
||||||
</StackPane>
|
<HBox visible="${controller.draggingVaultOver}" managed="${controller.draggingVaultOver}" spacing="6" styleClass="drag-n-drop-header" alignment="CENTER" VBox.vgrow="NEVER">
|
||||||
</graphic>
|
<FontAwesome5IconView glyph="CHECK"/>
|
||||||
<tooltip>
|
<Label text="%main.dropZone.dropVault"/>
|
||||||
<Tooltip text="%main.preferencesBtn.tooltip"/>
|
</HBox>
|
||||||
</tooltip>
|
<Region VBox.vgrow="ALWAYS"/>
|
||||||
</Button>
|
</VBox>
|
||||||
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#close" focusTraversable="false">
|
</StackPane>
|
||||||
<graphic>
|
</VBox>
|
||||||
<FontAwesome5IconView glyph="TIMES"/>
|
<fx:include source="/fxml/main_window_resize.fxml"/>
|
||||||
</graphic>
|
</StackPane>
|
||||||
<tooltip>
|
|
||||||
<Tooltip text="%main.closeBtn.tooltip"/>
|
|
||||||
</tooltip>
|
|
||||||
</Button>
|
|
||||||
</children>
|
|
||||||
</HBox>
|
|
||||||
<StackPane VBox.vgrow="ALWAYS">
|
|
||||||
<SplitPane dividerPositions="0.33" orientation="HORIZONTAL">
|
|
||||||
<fx:include source="/fxml/vault_list.fxml" SplitPane.resizableWithParent="false"/>
|
|
||||||
<fx:include source="/fxml/vault_detail.fxml" SplitPane.resizableWithParent="true"/>
|
|
||||||
</SplitPane>
|
|
||||||
<Region styleClass="resizer" StackPane.alignment="BOTTOM_RIGHT" fx:id="resizer" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity"/>
|
|
||||||
<!-- TODO: use css instead of adding a Rectangle: -->
|
|
||||||
<Pane fx:id="dragAndDropIndicator">
|
|
||||||
<StackPane.margin>
|
|
||||||
<Insets topRightBottomLeft="24"/>
|
|
||||||
</StackPane.margin>
|
|
||||||
<Rectangle arcHeight="4" arcWidth="4" fill="gainsboro" strokeType="CENTERED" strokeWidth="3" strokeDashArray="20, 20" strokeLineJoin="ROUND" stroke="black" height="${dragAndDropIndicator.height}"
|
|
||||||
width="${dragAndDropIndicator.width}"/>
|
|
||||||
</Pane>
|
|
||||||
</StackPane>
|
|
||||||
</VBox>
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?import javafx.scene.Cursor?>
|
||||||
|
<?import javafx.scene.layout.AnchorPane?>
|
||||||
|
<?import javafx.scene.layout.Region?>
|
||||||
|
<AnchorPane xmlns="http://javafx.com/javafx"
|
||||||
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
|
fx:controller="org.cryptomator.ui.mainwindow.ResizeController"
|
||||||
|
nodeOrientation="LEFT_TO_RIGHT"
|
||||||
|
pickOnBounds="false">
|
||||||
|
<fx:define>
|
||||||
|
<Cursor fx:id="nwResize" fx:constant="NW_RESIZE"/>
|
||||||
|
<Cursor fx:id="neResize" fx:constant="NE_RESIZE"/>
|
||||||
|
</fx:define>
|
||||||
|
<Region fx:id="tlResizer" cursor="${nwResize}" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity" AnchorPane.topAnchor="0" AnchorPane.leftAnchor="0"/>
|
||||||
|
<Region fx:id="trResizer" cursor="${neResize}" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity" AnchorPane.topAnchor="0" AnchorPane.rightAnchor="0"/>
|
||||||
|
<Region fx:id="blResizer" cursor="${neResize}" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity" AnchorPane.bottomAnchor="0" AnchorPane.leftAnchor="0"/>
|
||||||
|
<Region fx:id="brResizer" cursor="${nwResize}" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity" AnchorPane.bottomAnchor="0" AnchorPane.rightAnchor="0"/>
|
||||||
|
</AnchorPane>
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<?import javafx.scene.layout.StackPane?>
|
||||||
|
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||||
|
<?import javafx.scene.layout.Region?>
|
||||||
|
<?import javafx.scene.control.Tooltip?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<HBox xmlns="http://javafx.com/javafx"
|
||||||
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
|
fx:id="titleBar"
|
||||||
|
fx:controller="org.cryptomator.ui.mainwindow.MainWindowTitleController"
|
||||||
|
styleClass="title"
|
||||||
|
alignment="CENTER"
|
||||||
|
minHeight="50"
|
||||||
|
maxHeight="50"
|
||||||
|
spacing="6">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="6" left="12" right="12" top="6"/>
|
||||||
|
</padding>
|
||||||
|
<children>
|
||||||
|
<Label text="Cryptomator"/>
|
||||||
|
<Region HBox.hgrow="ALWAYS"/>
|
||||||
|
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#showDonationKeyPreferences" focusTraversable="false" visible="${!controller.licenseHolder.validLicense}">
|
||||||
|
<graphic>
|
||||||
|
<StackPane>
|
||||||
|
<FontAwesome5IconView glyph="EXCLAMATION_CIRCLE" glyphSize="16"/>
|
||||||
|
<Region styleClass="update-indicator" StackPane.alignment="TOP_RIGHT" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity"/>
|
||||||
|
</StackPane>
|
||||||
|
</graphic>
|
||||||
|
<tooltip>
|
||||||
|
<Tooltip text="%main.donationKeyMissing.tooltip"/>
|
||||||
|
</tooltip>
|
||||||
|
</Button>
|
||||||
|
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#showPreferences" focusTraversable="false">
|
||||||
|
<graphic>
|
||||||
|
<StackPane>
|
||||||
|
<FontAwesome5IconView glyph="COGS" glyphSize="16"/>
|
||||||
|
<Region styleClass="update-indicator" visible="${controller.updateAvailable}" StackPane.alignment="TOP_RIGHT" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity"/>
|
||||||
|
</StackPane>
|
||||||
|
</graphic>
|
||||||
|
<tooltip>
|
||||||
|
<Tooltip text="%main.preferencesBtn.tooltip"/>
|
||||||
|
</tooltip>
|
||||||
|
</Button>
|
||||||
|
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#close" focusTraversable="false">
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="TIMES" glyphSize="16"/>
|
||||||
|
</graphic>
|
||||||
|
<tooltip>
|
||||||
|
<Tooltip text="%main.closeBtn.tooltip"/>
|
||||||
|
</tooltip>
|
||||||
|
</Button>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.layout.Region?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||||
|
<?import org.cryptomator.ui.controls.NiceSecurePasswordField?>
|
||||||
|
<?import org.cryptomator.ui.controls.PasswordStrengthIndicator?>
|
||||||
|
<VBox xmlns="http://javafx.com/javafx"
|
||||||
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
|
fx:controller="org.cryptomator.ui.common.NewPasswordController"
|
||||||
|
spacing="6"
|
||||||
|
alignment="CENTER_LEFT">
|
||||||
|
<fx:define>
|
||||||
|
<FontAwesome5IconView fx:id="checkmark" styleClass="glyph-icon-primary" glyph="CHECK"/>
|
||||||
|
<FontAwesome5IconView fx:id="cross" styleClass="glyph-icon-red" glyph="TIMES"/>
|
||||||
|
</fx:define>
|
||||||
|
<children>
|
||||||
|
<Label text="%newPassword.promptText" labelFor="$passwordField"/>
|
||||||
|
<NiceSecurePasswordField fx:id="passwordField"/>
|
||||||
|
<PasswordStrengthIndicator spacing="6" prefHeight="6" strength="${controller.passwordStrength}"/>
|
||||||
|
<Label fx:id="passwordStrengthLabel" styleClass="label-secondary" alignment="CENTER_RIGHT" maxWidth="Infinity"/>
|
||||||
|
|
||||||
|
<Region/>
|
||||||
|
|
||||||
|
<Label text="%newPassword.reenterPassword" labelFor="$reenterField"/>
|
||||||
|
<NiceSecurePasswordField fx:id="reenterField"/>
|
||||||
|
<Label fx:id="passwordMatchLabel" styleClass="label-secondary" alignment="CENTER_RIGHT" maxWidth="Infinity" graphicTextGap="6" contentDisplay="LEFT" />
|
||||||
|
</children>
|
||||||
|
</VBox>
|
||||||
@@ -7,7 +7,10 @@
|
|||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
fx:id="tabPane"
|
fx:id="tabPane"
|
||||||
fx:controller="org.cryptomator.ui.preferences.PreferencesController"
|
fx:controller="org.cryptomator.ui.preferences.PreferencesController"
|
||||||
minWidth="400"
|
minWidth="-Infinity"
|
||||||
|
maxWidth="-Infinity"
|
||||||
|
prefWidth="500"
|
||||||
|
tabMinWidth="60"
|
||||||
tabClosingPolicy="UNAVAILABLE"
|
tabClosingPolicy="UNAVAILABLE"
|
||||||
tabDragPolicy="FIXED">
|
tabDragPolicy="FIXED">
|
||||||
<tabs>
|
<tabs>
|
||||||
@@ -35,5 +38,13 @@
|
|||||||
<fx:include source="/fxml/preferences_updates.fxml"/>
|
<fx:include source="/fxml/preferences_updates.fxml"/>
|
||||||
</content>
|
</content>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
<Tab fx:id="donationKeyTab" text="%preferences.donationKey">
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="HEART"/>
|
||||||
|
</graphic>
|
||||||
|
<content>
|
||||||
|
<fx:include source="/fxml/preferences_donationkey.fxml"/>
|
||||||
|
</content>
|
||||||
|
</Tab>
|
||||||
</tabs>
|
</tabs>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Hyperlink?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.TextArea?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.StackPane?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<?import javafx.scene.shape.Circle?>
|
||||||
|
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||||
|
<?import org.cryptomator.ui.controls.FormattedLabel?>
|
||||||
|
<VBox xmlns="http://javafx.com/javafx"
|
||||||
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
|
fx:controller="org.cryptomator.ui.preferences.DonationKeyPreferencesController"
|
||||||
|
spacing="18">
|
||||||
|
<padding>
|
||||||
|
<Insets topRightBottomLeft="12"/>
|
||||||
|
</padding>
|
||||||
|
<children>
|
||||||
|
<StackPane VBox.vgrow="NEVER" prefHeight="60">
|
||||||
|
<HBox spacing="12" alignment="CENTER_LEFT" visible="${controller.licenseHolder.validLicense}">
|
||||||
|
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
|
||||||
|
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
||||||
|
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="USER_CROWN" glyphSize="24"/>
|
||||||
|
</StackPane>
|
||||||
|
<FormattedLabel format="%preferences.donationKey.registeredFor" arg1="${controller.licenseHolder.licenseSubject}" wrapText="true"/>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<HBox spacing="12" alignment="CENTER_LEFT" visible="${!controller.licenseHolder.validLicense}">
|
||||||
|
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
|
||||||
|
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
||||||
|
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="HAND_HOLDING_HEART" glyphSize="24"/>
|
||||||
|
</StackPane>
|
||||||
|
<VBox HBox.hgrow="ALWAYS" spacing="6">
|
||||||
|
<Label text="%preferences.donationKey.noDonationKey" wrapText="true" VBox.vgrow="ALWAYS"/>
|
||||||
|
<Hyperlink text="%preferences.donationKey.getDonationKey" onAction="#getDonationKey" contentDisplay="LEFT">
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="LINK"/>
|
||||||
|
</graphic>
|
||||||
|
</Hyperlink>
|
||||||
|
</VBox>
|
||||||
|
</HBox>
|
||||||
|
</StackPane>
|
||||||
|
|
||||||
|
<TextArea fx:id="donationKeyField" wrapText="true" VBox.vgrow="ALWAYS" prefRowCount="6"/>
|
||||||
|
</children>
|
||||||
|
</VBox>
|
||||||
@@ -21,17 +21,19 @@
|
|||||||
<children>
|
<children>
|
||||||
<HBox spacing="6" alignment="CENTER_LEFT">
|
<HBox spacing="6" alignment="CENTER_LEFT">
|
||||||
<Label text="%preferences.general.theme"/>
|
<Label text="%preferences.general.theme"/>
|
||||||
<ChoiceBox fx:id="themeChoiceBox"/>
|
<ChoiceBox fx:id="themeChoiceBox" disable="${!controller.licenseHolder.validLicense}"/>
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
<HBox spacing="6" alignment="CENTER_LEFT">
|
<HBox spacing="6" alignment="CENTER_LEFT">
|
||||||
<Label text="Interface Orientation" HBox.hgrow="NEVER"/>
|
<Label text="%preferences.general.interfaceOrientation" HBox.hgrow="NEVER"/>
|
||||||
<RadioButton fx:id="nodeOrientationLtr" text="Left to Right" alignment="CENTER_LEFT" toggleGroup="${nodeOrientation}"/>
|
<RadioButton fx:id="nodeOrientationLtr" text="%preferences.general.interfaceOrientation.ltr" alignment="CENTER_LEFT" toggleGroup="${nodeOrientation}"/>
|
||||||
<RadioButton fx:id="nodeOrientationRtl" text="Right to Left" alignment="CENTER_RIGHT" toggleGroup="${nodeOrientation}"/>
|
<RadioButton fx:id="nodeOrientationRtl" text="%preferences.general.interfaceOrientation.rtl" alignment="CENTER_RIGHT" toggleGroup="${nodeOrientation}"/>
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
<CheckBox fx:id="startHiddenCheckbox" text="%preferences.general.startHidden"/>
|
<CheckBox fx:id="startHiddenCheckbox" text="%preferences.general.startHidden" visible="${controller.trayMenuSupported}" managed="${controller.trayMenuSupported}"/>
|
||||||
|
|
||||||
<CheckBox fx:id="debugModeCheckbox" text="%preferences.general.debugLogging"/>
|
<CheckBox fx:id="debugModeCheckbox" text="%preferences.general.debugLogging"/>
|
||||||
|
|
||||||
|
<CheckBox fx:id="autoStartCheckbox" text="%preferences.general.autoStart" visible="${controller.autoStartSupported}" managed="${controller.autoStartSupported}" onAction="#toggleAutoStart"/>
|
||||||
</children>
|
</children>
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|||||||
@@ -1,46 +1,41 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import javafx.geometry.Insets?>
|
|
||||||
<?import javafx.scene.control.Button?>
|
<?import javafx.scene.control.Button?>
|
||||||
<?import javafx.scene.control.ButtonBar?>
|
<?import javafx.scene.control.ButtonBar?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.TextArea?>
|
<?import javafx.scene.control.TextArea?>
|
||||||
<?import javafx.scene.layout.HBox?>
|
<?import javafx.scene.layout.Region?>
|
||||||
<?import javafx.scene.layout.StackPane?>
|
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
<?import javafx.scene.shape.Circle?>
|
|
||||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||||
<?import org.cryptomator.ui.controls.FormattedLabel?>
|
<?import org.cryptomator.ui.controls.FormattedLabel?>
|
||||||
<VBox xmlns="http://javafx.com/javafx"
|
<VBox xmlns="http://javafx.com/javafx"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeyDisplayController"
|
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeyDisplayController"
|
||||||
minWidth="400"
|
minWidth="350"
|
||||||
maxWidth="400"
|
minHeight="280"
|
||||||
minHeight="145"
|
|
||||||
maxHeight="180"
|
|
||||||
spacing="12"
|
spacing="12"
|
||||||
alignment="TOP_CENTER">
|
alignment="TOP_LEFT">
|
||||||
<padding>
|
|
||||||
<Insets topRightBottomLeft="12"/>
|
|
||||||
</padding>
|
|
||||||
<children>
|
<children>
|
||||||
<HBox spacing="12" alignment="CENTER_LEFT" VBox.vgrow="ALWAYS">
|
<FormattedLabel format="%recoveryKey.display.message" arg1="${controller.vaultName}" wrapText="true"/>
|
||||||
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
|
|
||||||
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
|
||||||
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="CHECK" glyphSize="24"/>
|
|
||||||
</StackPane>
|
|
||||||
|
|
||||||
<VBox spacing="6" HBox.hgrow="ALWAYS">
|
<TextArea editable="false" text="${controller.recoveryKey}" wrapText="true" prefRowCount="4" fx:id="textarea"/>
|
||||||
<FormattedLabel format="%recoveryKey.display.message" arg1="${controller.vault.displayableName}" wrapText="true" VBox.vgrow="NEVER"/>
|
<ButtonBar buttonMinWidth="120" buttonOrder="+R">
|
||||||
<TextArea editable="false" text="${controller.recoveryKey}" wrapText="true" VBox.vgrow="ALWAYS"/>
|
<buttons>
|
||||||
</VBox>
|
<Button text="%generic.button.print" ButtonBar.buttonData="RIGHT" onAction="#printRecoveryKey" visible="${controller.printerSupported}">
|
||||||
</HBox>
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="PRINT"/>
|
||||||
|
</graphic>
|
||||||
|
</Button>
|
||||||
|
<Button text="%generic.button.copy" ButtonBar.buttonData="RIGHT" onAction="#copyRecoveryKey">
|
||||||
|
<graphic>
|
||||||
|
<FontAwesome5IconView glyph="COPY"/>
|
||||||
|
</graphic>
|
||||||
|
</Button>
|
||||||
|
</buttons>
|
||||||
|
</ButtonBar>
|
||||||
|
|
||||||
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
|
<Region VBox.vgrow="ALWAYS"/>
|
||||||
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
|
|
||||||
<buttons>
|
<Label text="%recoveryKey.display.StorageHints" wrapText="true"/>
|
||||||
<Button text="%generic.button.done" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
|
|
||||||
</buttons>
|
|
||||||
</ButtonBar>
|
|
||||||
</VBox>
|
|
||||||
</children>
|
</children>
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.ButtonBar?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<?import javafx.scene.layout.Region?>
|
||||||
|
<VBox xmlns="http://javafx.com/javafx"
|
||||||
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
|
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeySuccessController"
|
||||||
|
minWidth="400"
|
||||||
|
maxWidth="400"
|
||||||
|
minHeight="145"
|
||||||
|
spacing="12"
|
||||||
|
alignment="TOP_CENTER">
|
||||||
|
<padding>
|
||||||
|
<Insets topRightBottomLeft="12"/>
|
||||||
|
</padding>
|
||||||
|
<children>
|
||||||
|
<fx:include source="/fxml/recoverykey_display.fxml"/>
|
||||||
|
|
||||||
|
<Region VBox.vgrow="ALWAYS"/>
|
||||||
|
|
||||||
|
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
|
||||||
|
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
|
||||||
|
<buttons>
|
||||||
|
<Button text="%generic.button.done" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
|
||||||
|
</buttons>
|
||||||
|
</ButtonBar>
|
||||||
|
</VBox>
|
||||||
|
</children>
|
||||||
|
</VBox>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.TextArea?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.StackPane?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<?import javafx.scene.shape.Circle?>
|
||||||
|
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||||
|
<VBox xmlns="http://javafx.com/javafx"
|
||||||
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
|
fx:controller="org.cryptomator.ui.common.StackTraceController"
|
||||||
|
minWidth="300"
|
||||||
|
spacing="12">
|
||||||
|
<children>
|
||||||
|
<HBox spacing="12" VBox.vgrow="ALWAYS">
|
||||||
|
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
|
||||||
|
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
||||||
|
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="EXCLAMATION" glyphSize="24"/>
|
||||||
|
</StackPane>
|
||||||
|
<VBox spacing="6" HBox.hgrow="ALWAYS">
|
||||||
|
<Label text="%generic.error.title" wrapText="true"/>
|
||||||
|
<Label text="%generic.error.instruction" wrapText="true"/>
|
||||||
|
</VBox>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<TextArea text="${controller.stackTrace}" prefRowCount="5" editable="false"/>
|
||||||
|
</children>
|
||||||
|
</VBox>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.ButtonBar?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<?import javafx.scene.layout.Region?>
|
||||||
|
<VBox xmlns="http://javafx.com/javafx"
|
||||||
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
|
fx:controller="org.cryptomator.ui.unlock.UnlockGenericErrorController"
|
||||||
|
minWidth="400"
|
||||||
|
maxWidth="400"
|
||||||
|
minHeight="145"
|
||||||
|
spacing="12">
|
||||||
|
<padding>
|
||||||
|
<Insets topRightBottomLeft="12"/>
|
||||||
|
</padding>
|
||||||
|
<children>
|
||||||
|
<fx:include source="/fxml/stacktrace.fxml"/>
|
||||||
|
|
||||||
|
<Region VBox.vgrow="ALWAYS"/>
|
||||||
|
|
||||||
|
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
|
||||||
|
<ButtonBar buttonMinWidth="120" buttonOrder="B+U">
|
||||||
|
<buttons>
|
||||||
|
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" cancelButton="true" onAction="#back"/>
|
||||||
|
<Region ButtonBar.buttonData="OTHER"/>
|
||||||
|
</buttons>
|
||||||
|
</ButtonBar>
|
||||||
|
</VBox>
|
||||||
|
</children>
|
||||||
|
</VBox>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.ButtonBar?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.Region?>
|
||||||
|
<?import javafx.scene.layout.StackPane?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<?import javafx.scene.shape.Circle?>
|
||||||
|
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||||
|
<?import org.cryptomator.ui.controls.FormattedLabel?>
|
||||||
|
<VBox xmlns="http://javafx.com/javafx"
|
||||||
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
|
fx:controller="org.cryptomator.ui.unlock.UnlockInvalidMountPointController"
|
||||||
|
minWidth="400"
|
||||||
|
maxWidth="400"
|
||||||
|
minHeight="145"
|
||||||
|
spacing="12">
|
||||||
|
<padding>
|
||||||
|
<Insets topRightBottomLeft="12"/>
|
||||||
|
</padding>
|
||||||
|
<children>
|
||||||
|
<HBox spacing="12" VBox.vgrow="ALWAYS">
|
||||||
|
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
|
||||||
|
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
||||||
|
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="EXCLAMATION" glyphSize="24"/>
|
||||||
|
</StackPane>
|
||||||
|
<VBox spacing="6" HBox.hgrow="ALWAYS">
|
||||||
|
<FormattedLabel format="%unlock.error.invalidMountPoint" arg1="${controller.mountPoint}" wrapText="true"/>
|
||||||
|
</VBox>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<Region VBox.vgrow="ALWAYS"/>
|
||||||
|
|
||||||
|
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
|
||||||
|
<ButtonBar buttonMinWidth="120" buttonOrder="B+U">
|
||||||
|
<buttons>
|
||||||
|
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" cancelButton="true" onAction="#back"/>
|
||||||
|
<Region ButtonBar.buttonData="OTHER"/>
|
||||||
|
</buttons>
|
||||||
|
</ButtonBar>
|
||||||
|
</VBox>
|
||||||
|
</children>
|
||||||
|
</VBox>
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
<?import javafx.scene.layout.HBox?>
|
<?import javafx.scene.control.Button?>
|
||||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
|
||||||
<?import javafx.scene.layout.VBox?>
|
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.Tooltip?>
|
<?import javafx.scene.control.Tooltip?>
|
||||||
<?import javafx.scene.control.Button?>
|
<?import javafx.scene.layout.HBox?>
|
||||||
<?import javafx.scene.layout.Region?>
|
<?import javafx.scene.layout.Region?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||||
<?import org.cryptomator.ui.controls.ThrougputLabel?>
|
<?import org.cryptomator.ui.controls.ThrougputLabel?>
|
||||||
<VBox xmlns="http://javafx.com/javafx"
|
<VBox xmlns="http://javafx.com/javafx"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
<FontAwesome5IconView glyph="HDD" glyphSize="24"/>
|
<FontAwesome5IconView glyph="HDD" glyphSize="24"/>
|
||||||
<VBox spacing="4" alignment="CENTER_LEFT">
|
<VBox spacing="4" alignment="CENTER_LEFT">
|
||||||
<Label text="%main.vaultDetail.revealBtn"/>
|
<Label text="%main.vaultDetail.revealBtn"/>
|
||||||
<Label styleClass="label-small" text="${controller.vault.accessPoint}" textOverrun="CENTER_ELLIPSIS"/>
|
<Label styleClass="label-extra-small" text="${controller.vault.accessPoint}" textOverrun="CENTER_ELLIPSIS"/>
|
||||||
</VBox>
|
</VBox>
|
||||||
</HBox>
|
</HBox>
|
||||||
</graphic>
|
</graphic>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
fx:id="tabPane"
|
fx:id="tabPane"
|
||||||
fx:controller="org.cryptomator.ui.vaultoptions.VaultOptionsController"
|
fx:controller="org.cryptomator.ui.vaultoptions.VaultOptionsController"
|
||||||
minWidth="400"
|
minWidth="400"
|
||||||
|
tabMinWidth="60"
|
||||||
tabClosingPolicy="UNAVAILABLE"
|
tabClosingPolicy="UNAVAILABLE"
|
||||||
tabDragPolicy="FIXED">
|
tabDragPolicy="FIXED">
|
||||||
<tabs>
|
<tabs>
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
<?import javafx.scene.control.ToggleGroup?>
|
<?import javafx.scene.control.ToggleGroup?>
|
||||||
<?import javafx.scene.layout.HBox?>
|
<?import javafx.scene.layout.HBox?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
<?import javafx.scene.text.Text?>
|
|
||||||
<?import org.cryptomator.ui.controls.AlphanumericTextField?>
|
<?import org.cryptomator.ui.controls.AlphanumericTextField?>
|
||||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||||
<VBox xmlns="http://javafx.com/javafx"
|
<VBox xmlns="http://javafx.com/javafx"
|
||||||
@@ -32,35 +31,36 @@
|
|||||||
<CheckBox fx:id="readOnlyCheckbox" text="%vaultOptions.mount.readonly"/>
|
<CheckBox fx:id="readOnlyCheckbox" text="%vaultOptions.mount.readonly"/>
|
||||||
|
|
||||||
<CheckBox fx:id="customMountFlagsCheckbox" text="%vaultOptions.mount.customMountFlags" onAction="#toggleUseCustomMountFlags"/>
|
<CheckBox fx:id="customMountFlagsCheckbox" text="%vaultOptions.mount.customMountFlags" onAction="#toggleUseCustomMountFlags"/>
|
||||||
<HBox>
|
|
||||||
<padding>
|
<TextField fx:id="mountFlags" HBox.hgrow="ALWAYS" maxWidth="Infinity">
|
||||||
<Insets left="25"/>
|
<VBox.margin>
|
||||||
</padding>
|
<Insets left="24"/>
|
||||||
<children>
|
</VBox.margin>
|
||||||
<TextField fx:id="mountFlags" HBox.hgrow="ALWAYS" maxWidth="Infinity"/>
|
</TextField>
|
||||||
</children>
|
|
||||||
</HBox>
|
<Label text="%vaultOptions.mount.mountPoint">
|
||||||
|
<VBox.margin>
|
||||||
<Text text="TODO Mount Point"/>
|
<Insets top="9"/>
|
||||||
<RadioButton toggleGroup="${mountPoint}" fx:id="mountPointAuto" text="TODO Automatically pick a suitable location"/>
|
</VBox.margin>
|
||||||
|
</Label>
|
||||||
|
<RadioButton toggleGroup="${mountPoint}" fx:id="mountPointAuto" text="%vaultOptions.mount.mountPoint.auto"/>
|
||||||
<HBox spacing="6" visible="${controller.osIsWindows}" managed="${controller.osIsWindows}">
|
<HBox spacing="6" visible="${controller.osIsWindows}" managed="${controller.osIsWindows}">
|
||||||
<RadioButton toggleGroup="${mountPoint}" fx:id="mountPointWinDriveLetter" text="TODO Choose specific drive letter"/>
|
<RadioButton toggleGroup="${mountPoint}" fx:id="mountPointWinDriveLetter" text="%vaultOptions.mount.mountPoint.driveLetter"/>
|
||||||
<ChoiceBox fx:id="driveLetterSelection" disable="${!mountPointWinDriveLetter.selected}"/>
|
<ChoiceBox fx:id="driveLetterSelection" disable="${!mountPointWinDriveLetter.selected}"/>
|
||||||
</HBox>
|
</HBox>
|
||||||
<RadioButton toggleGroup="${mountPoint}" fx:id="mountPointCustomDir" text="TODO Choose empty directory"/>
|
<HBox spacing="6" alignment="CENTER_LEFT">
|
||||||
<HBox visible="${mountPointCustomDir.selected}">
|
<RadioButton toggleGroup="${mountPoint}" fx:id="mountPointCustomDir" text="%vaultOptions.mount.mountPoint.custom"/>
|
||||||
<padding>
|
<Button text="%vaultOptions.mount.mountPoint.directoryPickerButton" onAction="#chooseCustomMountPoint" contentDisplay="LEFT" disable="${!mountPointCustomDir.selected}">
|
||||||
<Insets left="24"/>
|
<graphic>
|
||||||
</padding>
|
<FontAwesome5IconView glyph="FOLDER_OPEN" glyphSize="15"/>
|
||||||
<children>
|
</graphic>
|
||||||
<TextField text="${controller.customMountPath}" HBox.hgrow="ALWAYS" maxWidth="Infinity" disable="true"/>
|
</Button>
|
||||||
<Button text="TODO change" onAction="#chooseCustomMountPoint" contentDisplay="LEFT">
|
|
||||||
<graphic>
|
|
||||||
<FontAwesome5IconView glyph="FOLDER_OPEN" glyphSize="15"/>
|
|
||||||
</graphic>
|
|
||||||
</Button>
|
|
||||||
</children>
|
|
||||||
</HBox>
|
</HBox>
|
||||||
|
<TextField text="${controller.customMountPath}" visible="${mountPointCustomDir.selected}" maxWidth="Infinity" disable="true">
|
||||||
|
<VBox.margin>
|
||||||
|
<Insets left="24"/>
|
||||||
|
</VBox.margin>
|
||||||
|
</TextField>
|
||||||
</children>
|
</children>
|
||||||
|
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|||||||
@@ -7,12 +7,18 @@ generic.button.apply=Apply
|
|||||||
generic.button.back=Back
|
generic.button.back=Back
|
||||||
generic.button.cancel=Cancel
|
generic.button.cancel=Cancel
|
||||||
generic.button.change=Change
|
generic.button.change=Change
|
||||||
|
generic.button.copy=Copy
|
||||||
generic.button.done=Done
|
generic.button.done=Done
|
||||||
generic.button.next=Next
|
generic.button.next=Next
|
||||||
|
generic.button.print=Print
|
||||||
|
## Error
|
||||||
|
generic.error.title=An unexpected error occured
|
||||||
|
generic.error.instruction=This should not have happened. Please report the error text below and include a description of what steps did lead to this error.
|
||||||
|
|
||||||
# Tray Menu
|
# Tray Menu
|
||||||
traymenu.showMainWindow=Show
|
traymenu.showMainWindow=Show
|
||||||
traymenu.showPreferencesWindow=Preferences
|
traymenu.showPreferencesWindow=Preferences
|
||||||
|
traymenu.lockAllVaults=Lock All
|
||||||
traymenu.quitApplication=Quit
|
traymenu.quitApplication=Quit
|
||||||
traymenu.vault.unlock=Unlock
|
traymenu.vault.unlock=Unlock
|
||||||
traymenu.vault.lock=Lock
|
traymenu.vault.lock=Lock
|
||||||
@@ -34,18 +40,14 @@ addvaultwizard.new.locationPrompt=…
|
|||||||
addvaultwizard.new.directoryPickerLabel=Custom Location
|
addvaultwizard.new.directoryPickerLabel=Custom Location
|
||||||
addvaultwizard.new.directoryPickerButton=Choose…
|
addvaultwizard.new.directoryPickerButton=Choose…
|
||||||
addvaultwizard.new.directoryPickerTitle=Select Directory
|
addvaultwizard.new.directoryPickerTitle=Select Directory
|
||||||
addvaultwizard.new.enterPassword=Enter a password for the vault
|
|
||||||
addvaultwizard.new.fileAlreadyExists=Vault can not be created at this path because some object already exists.
|
addvaultwizard.new.fileAlreadyExists=Vault can not be created at this path because some object already exists.
|
||||||
addvaultwizard.new.invalidName=Invalid vault name. Please consider a regular directory name.
|
addvaultwizard.new.invalidName=Invalid vault name. Please consider a regular directory name.
|
||||||
addvaultwizard.new.ioException=Selected directory failed a basic test. Make sure to have the appropriate access rights and nothing interferes with Cryptomator.
|
addvaultwizard.new.ioException=Selected directory failed a basic test. Make sure to have the appropriate access rights and nothing interferes with Cryptomator.
|
||||||
### Password
|
### Password
|
||||||
addvaultwizard.new.reenterPassword=Confirm the password
|
|
||||||
addvaultwizard.new.passwordsMatch=Passwords match!
|
|
||||||
addvaultwizard.new.passwordsDoNotMatch=Passwords do not match
|
|
||||||
addvaultwizard.new.createVaultBtn=Create Vault
|
addvaultwizard.new.createVaultBtn=Create Vault
|
||||||
### Recovery Key
|
addvaultwizard.new.generateRecoveryKeyChoice=You won't be able to access your data without your password. Do you want a recovery key for the case you lose your password?
|
||||||
addvaultwizard.new.recoveryKeyInstruction=This is your recovery key. Keep it safe, it is your only chance to recover your data if you lose your password.
|
addvaultwizard.new.generateRecoveryKeyChoice.yes=Yes please, better safe than sorry
|
||||||
addvaultwizard.new.recoveryKeySavedCheckbox=Yes, I've made a secure backup of this recovery key
|
addvaultwizard.new.generateRecoveryKeyChoice.no=No thanks, I will not lose my password
|
||||||
### Information
|
### Information
|
||||||
addvault.new.readme.storageLocation.fileName=WHAT IS THIS DIRECTORY.rtf
|
addvault.new.readme.storageLocation.fileName=WHAT IS THIS DIRECTORY.rtf
|
||||||
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ VAULT FILES ⚠️
|
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ VAULT FILES ⚠️
|
||||||
@@ -73,10 +75,6 @@ removeVault.confirmBtn=Remove Vault
|
|||||||
# Change Password
|
# Change Password
|
||||||
changepassword.title=Change Password
|
changepassword.title=Change Password
|
||||||
changepassword.enterOldPassword=Enter the current password for "%s"
|
changepassword.enterOldPassword=Enter the current password for "%s"
|
||||||
changepassword.enterNewPassword=Enter a new password for your vault
|
|
||||||
changepassword.reenterNewPassword=Confirm the new password
|
|
||||||
changepassword.passwordsMatch=Passwords match!
|
|
||||||
changepassword.passwordsDoNotMatch=Passwords do not match
|
|
||||||
changepassword.finalConfirmation=I understand that I will not be able to recover my data if I forget my password
|
changepassword.finalConfirmation=I understand that I will not be able to recover my data if I forget my password
|
||||||
|
|
||||||
# Forget Password
|
# Forget Password
|
||||||
@@ -92,6 +90,8 @@ unlock.unlockBtn=Unlock
|
|||||||
## Success
|
## Success
|
||||||
unlock.success.message=Unlocked "%s" successfully! Your vault is now accessible.
|
unlock.success.message=Unlocked "%s" successfully! Your vault is now accessible.
|
||||||
unlock.success.revealBtn=Reveal Vault
|
unlock.success.revealBtn=Reveal Vault
|
||||||
|
## Invalid Mount Point
|
||||||
|
unlock.error.invalidMountPoint=Mount point is not an empty directory: %s
|
||||||
|
|
||||||
# Migration
|
# Migration
|
||||||
migration.title=Upgrade Vault
|
migration.title=Upgrade Vault
|
||||||
@@ -112,6 +112,10 @@ preferences.general=General
|
|||||||
preferences.general.theme=Look & Feel
|
preferences.general.theme=Look & Feel
|
||||||
preferences.general.startHidden=Hide window when starting Cryptomator
|
preferences.general.startHidden=Hide window when starting Cryptomator
|
||||||
preferences.general.debugLogging=Enable debug logging
|
preferences.general.debugLogging=Enable debug logging
|
||||||
|
preferences.general.autoStart=Launch Cryptomator on system start
|
||||||
|
preferences.general.interfaceOrientation=Interface Orientation
|
||||||
|
preferences.general.interfaceOrientation.ltr=Left to Right
|
||||||
|
preferences.general.interfaceOrientation.rtl=Right to Left
|
||||||
## Volume
|
## Volume
|
||||||
preferences.volume=Virtual Drive
|
preferences.volume=Virtual Drive
|
||||||
preferences.volume.type=Volume Type
|
preferences.volume.type=Volume Type
|
||||||
@@ -123,10 +127,19 @@ preferences.updates.currentVersion=Current Version: %s
|
|||||||
preferences.updates.autoUpdateCheck=Check for updates automatically
|
preferences.updates.autoUpdateCheck=Check for updates automatically
|
||||||
preferences.updates.checkNowBtn=Check Now
|
preferences.updates.checkNowBtn=Check Now
|
||||||
preferences.updates.updateAvailable=Update to version %s available.
|
preferences.updates.updateAvailable=Update to version %s available.
|
||||||
|
## Donation Key
|
||||||
|
preferences.donationKey=Donation
|
||||||
|
preferences.donationKey.registeredFor=Registered for %s
|
||||||
|
preferences.donationKey.noDonationKey=No valid donation key found. It's like a license key but for awesome people using free software. ;-)
|
||||||
|
preferences.donationKey.getDonationKey=Get a donation key
|
||||||
|
|
||||||
# Main Window
|
# Main Window
|
||||||
main.closeBtn.tooltip=Close
|
main.closeBtn.tooltip=Close
|
||||||
main.preferencesBtn.tooltip=Preferences
|
main.preferencesBtn.tooltip=Preferences
|
||||||
|
main.donationKeyMissing.tooltip=Please consider donating
|
||||||
|
## Drag 'n' Drop
|
||||||
|
main.dropZone.dropVault=Add this vault
|
||||||
|
main.dropZone.unknownDragboardContent=If you want to add a vault, drag it to this window
|
||||||
## Vault List
|
## Vault List
|
||||||
main.vaultlist.emptyList.onboardingInstruction=Click here to add a vault
|
main.vaultlist.emptyList.onboardingInstruction=Click here to add a vault
|
||||||
main.vaultlist.contextMenu.remove=Remove Vault
|
main.vaultlist.contextMenu.remove=Remove Vault
|
||||||
@@ -166,14 +179,25 @@ vaultOptions.mount.readonly=Read-Only
|
|||||||
vaultOptions.mount.driveName=Drive Name
|
vaultOptions.mount.driveName=Drive Name
|
||||||
vaultOptions.mount.customMountFlags=Custom Mount Flags
|
vaultOptions.mount.customMountFlags=Custom Mount Flags
|
||||||
vaultOptions.mount.winDriveLetterOccupied=occupied
|
vaultOptions.mount.winDriveLetterOccupied=occupied
|
||||||
vaultOptions.mount.winDirChooser=Pick an empty directory
|
vaultOptions.mount.mountPoint=Mount Point
|
||||||
|
vaultOptions.mount.mountPoint.auto=Automatically pick a suitable location
|
||||||
|
vaultOptions.mount.mountPoint.driveLetter=Use assigned drive letter
|
||||||
|
vaultOptions.mount.mountPoint.custom=Custom path
|
||||||
|
vaultOptions.mount.mountPoint.directoryPickerButton=Choose…
|
||||||
|
vaultOptions.mount.mountPoint.directoryPickerTitle=Pick an empty directory
|
||||||
|
|
||||||
# Recovery Key
|
# Recovery Key
|
||||||
recoveryKey.title=Recovery Key
|
recoveryKey.title=Recovery Key
|
||||||
recoveryKey.enterPassword.prompt=Enter your password to show the recovery key for "%s":
|
recoveryKey.enterPassword.prompt=Enter your password to show the recovery key for "%s":
|
||||||
recoveryKey.display.message=Recovery Key for "%s":
|
recoveryKey.display.message=The following recovery key can be used to restore access to "%s":
|
||||||
|
recoveryKey.display.StorageHints=Keep it somewhere very secure, e.g.:\n • Store it using a password manager\n • Save it on a USB flash drive\n • Print it on paper
|
||||||
|
|
||||||
# Misc
|
# New Password
|
||||||
|
newPassword.promptText=Enter a new password
|
||||||
|
newPassword.reenterPassword=Confirm the new password
|
||||||
|
newPassword.passwordsMatch=Passwords match!
|
||||||
|
newPassword.passwordsDoNotMatch=Passwords do not match
|
||||||
|
passwordStrength.messageLabel.tooShort=Use at least %d characters
|
||||||
passwordStrength.messageLabel.0=Very weak
|
passwordStrength.messageLabel.0=Very weak
|
||||||
passwordStrength.messageLabel.1=Weak
|
passwordStrength.messageLabel.1=Weak
|
||||||
passwordStrength.messageLabel.2=Fair
|
passwordStrength.messageLabel.2=Fair
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ generic.button.cancel=الغاء
|
|||||||
generic.button.change=تغيير
|
generic.button.change=تغيير
|
||||||
generic.button.done=تم
|
generic.button.done=تم
|
||||||
generic.button.next=التالي
|
generic.button.next=التالي
|
||||||
|
## Error
|
||||||
|
|
||||||
# Tray Menu
|
# Tray Menu
|
||||||
traymenu.showMainWindow=اظهار
|
traymenu.showMainWindow=اظهار
|
||||||
@@ -33,15 +34,10 @@ addvaultwizard.new.locationPrompt=…
|
|||||||
addvaultwizard.new.directoryPickerLabel=موقع مخصص
|
addvaultwizard.new.directoryPickerLabel=موقع مخصص
|
||||||
addvaultwizard.new.directoryPickerButton=اختر…
|
addvaultwizard.new.directoryPickerButton=اختر…
|
||||||
addvaultwizard.new.directoryPickerTitle=اختر القاموس
|
addvaultwizard.new.directoryPickerTitle=اختر القاموس
|
||||||
addvaultwizard.new.enterPassword=أدخل كلمة المرور لهذا الحساب
|
|
||||||
addvaultwizard.new.fileAlreadyExists=لا يمكن إنشاء مخزن على هذا المسار لأن بعض الملفات موجودة مسبقا.
|
addvaultwizard.new.fileAlreadyExists=لا يمكن إنشاء مخزن على هذا المسار لأن بعض الملفات موجودة مسبقا.
|
||||||
addvaultwizard.new.invalidName=اسم المخزن غير صالح. يرجى النظر في اسم الدليل المعتاد.
|
addvaultwizard.new.invalidName=اسم المخزن غير صالح. يرجى النظر في اسم الدليل المعتاد.
|
||||||
### Password
|
### Password
|
||||||
addvaultwizard.new.reenterPassword=تأكيد كلمة المرور
|
|
||||||
addvaultwizard.new.passwordsMatch=كلمات المرور متطابقة!
|
|
||||||
addvaultwizard.new.passwordsDoNotMatch=كلمات المرور غير متطابقة
|
|
||||||
addvaultwizard.new.createVaultBtn=انشئ حافظة
|
addvaultwizard.new.createVaultBtn=انشئ حافظة
|
||||||
### Recovery Key
|
|
||||||
### Information
|
### Information
|
||||||
addvault.new.readme.storageLocation.4=لو انك تحتاج مساعدة, جرب "%s".
|
addvault.new.readme.storageLocation.4=لو انك تحتاج مساعدة, جرب "%s".
|
||||||
## Existing
|
## Existing
|
||||||
@@ -52,10 +48,6 @@ addvaultwizard.existing.chooseBtn=اختر…
|
|||||||
removeVault.confirmBtn=احذف الحافظة
|
removeVault.confirmBtn=احذف الحافظة
|
||||||
|
|
||||||
# Change Password
|
# Change Password
|
||||||
changepassword.enterNewPassword=ادخل كلمة المرور الجديدة لحافظتك
|
|
||||||
changepassword.reenterNewPassword=تأكيد كلمة المرور الجديدة
|
|
||||||
changepassword.passwordsMatch=كلمات المرور متطابقة!
|
|
||||||
changepassword.passwordsDoNotMatch=كلمات المرور غير متطابقة
|
|
||||||
|
|
||||||
# Forget Password
|
# Forget Password
|
||||||
forgetPassword.title=نسيت كلمة المرور
|
forgetPassword.title=نسيت كلمة المرور
|
||||||
@@ -67,6 +59,7 @@ unlock.savePassword=احفظ كلمة المرور
|
|||||||
unlock.unlockBtn=افتح
|
unlock.unlockBtn=افتح
|
||||||
## Success
|
## Success
|
||||||
unlock.success.revealBtn=افتح الحافظة
|
unlock.success.revealBtn=افتح الحافظة
|
||||||
|
## Invalid Mount Point
|
||||||
|
|
||||||
# Migration
|
# Migration
|
||||||
migration.title=ترقية الحافظة
|
migration.title=ترقية الحافظة
|
||||||
@@ -87,9 +80,11 @@ preferences.volume.webdav.port=منفذ WebDav
|
|||||||
preferences.updates=تحديثات
|
preferences.updates=تحديثات
|
||||||
preferences.updates.autoUpdateCheck=تحقق من التحديثات اوتوماتيكيا
|
preferences.updates.autoUpdateCheck=تحقق من التحديثات اوتوماتيكيا
|
||||||
preferences.updates.checkNowBtn=تحقق الان
|
preferences.updates.checkNowBtn=تحقق الان
|
||||||
|
## Donation Key
|
||||||
|
|
||||||
# Main Window
|
# Main Window
|
||||||
main.preferencesBtn.tooltip=تفضيلات
|
main.preferencesBtn.tooltip=تفضيلات
|
||||||
|
## Drag 'n' Drop
|
||||||
## Vault List
|
## Vault List
|
||||||
main.vaultlist.addVaultBtn=أضِف مخزنًا
|
main.vaultlist.addVaultBtn=أضِف مخزنًا
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
@@ -108,9 +103,10 @@ main.vaultDetail.migrateButton=ترقية الحافظة
|
|||||||
## General
|
## General
|
||||||
vaultOptions.general=عام
|
vaultOptions.general=عام
|
||||||
## Mount
|
## Mount
|
||||||
|
vaultOptions.mount.mountPoint.directoryPickerButton=اختر…
|
||||||
|
|
||||||
# Recovery Key
|
# Recovery Key
|
||||||
|
|
||||||
# Misc
|
# New Password
|
||||||
|
|
||||||
# Quit
|
# Quit
|
||||||
|
|||||||
@@ -6,8 +6,11 @@ generic.button.apply=Použít
|
|||||||
generic.button.back=Zpět
|
generic.button.back=Zpět
|
||||||
generic.button.cancel=Zrušit
|
generic.button.cancel=Zrušit
|
||||||
generic.button.change=Změnit
|
generic.button.change=Změnit
|
||||||
|
generic.button.copy=Kopírovat
|
||||||
generic.button.done=Hotovo
|
generic.button.done=Hotovo
|
||||||
generic.button.next=Další
|
generic.button.next=Další
|
||||||
|
generic.button.print=Vytisknout
|
||||||
|
## Error
|
||||||
|
|
||||||
# Tray Menu
|
# Tray Menu
|
||||||
traymenu.showMainWindow=Zobrazit
|
traymenu.showMainWindow=Zobrazit
|
||||||
@@ -33,16 +36,12 @@ addvaultwizard.new.locationPrompt=...
|
|||||||
addvaultwizard.new.directoryPickerLabel=Vlastní umístění
|
addvaultwizard.new.directoryPickerLabel=Vlastní umístění
|
||||||
addvaultwizard.new.directoryPickerButton=Vybrat...
|
addvaultwizard.new.directoryPickerButton=Vybrat...
|
||||||
addvaultwizard.new.directoryPickerTitle=Vyberte adresář
|
addvaultwizard.new.directoryPickerTitle=Vyberte adresář
|
||||||
addvaultwizard.new.enterPassword=Zadejte heslo pro trezor
|
|
||||||
addvaultwizard.new.fileAlreadyExists=Trezor nemůže být vytvořen na tomto umístění, protože stejný objekt již existuje.
|
addvaultwizard.new.fileAlreadyExists=Trezor nemůže být vytvořen na tomto umístění, protože stejný objekt již existuje.
|
||||||
addvaultwizard.new.invalidName=Neplatné jméno trezoru. Prosím změňte jméno adresáře.
|
addvaultwizard.new.invalidName=Neplatné jméno trezoru. Prosím změňte jméno adresáře.
|
||||||
### Password
|
### Password
|
||||||
addvaultwizard.new.reenterPassword=Potvrďte heslo
|
|
||||||
addvaultwizard.new.passwordsMatch=Hesla se shodují!
|
|
||||||
addvaultwizard.new.passwordsDoNotMatch=Hesla se neshodují
|
|
||||||
addvaultwizard.new.createVaultBtn=Vytvořit trezor
|
addvaultwizard.new.createVaultBtn=Vytvořit trezor
|
||||||
### Recovery Key
|
addvaultwizard.new.generateRecoveryKeyChoice.yes=Ano prosím, jistota je jistota
|
||||||
addvaultwizard.new.recoveryKeyInstruction=Toto je tvůj obnovovací klíč. Uchovej jej v bezpečí, je to jediná šance, jak obnovit data, pokud ztratíš heslo.
|
addvaultwizard.new.generateRecoveryKeyChoice.no=Ne, díky, neztratím své heslo
|
||||||
### Information
|
### Information
|
||||||
addvault.new.readme.storageLocation.fileName=CO JE TOTO ZA ADRESÁŘ.rtf
|
addvault.new.readme.storageLocation.fileName=CO JE TOTO ZA ADRESÁŘ.rtf
|
||||||
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ OBSAH TREZORU ⚠️
|
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ OBSAH TREZORU ⚠️
|
||||||
@@ -69,10 +68,6 @@ removeVault.confirmBtn=Odstranit trezor
|
|||||||
# Change Password
|
# Change Password
|
||||||
changepassword.title=Změnit heslo
|
changepassword.title=Změnit heslo
|
||||||
changepassword.enterOldPassword=Zadejte současné heslo pro "%s"
|
changepassword.enterOldPassword=Zadejte současné heslo pro "%s"
|
||||||
changepassword.enterNewPassword=Zadejte nové heslo pro váš trezor
|
|
||||||
changepassword.reenterNewPassword=Potvrď nové heslo
|
|
||||||
changepassword.passwordsMatch=Hesla se shodují!
|
|
||||||
changepassword.passwordsDoNotMatch=Hesla se neshodují
|
|
||||||
changepassword.finalConfirmation=UPOZORNĚNÍ: Pokud heslo zapomenete/ztratíte, nenávratně příjdete o přístup ke svému obsahu!
|
changepassword.finalConfirmation=UPOZORNĚNÍ: Pokud heslo zapomenete/ztratíte, nenávratně příjdete o přístup ke svému obsahu!
|
||||||
|
|
||||||
# Forget Password
|
# Forget Password
|
||||||
@@ -88,6 +83,7 @@ unlock.unlockBtn=Odemknout
|
|||||||
## Success
|
## Success
|
||||||
unlock.success.message=Trezor "%s" byl úspěšně odemčen a nyní je dostupný.
|
unlock.success.message=Trezor "%s" byl úspěšně odemčen a nyní je dostupný.
|
||||||
unlock.success.revealBtn=Zobrazit trezor
|
unlock.success.revealBtn=Zobrazit trezor
|
||||||
|
## Invalid Mount Point
|
||||||
|
|
||||||
# Migration
|
# Migration
|
||||||
migration.title=Upgrade trezoru
|
migration.title=Upgrade trezoru
|
||||||
@@ -119,10 +115,18 @@ preferences.updates.currentVersion=Aktuální verze: %s
|
|||||||
preferences.updates.autoUpdateCheck=Automaticky kontrolovat aktualizace
|
preferences.updates.autoUpdateCheck=Automaticky kontrolovat aktualizace
|
||||||
preferences.updates.checkNowBtn=Zkontrolovat nyní
|
preferences.updates.checkNowBtn=Zkontrolovat nyní
|
||||||
preferences.updates.updateAvailable=Dostupná nová verze %s.
|
preferences.updates.updateAvailable=Dostupná nová verze %s.
|
||||||
|
## Donation Key
|
||||||
|
preferences.donationKey=Příspěvek
|
||||||
|
preferences.donationKey.registeredFor=Registrováno na: %s
|
||||||
|
preferences.donationKey.getDonationKey=Získat darovací klíč
|
||||||
|
|
||||||
# Main Window
|
# Main Window
|
||||||
main.closeBtn.tooltip=Zavřít
|
main.closeBtn.tooltip=Zavřít
|
||||||
main.preferencesBtn.tooltip=Nastavení
|
main.preferencesBtn.tooltip=Nastavení
|
||||||
|
main.donationKeyMissing.tooltip=Prosím, zvažte darování
|
||||||
|
## Drag 'n' Drop
|
||||||
|
main.dropZone.dropVault=Přidat tento trezor
|
||||||
|
main.dropZone.unknownDragboardContent=Pokud chcete přidat trezor, přetáhněte jej do tohoto okna
|
||||||
## Vault List
|
## Vault List
|
||||||
main.vaultlist.emptyList.onboardingInstruction=Klikněte sem pro přidání nového trezoru
|
main.vaultlist.emptyList.onboardingInstruction=Klikněte sem pro přidání nového trezoru
|
||||||
main.vaultlist.contextMenu.remove=Odstranit trezor
|
main.vaultlist.contextMenu.remove=Odstranit trezor
|
||||||
@@ -144,6 +148,7 @@ main.vaultDetail.throughput.kbps=%.1f kiB/s
|
|||||||
main.vaultDetail.throughput.mbps=%.1f MiB/s
|
main.vaultDetail.throughput.mbps=%.1f MiB/s
|
||||||
### Needs Migration
|
### Needs Migration
|
||||||
main.vaultDetail.migrateButton=Upgrade trezoru
|
main.vaultDetail.migrateButton=Upgrade trezoru
|
||||||
|
main.vaultDetail.migratePrompt=Váš trezor musí být aktualizován na nový formát, než k němu budete mít přístup
|
||||||
|
|
||||||
# Wrong File Alert
|
# Wrong File Alert
|
||||||
wrongFileAlert.title=neznámý soubor
|
wrongFileAlert.title=neznámý soubor
|
||||||
@@ -161,14 +166,20 @@ vaultOptions.mount.readonly=Pouze pro čtení
|
|||||||
vaultOptions.mount.driveName=Jméno jednotky
|
vaultOptions.mount.driveName=Jméno jednotky
|
||||||
vaultOptions.mount.customMountFlags=Vlastní parametry
|
vaultOptions.mount.customMountFlags=Vlastní parametry
|
||||||
vaultOptions.mount.winDriveLetterOccupied=obsazené
|
vaultOptions.mount.winDriveLetterOccupied=obsazené
|
||||||
vaultOptions.mount.winDirChooser=Vyberte prázdný adresář
|
vaultOptions.mount.mountPoint=Přípojný bod
|
||||||
|
vaultOptions.mount.mountPoint.auto=Automaticky vybrat vhodné místo
|
||||||
|
vaultOptions.mount.mountPoint.driveLetter=Použít přiřazené písmeno
|
||||||
|
vaultOptions.mount.mountPoint.custom=Vlastní cesta
|
||||||
|
vaultOptions.mount.mountPoint.directoryPickerButton=Vybrat...
|
||||||
|
vaultOptions.mount.mountPoint.directoryPickerTitle=Vyberte prázdný adresář
|
||||||
|
|
||||||
# Recovery Key
|
# Recovery Key
|
||||||
recoveryKey.title=Klíč k obnově
|
recoveryKey.title=Klíč k obnově
|
||||||
recoveryKey.enterPassword.prompt=Zadejte své heslo pro zobrazení obnovovacího klíče pro "%s":
|
recoveryKey.enterPassword.prompt=Zadejte své heslo pro zobrazení obnovovacího klíče pro "%s":
|
||||||
recoveryKey.display.message=Klíč k obnově "%s":
|
recoveryKey.display.message=Následující obnovovací klíč může být použit k obnovení přístupu k "%s":
|
||||||
|
recoveryKey.display.StorageHints=Uchovejte ho někde velmi bezpečně, např.\n • Uložit pomocí správce hesel\n • Uložit na USB flash disk\n • Vytisknout na papír
|
||||||
|
|
||||||
# Misc
|
# New Password
|
||||||
passwordStrength.messageLabel.0=velmi slabé
|
passwordStrength.messageLabel.0=velmi slabé
|
||||||
passwordStrength.messageLabel.1=slabé
|
passwordStrength.messageLabel.1=slabé
|
||||||
passwordStrength.messageLabel.2=dostatečné
|
passwordStrength.messageLabel.2=dostatečné
|
||||||
|
|||||||
@@ -6,12 +6,18 @@ generic.button.apply=Übernehmen
|
|||||||
generic.button.back=Zurück
|
generic.button.back=Zurück
|
||||||
generic.button.cancel=Abbrechen
|
generic.button.cancel=Abbrechen
|
||||||
generic.button.change=Ändern
|
generic.button.change=Ändern
|
||||||
|
generic.button.copy=Kopieren
|
||||||
generic.button.done=Fertig
|
generic.button.done=Fertig
|
||||||
generic.button.next=Weiter
|
generic.button.next=Weiter
|
||||||
|
generic.button.print=Drucken
|
||||||
|
## Error
|
||||||
|
generic.error.title=Ein unerwarteter Fehler ist aufgetreten
|
||||||
|
generic.error.instruction=Dies hätte nicht passieren dürfen. Bitte melde den Fehlertext unten und füge eine Beschreibung hinzu, welche Schritte zu diesem Fehler geführt haben.
|
||||||
|
|
||||||
# Tray Menu
|
# Tray Menu
|
||||||
traymenu.showMainWindow=Anzeigen
|
traymenu.showMainWindow=Anzeigen
|
||||||
traymenu.showPreferencesWindow=Einstellungen
|
traymenu.showPreferencesWindow=Einstellungen
|
||||||
|
traymenu.lockAllVaults=Alle sperren
|
||||||
traymenu.quitApplication=Beenden
|
traymenu.quitApplication=Beenden
|
||||||
traymenu.vault.unlock=Entsperren
|
traymenu.vault.unlock=Entsperren
|
||||||
traymenu.vault.lock=Sperren
|
traymenu.vault.lock=Sperren
|
||||||
@@ -32,32 +38,28 @@ addvaultwizard.new.locationLabel=Speicherort
|
|||||||
addvaultwizard.new.locationPrompt=…
|
addvaultwizard.new.locationPrompt=…
|
||||||
addvaultwizard.new.directoryPickerLabel=Eigener Ort
|
addvaultwizard.new.directoryPickerLabel=Eigener Ort
|
||||||
addvaultwizard.new.directoryPickerButton=Durchsuchen …
|
addvaultwizard.new.directoryPickerButton=Durchsuchen …
|
||||||
addvaultwizard.new.directoryPickerTitle=Wähle ein Verzeichnis
|
addvaultwizard.new.directoryPickerTitle=Verzeichnis auswählen
|
||||||
addvaultwizard.new.enterPassword=Vergib ein Passwort für den Tresor
|
|
||||||
addvaultwizard.new.fileAlreadyExists=Der Tresor konnte nicht erstellt werden, da der Speicherort bereits belegt ist.
|
addvaultwizard.new.fileAlreadyExists=Der Tresor konnte nicht erstellt werden, da der Speicherort bereits belegt ist.
|
||||||
addvaultwizard.new.invalidName=Ungültiger Tresorname. Tresore müssen wie Verzeichnisse benannt werden.
|
addvaultwizard.new.invalidName=Ungültiger Tresorname. Tresore müssen wie Verzeichnisse benannt werden.
|
||||||
addvaultwizard.new.ioException=Auf das ausgewählte Verzeichnis kann nicht zugegriffen werden. Prüfe die Zugriffsrechte und stelle sicher, dass Cryptomator nicht von einer anderen Anwendung blockiert wird.
|
addvaultwizard.new.ioException=Auf das ausgewählte Verzeichnis kann nicht zugegriffen werden. Prüfe die Zugriffsrechte und stelle sicher, dass Cryptomator nicht von einer anderen Anwendung blockiert wird.
|
||||||
### Password
|
### Password
|
||||||
addvaultwizard.new.reenterPassword=Bestätige das Passwort
|
|
||||||
addvaultwizard.new.passwordsMatch=Passwörter stimmen überein!
|
|
||||||
addvaultwizard.new.passwordsDoNotMatch=Passwörter stimmen nicht überein
|
|
||||||
addvaultwizard.new.createVaultBtn=Tresor erstellen
|
addvaultwizard.new.createVaultBtn=Tresor erstellen
|
||||||
### Recovery Key
|
addvaultwizard.new.generateRecoveryKeyChoice=Ohne dieses Passwort kannst du auf deine Daten nicht mehr zugreifen. Möchtest du für den Fall eines Passwortverlusts einen Wiederherstellungsschlüssel erstellen?
|
||||||
addvaultwizard.new.recoveryKeyInstruction=Dies ist dein Wiederherstellungsschlüssel. Bewahre ihn sicher auf. Ohne ihn gibt es keine Möglichkeit deine Daten wiederherzustellen, wenn du dein Kennwort verloren hast.
|
addvaultwizard.new.generateRecoveryKeyChoice.yes=Ja bitte, sicher ist sicher
|
||||||
addvaultwizard.new.recoveryKeySavedCheckbox=Ja, ich habe eine Datensicherung dieses Wiederherstellungsschlüssels durchgeführt
|
addvaultwizard.new.generateRecoveryKeyChoice.no=Nein danke, ich werde mein Passwort nicht verlieren
|
||||||
### Information
|
### Information
|
||||||
addvault.new.readme.storageLocation.fileName=WAS BEDEUTET DIESER ORDNER.rtf
|
addvault.new.readme.storageLocation.fileName=WAS BEDEUTET DIESER ORDNER.rtf
|
||||||
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ TRESORDATEIEN ⚠️
|
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ TRESORDATEIEN ⚠️
|
||||||
addvault.new.readme.storageLocation.2=Dies ist der Speicherort deines Tresors. Ändere {\\b KEINE} Dateien in diesem Verzeichnis.
|
addvault.new.readme.storageLocation.2=Dies ist der Speicherort deines Tresors. Ändere {\\b KEINE} Dateien in diesem Verzeichnis.
|
||||||
addvault.new.readme.storageLocation.3=Wenn du Dateien mit Cryptomator verschlüsseln möchtest, entsperre den Tresor und verwende das zur Verfügung gestellte Laufwerk.
|
addvault.new.readme.storageLocation.3=Wenn du Dateien mit Cryptomator verschlüsseln möchtest, entsperre den Tresor und verwende das zur Verfügung gestellte Laufwerk.
|
||||||
addvault.new.readme.storageLocation.4=Falls du Hilfe brauchst, versuche %s.
|
addvault.new.readme.storageLocation.4=Falls du Hilfe brauchst, versuche %s.
|
||||||
addvault.new.readme.accessLocation.fileName=WILLKOMMEN ZUM TRESOR.rtf
|
addvault.new.readme.accessLocation.fileName=WILLKOMMEN ZU DEINEM TRESOR.rtf
|
||||||
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ VERSCHLÜSSELTES LAUFWERK 🔐️
|
addvault.new.readme.accessLocation.1=\\fs40\\qc 🔐️ VERSCHLÜSSELTES LAUFWERK 🔐️
|
||||||
addvault.new.readme.accessLocation.2=Dies ist dein Tresorlaufwerk. Alle zu diesem Laufwerk hinzugefügten Dateien werden mit Cryptomator verschlüsselt. Um zu einem späteren Zeitpunkt auf dieses Laufwerk zuzugreifen, entsperre es einfach wieder aus der Cryptomator-Anwendung heraus.
|
addvault.new.readme.accessLocation.2=Dies ist dein Tresorlaufwerk. Alle zu diesem Laufwerk hinzugefügten Dateien werden von Cryptomator verschlüsselt. Um zu einem späteren Zeitpunkt auf dieses Laufwerk zuzugreifen, entsperre es einfach wieder aus der Cryptomator-Anwendung heraus.
|
||||||
addvault.new.readme.accessLocation.3=Diese Datei kannst du löschen.
|
addvault.new.readme.accessLocation.3=Diese Datei kannst du löschen.
|
||||||
## Existing
|
## Existing
|
||||||
addvaultwizard.existing.instruction=Wähle die Datei „masterkey.cryptomator“ deines vorhandenen Tresors aus.
|
addvaultwizard.existing.instruction=Wähle die Datei „masterkey.cryptomator“ deines vorhandenen Tresors aus.
|
||||||
addvaultwizard.existing.chooseBtn=Durchsuchen…
|
addvaultwizard.existing.chooseBtn=Durchsuchen …
|
||||||
addvaultwizard.existing.filePickerTitle=Masterkey-Datei auswählen
|
addvaultwizard.existing.filePickerTitle=Masterkey-Datei auswählen
|
||||||
addvaultwizard.existing.error=Der Tresor „%s“ ist kein gültiger Tresor und kann nicht hinzugefügt werden. Weitere Informationen findest du im Diagnoseprotokoll.
|
addvaultwizard.existing.error=Der Tresor „%s“ ist kein gültiger Tresor und kann nicht hinzugefügt werden. Weitere Informationen findest du im Diagnoseprotokoll.
|
||||||
## Success
|
## Success
|
||||||
@@ -71,11 +73,7 @@ removeVault.confirmBtn=Tresor entfernen
|
|||||||
|
|
||||||
# Change Password
|
# Change Password
|
||||||
changepassword.title=Password ändern
|
changepassword.title=Password ändern
|
||||||
changepassword.enterOldPassword=Gib dein derzeitiges Passwort für "%s" ein
|
changepassword.enterOldPassword=Gib dein aktuelles Passwort für „%s“ ein
|
||||||
changepassword.enterNewPassword=Gib ein neues Tresorpasswort ein
|
|
||||||
changepassword.reenterNewPassword=Bestätige das neue Passwort
|
|
||||||
changepassword.passwordsMatch=Passwörter stimmen überein!
|
|
||||||
changepassword.passwordsDoNotMatch=Passwörter stimmen nicht überein
|
|
||||||
changepassword.finalConfirmation=Mir ist bewusst, dass ich den Tresor ohne das Passwort NICHT mehr öffnen kann
|
changepassword.finalConfirmation=Mir ist bewusst, dass ich den Tresor ohne das Passwort NICHT mehr öffnen kann
|
||||||
|
|
||||||
# Forget Password
|
# Forget Password
|
||||||
@@ -85,12 +83,14 @@ forgetPassword.confirmBtn=Passwort vergessen
|
|||||||
|
|
||||||
# Unlock
|
# Unlock
|
||||||
unlock.title=Tresor entsperren
|
unlock.title=Tresor entsperren
|
||||||
unlock.passwordPrompt=Gib das Passwort für "%s" ein:
|
unlock.passwordPrompt=Gib das Passwort für „%s“ ein:
|
||||||
unlock.savePassword=Passwort speichern
|
unlock.savePassword=Passwort speichern
|
||||||
unlock.unlockBtn=Entsperren
|
unlock.unlockBtn=Entsperren
|
||||||
## Success
|
## Success
|
||||||
unlock.success.message="%s" erfolgreich entsperrt!
|
unlock.success.message=„%s“ erfolgreich entsperrt! Nun kannst du auf deinen Tresor zugreifen.
|
||||||
unlock.success.revealBtn=Tresor anzeigen
|
unlock.success.revealBtn=Tresor anzeigen
|
||||||
|
## Invalid Mount Point
|
||||||
|
unlock.error.invalidMountPoint=Einhängepunkt ist kein leeres Verzeichnis: %s
|
||||||
|
|
||||||
# Migration
|
# Migration
|
||||||
migration.title=Tresor aktualisieren
|
migration.title=Tresor aktualisieren
|
||||||
@@ -111,6 +111,10 @@ preferences.general=Allgemein
|
|||||||
preferences.general.theme=Erscheinungsbild
|
preferences.general.theme=Erscheinungsbild
|
||||||
preferences.general.startHidden=Cryptomator im Hintergrund starten
|
preferences.general.startHidden=Cryptomator im Hintergrund starten
|
||||||
preferences.general.debugLogging=Diagnoseprotokoll aktivieren
|
preferences.general.debugLogging=Diagnoseprotokoll aktivieren
|
||||||
|
preferences.general.autoStart=Cryptomator beim Systemstart starten
|
||||||
|
preferences.general.interfaceOrientation=Oberflächenausrichtung
|
||||||
|
preferences.general.interfaceOrientation.ltr=Von links nach rechts
|
||||||
|
preferences.general.interfaceOrientation.rtl=Von rechts nach links
|
||||||
## Volume
|
## Volume
|
||||||
preferences.volume=Tresorlaufwerk
|
preferences.volume=Tresorlaufwerk
|
||||||
preferences.volume.type=Laufwerkstyp
|
preferences.volume.type=Laufwerkstyp
|
||||||
@@ -122,10 +126,19 @@ preferences.updates.currentVersion=Aktuelle Version: %s
|
|||||||
preferences.updates.autoUpdateCheck=Automatisch nach Updates suchen
|
preferences.updates.autoUpdateCheck=Automatisch nach Updates suchen
|
||||||
preferences.updates.checkNowBtn=Jetzt suchen
|
preferences.updates.checkNowBtn=Jetzt suchen
|
||||||
preferences.updates.updateAvailable=Update auf Version %s verfügbar.
|
preferences.updates.updateAvailable=Update auf Version %s verfügbar.
|
||||||
|
## Donation Key
|
||||||
|
preferences.donationKey=Spende
|
||||||
|
preferences.donationKey.registeredFor=Registriert für %s
|
||||||
|
preferences.donationKey.noDonationKey=Kein gültiger Spendenschlüssel gefunden. Er ist wie ein Lizenzschlüssel, aber für tolle Leute, die freie Software verwenden. ;-)
|
||||||
|
preferences.donationKey.getDonationKey=Einen Spendenschlüssel erhalten
|
||||||
|
|
||||||
# Main Window
|
# Main Window
|
||||||
main.closeBtn.tooltip=Schließen
|
main.closeBtn.tooltip=Schließen
|
||||||
main.preferencesBtn.tooltip=Einstellungen
|
main.preferencesBtn.tooltip=Einstellungen
|
||||||
|
main.donationKeyMissing.tooltip=Bitte denke über eine Spende nach
|
||||||
|
## Drag 'n' Drop
|
||||||
|
main.dropZone.dropVault=Diesen Tresor hinzufügen
|
||||||
|
main.dropZone.unknownDragboardContent=Wenn Sie einen Tresor hinzufügen möchten, ziehen Sie ihn in dieses Fenster
|
||||||
## Vault List
|
## Vault List
|
||||||
main.vaultlist.emptyList.onboardingInstruction=Klicke hier, um einen Tresor hinzuzufügen
|
main.vaultlist.emptyList.onboardingInstruction=Klicke hier, um einen Tresor hinzuzufügen
|
||||||
main.vaultlist.contextMenu.remove=Tresor entfernen
|
main.vaultlist.contextMenu.remove=Tresor entfernen
|
||||||
@@ -152,7 +165,7 @@ main.vaultDetail.migratePrompt=Dein Tresor muss auf ein neues Format aktualisier
|
|||||||
# Wrong File Alert
|
# Wrong File Alert
|
||||||
wrongFileAlert.title=Unbekannte Datei
|
wrongFileAlert.title=Unbekannte Datei
|
||||||
wrongFileAlert.btn=OK
|
wrongFileAlert.btn=OK
|
||||||
wrongFileAlert.information=Du hast versucht, eine Datei oder einen Ordner hinzuzufügen, der scheinbar kein Cryptomator-Tresor ist. Um Dateien zu verschlüsseln, erstelle und entsperre einen Tresor.
|
wrongFileAlert.information=Du hast versucht, eine Datei oder einen Ordner hinzuzufügen, der anscheinend kein Cryptomator-Tresor ist. Zum Verschlüsseln deiner Daten musst du zuerst einen Tresor erstellen und diesen dann entsperren.
|
||||||
|
|
||||||
# Vault Options
|
# Vault Options
|
||||||
## General
|
## General
|
||||||
@@ -165,14 +178,25 @@ vaultOptions.mount.readonly=Schreibgeschützt
|
|||||||
vaultOptions.mount.driveName=Laufwerksname
|
vaultOptions.mount.driveName=Laufwerksname
|
||||||
vaultOptions.mount.customMountFlags=Benutzerdefinierte Mount-Flags
|
vaultOptions.mount.customMountFlags=Benutzerdefinierte Mount-Flags
|
||||||
vaultOptions.mount.winDriveLetterOccupied=belegt
|
vaultOptions.mount.winDriveLetterOccupied=belegt
|
||||||
vaultOptions.mount.winDirChooser=Wähle ein leeres Verzeichnis
|
vaultOptions.mount.mountPoint=Einhängepunkt
|
||||||
|
vaultOptions.mount.mountPoint.auto=Automatisch einen passenden Ort auswählen
|
||||||
|
vaultOptions.mount.mountPoint.driveLetter=Zugewiesenen Laufwerksbuchstaben verwenden
|
||||||
|
vaultOptions.mount.mountPoint.custom=Eigener Pfad
|
||||||
|
vaultOptions.mount.mountPoint.directoryPickerButton=Durchsuchen …
|
||||||
|
vaultOptions.mount.mountPoint.directoryPickerTitle=Wähle ein leeres Verzeichnis
|
||||||
|
|
||||||
# Recovery Key
|
# Recovery Key
|
||||||
recoveryKey.title=Wiederherstellungsschlüssel
|
recoveryKey.title=Wiederherstellungsschlüssel
|
||||||
recoveryKey.enterPassword.prompt=Gib dein Kennwort ein, um den Wiederherstellungsschlüssel für „%s“ anzuzeigen:
|
recoveryKey.enterPassword.prompt=Gib dein Passwort ein, um den Wiederherstellungsschlüssel für „%s“ anzuzeigen:
|
||||||
recoveryKey.display.message=Wiederherstellungsschlüssel für „%s“:
|
recoveryKey.display.message=Mit folgendem Wiederherstellungsschlüssel kannst du den Zugriff auf „%s“ wiederherstellen:
|
||||||
|
recoveryKey.display.StorageHints=Bewahre ihn möglichst sicher auf, z. B.\n • in einem Passwortmanager\n • auf einem USB-Speicherstick\n • auf Papier ausgedruckt
|
||||||
|
|
||||||
# Misc
|
# New Password
|
||||||
|
newPassword.promptText=Gib ein neues Passwort ein
|
||||||
|
newPassword.reenterPassword=Bestätige das neue Passwort
|
||||||
|
newPassword.passwordsMatch=Passwörter stimmen überein!
|
||||||
|
newPassword.passwordsDoNotMatch=Passwörter stimmen nicht überein
|
||||||
|
passwordStrength.messageLabel.tooShort=Verwende mindestens %d Zeichen
|
||||||
passwordStrength.messageLabel.0=Sehr schwach
|
passwordStrength.messageLabel.0=Sehr schwach
|
||||||
passwordStrength.messageLabel.1=Schwach
|
passwordStrength.messageLabel.1=Schwach
|
||||||
passwordStrength.messageLabel.2=Mittel
|
passwordStrength.messageLabel.2=Mittel
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
# Generics
|
# Generics
|
||||||
## Button
|
## Button
|
||||||
|
## Error
|
||||||
|
|
||||||
# Tray Menu
|
# Tray Menu
|
||||||
|
|
||||||
@@ -11,7 +12,6 @@
|
|||||||
### Name
|
### Name
|
||||||
### Location
|
### Location
|
||||||
### Password
|
### Password
|
||||||
### Recovery Key
|
|
||||||
### Information
|
### Information
|
||||||
## Existing
|
## Existing
|
||||||
## Success
|
## Success
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
# Unlock
|
# Unlock
|
||||||
## Success
|
## Success
|
||||||
|
## Invalid Mount Point
|
||||||
|
|
||||||
# Migration
|
# Migration
|
||||||
## Start
|
## Start
|
||||||
@@ -34,8 +35,10 @@
|
|||||||
## General
|
## General
|
||||||
## Volume
|
## Volume
|
||||||
## Updates
|
## Updates
|
||||||
|
## Donation Key
|
||||||
|
|
||||||
# Main Window
|
# Main Window
|
||||||
|
## Drag 'n' Drop
|
||||||
## Vault List
|
## Vault List
|
||||||
## Vault Detail
|
## Vault Detail
|
||||||
### Locked
|
### Locked
|
||||||
@@ -50,6 +53,6 @@
|
|||||||
|
|
||||||
# Recovery Key
|
# Recovery Key
|
||||||
|
|
||||||
# Misc
|
# New Password
|
||||||
|
|
||||||
# Quit
|
# Quit
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user