mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-04-20 17:46:52 -04:00
Merge branch 'release/1.5.0-beta2'
This commit is contained in:
4
.github/stale.yml
vendored
4
.github/stale.yml
vendored
@@ -1,12 +1,14 @@
|
||||
# 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
|
||||
daysUntilClose: 30
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- type:security-issue # never close automatically
|
||||
- type:feature-request # never close automatically
|
||||
- state:awaiting-response # handled by different bot
|
||||
- state:blocked
|
||||
- state:confirmed
|
||||
# Set to true to ignore issues in a milestone (defaults to false)
|
||||
exemptMilestones: true
|
||||
# Label to use when marking an issue as stale
|
||||
|
||||
4
.idea/compiler.xml
generated
4
.idea/compiler.xml
generated
@@ -31,10 +31,10 @@
|
||||
<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" />
|
||||
</processorPath>
|
||||
<module name="commons" />
|
||||
<module name="keychain" />
|
||||
<module name="ui" />
|
||||
<module name="launcher" />
|
||||
<module name="commons" />
|
||||
<module name="ui" />
|
||||
</profile>
|
||||
</annotationProcessing>
|
||||
</component>
|
||||
|
||||
14
.idea/encodings.xml
generated
14
.idea/encodings.xml
generated
@@ -1,11 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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/buildkit" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/commons" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/keychain" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/launcher" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/ui" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/commons/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/keychain/src/main/java" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/keychain/src/main/resources" charset="UTF-8" />
|
||||
<file url="file://$PROJECT_DIR$/main/launcher/src/main/java" 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>
|
||||
</project>
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.5.0-beta1</version>
|
||||
<version>1.5.0-beta2</version>
|
||||
</parent>
|
||||
<artifactId>buildkit</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.5.0-beta1</version>
|
||||
<version>1.5.0-beta2</version>
|
||||
</parent>
|
||||
<artifactId>commons</artifactId>
|
||||
<name>Cryptomator Commons</name>
|
||||
@@ -48,6 +48,12 @@
|
||||
<artifactId>easybind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JWT -->
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>java-jwt</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Google -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
|
||||
@@ -28,13 +28,23 @@ import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Module(subcomponents = {VaultComponent.class})
|
||||
public abstract class CommonsModule {
|
||||
|
||||
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
|
||||
@Singleton
|
||||
@Named("SemVer")
|
||||
@@ -56,7 +66,7 @@ public abstract class CommonsModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
static ScheduledExecutorService provideScheduledExecutorService(@Named("shutdownTaskScheduler") Consumer<Runnable> shutdownTaskScheduler) {
|
||||
static ScheduledExecutorService provideScheduledExecutorService(ShutdownHook shutdownHook) {
|
||||
final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(NUM_SCHEDULER_THREADS, r -> {
|
||||
Thread t = new Thread(r);
|
||||
@@ -64,7 +74,7 @@ public abstract class CommonsModule {
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
shutdownTaskScheduler.accept(executorService::shutdown);
|
||||
shutdownHook.runOnShutdown(executorService::shutdown);
|
||||
return executorService;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ public class Environment {
|
||||
private static final Path RELATIVE_HOME_DIR = Paths.get("~");
|
||||
private static final Path ABSOLUTE_HOME_DIR = Paths.get(USER_HOME);
|
||||
private static final char PATH_LIST_SEP = ':';
|
||||
private static final int DEFAULT_MIN_PW_LENGTH = 8;
|
||||
|
||||
@Inject
|
||||
public Environment() {
|
||||
@@ -37,6 +38,7 @@ public class Environment {
|
||||
LOG.debug("cryptomator.keychainPath: {}", System.getProperty("cryptomator.keychainPath"));
|
||||
LOG.debug("cryptomator.logDir: {}", System.getProperty("cryptomator.logDir"));
|
||||
LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir"));
|
||||
LOG.debug("cryptomator.minPwLength: {}", System.getProperty("cryptomator.minPwLength"));
|
||||
}
|
||||
|
||||
public boolean useCustomLogbackConfig() {
|
||||
@@ -63,6 +65,19 @@ public class Environment {
|
||||
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) {
|
||||
String value = System.getProperty(propertyName);
|
||||
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.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
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 UiTheme DEFAULT_THEME = UiTheme.LIGHT;
|
||||
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 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<UiTheme> theme = new SimpleObjectProperty<>(DEFAULT_THEME);
|
||||
private final ObjectProperty<NodeOrientation> userInterfaceOrientation = new SimpleObjectProperty<>(DEFAULT_USER_INTERFACE_ORIENTATION);
|
||||
private final StringProperty licenseKey = new SimpleStringProperty(DEFAULT_LICENSE_KEY);
|
||||
|
||||
private Consumer<Settings> saveCmd;
|
||||
|
||||
@@ -65,6 +69,7 @@ public class Settings {
|
||||
preferredVolumeImpl.addListener(this::somethingChanged);
|
||||
theme.addListener(this::somethingChanged);
|
||||
userInterfaceOrientation.addListener(this::somethingChanged);
|
||||
licenseKey.addListener(this::somethingChanged);
|
||||
}
|
||||
|
||||
void setSaveCmd(Consumer<Settings> saveCmd) {
|
||||
@@ -126,4 +131,8 @@ public class Settings {
|
||||
public ObjectProperty<NodeOrientation> 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("theme").value(value.theme().get().name());
|
||||
out.name("uiOrientation").value(value.userInterfaceOrientation().get().name());
|
||||
out.name("licenseKey").value(value.licenseKey().get());
|
||||
out.endObject();
|
||||
}
|
||||
|
||||
@@ -90,6 +91,9 @@ public class SettingsJsonAdapter extends TypeAdapter<Settings> {
|
||||
case "uiOrientation":
|
||||
settings.userInterfaceOrientation().set(parseUiOrientation(in.nextString()));
|
||||
break;
|
||||
case "licenseKey":
|
||||
settings.licenseKey().set(in.nextString());
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unsupported vault setting found in JSON: " + name);
|
||||
in.skipValue();
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.cryptomator.cryptofs.migration.Migrators;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
@@ -67,7 +68,13 @@ public class VaultListManager {
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -76,7 +83,7 @@ public class VaultListManager {
|
||||
return comp.vault();
|
||||
}
|
||||
|
||||
private VaultState determineVaultState(Path pathToVault) {
|
||||
public static VaultState determineVaultState(Path pathToVault) {
|
||||
try {
|
||||
if (!CryptoFileSystemProvider.containsVault(pathToVault, MASTERKEY_FILENAME)) {
|
||||
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>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.5.0-beta1</version>
|
||||
<version>1.5.0-beta2</version>
|
||||
</parent>
|
||||
<artifactId>keychain</artifactId>
|
||||
<name>System Keychain Access</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.5.0-beta1</version>
|
||||
<version>1.5.0-beta2</version>
|
||||
</parent>
|
||||
<artifactId>launcher</artifactId>
|
||||
<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 Optional<String> applicationVersion;
|
||||
private final CountDownLatch shutdownLatch;
|
||||
private final CleanShutdownPerformer shutdownPerformer;
|
||||
private final UiLauncher uiLauncher;
|
||||
|
||||
@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.debugMode = debugMode;
|
||||
this.ipcFactory = ipcFactory;
|
||||
this.applicationVersion = applicationVersion;
|
||||
this.shutdownLatch = shutdownLatch;
|
||||
this.shutdownPerformer = shutdownPerformer;
|
||||
this.uiLauncher = uiLauncher;
|
||||
}
|
||||
|
||||
@@ -90,7 +88,6 @@ public class Cryptomator {
|
||||
*/
|
||||
private int runGuiApplication() {
|
||||
try {
|
||||
shutdownPerformer.registerShutdownHook();
|
||||
uiLauncher.launch();
|
||||
shutdownLatch.await();
|
||||
LOG.info("UI shut down");
|
||||
|
||||
@@ -7,18 +7,10 @@ import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Module
|
||||
class CryptomatorModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("shutdownTaskScheduler")
|
||||
Consumer<Runnable> provideShutdownTaskScheduler(CleanShutdownPerformer shutdownPerformer) {
|
||||
return shutdownPerformer::scheduleShutdownTask;
|
||||
}
|
||||
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Named("shutdownLatch")
|
||||
|
||||
@@ -5,9 +5,8 @@ import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
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.ShutdownHook;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
@@ -16,26 +15,27 @@ import java.util.Map;
|
||||
|
||||
@Singleton
|
||||
public class LoggerConfiguration {
|
||||
|
||||
private static final double SHUTDOWN_DELAY_MS = 100;
|
||||
|
||||
|
||||
private final LoggerContext context;
|
||||
private final Environment environment;
|
||||
private final Appender<ILoggingEvent> stdout;
|
||||
private final Appender<ILoggingEvent> upgrade;
|
||||
private final Appender<ILoggingEvent> file;
|
||||
private final ShutdownHook shutdownHook;
|
||||
|
||||
@Inject
|
||||
LoggerConfiguration(LoggerContext context, //
|
||||
Environment environment, //
|
||||
@Named("stdoutAppender") Appender<ILoggingEvent> stdout, //
|
||||
@Named("upgradeAppender") Appender<ILoggingEvent> upgrade, //
|
||||
@Named("fileAppender") Appender<ILoggingEvent> file) {
|
||||
@Named("fileAppender") Appender<ILoggingEvent> file, //
|
||||
ShutdownHook shutdownHook) {
|
||||
this.context = context;
|
||||
this.environment = environment;
|
||||
this.stdout = stdout;
|
||||
this.upgrade = upgrade;
|
||||
this.file = file;
|
||||
this.shutdownHook = shutdownHook;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
@@ -62,9 +62,7 @@ public class LoggerConfiguration {
|
||||
upgrades.setAdditive(false);
|
||||
|
||||
// add shutdown hook
|
||||
DelayingShutdownHook shutdownHook = new DelayingShutdownHook();
|
||||
shutdownHook.setContext(context);
|
||||
shutdownHook.setDelay(Duration.buildByMilliseconds(SHUTDOWN_DELAY_MS));
|
||||
shutdownHook.runOnShutdown(ShutdownHook.PRIO_LAST, context::stop);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
24
main/pom.xml
24
main/pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.5.0-beta1</version>
|
||||
<version>1.5.0-beta2</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Cryptomator</name>
|
||||
|
||||
@@ -23,26 +23,25 @@
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<!-- dependency versions -->
|
||||
<!-- cryptomator dependencies -->
|
||||
<cryptomator.cryptofs.version>1.9.0-rc2</cryptomator.cryptofs.version>
|
||||
<cryptomator.jni.version>2.2.1</cryptomator.jni.version>
|
||||
<cryptomator.fuse.version>1.2.1</cryptomator.fuse.version>
|
||||
<cryptomator.dokany.version>1.1.11</cryptomator.dokany.version>
|
||||
<cryptomator.jni.version>2.2.2</cryptomator.jni.version>
|
||||
<cryptomator.fuse.version>1.2.2</cryptomator.fuse.version>
|
||||
<cryptomator.dokany.version>1.1.12</cryptomator.dokany.version>
|
||||
<cryptomator.webdav.version>1.0.10</cryptomator.webdav.version>
|
||||
|
||||
<!-- 3rd party dependencies -->
|
||||
<javafx.version>13.0.1</javafx.version>
|
||||
|
||||
<commons-lang3.version>3.9</commons-lang3.version>
|
||||
|
||||
<jwt.version>3.8.3</jwt.version>
|
||||
<easybind.version>1.0.3</easybind.version>
|
||||
|
||||
<guava.version>28.1-jre</guava.version>
|
||||
<dagger.version>2.25.2</dagger.version>
|
||||
<gson.version>2.8.6</gson.version>
|
||||
|
||||
<slf4j.version>1.7.29</slf4j.version>
|
||||
<logback.version>1.2.3</logback.version>
|
||||
|
||||
<!-- test dependencies -->
|
||||
<junit.jupiter.version>5.5.2</junit.jupiter.version>
|
||||
<mockito.version>3.1.0</mockito.version>
|
||||
<hamcrest.version>2.2</hamcrest.version>
|
||||
@@ -156,6 +155,13 @@
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>${commons-lang3.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JWT -->
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>java-jwt</artifactId>
|
||||
<version>${jwt.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- EasyBind -->
|
||||
<dependency>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>1.5.0-beta1</version>
|
||||
<version>1.5.0-beta2</version>
|
||||
</parent>
|
||||
<artifactId>ui</artifactId>
|
||||
<name>Cryptomator GUI</name>
|
||||
@@ -37,7 +37,7 @@
|
||||
<dependency>
|
||||
<groupId>org.fxmisc.easybind</groupId>
|
||||
<artifactId>easybind</artifactId>
|
||||
</dependency>
|
||||
</dependency>
|
||||
|
||||
<!-- Google -->
|
||||
<dependency>
|
||||
@@ -65,7 +65,7 @@
|
||||
<dependency>
|
||||
<groupId>com.nulab-inc</groupId>
|
||||
<artifactId>zxcvbn</artifactId>
|
||||
<version>1.2.7</version>
|
||||
<version>1.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Logging -->
|
||||
|
||||
@@ -15,14 +15,14 @@ import javax.inject.Inject;
|
||||
import java.nio.file.Path;
|
||||
|
||||
@AddVaultWizardScoped
|
||||
public class AddVaultFailureExisitingController implements FxController {
|
||||
public class AddVaultFailureExistingController implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> previousScene;
|
||||
private final StringBinding vaultName;
|
||||
|
||||
@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.previousScene = previousScene;
|
||||
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.FxmlFile;
|
||||
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.recoverykey.RecoveryKeyDisplayController;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
@@ -31,6 +34,13 @@ import java.util.ResourceBundle;
|
||||
@Module
|
||||
public abstract class AddVaultModule {
|
||||
|
||||
@Provides
|
||||
@AddVaultWizardScoped
|
||||
@Named("newPassword")
|
||||
static ObjectProperty<CharSequence> provideNewPasswordProperty() {
|
||||
return new SimpleObjectProperty<>("");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@AddVaultWizardWindow
|
||||
@AddVaultWizardScoped
|
||||
@@ -150,8 +160,8 @@ public abstract class AddVaultModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(AddVaultFailureExisitingController.class)
|
||||
abstract FxController bindAddVaultFailureExistingController(AddVaultFailureExisitingController controller);
|
||||
@FxControllerKey(AddVaultFailureExistingController.class)
|
||||
abstract FxController bindAddVaultFailureExistingController(AddVaultFailureExistingController controller);
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@@ -168,13 +178,27 @@ public abstract class AddVaultModule {
|
||||
@FxControllerKey(CreateNewVaultPasswordController.class)
|
||||
abstract FxController bindCreateNewVaultPasswordController(CreateNewVaultPasswordController controller);
|
||||
|
||||
@Binds
|
||||
@Provides
|
||||
@IntoMap
|
||||
@FxControllerKey(AddVaultSuccessController.class)
|
||||
abstract FxController bindAddVaultSuccessController(AddVaultSuccessController controller);
|
||||
@FxControllerKey(NewPasswordController.class)
|
||||
static FxController provideNewPasswordController(ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater, @Named("newPassword") ObjectProperty<CharSequence> password) {
|
||||
return new NewPasswordController(resourceBundle, strengthRater, password);
|
||||
}
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(CreateNewVaultRecoveryKeyController.class)
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -52,8 +52,10 @@ public class CreateNewVaultLocationController implements FxController {
|
||||
|
||||
private Path customVaultPath = DEFAULT_CUSTOM_VAULT_PATH;
|
||||
public ToggleGroup predefinedLocationToggler;
|
||||
public RadioButton iclouddriveRadioButton;
|
||||
public RadioButton dropboxRadioButton;
|
||||
public RadioButton gdriveRadioButton;
|
||||
public RadioButton onedriveRadioButton;
|
||||
public RadioButton customRadioButton;
|
||||
|
||||
@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) {
|
||||
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()));
|
||||
} else if (gdriveRadioButton.equals(newValue)) {
|
||||
vaultPath.set(locationPresets.getGdriveLocation().resolve(vaultName.get()));
|
||||
} else if (onedriveRadioButton.equals(newValue)) {
|
||||
vaultPath.set(locationPresets.getOnedriveLocation().resolve(vaultName.get()));
|
||||
} else if (customRadioButton.equals(newValue)) {
|
||||
vaultPath.set(customVaultPath.resolve(vaultName.get()));
|
||||
}
|
||||
|
||||
@@ -5,16 +5,14 @@ import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.control.Toggle;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
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.FxmlScene;
|
||||
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.fxmisc.easybind.EasyBind;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -58,6 +52,7 @@ public class CreateNewVaultPasswordController implements FxController {
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> chooseLocationScene;
|
||||
private final Lazy<Scene> recoveryKeyScene;
|
||||
private final Lazy<Scene> successScene;
|
||||
private final ExecutorService executor;
|
||||
private final RecoveryKeyFactory recoveryKeyFactory;
|
||||
private final StringProperty vaultNameProperty;
|
||||
@@ -66,26 +61,22 @@ public class CreateNewVaultPasswordController implements FxController {
|
||||
private final StringProperty recoveryKeyProperty;
|
||||
private final VaultListManager vaultListManager;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final PasswordStrengthUtil strengthRater;
|
||||
private final ObjectProperty<CharSequence> password;
|
||||
private final ReadmeGenerator readmeGenerator;
|
||||
private final IntegerProperty passwordStrength;
|
||||
private final BooleanProperty processing;
|
||||
private final BooleanProperty readyToCreateVault;
|
||||
private final ObjectBinding<ContentDisplay> createVaultButtonState;
|
||||
|
||||
public NiceSecurePasswordField passwordField;
|
||||
public NiceSecurePasswordField reenterField;
|
||||
public Label passwordStrengthLabel;
|
||||
public HBox passwordMatchBox;
|
||||
public FontAwesome5IconView checkmark;
|
||||
public FontAwesome5IconView cross;
|
||||
public Label passwordMatchLabel;
|
||||
public ToggleGroup recoveryKeyChoice;
|
||||
public Toggle showRecoveryKey;
|
||||
public Toggle skipRecoveryKey;
|
||||
|
||||
@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.chooseLocationScene = chooseLocationScene;
|
||||
this.recoveryKeyScene = recoveryKeyScene;
|
||||
this.successScene = successScene;
|
||||
this.executor = executor;
|
||||
this.recoveryKeyFactory = recoveryKeyFactory;
|
||||
this.vaultNameProperty = vaultName;
|
||||
@@ -94,9 +85,8 @@ public class CreateNewVaultPasswordController implements FxController {
|
||||
this.recoveryKeyProperty = recoveryKey;
|
||||
this.vaultListManager = vaultListManager;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.strengthRater = strengthRater;
|
||||
this.password = password;
|
||||
this.readmeGenerator = readmeGenerator;
|
||||
this.passwordStrength = new SimpleIntegerProperty(-1);
|
||||
this.processing = new SimpleBooleanProperty();
|
||||
this.readyToCreateVault = new SimpleBooleanProperty();
|
||||
this.createVaultButtonState = Bindings.createObjectBinding(this::getCreateVaultButtonState, processing);
|
||||
@@ -104,22 +94,8 @@ public class CreateNewVaultPasswordController implements FxController {
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
//binds the actual strength value to the rating of the password util
|
||||
passwordStrength.bind(Bindings.createIntegerBinding(() -> strengthRater.computeRate(passwordField.getCharacters().toString()), passwordField.textProperty()));
|
||||
//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));
|
||||
BooleanBinding isValidNewPassword = Bindings.createBooleanBinding(() -> password.get() != null && password.get().length() > 0, password);
|
||||
readyToCreateVault.bind(isValidNewPassword.and(recoveryKeyChoice.selectedToggleProperty().isNotNull()).and(processing.not()));
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -130,7 +106,7 @@ public class CreateNewVaultPasswordController implements FxController {
|
||||
@FXML
|
||||
public void next() {
|
||||
Path pathToVault = vaultPathProperty.get();
|
||||
|
||||
|
||||
try {
|
||||
Files.createDirectory(pathToVault);
|
||||
} catch (FileAlreadyExistsException e) {
|
||||
@@ -141,12 +117,41 @@ public class CreateNewVaultPasswordController implements FxController {
|
||||
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);
|
||||
Tasks.create(() -> {
|
||||
initializeVault(pathToVault, passwordField.getCharacters());
|
||||
return recoveryKeyFactory.createRecoveryKey(pathToVault, passwordField.getCharacters());
|
||||
initializeVault(pathToVault, password.get());
|
||||
return recoveryKeyFactory.createRecoveryKey(pathToVault, password.get());
|
||||
}).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 -> {
|
||||
// TODO show generic error screen
|
||||
LOG.error("", e);
|
||||
@@ -175,13 +180,11 @@ public class CreateNewVaultPasswordController implements FxController {
|
||||
}
|
||||
LOG.info("Created vault at {}", path);
|
||||
}
|
||||
|
||||
private void initializationSucceeded(Path pathToVault, String recoveryKey) {
|
||||
|
||||
private void initializationSucceeded(Path pathToVault) {
|
||||
try {
|
||||
Vault newVault = vaultListManager.add(pathToVault);
|
||||
vaultProperty.set(newVault);
|
||||
recoveryKeyProperty.set(recoveryKey);
|
||||
window.setScene(recoveryKeyScene.get());
|
||||
} catch (NoSuchFileException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
@@ -197,14 +200,6 @@ public class CreateNewVaultPasswordController implements FxController {
|
||||
return vaultNameProperty;
|
||||
}
|
||||
|
||||
public IntegerProperty passwordStrengthProperty() {
|
||||
return passwordStrength;
|
||||
}
|
||||
|
||||
public int getPasswordStrength() {
|
||||
return passwordStrength.get();
|
||||
}
|
||||
|
||||
public BooleanProperty readyToCreateVaultProperty() {
|
||||
return readyToCreateVault;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.cryptomator.ui.addvaultwizard;
|
||||
|
||||
import dagger.Lazy;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
@@ -10,33 +9,20 @@ import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
public class CreateNewVaultRecoveryKeyController implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
private final Lazy<Scene> successScene;
|
||||
private final StringProperty recoveryKeyProperty;
|
||||
|
||||
@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.successScene = successScene;
|
||||
this.recoveryKeyProperty = recoveryKey;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void next() {
|
||||
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 {
|
||||
|
||||
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[] 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> gdriveLocation;
|
||||
private final ReadOnlyObjectProperty<Path> onedriveLocation;
|
||||
private final BooleanBinding foundIclouddrive;
|
||||
private final BooleanBinding foundDropbox;
|
||||
private final BooleanBinding foundGdrive;
|
||||
private final BooleanBinding foundOnedrive;
|
||||
|
||||
@Inject
|
||||
public LocationPresets() {
|
||||
this.iclouddriveLocation = new SimpleObjectProperty<>(existingWritablePath(ICLOUDDRIVE_LOCATIONS));
|
||||
this.dropboxLocation = new SimpleObjectProperty<>(existingWritablePath(DROPBOX_LOCATIONS));
|
||||
this.gdriveLocation = new SimpleObjectProperty<>(existingWritablePath(GDRIVE_LOCATIONS));
|
||||
this.onedriveLocation = new SimpleObjectProperty<>(existingWritablePath(ONEDRIVE_LOCATIONS));
|
||||
this.foundIclouddrive = iclouddriveLocation.isNotNull();
|
||||
this.foundDropbox = dropboxLocation.isNotNull();
|
||||
this.foundGdrive = gdriveLocation.isNotNull();
|
||||
this.foundOnedrive = onedriveLocation.isNotNull();
|
||||
}
|
||||
|
||||
private static Path existingWritablePath(String... candidates) {
|
||||
@@ -49,6 +59,22 @@ public class LocationPresets {
|
||||
|
||||
/* 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() {
|
||||
return dropboxLocation;
|
||||
}
|
||||
@@ -73,7 +99,7 @@ public class LocationPresets {
|
||||
return gdriveLocation.get();
|
||||
}
|
||||
|
||||
public BooleanBinding froundGdriveProperty() {
|
||||
public BooleanBinding foundGdriveProperty() {
|
||||
return foundGdrive;
|
||||
}
|
||||
|
||||
@@ -81,4 +107,20 @@ public class LocationPresets {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.cryptomator.ui.changepassword;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
@@ -22,6 +23,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.io.IOException;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@@ -33,47 +35,24 @@ public class ChangePasswordController implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
private final Vault vault;
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final PasswordStrengthUtil strengthRater;
|
||||
private final IntegerProperty passwordStrength;
|
||||
private final ObjectProperty<CharSequence> newPassword;
|
||||
|
||||
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 Button finishButton;
|
||||
|
||||
@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.vault = vault;
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.strengthRater = strengthRater;
|
||||
this.passwordStrength = new SimpleIntegerProperty(-1);
|
||||
this.newPassword = newPassword;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
//binds the actual strength value to the rating of the password util
|
||||
passwordStrength.bind(Bindings.createIntegerBinding(() -> strengthRater.computeRate(newPasswordField.getCharacters().toString()), newPasswordField.textProperty()));
|
||||
//binding indicating if the passwords not match
|
||||
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));
|
||||
BooleanBinding hasNotConfirmedCheckbox = finalConfirmationCheckbox.selectedProperty().not();
|
||||
BooleanBinding isInvalidNewPassword = Bindings.createBooleanBinding(() -> newPassword.get() == null || newPassword.get().length() == 0, newPassword);
|
||||
finishButton.disableProperty().bind(hasNotConfirmedCheckbox.or(isInvalidNewPassword));
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -84,15 +63,15 @@ public class ChangePasswordController implements FxController {
|
||||
@FXML
|
||||
public void finish() {
|
||||
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());
|
||||
window.close();
|
||||
} catch (IOException e) {
|
||||
//TODO
|
||||
// TODO show generic error screen
|
||||
LOG.error("IO error occured during password change. Unable to perform operation.", e);
|
||||
e.printStackTrace();
|
||||
} catch (InvalidPassphraseException e) {
|
||||
//TODO
|
||||
// TODO shake
|
||||
LOG.info("Wrong old password.");
|
||||
}
|
||||
}
|
||||
@@ -102,12 +81,5 @@ public class ChangePasswordController implements FxController {
|
||||
public Vault getVault() {
|
||||
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.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.Modality;
|
||||
@@ -14,15 +16,25 @@ import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.NewPasswordController;
|
||||
import org.cryptomator.ui.common.PasswordStrengthUtil;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import java.nio.CharBuffer;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module
|
||||
abstract class ChangePasswordModule {
|
||||
|
||||
@Provides
|
||||
@ChangePasswordScoped
|
||||
@Named("newPassword")
|
||||
static ObjectProperty<CharSequence> provideNewPasswordProperty() {
|
||||
return new SimpleObjectProperty<>("");
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ChangePasswordWindow
|
||||
@@ -58,5 +70,12 @@ abstract class ChangePasswordModule {
|
||||
@IntoMap
|
||||
@FxControllerKey(ChangePasswordController.class)
|
||||
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"), //
|
||||
QUIT("/fxml/quit.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"), //
|
||||
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"), //
|
||||
VAULT_OPTIONS("/fxml/vault_options.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;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.nulabinc.zxcvbn.Zxcvbn;
|
||||
import org.cryptomator.common.Environment;
|
||||
import org.cryptomator.ui.fxapp.FxApplicationScoped;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
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 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 int minPwLength;
|
||||
private final Zxcvbn zxcvbn;
|
||||
|
||||
@Inject
|
||||
public PasswordStrengthUtil(ResourceBundle resourceBundle) {
|
||||
public PasswordStrengthUtil(ResourceBundle resourceBundle, Environment environment) {
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.minPwLength = environment.getMinPwLength();
|
||||
this.zxcvbn = new Zxcvbn();
|
||||
this.sanitizedInputs = List.of("cryptomator");
|
||||
}
|
||||
|
||||
public int computeRate(String password) {
|
||||
if (Strings.isNullOrEmpty(password)) {
|
||||
public boolean fulfillsMinimumRequirements(CharSequence password) {
|
||||
return password.length() >= minPwLength;
|
||||
}
|
||||
|
||||
public int computeRate(CharSequence password) {
|
||||
if (password == null || password.length() < minPwLength) {
|
||||
return -1;
|
||||
} else {
|
||||
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) {
|
||||
if (resourceBundle.containsKey(RESSOURCE_PREFIX + score.intValue())) {
|
||||
return resourceBundle.getString("passwordStrength.messageLabel." + score.intValue());
|
||||
if (score.intValue() == -1) {
|
||||
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 {
|
||||
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"), //
|
||||
COG("\uF013"), //
|
||||
COGS("\uF085"), //
|
||||
COPY("\uF0C5"), //
|
||||
EXCLAMATION("\uF12A"),
|
||||
EXCLAMATION_CIRCLE("\uF06A"), //
|
||||
EXCLAMATION_TRIANGLE("\uF071"), //
|
||||
EYE("\uF06E"), //
|
||||
EYE_SLASH("\uF070"), //
|
||||
FILE_IMPORT("\uF56F"), //
|
||||
FOLDER_OPEN("\uF07C"), //
|
||||
HAND_HOLDING_HEART("\uF4BE"), //
|
||||
HEART("\uF004"), //
|
||||
HDD("\uF0A0"), //
|
||||
KEY("\uF084"), //
|
||||
LINK("\uF0C1"), //
|
||||
LOCK_ALT("\uF30D"), //
|
||||
LOCK_OPEN_ALT("\uF3C2"), //
|
||||
MINUS("\uF068"), //
|
||||
PLUS("\uF067"), //
|
||||
PRINT("\uF02F"), //
|
||||
QUESTION("\uF128"), //
|
||||
SPARKLES("\uF890"), //
|
||||
SPINNER("\uF110"), //
|
||||
SYNC("\uF021"), //
|
||||
TIMES("\uF00D"), //
|
||||
USER_CROWN("\uF6A4"), //
|
||||
WRENCH("\uF0AD"), //
|
||||
;
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ public class NiceSecurePasswordField extends StackPane {
|
||||
}
|
||||
|
||||
public void swipe() {
|
||||
passwordField.swipe();;
|
||||
passwordField.swipe();
|
||||
}
|
||||
|
||||
public void selectAll() {
|
||||
|
||||
@@ -9,6 +9,7 @@ import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableSet;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.LicenseHolder;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.UiTheme;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
@@ -16,8 +17,10 @@ import org.cryptomator.jni.JniException;
|
||||
import org.cryptomator.jni.MacApplicationUiAppearance;
|
||||
import org.cryptomator.jni.MacApplicationUiState;
|
||||
import org.cryptomator.jni.MacFunctions;
|
||||
import org.cryptomator.ui.common.VaultService;
|
||||
import org.cryptomator.ui.mainwindow.MainWindowComponent;
|
||||
import org.cryptomator.ui.preferences.PreferencesComponent;
|
||||
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
||||
import org.cryptomator.ui.quit.QuitComponent;
|
||||
import org.cryptomator.ui.unlock.UnlockComponent;
|
||||
import org.slf4j.Logger;
|
||||
@@ -38,17 +41,21 @@ public class FxApplication extends Application {
|
||||
private final UnlockComponent.Builder unlockWindowBuilder;
|
||||
private final QuitComponent.Builder quitWindowBuilder;
|
||||
private final Optional<MacFunctions> macFunctions;
|
||||
private final VaultService vaultService;
|
||||
private final LicenseHolder licenseHolder;
|
||||
private final ObservableSet<Stage> visibleStages = FXCollections.observableSet();
|
||||
private final BooleanBinding hasVisibleStages = Bindings.isNotEmpty(visibleStages);
|
||||
|
||||
@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.mainWindow = mainWindow;
|
||||
this.preferencesWindow = preferencesWindow;
|
||||
this.unlockWindowBuilder = unlockWindowBuilder;
|
||||
this.quitWindowBuilder = quitWindowBuilder;
|
||||
this.macFunctions = macFunctions;
|
||||
this.vaultService = vaultService;
|
||||
this.licenseHolder = licenseHolder;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
@@ -79,9 +86,9 @@ public class FxApplication extends Application {
|
||||
}
|
||||
}
|
||||
|
||||
public void showPreferencesWindow() {
|
||||
public void showPreferencesWindow(SelectedPreferencesTab selectedTab) {
|
||||
Platform.runLater(() -> {
|
||||
Stage stage = preferencesWindow.get().showPreferencesWindow();
|
||||
Stage stage = preferencesWindow.get().showPreferencesWindow(selectedTab);
|
||||
addVisibleStage(stage);
|
||||
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) {
|
||||
loadSelectedStyleSheet(newValue);
|
||||
}
|
||||
|
||||
private void loadSelectedStyleSheet(UiTheme theme) {
|
||||
private void loadSelectedStyleSheet(UiTheme desiredTheme) {
|
||||
UiTheme theme = licenseHolder.isValidLicense() ? desiredTheme : UiTheme.LIGHT;
|
||||
switch (theme) {
|
||||
// case CUSTOM:
|
||||
// // TODO
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.fxapp;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import javafx.application.Application;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.image.Image;
|
||||
@@ -44,6 +46,9 @@ abstract class FxApplicationModule {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Binds
|
||||
abstract Application bindApplication(FxApplication application);
|
||||
|
||||
@Provides
|
||||
static MainWindowComponent provideMainWindowComponent(MainWindowComponent.Builder builder) {
|
||||
|
||||
@@ -1,30 +1,24 @@
|
||||
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.scene.input.DragEvent;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import javafx.scene.layout.HBox;
|
||||
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 javafx.scene.layout.StackPane;
|
||||
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.wrongfilealert.WrongFileAlertComponent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@MainWindowScoped
|
||||
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 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 WrongFileAlertComponent.Builder wrongFileAlert;
|
||||
public HBox titleBar;
|
||||
public VBox root;
|
||||
public Pane dragAndDropIndicator;
|
||||
public Region resizer;
|
||||
private double xOffset;
|
||||
private double yOffset;
|
||||
private final BooleanProperty draggingOver = new SimpleBooleanProperty();
|
||||
private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
|
||||
public StackPane root;
|
||||
|
||||
@Inject
|
||||
public MainWindowController(@MainWindow Stage window, FxApplication application, @Named("trayMenuSupported") boolean minimizeToSysTray, UpdateChecker updateChecker, VaultListManager vaultListManager, WrongFileAlertComponent.Builder wrongFileAlert) {
|
||||
this.window = window;
|
||||
this.application = application;
|
||||
this.minimizeToSysTray = minimizeToSysTray;
|
||||
this.updateChecker = updateChecker;
|
||||
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
|
||||
public MainWindowController(VaultListManager vaultListManager, WrongFileAlertComponent.Builder wrongFileAlert) {
|
||||
this.vaultListManager = vaultListManager;
|
||||
this.wrongFileAlert = wrongFileAlert;
|
||||
}
|
||||
@@ -60,77 +41,70 @@ public class MainWindowController implements FxController {
|
||||
@FXML
|
||||
public void initialize() {
|
||||
LOG.debug("init MainWindowController");
|
||||
titleBar.setOnMousePressed(event -> {
|
||||
xOffset = event.getSceneX();
|
||||
yOffset = event.getSceneY();
|
||||
});
|
||||
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();
|
||||
});
|
||||
root.setOnDragEntered(this::handleDragEvent);
|
||||
root.setOnDragOver(this::handleDragEvent);
|
||||
root.setOnDragDropped(this::handleDragEvent);
|
||||
root.setOnDragExited(this::handleDragEvent);
|
||||
}
|
||||
|
||||
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 {
|
||||
if (pathToVault.getFileName().toString().equals(MASTERKEY_FILENAME)) {
|
||||
return Stream.of(vaultListManager.add(pathToVault.getParent()));
|
||||
vaultListManager.add(pathToVault.getParent());
|
||||
} else {
|
||||
return Stream.of(vaultListManager.add(pathToVault));
|
||||
vaultListManager.add(pathToVault);
|
||||
}
|
||||
} catch (NoSuchFileException e) {
|
||||
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 */
|
||||
|
||||
public BooleanBinding updateAvailableProperty() {
|
||||
return updateAvailable;
|
||||
public BooleanProperty draggingOverProperty() {
|
||||
return draggingOver;
|
||||
}
|
||||
|
||||
public boolean isUpdateAvailable() {
|
||||
return updateAvailable.get();
|
||||
public boolean isDraggingOver() {
|
||||
return draggingOver.get();
|
||||
}
|
||||
|
||||
public BooleanProperty draggingVaultOverProperty() {
|
||||
return draggingVaultOver;
|
||||
}
|
||||
|
||||
public boolean isDraggingVaultOver() {
|
||||
return draggingVaultOver.get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,16 @@ abstract class MainWindowModule {
|
||||
@FxControllerKey(MainWindowController.class)
|
||||
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
|
||||
@IntoMap
|
||||
@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);
|
||||
|
||||
private final Lazy<MainWindowController> mainWindowController;
|
||||
private final Lazy<MainWindowTitleController> mainWindowTitleController;
|
||||
private final Lazy<VaultListController> vaultListController;
|
||||
|
||||
@Inject
|
||||
public MainWindowSceneFactory(Settings settings, Lazy<MainWindowController> mainWindowController, Lazy<VaultListController> vaultListController) {
|
||||
public MainWindowSceneFactory(Settings settings, Lazy<MainWindowTitleController> mainWindowTitleController, Lazy<VaultListController> vaultListController) {
|
||||
super(settings);
|
||||
this.mainWindowController = mainWindowController;
|
||||
this.mainWindowTitleController = mainWindowTitleController;
|
||||
this.vaultListController = vaultListController;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupDefaultAccelerators(Scene scene, Stage stage) {
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
scene.getAccelerators().put(ALT_F4, mainWindowController.get()::close);
|
||||
scene.getAccelerators().put(ALT_F4, mainWindowTitleController.get()::close);
|
||||
} 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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,53 +4,32 @@ import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
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.Tasks;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.cryptomator.ui.common.VaultService;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
@MainWindowScoped
|
||||
public class VaultDetailUnlockedController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(VaultDetailUnlockedController.class);
|
||||
|
||||
private final ReadOnlyObjectProperty<Vault> vault;
|
||||
private final ExecutorService executor;
|
||||
private final VaultService vaultService;
|
||||
|
||||
@Inject
|
||||
public VaultDetailUnlockedController(ObjectProperty<Vault> vault, ExecutorService executor) {
|
||||
public VaultDetailUnlockedController(ObjectProperty<Vault> vault, VaultService vaultService) {
|
||||
this.vault = vault;
|
||||
this.executor = executor;
|
||||
this.vaultService = vaultService;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void revealAccessLocation() {
|
||||
try {
|
||||
vault.get().reveal();
|
||||
} catch (Volume.VolumeException e) {
|
||||
LOG.error("Failed to reveal vault.", e);
|
||||
}
|
||||
vaultService.reveal(vault.get());
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void lock() {
|
||||
Vault v = vault.get();
|
||||
v.setState(VaultState.PROCESSING);
|
||||
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);
|
||||
vaultService.lock(vault.get(), false);
|
||||
// TODO count lock attempts, and allow forced lock
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
package org.cryptomator.ui.mainwindow;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.stage.Stage;
|
||||
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.common.FxController;
|
||||
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 final Stage window;
|
||||
private final ObservableList<Vault> vaults;
|
||||
private final ObjectProperty<Vault> selectedVault;
|
||||
private final VaultListCellFactory cellFactory;
|
||||
@@ -34,8 +37,7 @@ public class VaultListController implements FxController {
|
||||
public ListView<Vault> vaultList;
|
||||
|
||||
@Inject
|
||||
VaultListController(@MainWindow Stage window, ObservableList<Vault> vaults, ObjectProperty<Vault> selectedVault, VaultListCellFactory cellFactory, AddVaultWizardComponent.Builder addVaultWizard, RemoveVaultComponent.Builder removeVault) {
|
||||
this.window = window;
|
||||
VaultListController(ObservableList<Vault> vaults, ObjectProperty<Vault> selectedVault, VaultListCellFactory cellFactory, AddVaultWizardComponent.Builder addVaultWizard, RemoveVaultComponent.Builder removeVault) {
|
||||
this.vaults = vaults;
|
||||
this.selectedVault = selectedVault;
|
||||
this.cellFactory = cellFactory;
|
||||
@@ -43,6 +45,7 @@ public class VaultListController implements FxController {
|
||||
this.removeVault = removeVault;
|
||||
this.noVaultSelected = selectedVault.isNull();
|
||||
this.emptyVaultList = Bindings.isEmpty(vaults);
|
||||
selectedVault.addListener(this::selectedVaultDidChange);
|
||||
}
|
||||
|
||||
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
|
||||
public void didClickAddVault() {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
package org.cryptomator.ui.preferences;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.NodeOrientation;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ChoiceBox;
|
||||
@@ -8,6 +11,7 @@ import javafx.scene.control.RadioButton;
|
||||
import javafx.scene.control.Toggle;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.util.StringConverter;
|
||||
import org.cryptomator.common.LicenseHolder;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.settings.UiTheme;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
@@ -15,25 +19,38 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
@PreferencesScoped
|
||||
public class GeneralPreferencesController implements FxController {
|
||||
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GeneralPreferencesController.class);
|
||||
|
||||
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 CheckBox startHiddenCheckbox;
|
||||
public CheckBox debugModeCheckbox;
|
||||
public CheckBox autoStartCheckbox;
|
||||
public ToggleGroup nodeOrientation;
|
||||
public RadioButton nodeOrientationLtr;
|
||||
public RadioButton nodeOrientationRtl;
|
||||
|
||||
@Inject
|
||||
GeneralPreferencesController(Settings settings) {
|
||||
GeneralPreferencesController(Settings settings, @Named("trayMenuSupported") boolean trayMenuSupported, Optional<AutoStartStrategy> autoStartStrategy, LicenseHolder licenseHolder, ExecutorService executor) {
|
||||
this.settings = settings;
|
||||
this.trayMenuSupported = trayMenuSupported;
|
||||
this.autoStartStrategy = autoStartStrategy;
|
||||
this.licenseHolder = licenseHolder;
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
themeChoiceBox.getItems().addAll(UiTheme.values());
|
||||
themeChoiceBox.valueProperty().bindBidirectional(settings.theme());
|
||||
@@ -43,9 +60,23 @@ public class GeneralPreferencesController implements FxController {
|
||||
|
||||
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);
|
||||
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) {
|
||||
@@ -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 */
|
||||
|
||||
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.Subcomponent;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
|
||||
@@ -23,7 +23,10 @@ public interface PreferencesComponent {
|
||||
@FxmlScene(FxmlFile.PREFERENCES)
|
||||
Lazy<Scene> scene();
|
||||
|
||||
default Stage showPreferencesWindow() {
|
||||
ObjectProperty<SelectedPreferencesTab> selectedTabProperty();
|
||||
|
||||
default Stage showPreferencesWindow(SelectedPreferencesTab selectedTab) {
|
||||
selectedTabProperty().set(selectedTab);
|
||||
Stage stage = window();
|
||||
stage.setScene(scene().get());
|
||||
stage.show();
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package org.cryptomator.ui.preferences;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TabPane;
|
||||
@@ -15,26 +18,50 @@ import javax.inject.Inject;
|
||||
public class PreferencesController implements FxController {
|
||||
|
||||
private final Stage window;
|
||||
private final ObjectProperty<SelectedPreferencesTab> selectedTabProperty;
|
||||
private final BooleanBinding updateAvailable;
|
||||
public TabPane tabPane;
|
||||
public Tab generalTab;
|
||||
public Tab volumeTab;
|
||||
public Tab updatesTab;
|
||||
public Tab donationKeyTab;
|
||||
|
||||
@Inject
|
||||
public PreferencesController(@PreferencesWindow Stage window, UpdateChecker updateChecker) {
|
||||
public PreferencesController(@PreferencesWindow Stage window, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, UpdateChecker updateChecker) {
|
||||
this.window = window;
|
||||
this.selectedTabProperty = selectedTabProperty;
|
||||
this.updateAvailable = updateChecker.latestVersionProperty().isNotNull();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
window.setOnShowing(this::windowWillAppear);
|
||||
selectedTabProperty.addListener(observable -> this.selectChosenTab());
|
||||
}
|
||||
|
||||
private void windowWillAppear(@SuppressWarnings("unused") WindowEvent windowEvent) {
|
||||
if (updateAvailable.get()) {
|
||||
tabPane.getSelectionModel().select(updatesTab);
|
||||
private void selectChosenTab() {
|
||||
Tab toBeSelected = getTabToSelect(selectedTabProperty.get());
|
||||
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.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.Stage;
|
||||
@@ -20,9 +22,15 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
@Module
|
||||
@Module(includes = {AutoStartModule.class})
|
||||
abstract class PreferencesModule {
|
||||
|
||||
@Provides
|
||||
@PreferencesScoped
|
||||
static ObjectProperty<SelectedPreferencesTab> provideSelectedTabProperty() {
|
||||
return new SimpleObjectProperty<>(SelectedPreferencesTab.ANY);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@PreferencesWindow
|
||||
@PreferencesScoped
|
||||
@@ -70,4 +78,9 @@ abstract class PreferencesModule {
|
||||
@FxControllerKey(VolumePreferencesController.class)
|
||||
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;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.concurrent.ScheduledService;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.common.vaults.Volume;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.VaultService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.awt.desktop.QuitResponse;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@QuitScoped
|
||||
public class QuitController implements FxController {
|
||||
@@ -30,15 +26,17 @@ public class QuitController implements FxController {
|
||||
private final Stage window;
|
||||
private final QuitResponse response;
|
||||
private final ObservableList<Vault> unlockedVaults;
|
||||
private final ExecutorService executor;
|
||||
private final ExecutorService executorService;
|
||||
private final VaultService vaultService;
|
||||
public Button lockAndQuitButton;
|
||||
|
||||
@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.response = response;
|
||||
this.unlockedVaults = vaults.filtered(Vault::isUnlocked);
|
||||
this.executor = executor;
|
||||
this.executorService = executorService;
|
||||
this.vaultService = vaultService;
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -52,84 +50,24 @@ public class QuitController implements FxController {
|
||||
public void lockAndQuit() {
|
||||
lockAndQuitButton.setDisable(true);
|
||||
lockAndQuitButton.setContentDisplay(ContentDisplay.LEFT);
|
||||
|
||||
Iterator<Vault> toBeLocked = List.copyOf(unlockedVaults).iterator();
|
||||
ScheduledService<Void> lockAllService = new LockAllVaultsService(executor, toBeLocked);
|
||||
lockAllService.setOnSucceeded(evt -> {
|
||||
if (!toBeLocked.hasNext()) {
|
||||
|
||||
Task<Collection<Vault>> lockAllTask = vaultService.createLockAllTask(unlockedVaults, false);
|
||||
lockAllTask.setOnSucceeded(evt -> {
|
||||
LOG.info("Locked {}", lockAllTask.getValue().stream().map(Vault::getDisplayableName).collect(Collectors.joining(", ")));
|
||||
if (unlockedVaults.isEmpty()) {
|
||||
window.close();
|
||||
response.performQuit();
|
||||
}
|
||||
});
|
||||
lockAllService.setOnFailed(evt -> {
|
||||
lockAllTask.setOnFailed(evt -> {
|
||||
LOG.warn("Locking failed", lockAllTask.getException());
|
||||
lockAndQuitButton.setDisable(false);
|
||||
lockAndQuitButton.setContentDisplay(ContentDisplay.TEXT_ONLY);
|
||||
// TODO: show force lock or force quit scene (and DO NOT cancelQuit() here!)
|
||||
// see https://github.com/cryptomator/cryptomator/blob/1.4.16/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java#L151-L163
|
||||
response.cancelQuit();
|
||||
});
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ public class RecoveryKeyCreationController implements FxController {
|
||||
public NiceSecurePasswordField passwordField;
|
||||
|
||||
@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.successScene = successScene;
|
||||
this.vault = vault;
|
||||
|
||||
@@ -1,26 +1,73 @@
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
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 org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@RecoveryKeyScoped
|
||||
public class RecoveryKeyDisplayController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyDisplayController.class);
|
||||
|
||||
private final Stage window;
|
||||
private final Vault vault;
|
||||
private final StringProperty recoveryKeyProperty;
|
||||
|
||||
@Inject
|
||||
public RecoveryKeyDisplayController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow StringProperty recoveryKey) {
|
||||
private final String vaultName;
|
||||
private final String recoveryKey;
|
||||
|
||||
public RecoveryKeyDisplayController(Stage window, String vaultName, String recoveryKey) {
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
this.recoveryKeyProperty = recoveryKey;
|
||||
this.vaultName = vaultName;
|
||||
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
|
||||
@@ -30,15 +77,15 @@ public class RecoveryKeyDisplayController implements FxController {
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public Vault getVault() {
|
||||
return vault;
|
||||
}
|
||||
|
||||
public ReadOnlyStringProperty recoveryKeyProperty() {
|
||||
return recoveryKeyProperty;
|
||||
public boolean isPrinterSupported() {
|
||||
return Printer.getDefaultPrinter() != null;
|
||||
}
|
||||
|
||||
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.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.ui.common.DefaultSceneFactory;
|
||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
@@ -63,10 +64,10 @@ abstract class RecoveryKeyModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_DISPLAY)
|
||||
@FxmlScene(FxmlFile.RECOVERYKEY_SUCCESS)
|
||||
@RecoveryKeyScoped
|
||||
static Scene provideRecoveryKeyDisplayScene(@RecoveryKeyWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene("/fxml/recoverykey_display.fxml");
|
||||
static Scene provideRecoveryKeySuccessScene(@RecoveryKeyWindow FXMLLoaderFactory fxmlLoaders) {
|
||||
return fxmlLoaders.createScene("/fxml/recoverykey_success.fxml");
|
||||
}
|
||||
|
||||
// ------------------
|
||||
@@ -76,9 +77,16 @@ abstract class RecoveryKeyModule {
|
||||
@FxControllerKey(RecoveryKeyCreationController.class)
|
||||
abstract FxController bindRecoveryKeyCreationController(RecoveryKeyCreationController controller);
|
||||
|
||||
@Binds
|
||||
@Provides
|
||||
@IntoMap
|
||||
@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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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.beans.Observable;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.cryptomator.common.ShutdownHook;
|
||||
import org.cryptomator.common.settings.Settings;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.common.vaults.VaultState;
|
||||
import org.cryptomator.common.vaults.Volume;
|
||||
import org.cryptomator.ui.fxapp.FxApplication;
|
||||
import org.cryptomator.ui.launcher.FxApplicationStarter;
|
||||
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -37,17 +40,17 @@ class TrayMenuController {
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final FxApplicationStarter fxApplicationStarter;
|
||||
private final CountDownLatch shutdownLatch;
|
||||
private final Settings settings;
|
||||
private final ShutdownHook shutdownHook;
|
||||
private final ObservableList<Vault> vaults;
|
||||
private final PopupMenu menu;
|
||||
private final AtomicBoolean allowSuddenTermination;
|
||||
|
||||
@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.fxApplicationStarter = fxApplicationStarter;
|
||||
this.shutdownLatch = shutdownLatch;
|
||||
this.settings = settings;
|
||||
this.shutdownHook = shutdownHook;
|
||||
this.vaults = vaults;
|
||||
this.menu = new PopupMenu();
|
||||
this.allowSuddenTermination = new AtomicBoolean(true);
|
||||
@@ -71,6 +74,7 @@ class TrayMenuController {
|
||||
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_QUIT_HANDLER)) {
|
||||
Desktop.getDesktop().setQuitHandler(this::handleQuitRequest);
|
||||
}
|
||||
shutdownHook.runOnShutdown(this::forceUnmountRemainingVaults);
|
||||
|
||||
// allow sudden termination
|
||||
if (Desktop.getDesktop().isSupported(Desktop.Action.APP_SUDDEN_TERMINATION)) {
|
||||
@@ -112,6 +116,11 @@ class TrayMenuController {
|
||||
}
|
||||
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"));
|
||||
quitApplicationItem.addActionListener(this::quitApplication);
|
||||
menu.add(quitApplicationItem);
|
||||
@@ -126,11 +135,11 @@ class TrayMenuController {
|
||||
submenu.add(unlockItem);
|
||||
} else if (vault.isUnlocked()) {
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -145,12 +154,24 @@ class TrayMenuController {
|
||||
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) {
|
||||
fxApplicationStarter.get(true).thenAccept(app -> app.showMainWindow());
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -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.ObjectBinding;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.WritableValue;
|
||||
@@ -32,6 +33,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import java.nio.file.DirectoryNotEmptyException;
|
||||
import java.nio.file.NotDirectoryException;
|
||||
import java.util.Arrays;
|
||||
@@ -49,19 +51,25 @@ public class UnlockController implements FxController {
|
||||
private final ObjectBinding<ContentDisplay> unlockButtonState;
|
||||
private final Optional<KeychainAccess> keychainAccess;
|
||||
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 BooleanProperty unlockButtonDisabled;
|
||||
public NiceSecurePasswordField passwordField;
|
||||
public CheckBox savePassword;
|
||||
|
||||
@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.vault = vault;
|
||||
this.executor = executor;
|
||||
this.unlockButtonState = Bindings.createObjectBinding(this::getUnlockButtonState, vault.stateProperty());
|
||||
this.keychainAccess = keychainAccess;
|
||||
this.successScene = successScene;
|
||||
this.invalidMountPointScene = invalidMountPointScene;
|
||||
this.genericErrorScene = genericErrorScene;
|
||||
this.genericErrorCause = genericErrorCause;
|
||||
this.forgetPassword = forgetPassword;
|
||||
this.unlockButtonDisabled = new SimpleBooleanProperty();
|
||||
}
|
||||
@@ -100,17 +108,16 @@ public class UnlockController implements FxController {
|
||||
shakeWindow();
|
||||
passwordField.selectAll();
|
||||
passwordField.requestFocus();
|
||||
}).onError(UnsupportedVaultFormatException.class, e -> {
|
||||
// TODO
|
||||
}).onError(NotDirectoryException.class, e -> {
|
||||
LOG.error("Unlock failed. Mount point not a directory: {}", e.getMessage());
|
||||
// TODO
|
||||
window.setScene(invalidMountPointScene.get());
|
||||
}).onError(DirectoryNotEmptyException.class, e -> {
|
||||
LOG.error("Unlock failed. Mount point not empty: {}", e.getMessage());
|
||||
// TODO
|
||||
window.setScene(invalidMountPointScene.get());
|
||||
}).onError(Exception.class, e -> { // including RuntimeExceptions
|
||||
LOG.error("Unlock failed for technical reasons.", e);
|
||||
// TODO
|
||||
genericErrorCause.set(e);
|
||||
window.setScene(genericErrorScene.get());
|
||||
}).andFinally(() -> {
|
||||
if (!vault.isUnlocked()) {
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -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.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.Modality;
|
||||
@@ -14,6 +16,7 @@ import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
import org.cryptomator.ui.common.FxmlFile;
|
||||
import org.cryptomator.ui.common.FxmlScene;
|
||||
import org.cryptomator.ui.common.StackTraceController;
|
||||
import org.cryptomator.ui.forgetPassword.ForgetPasswordComponent;
|
||||
|
||||
import javax.inject.Named;
|
||||
@@ -44,6 +47,13 @@ abstract class UnlockModule {
|
||||
return stage;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("genericErrorCause")
|
||||
@UnlockScoped
|
||||
static ObjectProperty<Exception> provideGenericErrorCause() {
|
||||
return new SimpleObjectProperty<>();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FxmlScene(FxmlFile.UNLOCK)
|
||||
@UnlockScoped
|
||||
@@ -58,6 +68,21 @@ abstract class UnlockModule {
|
||||
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)
|
||||
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.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.common.vaults.Vault;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
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;
|
||||
|
||||
@@ -26,14 +26,16 @@ public class UnlockSuccessController implements FxController {
|
||||
private final Stage window;
|
||||
private final Vault vault;
|
||||
private final ExecutorService executor;
|
||||
private final VaultService vaultService;
|
||||
private final ObjectProperty<ContentDisplay> revealButtonState;
|
||||
private final BooleanProperty revealButtonDisabled;
|
||||
|
||||
@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.vault = vault;
|
||||
this.executor = executor;
|
||||
this.vaultService = vaultService;
|
||||
this.revealButtonState = new SimpleObjectProperty<>(ContentDisplay.TEXT_ONLY);
|
||||
this.revealButtonDisabled = new SimpleBooleanProperty();
|
||||
}
|
||||
@@ -49,17 +51,19 @@ public class UnlockSuccessController implements FxController {
|
||||
LOG.trace("UnlockSuccessController.revealAndClose()");
|
||||
revealButtonState.set(ContentDisplay.LEFT);
|
||||
revealButtonDisabled.set(true);
|
||||
Tasks.create(() -> {
|
||||
vault.reveal();
|
||||
}).onSuccess(() -> {
|
||||
window.close();
|
||||
}).onError(InvalidPassphraseException.class, e -> {
|
||||
// TODO
|
||||
LOG.warn("Reveal failed.", e);
|
||||
}).andFinally(() -> {
|
||||
|
||||
Task<Vault> revealTask = vaultService.createRevealTask(vault);
|
||||
revealTask.setOnSucceeded(evt -> {
|
||||
revealButtonState.set(ContentDisplay.TEXT_ONLY);
|
||||
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 */
|
||||
|
||||
@@ -84,12 +84,7 @@ public class MountOptionsController implements FxController {
|
||||
driveLetterSelection.getItems().addAll(windowsDriveLetters.getAllDriveLetters());
|
||||
driveLetterSelection.setConverter(new WinDriveLetterLabelConverter(windowsDriveLetters));
|
||||
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()) {
|
||||
mountPoint.selectToggle(mountPointCustomDir);
|
||||
} else if (!Strings.isNullOrEmpty(vault.getVaultSettings().winDriveLetter().get())) {
|
||||
@@ -97,6 +92,13 @@ public class MountOptionsController implements FxController {
|
||||
} else {
|
||||
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
|
||||
@@ -116,7 +118,7 @@ public class MountOptionsController implements FxController {
|
||||
@FXML
|
||||
private void chooseCustomMountPoint() {
|
||||
DirectoryChooser directoryChooser = new DirectoryChooser();
|
||||
directoryChooser.setTitle(resourceBundle.getString("vaultOptions.mount.winDirChooser"));
|
||||
directoryChooser.setTitle(resourceBundle.getString("vaultOptions.mount.mountPoint.directoryPickerTitle"));
|
||||
try {
|
||||
directoryChooser.setInitialDirectory(Path.of(System.getProperty("user.home")).toFile());
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
SCROLL_BAR_THUMB_NORMAL: GRAY_3;
|
||||
SCROLL_BAR_THUMB_HOVER: GRAY_4;
|
||||
INDICATOR_BG: RED_5;
|
||||
DRAG_N_DROP_INDICATOR_BG: GRAY_3;
|
||||
PROGRESS_INDICATOR_BEGIN: GRAY_7;
|
||||
PROGRESS_INDICATOR_END: GRAY_5;
|
||||
PROGRESS_BAR_BG: GRAY_2;
|
||||
@@ -116,6 +117,10 @@
|
||||
-fx-font-size: 0.8em;
|
||||
}
|
||||
|
||||
.label-extra-small {
|
||||
-fx-font-size: 0.64em;
|
||||
}
|
||||
|
||||
.text-flow > * {
|
||||
-fx-fill: TEXT_FILL;
|
||||
}
|
||||
@@ -179,20 +184,6 @@
|
||||
-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 {
|
||||
-fx-background-color: PRIMARY_BG, white, INDICATOR_BG;
|
||||
-fx-background-insets: 0, 1px, 2px;
|
||||
@@ -201,6 +192,16 @@
|
||||
-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 *
|
||||
@@ -212,34 +213,36 @@
|
||||
}
|
||||
|
||||
.tab-pane > .tab-header-area {
|
||||
-fx-padding: 6px 12px 0 12px;
|
||||
-fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG;
|
||||
-fx-background-insets: 0, 0 0 1px 0;
|
||||
-fx-padding: 0 12px;
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 0 0 3px 0;
|
||||
}
|
||||
|
||||
.tab-pane > .tab-header-area > .headers-region > .tab {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, MAIN_BG;
|
||||
-fx-background-insets: 0 0 1px 0, 1px;
|
||||
-fx-background-radius: 4px 4px 0 0;
|
||||
-fx-padding: 0.2em 1em 0.2em 1em;
|
||||
.tab-pane .tab {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 0 0 3px 0;
|
||||
-fx-padding: 6px 12px;
|
||||
}
|
||||
|
||||
.tab-pane > .tab-header-area > .headers-region > .tab:selected {
|
||||
-fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG;
|
||||
-fx-background-insets: 0, 1px 1px 0 1px;
|
||||
.tab-pane .tab:selected {
|
||||
-fx-background-color: PRIMARY_BG, CONTROL_PRIMARY_LIGHT_BG_NORMAL;
|
||||
}
|
||||
|
||||
.tab-pane > .tab-header-area > .headers-region > .tab > .tab-container > .tab-label {
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
.tab-pane .tab .tab-label {
|
||||
-fx-text-fill: SECONDARY_BG;
|
||||
-fx-alignment: CENTER;
|
||||
}
|
||||
|
||||
.tab-pane > .tab-header-area > .headers-region > .tab .glyph-icon {
|
||||
.tab-pane .tab .glyph-icon {
|
||||
-fx-fill: SECONDARY_BG;
|
||||
}
|
||||
|
||||
.tab-pane > .tab-header-area > .headers-region > .tab:selected .glyph-icon {
|
||||
-fx-fill: TEXT_FILL;
|
||||
.tab-pane .tab:selected .glyph-icon {
|
||||
-fx-fill: PRIMARY_BG;
|
||||
}
|
||||
|
||||
.tab-pane .tab:selected .tab-label {
|
||||
-fx-text-fill: TEXT_FILL_PRIMARY;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -672,7 +675,6 @@
|
||||
******************************************************************************/
|
||||
|
||||
.choice-box {
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 1px;
|
||||
-fx-background-radius: 4px;
|
||||
@@ -683,6 +685,18 @@
|
||||
-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 {
|
||||
-fx-padding: 0 0 0 0.3em;
|
||||
}
|
||||
@@ -694,6 +708,10 @@
|
||||
-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 {
|
||||
-fx-translate-x: -1.4em;
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
SCROLL_BAR_THUMB_NORMAL: GRAY_7;
|
||||
SCROLL_BAR_THUMB_HOVER: GRAY_6;
|
||||
INDICATOR_BG: RED_5;
|
||||
DRAG_N_DROP_INDICATOR_BG: GRAY_5;
|
||||
PROGRESS_INDICATOR_BEGIN: GRAY_2;
|
||||
PROGRESS_INDICATOR_END: GRAY_4;
|
||||
PROGRESS_BAR_BG: GRAY_8;
|
||||
@@ -116,6 +117,10 @@
|
||||
-fx-font-size: 0.8em;
|
||||
}
|
||||
|
||||
.label-extra-small {
|
||||
-fx-font-size: 0.64em;
|
||||
}
|
||||
|
||||
.text-flow > * {
|
||||
-fx-fill: TEXT_FILL;
|
||||
}
|
||||
@@ -179,20 +184,6 @@
|
||||
-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 {
|
||||
-fx-background-color: PRIMARY_BG, white, INDICATOR_BG;
|
||||
-fx-background-insets: 0, 1px, 2px;
|
||||
@@ -201,6 +192,16 @@
|
||||
-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 *
|
||||
@@ -212,34 +213,36 @@
|
||||
}
|
||||
|
||||
.tab-pane > .tab-header-area {
|
||||
-fx-padding: 6px 12px 0 12px;
|
||||
-fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG;
|
||||
-fx-background-insets: 0, 0 0 1px 0;
|
||||
-fx-padding: 0 12px;
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 0 0 3px 0;
|
||||
}
|
||||
|
||||
.tab-pane > .tab-header-area > .headers-region > .tab {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, MAIN_BG;
|
||||
-fx-background-insets: 0 0 1px 0, 1px;
|
||||
-fx-background-radius: 4px 4px 0 0;
|
||||
-fx-padding: 0.2em 1em 0.2em 1em;
|
||||
.tab-pane .tab {
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 0 0 3px 0;
|
||||
-fx-padding: 6px 12px;
|
||||
}
|
||||
|
||||
.tab-pane > .tab-header-area > .headers-region > .tab:selected {
|
||||
-fx-background-color: CONTROL_BORDER_FOCUSED, MAIN_BG;
|
||||
-fx-background-insets: 0, 1px 1px 0 1px;
|
||||
.tab-pane .tab:selected {
|
||||
-fx-background-color: PRIMARY_BG, CONTROL_PRIMARY_LIGHT_BG_NORMAL;
|
||||
}
|
||||
|
||||
.tab-pane > .tab-header-area > .headers-region > .tab > .tab-container > .tab-label {
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
.tab-pane .tab .tab-label {
|
||||
-fx-text-fill: SECONDARY_BG;
|
||||
-fx-alignment: CENTER;
|
||||
}
|
||||
|
||||
.tab-pane > .tab-header-area > .headers-region > .tab .glyph-icon {
|
||||
.tab-pane .tab .glyph-icon {
|
||||
-fx-fill: SECONDARY_BG;
|
||||
}
|
||||
|
||||
.tab-pane > .tab-header-area > .headers-region > .tab:selected .glyph-icon {
|
||||
-fx-fill: TEXT_FILL;
|
||||
.tab-pane .tab:selected .glyph-icon {
|
||||
-fx-fill: PRIMARY_BG;
|
||||
}
|
||||
|
||||
.tab-pane .tab:selected .tab-label {
|
||||
-fx-text-fill: TEXT_FILL_PRIMARY;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -672,7 +675,6 @@
|
||||
******************************************************************************/
|
||||
|
||||
.choice-box {
|
||||
-fx-text-fill: TEXT_FILL;
|
||||
-fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL;
|
||||
-fx-background-insets: 0, 1px;
|
||||
-fx-background-radius: 4px;
|
||||
@@ -683,6 +685,18 @@
|
||||
-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 {
|
||||
-fx-padding: 0 0 0 0.3em;
|
||||
}
|
||||
@@ -694,6 +708,10 @@
|
||||
-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 {
|
||||
-fx-translate-x: -1.4em;
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.addvaultwizard.ChooseExistingVaultController"
|
||||
prefWidth="400"
|
||||
prefHeight="400"
|
||||
prefWidth="450"
|
||||
prefHeight="450"
|
||||
spacing="12"
|
||||
alignment="CENTER">
|
||||
<padding>
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
<?import org.cryptomator.ui.controls.FormattedLabel?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.addvaultwizard.AddVaultFailureExisitingController"
|
||||
minWidth="400"
|
||||
maxWidth="400"
|
||||
fx:controller="org.cryptomator.ui.addvaultwizard.AddVaultFailureExistingController"
|
||||
minWidth="450"
|
||||
maxWidth="450"
|
||||
minHeight="145"
|
||||
spacing="12">
|
||||
<padding>
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.addvaultwizard.CreateNewVaultLocationController"
|
||||
prefWidth="400"
|
||||
prefHeight="400"
|
||||
prefWidth="450"
|
||||
prefHeight="450"
|
||||
spacing="12"
|
||||
alignment="CENTER_LEFT">
|
||||
<fx:define>
|
||||
@@ -29,8 +29,10 @@
|
||||
|
||||
<VBox spacing="6">
|
||||
<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="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">
|
||||
<RadioButton fx:id="customRadioButton" toggleGroup="${predefinedLocationToggler}" text="%addvaultwizard.new.directoryPickerLabel"/>
|
||||
<Button contentDisplay="LEFT" text="%addvaultwizard.new.directoryPickerButton" onAction="#chooseCustomVaultPath" disable="${controller.usePresetPath}">
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.addvaultwizard.CreateNewVaultNameController"
|
||||
prefWidth="400"
|
||||
prefHeight="400"
|
||||
prefWidth="450"
|
||||
prefHeight="450"
|
||||
spacing="12"
|
||||
alignment="CENTER_LEFT">
|
||||
<padding>
|
||||
|
||||
@@ -3,42 +3,34 @@
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.control.CheckBox?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?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.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.addvaultwizard.CreateNewVaultPasswordController"
|
||||
prefWidth="400"
|
||||
prefHeight="400"
|
||||
prefWidth="450"
|
||||
prefHeight="450"
|
||||
spacing="12"
|
||||
alignment="CENTER_LEFT">
|
||||
<fx:define>
|
||||
<ToggleGroup fx:id="recoveryKeyChoice"/>
|
||||
</fx:define>
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="24"/>
|
||||
</padding>
|
||||
<children>
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
<fx:include source="/fxml/new_password.fxml"/>
|
||||
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
|
||||
<VBox spacing="6">
|
||||
<Label text="%addvaultwizard.new.enterPassword" labelFor="$passwordField"/>
|
||||
<NiceSecurePasswordField fx:id="passwordField"/>
|
||||
<PasswordStrengthIndicator spacing="6" prefHeight="6" strength="${controller.passwordStrength}"/>
|
||||
<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>
|
||||
<Label text="%addvaultwizard.new.generateRecoveryKeyChoice" wrapText="true"/>
|
||||
<RadioButton fx:id="showRecoveryKey" toggleGroup="${recoveryKeyChoice}" text="%addvaultwizard.new.generateRecoveryKeyChoice.yes" />
|
||||
<RadioButton fx:id="skipRecoveryKey" toggleGroup="${recoveryKeyChoice}" text="%addvaultwizard.new.generateRecoveryKeyChoice.no" />
|
||||
</VBox>
|
||||
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
|
||||
@@ -3,16 +3,13 @@
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?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.VBox?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.addvaultwizard.CreateNewVaultRecoveryKeyController"
|
||||
prefWidth="400"
|
||||
prefHeight="400"
|
||||
prefWidth="450"
|
||||
prefHeight="450"
|
||||
spacing="12"
|
||||
alignment="CENTER_LEFT">
|
||||
<padding>
|
||||
@@ -21,17 +18,13 @@
|
||||
<children>
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
|
||||
<Label text="%addvaultwizard.new.recoveryKeyInstruction" wrapText="true"/>
|
||||
|
||||
<TextArea editable="false" text="${controller.recoveryKey}" wrapText="true"/>
|
||||
|
||||
<CheckBox fx:id="finalConfirmationCheckbox" text="%addvaultwizard.new.recoveryKeySavedCheckbox" wrapText="true"/>
|
||||
<fx:include source="/fxml/recoverykey_display.fxml"/>
|
||||
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+X">
|
||||
<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>
|
||||
</ButtonBar>
|
||||
</children>
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.addvaultwizard.AddVaultSuccessController"
|
||||
prefWidth="400"
|
||||
prefHeight="400"
|
||||
prefWidth="450"
|
||||
prefHeight="450"
|
||||
spacing="12"
|
||||
alignment="TOP_CENTER">
|
||||
<padding>
|
||||
@@ -36,8 +36,8 @@
|
||||
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+IU">
|
||||
<buttons>
|
||||
<Button text="%generic.button.done" ButtonBar.buttonData="FINISH" onAction="#close"/>
|
||||
<Button text="%addvaultwizard.success.unlockNow" ButtonBar.buttonData="OTHER" onAction="#unlockAndClose" defaultButton="true"/>
|
||||
<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="${controller.vault.locked}" visible="${controller.vault.locked}"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</children>
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.addvaultwizard.AddVaultWelcomeController"
|
||||
prefWidth="400"
|
||||
prefHeight="400"
|
||||
prefWidth="450"
|
||||
prefHeight="450"
|
||||
spacing="12"
|
||||
alignment="TOP_CENTER">
|
||||
<padding>
|
||||
|
||||
@@ -28,22 +28,9 @@
|
||||
</VBox>
|
||||
|
||||
<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"/>
|
||||
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
|
||||
@@ -9,55 +9,32 @@
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Rectangle?>
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import javafx.scene.layout.Pane?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:id="root"
|
||||
fx:controller="org.cryptomator.ui.mainwindow.MainWindowController"
|
||||
styleClass="main-window">
|
||||
<HBox styleClass="title" fx:id="titleBar" alignment="CENTER" minHeight="50" maxHeight="50" VBox.vgrow="NEVER" 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="#showPreferences" focusTraversable="false">
|
||||
<graphic>
|
||||
<StackPane>
|
||||
<FontAwesome5IconView glyph="COGS"/>
|
||||
<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"/>
|
||||
</graphic>
|
||||
<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>
|
||||
<StackPane xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:id="root"
|
||||
fx:controller="org.cryptomator.ui.mainwindow.MainWindowController"
|
||||
styleClass="main-window">
|
||||
<VBox>
|
||||
<fx:include source="/fxml/main_window_title.fxml" VBox.vgrow="NEVER" />
|
||||
<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>
|
||||
|
||||
<VBox styleClass="drag-n-drop-indicator" visible="${controller.draggingOver}" alignment="TOP_CENTER">
|
||||
<HBox visible="${!controller.draggingVaultOver}" managed="${!controller.draggingVaultOver}" spacing="6" styleClass="drag-n-drop-header" alignment="CENTER" VBox.vgrow="NEVER">
|
||||
<FontAwesome5IconView glyph="EXCLAMATION_TRIANGLE"/>
|
||||
<Label text="%main.dropZone.unknownDragboardContent"/>
|
||||
</HBox>
|
||||
<HBox visible="${controller.draggingVaultOver}" managed="${controller.draggingVaultOver}" spacing="6" styleClass="drag-n-drop-header" alignment="CENTER" VBox.vgrow="NEVER">
|
||||
<FontAwesome5IconView glyph="CHECK"/>
|
||||
<Label text="%main.dropZone.dropVault"/>
|
||||
</HBox>
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
</VBox>
|
||||
</StackPane>
|
||||
</VBox>
|
||||
<fx:include source="/fxml/main_window_resize.fxml"/>
|
||||
</StackPane>
|
||||
|
||||
17
main/ui/src/main/resources/fxml/main_window_resize.fxml
Normal file
17
main/ui/src/main/resources/fxml/main_window_resize.fxml
Normal file
@@ -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>
|
||||
55
main/ui/src/main/resources/fxml/main_window_title.fxml
Normal file
55
main/ui/src/main/resources/fxml/main_window_title.fxml
Normal file
@@ -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>
|
||||
30
main/ui/src/main/resources/fxml/new_password.fxml
Normal file
30
main/ui/src/main/resources/fxml/new_password.fxml
Normal file
@@ -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"
|
||||
fx:id="tabPane"
|
||||
fx:controller="org.cryptomator.ui.preferences.PreferencesController"
|
||||
minWidth="400"
|
||||
minWidth="-Infinity"
|
||||
maxWidth="-Infinity"
|
||||
prefWidth="500"
|
||||
tabMinWidth="60"
|
||||
tabClosingPolicy="UNAVAILABLE"
|
||||
tabDragPolicy="FIXED">
|
||||
<tabs>
|
||||
@@ -35,5 +38,13 @@
|
||||
<fx:include source="/fxml/preferences_updates.fxml"/>
|
||||
</content>
|
||||
</Tab>
|
||||
<Tab fx:id="donationKeyTab" text="%preferences.donationKey">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="HEART"/>
|
||||
</graphic>
|
||||
<content>
|
||||
<fx:include source="/fxml/preferences_donationkey.fxml"/>
|
||||
</content>
|
||||
</Tab>
|
||||
</tabs>
|
||||
</TabPane>
|
||||
|
||||
48
main/ui/src/main/resources/fxml/preferences_donationkey.fxml
Normal file
48
main/ui/src/main/resources/fxml/preferences_donationkey.fxml
Normal file
@@ -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>
|
||||
<HBox spacing="6" alignment="CENTER_LEFT">
|
||||
<Label text="%preferences.general.theme"/>
|
||||
<ChoiceBox fx:id="themeChoiceBox"/>
|
||||
<ChoiceBox fx:id="themeChoiceBox" disable="${!controller.licenseHolder.validLicense}"/>
|
||||
</HBox>
|
||||
|
||||
<HBox spacing="6" alignment="CENTER_LEFT">
|
||||
<Label text="Interface Orientation" HBox.hgrow="NEVER"/>
|
||||
<RadioButton fx:id="nodeOrientationLtr" text="Left to Right" alignment="CENTER_LEFT" toggleGroup="${nodeOrientation}"/>
|
||||
<RadioButton fx:id="nodeOrientationRtl" text="Right to Left" alignment="CENTER_RIGHT" toggleGroup="${nodeOrientation}"/>
|
||||
<Label text="%preferences.general.interfaceOrientation" HBox.hgrow="NEVER"/>
|
||||
<RadioButton fx:id="nodeOrientationLtr" text="%preferences.general.interfaceOrientation.ltr" alignment="CENTER_LEFT" toggleGroup="${nodeOrientation}"/>
|
||||
<RadioButton fx:id="nodeOrientationRtl" text="%preferences.general.interfaceOrientation.rtl" alignment="CENTER_RIGHT" toggleGroup="${nodeOrientation}"/>
|
||||
</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="autoStartCheckbox" text="%preferences.general.autoStart" visible="${controller.autoStartSupported}" managed="${controller.autoStartSupported}" onAction="#toggleAutoStart"/>
|
||||
</children>
|
||||
</VBox>
|
||||
|
||||
@@ -1,46 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ButtonBar?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.TextArea?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?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.recoverykey.RecoveryKeyDisplayController"
|
||||
minWidth="400"
|
||||
maxWidth="400"
|
||||
minHeight="145"
|
||||
maxHeight="180"
|
||||
minWidth="350"
|
||||
minHeight="280"
|
||||
spacing="12"
|
||||
alignment="TOP_CENTER">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
alignment="TOP_LEFT">
|
||||
<children>
|
||||
<HBox spacing="12" alignment="CENTER_LEFT" VBox.vgrow="ALWAYS">
|
||||
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
|
||||
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
||||
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="CHECK" glyphSize="24"/>
|
||||
</StackPane>
|
||||
<FormattedLabel format="%recoveryKey.display.message" arg1="${controller.vaultName}" wrapText="true"/>
|
||||
|
||||
<VBox spacing="6" HBox.hgrow="ALWAYS">
|
||||
<FormattedLabel format="%recoveryKey.display.message" arg1="${controller.vault.displayableName}" wrapText="true" VBox.vgrow="NEVER"/>
|
||||
<TextArea editable="false" text="${controller.recoveryKey}" wrapText="true" VBox.vgrow="ALWAYS"/>
|
||||
</VBox>
|
||||
</HBox>
|
||||
<TextArea editable="false" text="${controller.recoveryKey}" wrapText="true" prefRowCount="4" fx:id="textarea"/>
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+R">
|
||||
<buttons>
|
||||
<Button text="%generic.button.print" ButtonBar.buttonData="RIGHT" onAction="#printRecoveryKey" visible="${controller.printerSupported}">
|
||||
<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">
|
||||
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
|
||||
<buttons>
|
||||
<Button text="%generic.button.done" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
|
||||
</buttons>
|
||||
</ButtonBar>
|
||||
</VBox>
|
||||
<Region VBox.vgrow="ALWAYS"/>
|
||||
|
||||
<Label text="%recoveryKey.display.StorageHints" wrapText="true"/>
|
||||
</children>
|
||||
</VBox>
|
||||
|
||||
32
main/ui/src/main/resources/fxml/recoverykey_success.fxml
Normal file
32
main/ui/src/main/resources/fxml/recoverykey_success.fxml
Normal file
@@ -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>
|
||||
29
main/ui/src/main/resources/fxml/stacktrace.fxml
Normal file
29
main/ui/src/main/resources/fxml/stacktrace.fxml
Normal file
@@ -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>
|
||||
32
main/ui/src/main/resources/fxml/unlock_generic_error.fxml
Normal file
32
main/ui/src/main/resources/fxml/unlock_generic_error.fxml
Normal file
@@ -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 org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.Tooltip?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import org.cryptomator.ui.controls.ThrougputLabel?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
@@ -18,7 +18,7 @@
|
||||
<FontAwesome5IconView glyph="HDD" glyphSize="24"/>
|
||||
<VBox spacing="4" alignment="CENTER_LEFT">
|
||||
<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>
|
||||
</HBox>
|
||||
</graphic>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
fx:id="tabPane"
|
||||
fx:controller="org.cryptomator.ui.vaultoptions.VaultOptionsController"
|
||||
minWidth="400"
|
||||
tabMinWidth="60"
|
||||
tabClosingPolicy="UNAVAILABLE"
|
||||
tabDragPolicy="FIXED">
|
||||
<tabs>
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
<?import javafx.scene.control.ToggleGroup?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Text?>
|
||||
<?import org.cryptomator.ui.controls.AlphanumericTextField?>
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
@@ -32,35 +31,36 @@
|
||||
<CheckBox fx:id="readOnlyCheckbox" text="%vaultOptions.mount.readonly"/>
|
||||
|
||||
<CheckBox fx:id="customMountFlagsCheckbox" text="%vaultOptions.mount.customMountFlags" onAction="#toggleUseCustomMountFlags"/>
|
||||
<HBox>
|
||||
<padding>
|
||||
<Insets left="25"/>
|
||||
</padding>
|
||||
<children>
|
||||
<TextField fx:id="mountFlags" HBox.hgrow="ALWAYS" maxWidth="Infinity"/>
|
||||
</children>
|
||||
</HBox>
|
||||
|
||||
<Text text="TODO Mount Point"/>
|
||||
<RadioButton toggleGroup="${mountPoint}" fx:id="mountPointAuto" text="TODO Automatically pick a suitable location"/>
|
||||
|
||||
<TextField fx:id="mountFlags" HBox.hgrow="ALWAYS" maxWidth="Infinity">
|
||||
<VBox.margin>
|
||||
<Insets left="24"/>
|
||||
</VBox.margin>
|
||||
</TextField>
|
||||
|
||||
<Label text="%vaultOptions.mount.mountPoint">
|
||||
<VBox.margin>
|
||||
<Insets top="9"/>
|
||||
</VBox.margin>
|
||||
</Label>
|
||||
<RadioButton toggleGroup="${mountPoint}" fx:id="mountPointAuto" text="%vaultOptions.mount.mountPoint.auto"/>
|
||||
<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}"/>
|
||||
</HBox>
|
||||
<RadioButton toggleGroup="${mountPoint}" fx:id="mountPointCustomDir" text="TODO Choose empty directory"/>
|
||||
<HBox visible="${mountPointCustomDir.selected}">
|
||||
<padding>
|
||||
<Insets left="24"/>
|
||||
</padding>
|
||||
<children>
|
||||
<TextField text="${controller.customMountPath}" HBox.hgrow="ALWAYS" maxWidth="Infinity" disable="true"/>
|
||||
<Button text="TODO change" onAction="#chooseCustomMountPoint" contentDisplay="LEFT">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="FOLDER_OPEN" glyphSize="15"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
</children>
|
||||
<HBox spacing="6" alignment="CENTER_LEFT">
|
||||
<RadioButton toggleGroup="${mountPoint}" fx:id="mountPointCustomDir" text="%vaultOptions.mount.mountPoint.custom"/>
|
||||
<Button text="%vaultOptions.mount.mountPoint.directoryPickerButton" onAction="#chooseCustomMountPoint" contentDisplay="LEFT" disable="${!mountPointCustomDir.selected}">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="FOLDER_OPEN" glyphSize="15"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
</HBox>
|
||||
<TextField text="${controller.customMountPath}" visible="${mountPointCustomDir.selected}" maxWidth="Infinity" disable="true">
|
||||
<VBox.margin>
|
||||
<Insets left="24"/>
|
||||
</VBox.margin>
|
||||
</TextField>
|
||||
</children>
|
||||
|
||||
</VBox>
|
||||
|
||||
@@ -7,12 +7,18 @@ generic.button.apply=Apply
|
||||
generic.button.back=Back
|
||||
generic.button.cancel=Cancel
|
||||
generic.button.change=Change
|
||||
generic.button.copy=Copy
|
||||
generic.button.done=Done
|
||||
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
|
||||
traymenu.showMainWindow=Show
|
||||
traymenu.showPreferencesWindow=Preferences
|
||||
traymenu.lockAllVaults=Lock All
|
||||
traymenu.quitApplication=Quit
|
||||
traymenu.vault.unlock=Unlock
|
||||
traymenu.vault.lock=Lock
|
||||
@@ -34,18 +40,14 @@ addvaultwizard.new.locationPrompt=…
|
||||
addvaultwizard.new.directoryPickerLabel=Custom Location
|
||||
addvaultwizard.new.directoryPickerButton=Choose…
|
||||
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.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.
|
||||
### Password
|
||||
addvaultwizard.new.reenterPassword=Confirm the password
|
||||
addvaultwizard.new.passwordsMatch=Passwords match!
|
||||
addvaultwizard.new.passwordsDoNotMatch=Passwords do not match
|
||||
addvaultwizard.new.createVaultBtn=Create Vault
|
||||
### Recovery Key
|
||||
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.recoveryKeySavedCheckbox=Yes, I've made a secure backup of this 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.generateRecoveryKeyChoice.yes=Yes please, better safe than sorry
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.no=No thanks, I will not lose my password
|
||||
### Information
|
||||
addvault.new.readme.storageLocation.fileName=WHAT IS THIS DIRECTORY.rtf
|
||||
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ VAULT FILES ⚠️
|
||||
@@ -73,10 +75,6 @@ removeVault.confirmBtn=Remove Vault
|
||||
# Change Password
|
||||
changepassword.title=Change Password
|
||||
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
|
||||
|
||||
# Forget Password
|
||||
@@ -92,6 +90,8 @@ unlock.unlockBtn=Unlock
|
||||
## Success
|
||||
unlock.success.message=Unlocked "%s" successfully! Your vault is now accessible.
|
||||
unlock.success.revealBtn=Reveal Vault
|
||||
## Invalid Mount Point
|
||||
unlock.error.invalidMountPoint=Mount point is not an empty directory: %s
|
||||
|
||||
# Migration
|
||||
migration.title=Upgrade Vault
|
||||
@@ -112,6 +112,10 @@ preferences.general=General
|
||||
preferences.general.theme=Look & Feel
|
||||
preferences.general.startHidden=Hide window when starting Cryptomator
|
||||
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
|
||||
preferences.volume=Virtual Drive
|
||||
preferences.volume.type=Volume Type
|
||||
@@ -123,10 +127,19 @@ preferences.updates.currentVersion=Current Version: %s
|
||||
preferences.updates.autoUpdateCheck=Check for updates automatically
|
||||
preferences.updates.checkNowBtn=Check Now
|
||||
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.closeBtn.tooltip=Close
|
||||
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
|
||||
main.vaultlist.emptyList.onboardingInstruction=Click here to add a vault
|
||||
main.vaultlist.contextMenu.remove=Remove Vault
|
||||
@@ -166,14 +179,25 @@ vaultOptions.mount.readonly=Read-Only
|
||||
vaultOptions.mount.driveName=Drive Name
|
||||
vaultOptions.mount.customMountFlags=Custom Mount Flags
|
||||
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
|
||||
recoveryKey.title=Recovery Key
|
||||
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.1=Weak
|
||||
passwordStrength.messageLabel.2=Fair
|
||||
|
||||
@@ -8,6 +8,7 @@ generic.button.cancel=الغاء
|
||||
generic.button.change=تغيير
|
||||
generic.button.done=تم
|
||||
generic.button.next=التالي
|
||||
## Error
|
||||
|
||||
# Tray Menu
|
||||
traymenu.showMainWindow=اظهار
|
||||
@@ -33,15 +34,10 @@ addvaultwizard.new.locationPrompt=…
|
||||
addvaultwizard.new.directoryPickerLabel=موقع مخصص
|
||||
addvaultwizard.new.directoryPickerButton=اختر…
|
||||
addvaultwizard.new.directoryPickerTitle=اختر القاموس
|
||||
addvaultwizard.new.enterPassword=أدخل كلمة المرور لهذا الحساب
|
||||
addvaultwizard.new.fileAlreadyExists=لا يمكن إنشاء مخزن على هذا المسار لأن بعض الملفات موجودة مسبقا.
|
||||
addvaultwizard.new.invalidName=اسم المخزن غير صالح. يرجى النظر في اسم الدليل المعتاد.
|
||||
### Password
|
||||
addvaultwizard.new.reenterPassword=تأكيد كلمة المرور
|
||||
addvaultwizard.new.passwordsMatch=كلمات المرور متطابقة!
|
||||
addvaultwizard.new.passwordsDoNotMatch=كلمات المرور غير متطابقة
|
||||
addvaultwizard.new.createVaultBtn=انشئ حافظة
|
||||
### Recovery Key
|
||||
### Information
|
||||
addvault.new.readme.storageLocation.4=لو انك تحتاج مساعدة, جرب "%s".
|
||||
## Existing
|
||||
@@ -52,10 +48,6 @@ addvaultwizard.existing.chooseBtn=اختر…
|
||||
removeVault.confirmBtn=احذف الحافظة
|
||||
|
||||
# Change Password
|
||||
changepassword.enterNewPassword=ادخل كلمة المرور الجديدة لحافظتك
|
||||
changepassword.reenterNewPassword=تأكيد كلمة المرور الجديدة
|
||||
changepassword.passwordsMatch=كلمات المرور متطابقة!
|
||||
changepassword.passwordsDoNotMatch=كلمات المرور غير متطابقة
|
||||
|
||||
# Forget Password
|
||||
forgetPassword.title=نسيت كلمة المرور
|
||||
@@ -67,6 +59,7 @@ unlock.savePassword=احفظ كلمة المرور
|
||||
unlock.unlockBtn=افتح
|
||||
## Success
|
||||
unlock.success.revealBtn=افتح الحافظة
|
||||
## Invalid Mount Point
|
||||
|
||||
# Migration
|
||||
migration.title=ترقية الحافظة
|
||||
@@ -87,9 +80,11 @@ preferences.volume.webdav.port=منفذ WebDav
|
||||
preferences.updates=تحديثات
|
||||
preferences.updates.autoUpdateCheck=تحقق من التحديثات اوتوماتيكيا
|
||||
preferences.updates.checkNowBtn=تحقق الان
|
||||
## Donation Key
|
||||
|
||||
# Main Window
|
||||
main.preferencesBtn.tooltip=تفضيلات
|
||||
## Drag 'n' Drop
|
||||
## Vault List
|
||||
main.vaultlist.addVaultBtn=أضِف مخزنًا
|
||||
## Vault Detail
|
||||
@@ -108,9 +103,10 @@ main.vaultDetail.migrateButton=ترقية الحافظة
|
||||
## General
|
||||
vaultOptions.general=عام
|
||||
## Mount
|
||||
vaultOptions.mount.mountPoint.directoryPickerButton=اختر…
|
||||
|
||||
# Recovery Key
|
||||
|
||||
# Misc
|
||||
# New Password
|
||||
|
||||
# Quit
|
||||
|
||||
@@ -6,8 +6,11 @@ generic.button.apply=Použít
|
||||
generic.button.back=Zpět
|
||||
generic.button.cancel=Zrušit
|
||||
generic.button.change=Změnit
|
||||
generic.button.copy=Kopírovat
|
||||
generic.button.done=Hotovo
|
||||
generic.button.next=Další
|
||||
generic.button.print=Vytisknout
|
||||
## Error
|
||||
|
||||
# Tray Menu
|
||||
traymenu.showMainWindow=Zobrazit
|
||||
@@ -33,16 +36,12 @@ addvaultwizard.new.locationPrompt=...
|
||||
addvaultwizard.new.directoryPickerLabel=Vlastní umístění
|
||||
addvaultwizard.new.directoryPickerButton=Vybrat...
|
||||
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.invalidName=Neplatné jméno trezoru. Prosím změňte jméno adresáře.
|
||||
### 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
|
||||
### Recovery Key
|
||||
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.yes=Ano prosím, jistota je jistota
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.no=Ne, díky, neztratím své heslo
|
||||
### Information
|
||||
addvault.new.readme.storageLocation.fileName=CO JE TOTO ZA ADRESÁŘ.rtf
|
||||
addvault.new.readme.storageLocation.1=\\fs40\\qc ⚠️ OBSAH TREZORU ⚠️
|
||||
@@ -69,10 +68,6 @@ removeVault.confirmBtn=Odstranit trezor
|
||||
# Change Password
|
||||
changepassword.title=Změnit heslo
|
||||
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!
|
||||
|
||||
# Forget Password
|
||||
@@ -88,6 +83,7 @@ unlock.unlockBtn=Odemknout
|
||||
## Success
|
||||
unlock.success.message=Trezor "%s" byl úspěšně odemčen a nyní je dostupný.
|
||||
unlock.success.revealBtn=Zobrazit trezor
|
||||
## Invalid Mount Point
|
||||
|
||||
# Migration
|
||||
migration.title=Upgrade trezoru
|
||||
@@ -119,10 +115,18 @@ preferences.updates.currentVersion=Aktuální verze: %s
|
||||
preferences.updates.autoUpdateCheck=Automaticky kontrolovat aktualizace
|
||||
preferences.updates.checkNowBtn=Zkontrolovat nyní
|
||||
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.closeBtn.tooltip=Zavřít
|
||||
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
|
||||
main.vaultlist.emptyList.onboardingInstruction=Klikněte sem pro přidání nového trezoru
|
||||
main.vaultlist.contextMenu.remove=Odstranit trezor
|
||||
@@ -144,6 +148,7 @@ main.vaultDetail.throughput.kbps=%.1f kiB/s
|
||||
main.vaultDetail.throughput.mbps=%.1f MiB/s
|
||||
### Needs Migration
|
||||
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
|
||||
wrongFileAlert.title=neznámý soubor
|
||||
@@ -161,14 +166,20 @@ vaultOptions.mount.readonly=Pouze pro čtení
|
||||
vaultOptions.mount.driveName=Jméno jednotky
|
||||
vaultOptions.mount.customMountFlags=Vlastní parametry
|
||||
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
|
||||
recoveryKey.title=Klíč k obnově
|
||||
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.1=slabé
|
||||
passwordStrength.messageLabel.2=dostatečné
|
||||
|
||||
@@ -6,12 +6,18 @@ generic.button.apply=Übernehmen
|
||||
generic.button.back=Zurück
|
||||
generic.button.cancel=Abbrechen
|
||||
generic.button.change=Ändern
|
||||
generic.button.copy=Kopieren
|
||||
generic.button.done=Fertig
|
||||
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
|
||||
traymenu.showMainWindow=Anzeigen
|
||||
traymenu.showPreferencesWindow=Einstellungen
|
||||
traymenu.lockAllVaults=Alle sperren
|
||||
traymenu.quitApplication=Beenden
|
||||
traymenu.vault.unlock=Entsperren
|
||||
traymenu.vault.lock=Sperren
|
||||
@@ -32,32 +38,28 @@ addvaultwizard.new.locationLabel=Speicherort
|
||||
addvaultwizard.new.locationPrompt=…
|
||||
addvaultwizard.new.directoryPickerLabel=Eigener Ort
|
||||
addvaultwizard.new.directoryPickerButton=Durchsuchen …
|
||||
addvaultwizard.new.directoryPickerTitle=Wähle ein Verzeichnis
|
||||
addvaultwizard.new.enterPassword=Vergib ein Passwort für den Tresor
|
||||
addvaultwizard.new.directoryPickerTitle=Verzeichnis auswählen
|
||||
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.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
|
||||
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
|
||||
### Recovery Key
|
||||
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.recoveryKeySavedCheckbox=Ja, ich habe eine Datensicherung dieses Wiederherstellungsschlüssels durchgeführt
|
||||
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.generateRecoveryKeyChoice.yes=Ja bitte, sicher ist sicher
|
||||
addvaultwizard.new.generateRecoveryKeyChoice.no=Nein danke, ich werde mein Passwort nicht verlieren
|
||||
### Information
|
||||
addvault.new.readme.storageLocation.fileName=WAS BEDEUTET DIESER ORDNER.rtf
|
||||
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.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.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.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.
|
||||
## Existing
|
||||
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.error=Der Tresor „%s“ ist kein gültiger Tresor und kann nicht hinzugefügt werden. Weitere Informationen findest du im Diagnoseprotokoll.
|
||||
## Success
|
||||
@@ -71,11 +73,7 @@ removeVault.confirmBtn=Tresor entfernen
|
||||
|
||||
# Change Password
|
||||
changepassword.title=Password ändern
|
||||
changepassword.enterOldPassword=Gib dein derzeitiges 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.enterOldPassword=Gib dein aktuelles Passwort für „%s“ ein
|
||||
changepassword.finalConfirmation=Mir ist bewusst, dass ich den Tresor ohne das Passwort NICHT mehr öffnen kann
|
||||
|
||||
# Forget Password
|
||||
@@ -85,12 +83,14 @@ forgetPassword.confirmBtn=Passwort vergessen
|
||||
|
||||
# Unlock
|
||||
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.unlockBtn=Entsperren
|
||||
## 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
|
||||
## Invalid Mount Point
|
||||
unlock.error.invalidMountPoint=Einhängepunkt ist kein leeres Verzeichnis: %s
|
||||
|
||||
# Migration
|
||||
migration.title=Tresor aktualisieren
|
||||
@@ -111,6 +111,10 @@ preferences.general=Allgemein
|
||||
preferences.general.theme=Erscheinungsbild
|
||||
preferences.general.startHidden=Cryptomator im Hintergrund starten
|
||||
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
|
||||
preferences.volume=Tresorlaufwerk
|
||||
preferences.volume.type=Laufwerkstyp
|
||||
@@ -122,10 +126,19 @@ preferences.updates.currentVersion=Aktuelle Version: %s
|
||||
preferences.updates.autoUpdateCheck=Automatisch nach Updates suchen
|
||||
preferences.updates.checkNowBtn=Jetzt suchen
|
||||
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.closeBtn.tooltip=Schließen
|
||||
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
|
||||
main.vaultlist.emptyList.onboardingInstruction=Klicke hier, um einen Tresor hinzuzufügen
|
||||
main.vaultlist.contextMenu.remove=Tresor entfernen
|
||||
@@ -152,7 +165,7 @@ main.vaultDetail.migratePrompt=Dein Tresor muss auf ein neues Format aktualisier
|
||||
# Wrong File Alert
|
||||
wrongFileAlert.title=Unbekannte Datei
|
||||
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
|
||||
## General
|
||||
@@ -165,14 +178,25 @@ vaultOptions.mount.readonly=Schreibgeschützt
|
||||
vaultOptions.mount.driveName=Laufwerksname
|
||||
vaultOptions.mount.customMountFlags=Benutzerdefinierte Mount-Flags
|
||||
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
|
||||
recoveryKey.title=Wiederherstellungsschlüssel
|
||||
recoveryKey.enterPassword.prompt=Gib dein Kennwort ein, um den Wiederherstellungsschlüssel für „%s“ anzuzeigen:
|
||||
recoveryKey.display.message=Wiederherstellungsschlüssel für „%s“:
|
||||
recoveryKey.enterPassword.prompt=Gib dein Passwort ein, um den Wiederherstellungsschlüssel für „%s“ anzuzeigen:
|
||||
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.1=Schwach
|
||||
passwordStrength.messageLabel.2=Mittel
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
# Generics
|
||||
## Button
|
||||
## Error
|
||||
|
||||
# Tray Menu
|
||||
|
||||
@@ -11,7 +12,6 @@
|
||||
### Name
|
||||
### Location
|
||||
### Password
|
||||
### Recovery Key
|
||||
### Information
|
||||
## Existing
|
||||
## Success
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
# Unlock
|
||||
## Success
|
||||
## Invalid Mount Point
|
||||
|
||||
# Migration
|
||||
## Start
|
||||
@@ -34,8 +35,10 @@
|
||||
## General
|
||||
## Volume
|
||||
## Updates
|
||||
## Donation Key
|
||||
|
||||
# Main Window
|
||||
## Drag 'n' Drop
|
||||
## Vault List
|
||||
## Vault Detail
|
||||
### Locked
|
||||
@@ -50,6 +53,6 @@
|
||||
|
||||
# Recovery Key
|
||||
|
||||
# Misc
|
||||
# New Password
|
||||
|
||||
# Quit
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user