Compare commits

..
Author SHA1 Message Date
Sebastian Stenzel a6e680e32d Merge branch 'release/1.5.0-beta2' 2020-01-09 17:15:55 +01:00
Sebastian Stenzel d60a024c63 preparing 1.5.0-beta2 2020-01-09 16:50:38 +01:00
Sebastian StenzelandGitHub cc1b536656 New Crowdin translations (#1039)
[ci skip]
2020-01-09 16:49:39 +01:00
Sebastian Stenzel 85e773abef enable themes for donators 2020-01-09 16:34:31 +01:00
Sebastian Stenzel 4690bc56aa fixes #1038 2020-01-09 16:04:46 +01:00
Sebastian Stenzel 292c2676fd fixes #980 2020-01-09 15:44:25 +01:00
Sebastian Stenzel f7c6604c7c fixes #1029 2020-01-09 15:16:13 +01:00
Sebastian Stenzel 22310eb957 updated donation key store uri 2020-01-09 14:38:23 +01:00
Sebastian Stenzel 6d17d1298b removed unused fields 2020-01-09 13:03:22 +01:00
Sebastian Stenzel e90a680620 Split up MainWindowController, as it grew too big. The title bar is now handled by MainWindowTitleController
(@zhouer add the minimize button for #1035 to this new controller and fxml file)
2020-01-09 12:18:24 +01:00
Sebastian Stenzel 35d0ccfe89 allow resizing main window on all four corners
fixes #995
2020-01-09 12:06:12 +01:00
Tobias Hagemann 62cc454367 updated license public key 2020-01-09 10:35:27 +01:00
Tobias Hagemann 8bb277fe56 added iCloud Drive and OneDrive location presets 2020-01-09 10:27:56 +01:00
Sebastian Stenzel 67485b76af Make sure that adding a vault located at ~/foo is noop when a vault located at /home/user/foo already exists
fixes #1032
2020-01-08 16:50:08 +01:00
Sebastian Stenzel 270e60d7cf updated logging
[ci skip]
2020-01-08 15:41:26 +01:00
Sebastian Stenzel 11ffec862e Added autostart support for windows
fixes #418
2020-01-07 17:56:05 +01:00
Sebastian Stenzel d37e0dd5e1 Determine autostart state async (#418) 2020-01-07 17:55:36 +01:00
Sebastian Stenzel 960c3665e0 Localizations 2020-01-06 12:54:51 +01:00
Sebastian Stenzel 3396c79c38 updated intellij config
[ci skip]
2020-01-06 12:45:01 +01:00
Sebastian Stenzel cc1517f1b8 Added checkbox for auto start on macOS (#418) 2020-01-06 12:37:36 +01:00
Sebastian Stenzel 4f3ff55ae7 updated stale config
[ci skip]
2020-01-06 09:48:18 +01:00
Sebastian Stenzel f51362e95e cleanup
[ci skip]
2020-01-06 09:46:14 +01:00
Sebastian Stenzel 415dfece58 sames as in 9b6145ce:
We only want the user to trigger the listener
2020-01-06 09:46:01 +01:00
Sebastian StenzelandGitHub 3bdb5353da New Crowdin translations (#1015)
[ci skip]
2020-01-04 22:11:48 +01:00
Sebastian StenzelandGitHub 6ddf1b59ef Merge pull request #1031 from zhouer/hide_start_hidden
Hide startHidden setting when system tray is not supported
2019-12-29 14:18:35 +01:00
En-Jan Chou ef88f11c4f Hide startHidden setting when system tray is not supported
For platform not supporting system tray, users will have no chance to show
window and modify settings back again if the window is hidden at startup.
2019-12-26 09:40:33 -05:00
Sebastian Stenzel ec800c5439 Enforce minimum password length (amendment to #1018) 2019-12-18 15:31:52 +01:00
Sebastian Stenzel a2f3f5d254 fixes #1018, references #979 2019-12-18 14:58:36 +01:00
Sebastian Stenzel f475f70adf Updated zxcvbn4j to 1.3.0, which allows us to swipe the password from memory even after measuring its strength 2019-12-18 14:40:04 +01:00
Sebastian StenzelandGitHub 93c3da66da Merge pull request #1024 from zhouer/fix_mount_point
Initialize radio button before binding settings to it
2019-12-18 12:16:12 +01:00
En-Jan Chou 9b6145ce3f Initialize radio button before binding settings to it
The usesIndividualMountPath setting will be cleared if it bind to
an uninitialized radio button. This patch sets radio button with
current settings before binding the setting to the radio button.
2019-12-18 02:39:03 -05:00
Sebastian Stenzel 148eed172a Deduplicated UI code
Made "Enter Password" and "Reenter Password" a reusable component that is included in its original places
preparation for #1018
2019-12-17 17:35:16 +01:00
Sebastian Stenzel c97f146964 enabled "lock all" from tray menu (#297, #863) 2019-12-16 14:44:14 +01:00
Sebastian Stenzel a1034f5663 Refactored "lock all" task 2019-12-16 14:43:33 +01:00
Sebastian Stenzel 938b351f33 enabled "reveal" from tray menu (#297) 2019-12-16 13:10:43 +01:00
Sebastian Stenzel d870ecbcdb moved reveal() to VaultService 2019-12-16 13:03:28 +01:00
Sebastian Stenzel 5dff2126c4 cleanup of new VaultService 2019-12-16 11:22:30 +01:00
Sebastian Stenzel 41a6cc15e8 run "lock all" service on shared executorservice 2019-12-16 10:32:35 +01:00
Sebastian Stenzel f21b30c009 removed unused field 2019-12-16 10:31:04 +01:00
Sebastian Stenzel d69b63acc3 enabled "lock" from tray menu (#297) 2019-12-11 15:57:39 +01:00
Sebastian Stenzel f95b2baad5 "lock" deduplication 2019-12-11 15:57:14 +01:00
Sebastian Stenzel 1a4d1fffb3 shutdown logback manually 2019-12-11 13:53:32 +01:00
Sebastian Stenzel ac536ba125 Some shutdown hook refactorings for #980 2019-12-04 17:50:30 +01:00
Sebastian Stenzel 362b225d66 Fixes #939 2019-11-27 16:39:40 +01:00
Sebastian Stenzel 7d0bdc1a63 Show stack trace in case of unexpected exceptions 2019-11-27 15:44:53 +01:00
Sebastian Stenzel 94e3b21e44 Typo
[ci skip]
2019-11-27 13:20:44 +01:00
Sebastian Stenzel 23f89c1dc9 Reevaluate vault state when selecting vault 2019-11-26 17:21:28 +01:00
Sebastian Stenzel ce9c2f2c7a removed unused import
[ci skip]
2019-11-26 17:21:08 +01:00
Tobias Hagemann 44e97ab046 smaller text size for path in reveal button [ci skip] 2019-11-26 17:02:13 +01:00
Sebastian Stenzel 5d0a1fd49f tabpane styling
[ci skip]
2019-11-26 16:39:32 +01:00
Sebastian Stenzel ffc1d8dc1e simplified 2019-11-22 14:23:55 +01:00
Sebastian Stenzel 54fca93bba deduplicated code 2019-11-22 14:21:51 +01:00
Sebastian Stenzel c281687910 added copy/print buttons to recovery key dialog 2019-11-22 13:43:40 +01:00
Sebastian Stenzel 6229d7abbe Updated password and recovery key instructions 2019-11-22 13:10:50 +01:00
Sebastian Stenzel 01644eddb6 increase wizard dimensions
[ci skip]
2019-11-21 16:58:05 +01:00
Sebastian Stenzel 6e30df3796 Let the user choose whether a recovery key should be shown during vault creation 2019-11-21 16:57:43 +01:00
Sebastian Stenzel f87fa319ff renamed class 2019-11-20 17:14:31 +01:00
Sebastian Stenzel 9b019726bb show badge for missing license key in window title 2019-11-20 17:12:11 +01:00
Sebastian StenzelandGitHub cceafb76ed New Crowdin translations
New translations strings.properties
[ci skip]
2019-11-20 17:09:01 +01:00
Tobias Hagemann 39e2994c69 updated drag-n-drop-indicator styling 2019-11-20 16:53:02 +01:00
Sebastian Stenzel 1ddfcc3219 reworked drag'n'drop l&f 2019-11-20 15:03:03 +01:00
Tobias Hagemann 687f11596e added disabled styling to choice-box 2019-11-19 21:52:52 +01:00
Sebastian Stenzel b445f614fb Made mount point settings localizable
(and some fxml cleanup)
[ci skip]
2019-11-19 21:39:00 +01:00
Sebastian Stenzel c6fc1d93a0 save donation key to settings 2019-11-19 21:20:00 +01:00
Sebastian Stenzel 28d58922e3 TextArea needs a prefRowCount, otherwise it attempts to use all available space, displacing other elements.
fixes #1012
2019-11-18 10:11:20 +01:00
Sebastian Stenzel b884ea7ddc added registration key to preferences 2019-11-15 15:56:35 +01:00
Sebastian Stenzel 1717e20b61 Add JWT verifier 2019-11-14 17:02:41 +01:00
Sebastian Stenzel 6222f96b01 Merge branch 'master' into develop 2019-11-12 13:40:34 +01:00
115 changed files with 3346 additions and 992 deletions
+3 -1
View File
@@ -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
+2 -2
View File
@@ -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>
+8 -6
View File
@@ -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>
+1 -1
View File
@@ -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>
+7 -1
View File
@@ -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());
}
}
+1 -1
View File
@@ -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>
+1 -1
View File
@@ -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);
}
}
+15 -9
View File
@@ -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>
+3 -3
View File
@@ -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) {
+49 -31
View File
@@ -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;
}
+49 -31
View File
@@ -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>
@@ -0,0 +1,17 @@
<?import javafx.scene.Cursor?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.Region?>
<AnchorPane xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.mainwindow.ResizeController"
nodeOrientation="LEFT_TO_RIGHT"
pickOnBounds="false">
<fx:define>
<Cursor fx:id="nwResize" fx:constant="NW_RESIZE"/>
<Cursor fx:id="neResize" fx:constant="NE_RESIZE"/>
</fx:define>
<Region fx:id="tlResizer" cursor="${nwResize}" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity" AnchorPane.topAnchor="0" AnchorPane.leftAnchor="0"/>
<Region fx:id="trResizer" cursor="${neResize}" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity" AnchorPane.topAnchor="0" AnchorPane.rightAnchor="0"/>
<Region fx:id="blResizer" cursor="${neResize}" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity" AnchorPane.bottomAnchor="0" AnchorPane.leftAnchor="0"/>
<Region fx:id="brResizer" cursor="${nwResize}" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity" AnchorPane.bottomAnchor="0" AnchorPane.rightAnchor="0"/>
</AnchorPane>
@@ -0,0 +1,55 @@
<?import javafx.scene.layout.StackPane?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.HBox?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Button?>
<HBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:id="titleBar"
fx:controller="org.cryptomator.ui.mainwindow.MainWindowTitleController"
styleClass="title"
alignment="CENTER"
minHeight="50"
maxHeight="50"
spacing="6">
<padding>
<Insets bottom="6" left="12" right="12" top="6"/>
</padding>
<children>
<Label text="Cryptomator"/>
<Region HBox.hgrow="ALWAYS"/>
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#showDonationKeyPreferences" focusTraversable="false" visible="${!controller.licenseHolder.validLicense}">
<graphic>
<StackPane>
<FontAwesome5IconView glyph="EXCLAMATION_CIRCLE" glyphSize="16"/>
<Region styleClass="update-indicator" StackPane.alignment="TOP_RIGHT" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity"/>
</StackPane>
</graphic>
<tooltip>
<Tooltip text="%main.donationKeyMissing.tooltip"/>
</tooltip>
</Button>
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#showPreferences" focusTraversable="false">
<graphic>
<StackPane>
<FontAwesome5IconView glyph="COGS" glyphSize="16"/>
<Region styleClass="update-indicator" visible="${controller.updateAvailable}" StackPane.alignment="TOP_RIGHT" prefWidth="10" prefHeight="10" maxWidth="-Infinity" maxHeight="-Infinity"/>
</StackPane>
</graphic>
<tooltip>
<Tooltip text="%main.preferencesBtn.tooltip"/>
</tooltip>
</Button>
<Button contentDisplay="GRAPHIC_ONLY" mnemonicParsing="false" onAction="#close" focusTraversable="false">
<graphic>
<FontAwesome5IconView glyph="TIMES" glyphSize="16"/>
</graphic>
<tooltip>
<Tooltip text="%main.closeBtn.tooltip"/>
</tooltip>
</Button>
</children>
</HBox>
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import org.cryptomator.ui.controls.NiceSecurePasswordField?>
<?import org.cryptomator.ui.controls.PasswordStrengthIndicator?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.common.NewPasswordController"
spacing="6"
alignment="CENTER_LEFT">
<fx:define>
<FontAwesome5IconView fx:id="checkmark" styleClass="glyph-icon-primary" glyph="CHECK"/>
<FontAwesome5IconView fx:id="cross" styleClass="glyph-icon-red" glyph="TIMES"/>
</fx:define>
<children>
<Label text="%newPassword.promptText" labelFor="$passwordField"/>
<NiceSecurePasswordField fx:id="passwordField"/>
<PasswordStrengthIndicator spacing="6" prefHeight="6" strength="${controller.passwordStrength}"/>
<Label fx:id="passwordStrengthLabel" styleClass="label-secondary" alignment="CENTER_RIGHT" maxWidth="Infinity"/>
<Region/>
<Label text="%newPassword.reenterPassword" labelFor="$reenterField"/>
<NiceSecurePasswordField fx:id="reenterField"/>
<Label fx:id="passwordMatchLabel" styleClass="label-secondary" alignment="CENTER_RIGHT" maxWidth="Infinity" graphicTextGap="6" contentDisplay="LEFT" />
</children>
</VBox>
@@ -7,7 +7,10 @@
xmlns:fx="http://javafx.com/fxml"
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>
@@ -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>
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.layout.Region?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.recoverykey.RecoveryKeySuccessController"
minWidth="400"
maxWidth="400"
minHeight="145"
spacing="12"
alignment="TOP_CENTER">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<fx:include source="/fxml/recoverykey_display.fxml"/>
<Region VBox.vgrow="ALWAYS"/>
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
<ButtonBar buttonMinWidth="120" buttonOrder="+C">
<buttons>
<Button text="%generic.button.done" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#close"/>
</buttons>
</ButtonBar>
</VBox>
</children>
</VBox>
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.common.StackTraceController"
minWidth="300"
spacing="12">
<children>
<HBox spacing="12" VBox.vgrow="ALWAYS">
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="EXCLAMATION" glyphSize="24"/>
</StackPane>
<VBox spacing="6" HBox.hgrow="ALWAYS">
<Label text="%generic.error.title" wrapText="true"/>
<Label text="%generic.error.instruction" wrapText="true"/>
</VBox>
</HBox>
<TextArea text="${controller.stackTrace}" prefRowCount="5" editable="false"/>
</children>
</VBox>
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.layout.Region?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.unlock.UnlockGenericErrorController"
minWidth="400"
maxWidth="400"
minHeight="145"
spacing="12">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<fx:include source="/fxml/stacktrace.fxml"/>
<Region VBox.vgrow="ALWAYS"/>
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
<ButtonBar buttonMinWidth="120" buttonOrder="B+U">
<buttons>
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" cancelButton="true" onAction="#back"/>
<Region ButtonBar.buttonData="OTHER"/>
</buttons>
</ButtonBar>
</VBox>
</children>
</VBox>
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Circle?>
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
<?import org.cryptomator.ui.controls.FormattedLabel?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.unlock.UnlockInvalidMountPointController"
minWidth="400"
maxWidth="400"
minHeight="145"
spacing="12">
<padding>
<Insets topRightBottomLeft="12"/>
</padding>
<children>
<HBox spacing="12" VBox.vgrow="ALWAYS">
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
<Circle styleClass="glyph-icon-primary" radius="24"/>
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="EXCLAMATION" glyphSize="24"/>
</StackPane>
<VBox spacing="6" HBox.hgrow="ALWAYS">
<FormattedLabel format="%unlock.error.invalidMountPoint" arg1="${controller.mountPoint}" wrapText="true"/>
</VBox>
</HBox>
<Region VBox.vgrow="ALWAYS"/>
<VBox alignment="BOTTOM_CENTER" VBox.vgrow="ALWAYS">
<ButtonBar buttonMinWidth="120" buttonOrder="B+U">
<buttons>
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" cancelButton="true" onAction="#back"/>
<Region ButtonBar.buttonData="OTHER"/>
</buttons>
</ButtonBar>
</VBox>
</children>
</VBox>
@@ -1,10 +1,10 @@
<?import javafx.scene.layout.HBox?>
<?import 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