Compare commits

...
41 Commits
Author SHA1 Message Date
Sebastian Stenzel 336d67195d Merge branch 'feature/663-refactored-launcher' into develop
fixes #663
2019-02-28 20:52:01 +01:00
Sebastian Stenzel 6677079623 partial revert of dd3c969f, now using Application.launch(MainApp.class) again 2019-02-28 20:46:24 +01:00
Sebastian Stenzel 0974a57671 Merge branch 'develop' into feature/663-refactored-launcher 2019-02-28 20:20:58 +01:00
Tobias Hagemann ab77673fed Fixed IllegalStateException when trying to show graceful shutdown dialog from non-FX thread 2019-02-28 15:38:45 +01:00
Tobias Hagemann a70401596f Updated localizations 2019-02-28 00:36:37 +01:00
Tobias Hagemann ab198271a1 #458, #841: Improved layout of the indicator inside SecPasswordField 2019-02-28 00:16:53 +01:00
Sebastian Stenzel d62edcda73 improved password field: added caps lock indicator (see #458) and a warning + tooltip if control characters are found in the password (fixes #841) 2019-02-27 22:15:01 +01:00
Sebastian Stenzel 8cba58075d fixes #840 2019-02-27 17:23:08 +01:00
Sebastian Stenzel 426f36ce04 fixes #753 2019-02-27 17:22:48 +01:00
Sebastian Stenzel dd3c969f0f FxApplication now part of the Dagger graph
(references #663)
2019-02-26 22:41:33 +01:00
Tobias Hagemann 6a270ceccd Updated dependencies 2019-02-26 13:43:23 +01:00
Sebastian Stenzel a3474e05eb Fixes #835 2019-02-26 12:26:14 +01:00
Sebastian Stenzel dd190b5a16 added vm arg for mount dir 2019-02-26 12:15:35 +01:00
Sebastian Stenzel fe722629be moved vault upgrade classes to custom package with custom log config 2019-02-26 12:15:15 +01:00
Sebastian Stenzel f8e5d8aefb added macOS run configuration 2019-02-25 21:20:12 +01:00
Sebastian Stenzel 3e3a4ceefc allow configuration of custom logback configuration via -Dlogback.configurationFile vm arg 2019-02-25 15:54:43 +01:00
Tobias Hagemann a09edad165 Removed unused terms and updated some texts 2019-02-25 15:42:16 +01:00
Tobias Hagemann 129e9c63f8 Clearer distinction between messageText and progressText in UnlockController 2019-02-25 15:33:54 +01:00
Sebastian StenzelandGitHub 9af58b8e6e Merge pull request #834 from adrian/fix-logging-tests
Fixed Environment logDir tests
2019-02-24 22:32:52 +01:00
Adrian Smith 1048ff5728 Fix DisplayName on testRelativeLogDir 2019-02-24 21:01:04 +00:00
Adrian Smith 6adb591c9a Fix EnvironmentTest.testRelativeLogDir 2019-02-24 20:04:31 +00:00
Sebastian Stenzel d06720838e Logback configuration is now done programmatically, fixes #832 2019-02-23 03:43:06 +01:00
Sebastian Stenzel cf020e5b96 disable UI control tests on headless systems 2019-02-22 16:50:05 +01:00
Sebastian Stenzel 4bfd1e6433 Improved SecPasswordField and added unit tests
SecPasswordFields will now normalize any input to NFC on the fly. Any input typed into the password field will now get converted to NFC on-the-fly. This allows subsequent code to keep working on the CharSequence returned by getCharacters() without the need of additional Normalization. Affects #521
2019-02-22 16:38:39 +01:00
Sebastian Stenzel deded33da8 fixed hard-coded path in log config and refinded logging in settingsprovider 2019-02-21 14:52:35 +01:00
Sebastian Stenzel be5fce0ee9 make sure the directory containing temporary mount points exists 2019-02-21 14:17:44 +01:00
Sebastian Stenzel 39f9da16f9 mount path is now configurable via -Dcryptomator.mountPointsDir and no longer hardcoded to ~/.Cryptomator or ~/Library/Application\ Support/Cryptomator
fixes #710
2019-02-21 14:03:52 +01:00
Sebastian Stenzel 53b0d5cb9f Environment now being injected into WindowsProtectedKeychainAccess 2019-02-21 13:38:30 +01:00
Sebastian Stenzel b9a120b51b internalized logback config, added -Dcryptomator.logDir=path/relative/to/home. external logback configuration can still be used via -Dlogback.configurationFile=/path/to/logback.xml 2019-02-21 11:39:56 +01:00
Sebastian Stenzel debcab47e2 Refactored "file open events" to "app launch events", fixes #55 2019-02-21 00:02:12 +01:00
Sebastian Stenzel 8814372c68 made Settings and DebugMode a Singleton 2019-02-20 23:28:33 +01:00
Sebastian Stenzel 98e5c3ff88 simplified handling of fileOpenRequests 2019-02-20 15:39:00 +01:00
Sebastian Stenzel f1c332f455 Refactored IPC, fixes #663 2019-02-19 16:46:21 +01:00
Sebastian Stenzel 79306ea498 Introduced new scope @FxApplicationScoped below @Singleton.
This allows us to initialize Dagger before we start the JavaFX application, which will in turn allow us to access Environment from within the IPC classes.

Affects #663
2019-02-19 00:11:58 +01:00
Sebastian Stenzel d7dda7d249 Preparations for allowing to specify multiple paths to which Cryptomator writes settings, logs, etc. see #710 2019-02-18 16:53:41 +01:00
Sebastian Stenzel 1e80f4bba4 Upgrade to JUnit 5 2019-02-18 15:28:11 +01:00
Sebastian Stenzel ffd3981f36 Merge branch 'master' into develop
# Conflicts:
#	main/ant-kit/pom.xml
#	main/commons/pom.xml
#	main/keychain/pom.xml
#	main/launcher/pom.xml
#	main/pom.xml
#	main/uber-jar/pom.xml
#	main/ui/pom.xml
2019-02-16 11:41:50 +01:00
Sebastian Stenzel aa23635744 Merge branch 'hotfix/1.4.5' 2019-02-16 11:41:08 +01:00
Sebastian Stenzel 0317e7c21d preparing 1.4.5 2019-02-16 11:40:50 +01:00
Sebastian Stenzel ec5e8bba30 fixes #826 2019-02-16 11:35:27 +01:00
Sebastian Stenzel 0caa9988d3 add version.txt to buildkit 2019-02-15 19:38:21 +01:00
105 changed files with 2233 additions and 1439 deletions
+3
View File
@@ -29,5 +29,8 @@
<module name="ui" />
</profile>
</annotationProcessing>
<bytecodeTargetLevel>
<module name="buildkit" target="11" />
</bytecodeTargetLevel>
</component>
</project>
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Desktop.iml" filepath="$PROJECT_DIR$/.idea/Desktop.iml" />
<module fileurl="file://$PROJECT_DIR$/main/buildkit/buildkit.iml" filepath="$PROJECT_DIR$/main/buildkit/buildkit.iml" />
</modules>
</component>
</project>
+17
View File
@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Cryptomator macOS" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="org.cryptomator.launcher.Cryptomator" />
<module name="launcher" />
<option name="VM_PARAMETERS" value="-Duser.language=en -Dcryptomator.settingsPath=&quot;~/Library/Application Support/Cryptomator/settings.json&quot; -Dcryptomator.ipcPortPath=&quot;~/Library/Application Support/Cryptomator/ipcPort.bin&quot; -Dcryptomator.logDir=&quot;~/Library/Logs/Cryptomator&quot; -Dcryptomator.mountPointsDir=&quot;/Volumes/&quot;" />
<option name="WORKING_DIRECTORY" value="$MAVEN_REPOSITORY$" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="org.cryptomator.launcher.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
+7
View File
@@ -7,6 +7,13 @@
<format>zip</format>
</formats>
<fileSets>
<fileSet>
<directory>target/</directory>
<includes>
<include>version.txt</include>
</includes>
<outputDirectory>libs</outputDirectory>
</fileSet>
<fileSet>
<directory>target/libs</directory>
<includes>
+7
View File
@@ -7,6 +7,13 @@
<format>zip</format>
</formats>
<fileSets>
<fileSet>
<directory>target/</directory>
<includes>
<include>version.txt</include>
</includes>
<outputDirectory>libs</outputDirectory>
</fileSet>
<fileSet>
<directory>target/libs</directory>
<includes>
+7
View File
@@ -7,6 +7,13 @@
<format>zip</format>
</formats>
<fileSets>
<fileSet>
<directory>target/</directory>
<includes>
<include>version.txt</include>
</includes>
<outputDirectory>libs</outputDirectory>
</fileSet>
<fileSet>
<directory>target/libs</directory>
<includes>
+29 -1
View File
@@ -18,8 +18,36 @@
</dependency>
</dependencies>
<build>
<build>
<plugins>
<!-- generate version.txt -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}</outputDirectory>
<resources>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
<includes>
<include>version.txt</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<!-- copy libraries to target/libs/: -->
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
@@ -0,0 +1 @@
${project.version}
@@ -0,0 +1,95 @@
package org.cryptomator.common;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
@Singleton
public class Environment {
private static final Logger LOG = LoggerFactory.getLogger(Environment.class);
private static final String USER_HOME = System.getProperty("user.home");
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 = ':';
@Inject
public Environment() {
LOG.debug("user.language: {}", System.getProperty("user.language"));
LOG.debug("user.region: {}", System.getProperty("user.region"));
LOG.debug("logback.configurationFile: {}", System.getProperty("logback.configurationFile"));
LOG.debug("cryptomator.settingsPath: {}", System.getProperty("cryptomator.settingsPath"));
LOG.debug("cryptomator.ipcPortPath: {}", System.getProperty("cryptomator.ipcPortPath"));
LOG.debug("cryptomator.keychainPath: {}", System.getProperty("cryptomator.keychainPath"));
LOG.debug("cryptomator.logDir: {}", System.getProperty("cryptomator.logDir"));
LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir"));
}
public boolean useCustomLogbackConfig() {
return getPath("logback.configurationFile").map(Files::exists).orElse(false);
}
public Stream<Path> getSettingsPath() {
return getPaths("cryptomator.settingsPath");
}
public Stream<Path> getIpcPortPath() {
return getPaths("cryptomator.ipcPortPath");
}
public Stream<Path> getKeychainPath() {
return getPaths("cryptomator.keychainPath");
}
public Optional<Path> getLogDir() {
return getPath("cryptomator.logDir").map(this::replaceHomeDir);
}
public Optional<Path> getMountPointsDir() {
return getPath("cryptomator.mountPointsDir").map(this::replaceHomeDir);
}
private Optional<Path> getPath(String propertyName) {
String value = System.getProperty(propertyName);
return Optional.ofNullable(value).map(Paths::get);
}
// visible for testing
Stream<Path> getPaths(String propertyName) {
Stream<String> rawSettingsPaths = getRawList(propertyName, PATH_LIST_SEP);
return rawSettingsPaths.filter(Predicate.not(Strings::isNullOrEmpty)).map(Paths::get).map(this::replaceHomeDir);
}
private Path replaceHomeDir(Path path) {
if (path.startsWith(RELATIVE_HOME_DIR)) {
return ABSOLUTE_HOME_DIR.resolve(RELATIVE_HOME_DIR.relativize(path));
} else {
return path;
}
}
private Stream<String> getRawList(String propertyName, char separator) {
String value = System.getProperty(propertyName);
if (value == null) {
return Stream.empty();
} else {
Iterable<String> iter = Splitter.on(separator).split(value);
Spliterator<String> spliter = Spliterators.spliteratorUnknownSize(iter.iterator(), Spliterator.ORDERED | Spliterator.IMMUTABLE);
return StreamSupport.stream(spliter, false);
}
}
}
@@ -0,0 +1,13 @@
package org.cryptomator.common;
import javax.inject.Scope;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface FxApplicationScoped {
}
@@ -19,6 +19,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Optional;
@@ -27,6 +28,7 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
import javax.inject.Inject;
import javax.inject.Provider;
@@ -34,6 +36,7 @@ import javax.inject.Singleton;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Environment;
import org.cryptomator.common.LazyInitializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -45,92 +48,77 @@ import com.google.gson.GsonBuilder;
public class SettingsProvider implements Provider<Settings> {
private static final Logger LOG = LoggerFactory.getLogger(SettingsProvider.class);
private static final Path DEFAULT_SETTINGS_PATH;
private static final long SAVE_DELAY_MS = 1000;
static {
final FileSystem fs = FileSystems.getDefault();
if (SystemUtils.IS_OS_WINDOWS) {
DEFAULT_SETTINGS_PATH = fs.getPath(SystemUtils.USER_HOME, "AppData/Roaming/Cryptomator/settings.json");
} else if (SystemUtils.IS_OS_MAC_OSX) {
DEFAULT_SETTINGS_PATH = fs.getPath(SystemUtils.USER_HOME, "Library/Application Support/Cryptomator/settings.json");
} else {
DEFAULT_SETTINGS_PATH = fs.getPath(SystemUtils.USER_HOME, ".Cryptomator/settings.json");
}
}
private final ScheduledExecutorService saveScheduler = Executors.newSingleThreadScheduledExecutor();
private final AtomicReference<ScheduledFuture<?>> scheduledSaveCmd = new AtomicReference<>();
private final AtomicReference<Settings> settings = new AtomicReference<>();
private final SettingsJsonAdapter settingsJsonAdapter = new SettingsJsonAdapter();
private final Environment env;
private final Gson gson;
@Inject
public SettingsProvider() {
public SettingsProvider(Environment env) {
this.env = env;
this.gson = new GsonBuilder() //
.setPrettyPrinting().setLenient().disableHtmlEscaping() //
.registerTypeAdapter(Settings.class, settingsJsonAdapter) //
.create();
}
private Path getSettingsPath() {
final String settingsPathProperty = System.getProperty("cryptomator.settingsPath");
return Optional.ofNullable(settingsPathProperty).filter(StringUtils::isNotBlank).map(this::replaceHomeDir).map(FileSystems.getDefault()::getPath).orElse(DEFAULT_SETTINGS_PATH);
}
private String replaceHomeDir(String path) {
if (path.startsWith("~/")) {
return SystemUtils.USER_HOME + path.substring(1);
} else {
return path;
}
}
@Override
public Settings get() {
return LazyInitializer.initializeLazily(settings, this::load);
}
private Settings load() {
Settings settings;
final Path settingsPath = getSettingsPath();
try (InputStream in = Files.newInputStream(settingsPath, StandardOpenOption.READ); //
Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
settings = gson.fromJson(reader, Settings.class);
Settings settings = env.getSettingsPath().flatMap(this::tryLoad).findFirst().orElse(new Settings());
settings.setSaveCmd(this::scheduleSave);
return settings;
}
private Stream<Settings> tryLoad(Path path) {
LOG.debug("Attempting to load settings from {}", path);
try (InputStream in = Files.newInputStream(path, StandardOpenOption.READ); //
Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
Settings settings = gson.fromJson(reader, Settings.class);
if (settings == null) {
throw new IOException("Unexpected EOF");
}
LOG.info("Settings loaded from " + settingsPath);
LOG.info("Settings loaded from {}", path);
return Stream.of(settings);
} catch (NoSuchFileException e) {
return Stream.empty();
} catch (IOException e) {
LOG.info("Failed to load settings, creating new one.");
settings = new Settings();
LOG.warn("Exception while loading settings from " + path, e);
return Stream.empty();
}
settings.setSaveCmd(this::scheduleSave);
return settings;
}
private void scheduleSave(Settings settings) {
if (settings == null) {
return;
}
ScheduledFuture<?> saveCmd = saveScheduler.schedule(() -> {
this.save(settings);
}, SAVE_DELAY_MS, TimeUnit.MILLISECONDS);
ScheduledFuture<?> previousSaveCmd = scheduledSaveCmd.getAndSet(saveCmd);
if (previousSaveCmd != null) {
previousSaveCmd.cancel(false);
}
final Optional<Path> settingsPath = env.getSettingsPath().findFirst(); // alway save to preferred (first) path
settingsPath.ifPresent(path -> {
Runnable saveCommand = () -> this.save(settings, path);
ScheduledFuture<?> scheduledTask = saveScheduler.schedule(saveCommand, SAVE_DELAY_MS, TimeUnit.MILLISECONDS);
ScheduledFuture<?> previouslyScheduledTask = scheduledSaveCmd.getAndSet(scheduledTask);
if (previouslyScheduledTask != null) {
previouslyScheduledTask.cancel(false);
}
});
}
private void save(Settings settings) {
private void save(Settings settings, Path settingsPath) {
assert settings != null : "method should only be invoked by #scheduleSave, which checks for null";
final Path settingsPath = getSettingsPath();
LOG.debug("Attempting to save settings to {}", settingsPath);
try {
Files.createDirectories(settingsPath.getParent());
try (OutputStream out = Files.newOutputStream(settingsPath, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); //
Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) {
gson.toJson(settings, writer);
LOG.info("Settings saved to " + settingsPath);
LOG.info("Settings saved to {}", settingsPath);
}
} catch (IOException e) {
LOG.error("Failed to save settings.", e);
@@ -0,0 +1,133 @@
package org.cryptomator.common;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@DisplayName("Environment Variables Test")
class EnvironmentTest {
private Environment env;
@BeforeAll
static void init() {
System.setProperty("user.home", "/home/testuser");
}
@BeforeEach
void initEach() {
env = new Environment();
}
@Test
@DisplayName("cryptomator.settingsPath=~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json")
public void testSettingsPath() {
System.setProperty("cryptomator.settingsPath", "~/.config/Cryptomator/settings.json:~/.Cryptomator/settings.json");
List<Path> result = env.getSettingsPath().collect(Collectors.toList());
MatcherAssert.assertThat(result, Matchers.hasSize(2));
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/settings.json"),
Paths.get("/home/testuser/.Cryptomator/settings.json")));
}
@Test
@DisplayName("cryptomator.ipcPortPath=~/.config/Cryptomator/ipcPort.bin:~/.Cryptomator/ipcPort.bin")
public void testIpcPortPath() {
System.setProperty("cryptomator.ipcPortPath", "~/.config/Cryptomator/ipcPort.bin:~/.Cryptomator/ipcPort.bin");
List<Path> result = env.getIpcPortPath().collect(Collectors.toList());
MatcherAssert.assertThat(result, Matchers.hasSize(2));
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/.config/Cryptomator/ipcPort.bin"),
Paths.get("/home/testuser/.Cryptomator/ipcPort.bin")));
}
@Test
@DisplayName("cryptomator.keychainPath=~/AppData/Roaming/Cryptomator/keychain.json")
public void testKeychainPath() {
System.setProperty("cryptomator.keychainPath", "~/AppData/Roaming/Cryptomator/keychain.json");
List<Path> result = env.getKeychainPath().collect(Collectors.toList());
MatcherAssert.assertThat(result, Matchers.hasSize(1));
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/AppData/Roaming/Cryptomator/keychain.json")));
}
@Test
@DisplayName("cryptomator.logDir=/foo/bar")
public void testAbsoluteLogDir() {
System.setProperty("cryptomator.logDir", "/foo/bar");
Optional<Path> logDir = env.getLogDir();
Assertions.assertTrue(logDir.isPresent());
}
@Test
@DisplayName("cryptomator.logDir=~/foo/bar")
public void testRelativeLogDir() {
System.setProperty("cryptomator.logDir", "~/foo/bar");
Optional<Path> logDir = env.getLogDir();
Assertions.assertTrue(logDir.isPresent());
Assertions.assertEquals(Paths.get("/home/testuser/foo/bar"), logDir.get());
}
@Nested
@DisplayName("Path Lists")
class SettingsPath {
@Test
@DisplayName("test.path.property=")
public void testEmptyList() {
System.setProperty("test.path.property", "");
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
MatcherAssert.assertThat(result, Matchers.hasSize(0));
}
@Test
@DisplayName("test.path.property=/foo/bar/test")
public void testSingleAbsolutePath() {
System.setProperty("test.path.property", "/foo/bar/test");
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
MatcherAssert.assertThat(result, Matchers.hasSize(1));
MatcherAssert.assertThat(result, Matchers.hasItem(Paths.get("/foo/bar/test")));
}
@Test
@DisplayName("test.path.property=~/test")
public void testSingleHomeRelativePath() {
System.setProperty("test.path.property", "~/test");
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
MatcherAssert.assertThat(result, Matchers.hasSize(1));
MatcherAssert.assertThat(result, Matchers.hasItem(Paths.get("/home/testuser/test")));
}
@Test
@DisplayName("test.path.property=~/test:~/test2:/foo/bar/test")
public void testMultiplePaths() {
System.setProperty("test.path.property", "~/test:~/test2:/foo/bar/test");
List<Path> result = env.getPaths("test.path.property").collect(Collectors.toList());
MatcherAssert.assertThat(result, Matchers.hasSize(3));
MatcherAssert.assertThat(result, Matchers.contains(Paths.get("/home/testuser/test"),
Paths.get("/home/testuser/test2"),
Paths.get("/foo/bar/test")));
}
}
}
@@ -8,10 +8,10 @@
*******************************************************************************/
package org.cryptomator.common;
import java.util.Comparator;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.Assert;
import org.junit.Test;
import java.util.Comparator;
public class SemVerComparatorTest {
@@ -21,38 +21,38 @@ public class SemVerComparatorTest {
@Test
public void compareEqualVersions() {
Assert.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4")));
Assert.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-alpha")));
Assert.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4+20170101", "1.23.4+20171231")));
Assert.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4-alpha+20170101", "1.23.4-alpha+20171231")));
Assertions.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4")));
Assertions.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-alpha")));
Assertions.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4+20170101", "1.23.4+20171231")));
Assertions.assertEquals(0, Integer.signum(semVerComparator.compare("1.23.4-alpha+20170101", "1.23.4-alpha+20171231")));
}
// newer versions in first argument
@Test
public void compareHigherToLowerVersions() {
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.5", "1.23.4")));
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.24.4", "1.23.4")));
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23")));
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4-SNAPSHOT")));
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4-56.78")));
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-beta", "1.23.4-alpha")));
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-alpha.1", "1.23.4-alpha")));
Assert.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-56.79", "1.23.4-56.78")));
Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.5", "1.23.4")));
Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.24.4", "1.23.4")));
Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23")));
Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4-SNAPSHOT")));
Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.4-56.78")));
Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-beta", "1.23.4-alpha")));
Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-alpha.1", "1.23.4-alpha")));
Assertions.assertEquals(1, Integer.signum(semVerComparator.compare("1.23.4-56.79", "1.23.4-56.78")));
}
// newer versions in second argument
@Test
public void compareLowerToHigherVersions() {
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.5")));
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.24.4")));
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23", "1.23.4")));
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-SNAPSHOT", "1.23.4")));
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-56.78", "1.23.4")));
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-beta")));
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-alpha.1")));
Assert.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-56.78", "1.23.4-56.79")));
Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.23.5")));
Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4", "1.24.4")));
Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23", "1.23.4")));
Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-SNAPSHOT", "1.23.4")));
Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-56.78", "1.23.4")));
Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-beta")));
Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-alpha", "1.23.4-alpha.1")));
Assertions.assertEquals(-1, Integer.signum(semVerComparator.compare("1.23.4-56.78", "1.23.4-56.79")));
}
}
@@ -5,10 +5,10 @@
*******************************************************************************/
package org.cryptomator.common.settings;
import java.io.IOException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
public class SettingsJsonAdapterTest {
@@ -26,12 +26,12 @@ public class SettingsJsonAdapterTest {
Settings settings = adapter.fromJson(json);
Assert.assertTrue(settings.checkForUpdates().get());
Assert.assertEquals(2, settings.getDirectories().size());
Assert.assertEquals(8080, settings.port().get());
Assert.assertEquals(42, settings.numTrayNotifications().get());
Assert.assertEquals("dav", settings.preferredGvfsScheme().get());
Assert.assertEquals(VolumeImpl.FUSE, settings.preferredVolumeImpl().get());
Assertions.assertTrue(settings.checkForUpdates().get());
Assertions.assertEquals(2, settings.getDirectories().size());
Assertions.assertEquals(8080, settings.port().get());
Assertions.assertEquals(42, settings.numTrayNotifications().get());
Assertions.assertEquals("dav", settings.preferredGvfsScheme().get());
Assertions.assertEquals(VolumeImpl.FUSE, settings.preferredVolumeImpl().get());
}
}
@@ -5,12 +5,12 @@
*******************************************************************************/
package org.cryptomator.common.settings;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.io.IOException;
import java.util.function.Consumer;
import org.junit.Test;
import org.mockito.Mockito;
public class SettingsTest {
@Test
@@ -5,15 +5,14 @@
*******************************************************************************/
package org.cryptomator.common.settings;
import com.google.gson.stream.JsonReader;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.StringReader;
import java.nio.file.Paths;
import org.junit.Assert;
import org.junit.Test;
import com.google.gson.stream.JsonReader;
public class VaultSettingsJsonAdapterTest {
private final VaultSettingsJsonAdapter adapter = new VaultSettingsJsonAdapter();
@@ -24,11 +23,11 @@ public class VaultSettingsJsonAdapterTest {
JsonReader jsonReader = new JsonReader(new StringReader(json));
VaultSettings vaultSettings = adapter.read(jsonReader);
Assert.assertEquals("foo", vaultSettings.getId());
Assert.assertEquals(Paths.get("/foo/bar"), vaultSettings.path().get());
Assert.assertEquals("test", vaultSettings.mountName().get());
Assert.assertEquals("X", vaultSettings.winDriveLetter().get());
Assert.assertEquals("/home/test/crypto", vaultSettings.individualMountPath().get());
Assertions.assertEquals("foo", vaultSettings.getId());
Assertions.assertEquals(Paths.get("/foo/bar"), vaultSettings.path().get());
Assertions.assertEquals("test", vaultSettings.mountName().get());
Assertions.assertEquals("X", vaultSettings.winDriveLetter().get());
Assertions.assertEquals("/home/test/crypto", vaultSettings.individualMountPath().get());
}
}
@@ -8,9 +8,9 @@
*******************************************************************************/
package org.cryptomator.common.settings;
import static org.junit.Assert.assertEquals;
import org.junit.jupiter.api.Test;
import org.junit.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class VaultSettingsTest {
+5
View File
@@ -10,6 +10,11 @@
<name>System Keychain Access</name>
<dependencies>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>commons</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
@@ -5,30 +5,6 @@
*******************************************************************************/
package org.cryptomator.keychain;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.io.Writer;
import java.lang.reflect.Type;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import javax.inject.Inject;
import com.google.common.io.BaseEncoding;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -42,11 +18,36 @@ import com.google.gson.JsonSerializer;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Environment;
import org.cryptomator.jni.WinDataProtection;
import org.cryptomator.jni.WinFunctions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.io.Writer;
import java.lang.reflect.Type;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import static java.nio.charset.StandardCharsets.UTF_8;
class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
@@ -57,24 +58,13 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
.disableHtmlEscaping().create();
private final Optional<WinFunctions> winFunctions;
private final Path keychainPath;
private final List<Path> keychainPaths;
private Map<String, KeychainEntry> keychainEntries;
@Inject
public WindowsProtectedKeychainAccess(Optional<WinFunctions> winFunctions) {
public WindowsProtectedKeychainAccess(Optional<WinFunctions> winFunctions, Environment environment) {
this.winFunctions = winFunctions;
String keychainPathProperty = System.getProperty("cryptomator.keychainPath");
if (keychainPathProperty == null) {
LOG.warn("Windows DataProtection module loaded, but no cryptomator.keychainPath property found.");
}
if (keychainPathProperty != null) {
if (keychainPathProperty.startsWith("~/")) {
keychainPathProperty = SystemUtils.USER_HOME + keychainPathProperty.substring(1);
}
this.keychainPath = FileSystems.getDefault().getPath(keychainPathProperty);
} else {
this.keychainPath = null;
}
this.keychainPaths = environment.getKeychainPath().collect(Collectors.toList());
}
private WinDataProtection dataProtection() {
@@ -124,7 +114,7 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
@Override
public boolean isSupported() {
return SystemUtils.IS_OS_WINDOWS && winFunctions.isPresent() && keychainPath != null;
return SystemUtils.IS_OS_WINDOWS && winFunctions.isPresent() && !keychainPaths.isEmpty();
}
private byte[] generateSalt() {
@@ -138,30 +128,44 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
private void loadKeychainEntriesIfNeeded() {
if (keychainEntries == null) {
loadKeychainEntries();
}
assert keychainEntries != null;
}
private void loadKeychainEntries() {
Type type = new TypeToken<Map<String, KeychainEntry>>() {
}.getType();
try (InputStream in = Files.newInputStream(keychainPath, StandardOpenOption.READ); //
Reader reader = new InputStreamReader(in, UTF_8)) {
keychainEntries = GSON.fromJson(reader, type);
} catch (JsonParseException | NoSuchFileException e) {
LOG.info("Creating new keychain at path {}", keychainPath);
} catch (IOException e) {
throw new UncheckedIOException("Could not read keychain from path " + keychainPath, e);
for (Path keychainPath : keychainPaths) {
Optional<Map<String, KeychainEntry>> keychain = loadKeychainEntries(keychainPath);
if (keychain.isPresent()) {
keychainEntries = keychain.get();
break;
}
}
}
if (keychainEntries == null) {
LOG.info("Unable to load existing keychain file, creating new keychain.");
keychainEntries = new HashMap<>();
}
}
private Optional<Map<String, KeychainEntry>> loadKeychainEntries(Path keychainPath) {
LOG.debug("Attempting to load keychain from {}", keychainPath);
Type type = new TypeToken<Map<String, KeychainEntry>>() {
}.getType();
try (InputStream in = Files.newInputStream(keychainPath, StandardOpenOption.READ); //
Reader reader = new InputStreamReader(in, UTF_8)) {
return Optional.of(GSON.fromJson(reader, type));
} catch (NoSuchFileException | JsonParseException e) {
return Optional.empty();
} catch (IOException e) {
throw new UncheckedIOException("Could not read keychain from path " + keychainPath, e);
}
}
private void saveKeychainEntries() {
if (keychainPaths.isEmpty()) {
throw new IllegalStateException("Can't save keychain if no keychain path is specified.");
}
saveKeychainEntries(keychainPaths.get(0));
}
private void saveKeychainEntries(Path keychainPath) {
try (OutputStream out = Files.newOutputStream(keychainPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); //
Writer writer = new OutputStreamWriter(out, UTF_8)) {
Writer writer = new OutputStreamWriter(out, UTF_8)) {
GSON.toJson(keychainEntries, writer);
} catch (IOException e) {
throw new UncheckedIOException("Could not read keychain from path " + keychainPath, e);
@@ -169,6 +173,7 @@ class WindowsProtectedKeychainAccess implements KeychainAccessStrategy {
}
private static class KeychainEntry {
@SerializedName("ciphertext")
byte[] ciphertext;
@SerializedName("salt")
@@ -5,20 +5,20 @@
*******************************************************************************/
package org.cryptomator.keychain;
import java.util.Optional;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.Assert;
import org.junit.Test;
import java.util.Optional;
public class KeychainModuleTest {
@Test
public void testGetKeychain() {
Optional<KeychainAccess> keychainAccess = DaggerTestKeychainComponent.builder().keychainModule(new TestKeychainModule()).build().keychainAccess();
Assert.assertTrue(keychainAccess.isPresent());
Assert.assertTrue(keychainAccess.get() instanceof MapKeychainAccess);
Assertions.assertTrue(keychainAccess.isPresent());
Assertions.assertTrue(keychainAccess.get() instanceof MapKeychainAccess);
keychainAccess.get().storePassphrase("test", "asd");
Assert.assertArrayEquals("asd".toCharArray(), keychainAccess.get().loadPassphrase("test"));
Assertions.assertArrayEquals("asd".toCharArray(), keychainAccess.get().loadPassphrase("test"));
}
}
@@ -5,11 +5,10 @@
*******************************************************************************/
package org.cryptomator.keychain;
import java.util.Optional;
import dagger.Component;
import javax.inject.Singleton;
import dagger.Component;
import java.util.Optional;
@Singleton
@Component(modules = KeychainModule.class)
@@ -5,61 +5,54 @@
*******************************************************************************/
package org.cryptomator.keychain;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import org.cryptomator.common.Environment;
import org.cryptomator.jni.WinDataProtection;
import org.cryptomator.jni.WinFunctions;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mockito;
import org.mockito.stubbing.Answer;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
import java.util.stream.Stream;
public class WindowsProtectedKeychainAccessTest {
@Rule
public final ExpectedException thrown = ExpectedException.none();
private Path tmpFile;
private Path keychainPath;
private WindowsProtectedKeychainAccess keychain;
@Before
public void setup() throws IOException, ReflectiveOperationException {
tmpFile = Files.createTempFile("unit-tests", ".tmp");
System.setProperty("cryptomator.keychainPath", tmpFile.toAbsolutePath().normalize().toString());
@BeforeEach
public void setup(@TempDir Path tempDir) throws IOException {
keychainPath = tempDir.resolve("keychainfile.tmp");
Environment env = Mockito.mock(Environment.class);
Mockito.when(env.getKeychainPath()).thenReturn(Stream.of(keychainPath));
WinFunctions winFunctions = Mockito.mock(WinFunctions.class);
WinDataProtection winDataProtection = Mockito.mock(WinDataProtection.class);
Mockito.when(winFunctions.dataProtection()).thenReturn(winDataProtection);
Answer<byte[]> answerReturningFirstArg = invocation -> ((byte[]) invocation.getArgument(0)).clone();
Mockito.when(winDataProtection.protect(Mockito.any(), Mockito.any())).thenAnswer(answerReturningFirstArg);
Mockito.when(winDataProtection.unprotect(Mockito.any(), Mockito.any())).thenAnswer(answerReturningFirstArg);
keychain = new WindowsProtectedKeychainAccess(Optional.of(winFunctions));
}
@After
public void teardown() throws IOException {
Files.deleteIfExists(tmpFile);
keychain = new WindowsProtectedKeychainAccess(Optional.of(winFunctions), env);
}
@Test
public void testStoreAndLoad() {
public void testStoreAndLoad() throws IOException {
String storedPw1 = "topSecret";
String storedPw2 = "bottomSecret";
keychain.storePassphrase("myPassword", storedPw1);
keychain.storePassphrase("myOtherPassword", storedPw2);
String loadedPw1 = new String(keychain.loadPassphrase("myPassword"));
String loadedPw2 = new String(keychain.loadPassphrase("myOtherPassword"));
Assert.assertEquals(storedPw1, loadedPw1);
Assert.assertEquals(storedPw2, loadedPw2);
Assertions.assertEquals(storedPw1, loadedPw1);
Assertions.assertEquals(storedPw2, loadedPw2);
keychain.deletePassphrase("myPassword");
Assert.assertNull(keychain.loadPassphrase("myPassword"));
Assert.assertNull(keychain.loadPassphrase("nonExistingPassword"));
Assertions.assertNull(keychain.loadPassphrase("myPassword"));
Assertions.assertNotNull(keychain.loadPassphrase("myOtherPassword"));
Assertions.assertNull(keychain.loadPassphrase("nonExistingPassword"));
}
}
@@ -1,20 +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.Optional;
public class ApplicationVersion {
public static String orElse(String other) {
return get().orElse(other);
}
public static Optional<String> get() {
return Optional.ofNullable(Cryptomator.class.getPackage().getImplementationVersion());
}
}
@@ -5,57 +5,127 @@
*******************************************************************************/
package org.cryptomator.launcher;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.logging.DebugMode;
import org.cryptomator.logging.LoggerConfiguration;
import org.cryptomator.ui.controllers.MainController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javafx.application.Application;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
@Singleton
public class Cryptomator {
// DaggerCryptomatorComponent gets generated by Dagger.
// Run Maven and include target/generated-sources/annotations in your IDE.
private static final CryptomatorComponent CRYPTOMATOR_COMPONENT = DaggerCryptomatorComponent.create();
private static final Logger LOG = LoggerFactory.getLogger(Cryptomator.class);
static final BlockingQueue<Path> FILE_OPEN_REQUESTS = new ArrayBlockingQueue<>(10);
public static void main(String[] args) {
LOG.info("Starting Cryptomator {} on {} {} ({})", ApplicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
private final LoggerConfiguration logConfig;
private final DebugMode debugMode;
private final IpcFactory ipcFactory;
private final Optional<String> applicationVersion;
FileOpenRequestHandler fileOpenRequestHandler = new FileOpenRequestHandler(FILE_OPEN_REQUESTS);
try (InterProcessCommunicator communicator = InterProcessCommunicator.start(new IpcProtocolImpl(fileOpenRequestHandler))) {
if (communicator.isServer()) {
fileOpenRequestHandler.handleLaunchArgs(args);
CleanShutdownPerformer.registerShutdownHook();
Application.launch(MainApplication.class, args);
} else {
communicator.handleLaunchArgs(args);
LOG.info("Found running application instance. Shutting down.");
}
} catch (IOException e) {
LOG.error("Failed to initiate inter-process communication.", e);
} catch (Throwable e) {
LOG.error("Error during startup", e);
}
System.exit(0); // end remaining non-daemon threads.
@Inject
Cryptomator(LoggerConfiguration logConfig, DebugMode debugMode, IpcFactory ipcFactory, @Named("applicationVersion") Optional<String> applicationVersion) {
this.logConfig = logConfig;
this.debugMode = debugMode;
this.ipcFactory = ipcFactory;
this.applicationVersion = applicationVersion;
}
private static class IpcProtocolImpl implements InterProcessCommunicationProtocol {
public static void main(String[] args) {
int exitCode = CRYPTOMATOR_COMPONENT.application().run(args);
System.exit(exitCode); // end remaining non-daemon threads.
}
private final FileOpenRequestHandler fileOpenRequestHandler;
/**
* Main entry point of the application launcher.
* @param args The arguments passed to this program via {@link #main(String[])}.
* @return Nonzero exit code in case of an error.
*/
private int run(String[] args) {
logConfig.init();
LOG.info("Starting Cryptomator {} on {} {} ({})", applicationVersion.orElse("SNAPSHOT"), SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
// TODO: inject?
public IpcProtocolImpl(FileOpenRequestHandler fileOpenRequestHandler) {
this.fileOpenRequestHandler = fileOpenRequestHandler;
if (sendArgsToRunningInstance(args)) {
LOG.info("Found running application instance. Shutting down...");
return 0;
}
try {
runGuiApplication();
LOG.info("Shutting down...");
return 0;
} catch (Throwable e) {
LOG.error("Terminating due to error", e);
return 1;
}
}
/**
* Attempts to create an IPC connection to a running Cryptomator instance and sends it the given args.
* If no external process could be reached, the args will be handled by the loopback IPC endpoint.
*
* @param args Arguments to send to the instance (if possible)
* @return <code>true</code> if a different process could be reached, <code>false</code> otherwise.
*/
private boolean sendArgsToRunningInstance(String[] args) {
try (IpcFactory.IpcEndpoint endpoint = ipcFactory.create()) {
endpoint.getRemote().handleLaunchArgs(args); // if we are the server, getRemote() returns self.
return endpoint.isConnectedToRemote();
} catch (IOException e) {
LOG.error("Failed to initiate inter-process communication.", e);
return false;
}
}
/**
* Launches the JavaFX application and waits until shutdown is requested.
*/
private void runGuiApplication() {
debugMode.initialize();
CleanShutdownPerformer.registerShutdownHook();
Application.launch(MainApp.class);
// Platform.startup(() -> {
// assert Platform.isFxApplicationThread();
// FxApplication app = CRYPTOMATOR_COMPONENT.fxApplicationComponent().application();
// app.start();
// });
}
// We need a separate FX Application class, until we can use the module system. See https://stackoverflow.com/q/54756176/4014509
public static class MainApp extends Application {
@Override
public void start(Stage primaryStage) {
LOG.info("JavaFX application started.");
primaryStage.setMinWidth(652.0);
primaryStage.setMinHeight(440.0);
FxApplicationComponent fxApplicationComponent = CRYPTOMATOR_COMPONENT.fxApplicationComponent() //
.fxApplication(this) //
.mainWindow(primaryStage) //
.build();
MainController mainCtrl = fxApplicationComponent.fxmlLoader().load("/fxml/main.fxml");
mainCtrl.initStage(primaryStage);
primaryStage.show();
}
@Override
public void handleLaunchArgs(String[] args) {
LOG.info("Received launch args: {}", Arrays.stream(args).reduce((a, b) -> a + ", " + b).orElse(""));
fileOpenRequestHandler.handleLaunchArgs(args);
public void stop() {
LOG.info("JavaFX application stopped.");
}
}
@@ -0,0 +1,21 @@
package org.cryptomator.launcher;
import dagger.Component;
import org.cryptomator.common.CommonsModule;
import org.cryptomator.common.Environment;
import org.cryptomator.logging.DebugMode;
import org.cryptomator.logging.LoggerModule;
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.Optional;
@Singleton
@Component(modules = {CryptomatorModule.class, CommonsModule.class, LoggerModule.class})
public interface CryptomatorComponent {
Cryptomator application();
FxApplicationComponent.Builder fxApplicationComponent();
}
@@ -0,0 +1,39 @@
package org.cryptomator.launcher;
import dagger.Module;
import dagger.Provides;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.SettingsProvider;
import org.cryptomator.ui.model.AppLaunchEvent;
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.Optional;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
@Module
class CryptomatorModule {
@Provides
@Singleton
static Settings provideSettings(SettingsProvider settingsProvider) {
return settingsProvider.get();
}
@Provides
@Singleton
@Named("launchEventQueue")
static BlockingQueue<AppLaunchEvent> provideFileOpenRequests() {
return new ArrayBlockingQueue<>(10);
}
@Provides
@Singleton
@Named("applicationVersion")
static Optional<String> provideApplicationVersion() {
return Optional.ofNullable(Cryptomator.class.getPackage().getImplementationVersion());
}
}
@@ -7,51 +7,71 @@
package org.cryptomator.launcher;
import java.awt.Desktop;
import java.awt.desktop.OpenFilesEvent;
import java.awt.desktop.QuitStrategy;
import java.io.File;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.function.Function;
import java.util.stream.Stream;
import org.cryptomator.ui.model.AppLaunchEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
@Singleton
class FileOpenRequestHandler {
private static final Logger LOG = LoggerFactory.getLogger(FileOpenRequestHandler.class);
private final BlockingQueue<Path> fileOpenRequests;
private final BlockingQueue<AppLaunchEvent> launchEventQueue;
public FileOpenRequestHandler(BlockingQueue<Path> fileOpenRequests) {
this.fileOpenRequests = fileOpenRequests;
@Inject
public FileOpenRequestHandler(@Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue) {
this.launchEventQueue = launchEventQueue;
try {
Desktop.getDesktop().setOpenFileHandler(e -> {
e.getFiles().stream().map(File::toPath).forEach(fileOpenRequests::add);
});
Desktop.getDesktop().setOpenFileHandler(this::openFiles);
} catch (UnsupportedOperationException e) {
LOG.info("Unable to setOpenFileHandler, probably not supported on this OS.");
}
}
private void openFiles(final OpenFilesEvent evt) {
Stream<Path> pathsToOpen = evt.getFiles().stream().map(File::toPath);
AppLaunchEvent launchEvent = new AppLaunchEvent(pathsToOpen);
tryToEnqueueFileOpenRequest(launchEvent);
}
public void handleLaunchArgs(String[] args) {
handleLaunchArgs(FileSystems.getDefault(), args);
}
// visible for testing
void handleLaunchArgs(FileSystem fs, String[] args) {
for (String arg : args) {
Stream<Path> pathsToOpen = Arrays.stream(args).map(str -> {
try {
Path path = fs.getPath(arg);
tryToEnqueueFileOpenRequest(path);
return fs.getPath(str);
} catch (InvalidPathException e) {
LOG.trace("{} not a valid path", arg);
LOG.trace("Argument not a valid path: {}", str);
return null;
}
}
}).filter(Objects::nonNull);
AppLaunchEvent launchEvent = new AppLaunchEvent(pathsToOpen);
tryToEnqueueFileOpenRequest(launchEvent);
}
private void tryToEnqueueFileOpenRequest(Path path) {
if (!fileOpenRequests.offer(path)) {
LOG.warn("{} could not be enqueued for opening.", path);
private void tryToEnqueueFileOpenRequest(AppLaunchEvent launchEvent) {
if (!launchEventQueue.offer(launchEvent)) {
LOG.warn("Could not enqueue application launch event.", launchEvent);
}
}
@@ -5,19 +5,33 @@
*******************************************************************************/
package org.cryptomator.launcher;
import javax.inject.Singleton;
import dagger.BindsInstance;
import dagger.Subcomponent;
import javafx.application.Application;
import javafx.stage.Stage;
import org.cryptomator.common.FxApplicationScoped;
import org.cryptomator.logging.DebugMode;
import org.cryptomator.ui.controllers.ViewControllerLoader;
import dagger.Component;
import javax.inject.Named;
@Singleton
@Component(modules = LauncherModule.class)
interface LauncherComponent {
@FxApplicationScoped
@Subcomponent(modules = FxApplicationModule.class)
interface FxApplicationComponent {
ViewControllerLoader fxmlLoader();
DebugMode debugMode();
@Subcomponent.Builder
interface Builder {
@BindsInstance
Builder fxApplication(Application application);
@BindsInstance
Builder mainWindow(@Named("mainWindow") Stage mainWindow);
FxApplicationComponent build();
}
}
@@ -0,0 +1,26 @@
/*******************************************************************************
* 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 dagger.Module;
import dagger.Provides;
import org.cryptomator.common.FxApplicationScoped;
import org.cryptomator.ui.UiModule;
import javax.inject.Named;
import java.util.function.Consumer;
@Module(includes = {UiModule.class})
class FxApplicationModule {
@Provides
@FxApplicationScoped
@Named("shutdownTaskScheduler")
Consumer<Runnable> provideShutdownTaskScheduler() {
return CleanShutdownPerformer::scheduleShutdownTask;
}
}
@@ -1,274 +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.io.Closeable;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.rmi.ConnectException;
import java.rmi.ConnectIOException;
import java.rmi.NotBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.rmi.server.RMISocketFactory;
import java.rmi.server.UnicastRemoteObject;
import org.apache.commons.lang3.SystemUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.io.MoreFiles;
/**
* First running application on a machine opens a server socket. Further processes will connect as clients.
*/
abstract class InterProcessCommunicator implements InterProcessCommunicationProtocol, Closeable {
private static final Logger LOG = LoggerFactory.getLogger(InterProcessCommunicator.class);
private static final String RMI_NAME = "Cryptomator";
public abstract boolean isServer();
/**
* @param endpoint The server-side communication endpoint.
* @return Either a client or a server communicator.
* @throws IOException In case of communication errors.
*/
public static InterProcessCommunicator start(InterProcessCommunicationProtocol endpoint) throws IOException {
return start(getIpcPortPath(), endpoint);
}
// visible for testing
static InterProcessCommunicator start(Path portFilePath, InterProcessCommunicationProtocol endpoint) throws IOException {
System.setProperty("java.rmi.server.hostname", "localhost");
try {
// try to connect to existing server:
ClientCommunicator client = new ClientCommunicator(portFilePath);
LOG.trace("Connected to running process.");
return client;
} catch (ConnectException | ConnectIOException | NotBoundException e) {
LOG.debug("Could not connect to running process.");
// continue
}
// spawn a new server:
LOG.trace("Spawning new server...");
ServerCommunicator server = new ServerCommunicator(endpoint, portFilePath);
LOG.debug("Server listening on port {}.", server.getPort());
return server;
}
private static Path getIpcPortPath() {
final String settingsPathProperty = System.getProperty("cryptomator.ipcPortPath");
if (settingsPathProperty == null) {
LOG.warn("System property cryptomator.ipcPortPath not set.");
return Paths.get(".ipcPort.tmp");
} else {
return Paths.get(replaceHomeDir(settingsPathProperty));
}
}
private static String replaceHomeDir(String path) {
if (path.startsWith("~/")) {
return SystemUtils.USER_HOME + path.substring(1);
} else {
return path;
}
}
public static class ClientCommunicator extends InterProcessCommunicator {
private final IpcProtocolRemote remote;
private ClientCommunicator(Path portFilePath) throws ConnectException, NotBoundException, RemoteException {
if (Files.notExists(portFilePath)) {
throw new ConnectException("No IPC port file.");
}
try {
int port = ClientCommunicator.readPort(portFilePath);
LOG.debug("Connecting to port {}...", port);
Registry registry = LocateRegistry.getRegistry("localhost", port, new ClientSocketFactory());
this.remote = (IpcProtocolRemote) registry.lookup(RMI_NAME);
} catch (IOException e) {
throw new ConnectException("Error reading IPC port file.");
}
}
private static int readPort(Path portFilePath) throws IOException {
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
try (ReadableByteChannel ch = Files.newByteChannel(portFilePath, StandardOpenOption.READ)) {
if (ch.read(buf) == Integer.BYTES) {
buf.flip();
return buf.getInt();
} else {
throw new IOException("Invalid IPC port file.");
}
}
}
@Override
public void handleLaunchArgs(String[] args) {
try {
remote.handleLaunchArgs(args);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
@Override
public boolean isServer() {
return false;
}
@Override
public void close() {
// no-op
}
}
public static class ServerCommunicator extends InterProcessCommunicator {
private final ServerSocket socket;
private final Registry registry;
private final IpcProtocolRemoteImpl remote;
private final Path portFilePath;
private ServerCommunicator(InterProcessCommunicationProtocol delegate, Path portFilePath) throws IOException {
this.socket = new ServerSocket(0, Byte.MAX_VALUE, InetAddress.getByName("localhost"));
RMIClientSocketFactory csf = RMISocketFactory.getDefaultSocketFactory();
SingletonServerSocketFactory ssf = new SingletonServerSocketFactory(socket);
this.registry = LocateRegistry.createRegistry(0, csf, ssf);
this.remote = new IpcProtocolRemoteImpl(delegate);
UnicastRemoteObject.exportObject(remote, 0);
registry.rebind(RMI_NAME, remote);
this.portFilePath = portFilePath;
ServerCommunicator.writePort(portFilePath, socket.getLocalPort());
}
private static void writePort(Path portFilePath, int port) throws IOException {
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
buf.putInt(port);
buf.flip();
MoreFiles.createParentDirectories(portFilePath);
try (WritableByteChannel ch = Files.newByteChannel(portFilePath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
if (ch.write(buf) != Integer.BYTES) {
throw new IOException("Did not write expected number of bytes.");
}
}
}
@Override
public void handleLaunchArgs(String[] args) {
throw new UnsupportedOperationException("Server doesn't invoke methods.");
}
@Override
public boolean isServer() {
return true;
}
private int getPort() {
return socket.getLocalPort();
}
@Override
public void close() {
try {
registry.unbind(RMI_NAME);
UnicastRemoteObject.unexportObject(remote, true);
socket.close();
Files.deleteIfExists(portFilePath);
LOG.debug("Server shut down.");
} catch (NotBoundException | IOException e) {
LOG.warn("Failed to close IPC Server.", e);
}
}
}
private static interface IpcProtocolRemote extends Remote {
void handleLaunchArgs(String[] args) throws RemoteException;
}
private static class IpcProtocolRemoteImpl implements IpcProtocolRemote {
private final InterProcessCommunicationProtocol delegate;
protected IpcProtocolRemoteImpl(InterProcessCommunicationProtocol delegate) throws RemoteException {
this.delegate = delegate;
}
@Override
public void handleLaunchArgs(String[] args) {
delegate.handleLaunchArgs(args);
}
}
/**
* Always returns the same pre-constructed server socket.
*/
private static class SingletonServerSocketFactory implements RMIServerSocketFactory {
private final ServerSocket socket;
public SingletonServerSocketFactory(ServerSocket socket) {
this.socket = socket;
}
@Override
public synchronized ServerSocket createServerSocket(int port) throws IOException {
if (port != 0) {
throw new IllegalArgumentException("This factory doesn't support specific ports.");
}
return this.socket;
}
}
/**
* Creates client sockets with short timeouts.
*/
private static class ClientSocketFactory implements RMIClientSocketFactory {
@Override
public Socket createSocket(String host, int port) throws IOException {
return new SocketWithFixedTimeout(host, port, 1000);
}
}
private static class SocketWithFixedTimeout extends Socket {
public SocketWithFixedTimeout(String host, int port, int timeoutInMs) throws UnknownHostException, IOException {
super(host, port);
super.setSoTimeout(timeoutInMs);
}
@Override
public synchronized void setSoTimeout(int timeout) throws SocketException {
// do nothing, timeout is fixed
}
}
}
@@ -0,0 +1,258 @@
/*******************************************************************************
* 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 com.google.common.io.MoreFiles;
import org.cryptomator.common.Environment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.rmi.NotBoundException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.rmi.server.RMISocketFactory;
import java.rmi.server.UnicastRemoteObject;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* First running application on a machine opens a server socket. Further processes will connect as clients.
*/
@Singleton
class IpcFactory {
private static final Logger LOG = LoggerFactory.getLogger(IpcFactory.class);
private static final String RMI_NAME = "Cryptomator";
private final List<Path> portFilePaths;
private final IpcProtocolImpl ipcHandler;
@Inject
public IpcFactory(Environment env, IpcProtocolImpl ipcHandler) {
this.portFilePaths = env.getIpcPortPath().collect(Collectors.toUnmodifiableList());
this.ipcHandler = ipcHandler;
}
public IpcEndpoint create() {
if (portFilePaths.isEmpty()) {
LOG.warn("No IPC port file path specified.");
return new SelfEndpoint(ipcHandler);
} else {
System.setProperty("java.rmi.server.hostname", "localhost");
return attemptClientConnection().or(this::createServerEndpoint).orElseGet(() -> new SelfEndpoint(ipcHandler));
}
}
private Optional<IpcEndpoint> attemptClientConnection() {
for (Path portFilePath : portFilePaths) {
try {
int port = readPort(portFilePath);
LOG.debug("[Client] Connecting to port {}...", port);
Registry registry = LocateRegistry.getRegistry("localhost", port, new ClientSocketFactory());
IpcProtocol remoteInterface = (IpcProtocol) registry.lookup(RMI_NAME);
return Optional.of(new ClientEndpoint(remoteInterface));
} catch (NotBoundException | IOException e) {
LOG.debug("[Client] Failed to connect.");
// continue with next portFilePath...
}
}
return Optional.empty();
}
private int readPort(Path portFilePath) throws IOException {
try (ReadableByteChannel ch = Files.newByteChannel(portFilePath, StandardOpenOption.READ)) {
LOG.debug("[Client] Reading IPC port from {}", portFilePath);
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
if (ch.read(buf) == Integer.BYTES) {
buf.flip();
return buf.getInt();
} else {
throw new IOException("Invalid IPC port file.");
}
}
}
private Optional<IpcEndpoint> createServerEndpoint() {
assert !portFilePaths.isEmpty();
Path portFilePath = portFilePaths.get(0);
try {
ServerSocket socket = new ServerSocket(0, Byte.MAX_VALUE, InetAddress.getByName("localhost"));
RMIClientSocketFactory csf = RMISocketFactory.getDefaultSocketFactory();
SingletonServerSocketFactory ssf = new SingletonServerSocketFactory(socket);
Registry registry = LocateRegistry.createRegistry(0, csf, ssf);
UnicastRemoteObject.exportObject(ipcHandler, 0);
registry.rebind(RMI_NAME, ipcHandler);
writePort(portFilePath, socket.getLocalPort());
return Optional.of(new ServerEndpoint(ipcHandler, socket, registry, portFilePath));
} catch (IOException e) {
LOG.warn("[Server] Failed to create IPC server.", e);
return Optional.empty();
}
}
private void writePort(Path portFilePath, int port) throws IOException {
ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES);
buf.putInt(port);
buf.flip();
MoreFiles.createParentDirectories(portFilePath);
try (WritableByteChannel ch = Files.newByteChannel(portFilePath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) {
if (ch.write(buf) != Integer.BYTES) {
throw new IOException("Did not write expected number of bytes.");
}
}
LOG.debug("[Server] Wrote IPC port {} to {}", port, portFilePath);
}
interface IpcEndpoint extends Closeable {
boolean isConnectedToRemote();
IpcProtocol getRemote();
}
static class SelfEndpoint implements IpcEndpoint {
protected final IpcProtocol remoteObject;
SelfEndpoint(IpcProtocol remoteObject) {
this.remoteObject = remoteObject;
}
@Override
public boolean isConnectedToRemote() {
return false;
}
@Override
public IpcProtocol getRemote() {
return remoteObject;
}
@Override
public void close() {
// no-op
}
}
static class ClientEndpoint implements IpcEndpoint {
private final IpcProtocol remoteInterface;
public ClientEndpoint(IpcProtocol remoteInterface) {
this.remoteInterface = remoteInterface;
}
public IpcProtocol getRemote() {
return remoteInterface;
}
@Override
public boolean isConnectedToRemote() {
return true;
}
@Override
public void close() {
// no-op
}
}
class ServerEndpoint extends SelfEndpoint {
private final ServerSocket socket;
private final Registry registry;
private final Path portFilePath;
private ServerEndpoint(IpcProtocol remoteObject, ServerSocket socket, Registry registry, Path portFilePath) {
super(remoteObject);
this.socket = socket;
this.registry = registry;
this.portFilePath = portFilePath;
}
@Override
public void close() {
try {
registry.unbind(RMI_NAME);
UnicastRemoteObject.unexportObject(remoteObject, true);
socket.close();
Files.deleteIfExists(portFilePath);
LOG.debug("[Server] Shut down");
} catch (NotBoundException | IOException e) {
LOG.warn("[Server] Error shutting down:", e);
}
}
}
/**
* Always returns the same pre-constructed server socket.
*/
private static class SingletonServerSocketFactory implements RMIServerSocketFactory {
private final ServerSocket socket;
public SingletonServerSocketFactory(ServerSocket socket) {
this.socket = socket;
}
@Override
public synchronized ServerSocket createServerSocket(int port) throws IOException {
if (port != 0) {
throw new IllegalArgumentException("This factory doesn't support specific ports.");
}
return this.socket;
}
}
/**
* Creates client sockets with short timeouts.
*/
private static class ClientSocketFactory implements RMIClientSocketFactory {
@Override
public Socket createSocket(String host, int port) throws IOException {
return new SocketWithFixedTimeout(host, port, 1000);
}
}
private static class SocketWithFixedTimeout extends Socket {
public SocketWithFixedTimeout(String host, int port, int timeoutInMs) throws UnknownHostException, IOException {
super(host, port);
super.setSoTimeout(timeoutInMs);
}
@Override
public synchronized void setSoTimeout(int timeout) throws SocketException {
// do nothing, timeout is fixed
}
}
}
@@ -5,6 +5,11 @@
*******************************************************************************/
package org.cryptomator.launcher;
public interface InterProcessCommunicationProtocol {
void handleLaunchArgs(String[] args);
import java.rmi.Remote;
import java.rmi.RemoteException;
interface IpcProtocol extends Remote {
void handleLaunchArgs(String[] args) throws RemoteException;
}
@@ -0,0 +1,28 @@
package org.cryptomator.launcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Arrays;
@Singleton
class IpcProtocolImpl implements IpcProtocol {
private static final Logger LOG = LoggerFactory.getLogger(IpcProtocolImpl.class);
private final FileOpenRequestHandler fileOpenRequestHandler;
@Inject
public IpcProtocolImpl(FileOpenRequestHandler fileOpenRequestHandler) {
this.fileOpenRequestHandler = fileOpenRequestHandler;
}
@Override
public void handleLaunchArgs(String[] args) {
LOG.info("Received launch args: {}", Arrays.stream(args).reduce((a, b) -> a + ", " + b).orElse(""));
fileOpenRequestHandler.handleLaunchArgs(args);
}
}
@@ -1,68 +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.nio.file.Path;
import java.util.Optional;
import java.util.concurrent.BlockingQueue;
import java.util.function.Consumer;
import javax.inject.Named;
import javax.inject.Singleton;
import org.cryptomator.ui.UiModule;
import dagger.Module;
import dagger.Provides;
import javafx.application.Application;
import javafx.stage.Stage;
@Module(includes = {UiModule.class})
class LauncherModule {
private final Application application;
private final Stage mainWindow;
public LauncherModule(Application application, Stage mainWindow) {
this.application = application;
this.mainWindow = mainWindow;
}
@Provides
@Singleton
Application provideApplication() {
return application;
}
@Provides
@Singleton
@Named("applicationVersion")
Optional<String> provideApplicationVersion() {
return ApplicationVersion.get();
}
@Provides
@Singleton
@Named("mainWindow")
Stage provideMainWindow() {
return mainWindow;
}
@Provides
@Singleton
@Named("fileOpenRequests")
BlockingQueue<Path> provideFileOpenRequests() {
return Cryptomator.FILE_OPEN_REQUESTS;
}
@Provides
@Singleton
@Named("shutdownTaskScheduler")
Consumer<Runnable> provideShutdownTaskScheduler() {
return CleanShutdownPerformer::scheduleShutdownTask;
}
}
@@ -1,45 +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 javafx.application.Application;
import javafx.stage.Stage;
import org.cryptomator.ui.controllers.MainController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MainApplication extends Application {
private static final Logger LOG = LoggerFactory.getLogger(MainApplication.class);
private Stage primaryStage;
@Override
public void start(Stage primaryStage) {
LOG.info("JavaFX application started.");
this.primaryStage = primaryStage;
primaryStage.setMinWidth(652.0);
primaryStage.setMinHeight(440.0);
LauncherModule launcherModule = new LauncherModule(this, primaryStage);
LauncherComponent launcherComponent = DaggerLauncherComponent.builder() //
.launcherModule(launcherModule) //
.build();
launcherComponent.debugMode().initialize();
MainController mainCtrl = launcherComponent.fxmlLoader().load("/fxml/main.fxml");
mainCtrl.initStage(primaryStage);
primaryStage.show();
}
@Override
public void stop() {
assert primaryStage != null;
primaryStage.hide();
LOG.info("JavaFX application stopped.");
}
}
@@ -5,77 +5,55 @@
*******************************************************************************/
package org.cryptomator.logging;
import static java.util.Arrays.asList;
import java.util.Collection;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.cryptomator.common.settings.Settings;
import org.slf4j.ILoggerFactory;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import javafx.beans.value.ObservableValue;
import org.cryptomator.common.settings.Settings;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Map;
@Singleton
public class DebugMode {
private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(DebugMode.class);
private static final Collection<LoggerUpgrade> LOGGER_UPGRADES = asList( //
loggerUpgrade(org.slf4j.Logger.ROOT_LOGGER_NAME, Level.INFO), //
loggerUpgrade("org.cryptomator", Level.TRACE), //
loggerUpgrade("org.eclipse.jetty.server.HttpChannel", Level.DEBUG) //
);
private final Settings settings;
private final LoggerContext context;
@Inject
public DebugMode(Settings settings) {
public DebugMode(Settings settings, LoggerContext context) {
this.settings = settings;
this.context = context;
}
public void initialize() {
if (settings.debugMode().get()) {
enable();
LOG.debug("Debug mode initialized");
}
setLogLevels(settings.debugMode().get());
settings.debugMode().addListener(this::logLevelChanged);
}
private void enable() {
ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
if (loggerFactory instanceof LoggerContext) {
LoggerContext context = (LoggerContext) loggerFactory;
LOGGER_UPGRADES.forEach(loggerUpgrade -> loggerUpgrade.execute(context));
private void logLevelChanged(@SuppressWarnings("unused") ObservableValue<? extends Boolean> observable, @SuppressWarnings("unused") Boolean oldValue, Boolean newValue) {
setLogLevels(newValue);
}
private void setLogLevels(boolean debugMode) {
if (debugMode) {
setLogLevels(LoggerModule.DEBUG_LOG_LEVELS);
LOG.debug("Debug mode enabled");
} else {
LOG.warn("SLF4J not bound to Logback.");
LOG.debug("Debug mode disabled");
setLogLevels(LoggerModule.DEFAULT_LOG_LEVELS);
}
}
private static LoggerUpgrade loggerUpgrade(String loggerName, Level minLevel) {
return new LoggerUpgrade(loggerName, minLevel);
}
private static class LoggerUpgrade {
private final Level level;
private final String loggerName;
public LoggerUpgrade(String loggerName, Level minLevel) {
this.loggerName = loggerName;
this.level = minLevel;
private void setLogLevels(Map<String, Level> logLevels) {
for (Map.Entry<String, Level> loglevel : logLevels.entrySet()) {
Logger logger = context.getLogger(loglevel.getKey());
logger.setLevel(loglevel.getValue());
}
public void execute(LoggerContext context) {
Logger logger = context.getLogger(loggerName);
if (logger != null && logger.getEffectiveLevel().isGreaterOrEqual(level)) {
logger.setLevel(level);
}
}
}
}
@@ -0,0 +1,71 @@
package org.cryptomator.logging;
import ch.qos.logback.classic.Level;
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 javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
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;
@Inject
LoggerConfiguration(LoggerContext context, //
Environment environment, //
@Named("stdoutAppender") Appender<ILoggingEvent> stdout, //
@Named("upgradeAppender") Appender<ILoggingEvent> upgrade, //
@Named("fileAppender") Appender<ILoggingEvent> file) {
this.context = context;
this.environment = environment;
this.stdout = stdout;
this.upgrade = upgrade;
this.file = file;
}
public void init() {
if (environment.useCustomLogbackConfig()) {
Logger root = context.getLogger(Logger.ROOT_LOGGER_NAME);
root.info("Using external logback configuration file.");
} else {
context.reset();
// configure loggers:
for (Map.Entry<String, Level> loglevel : LoggerModule.DEFAULT_LOG_LEVELS.entrySet()) {
Logger logger = context.getLogger(loglevel.getKey());
logger.setLevel(loglevel.getValue());
logger.setAdditive(false);
logger.addAppender(stdout);
logger.addAppender(file);
}
// configure upgrade logger:
Logger upgrades = context.getLogger("org.cryptomator.ui.model.upgrade");
upgrades.setLevel(Level.DEBUG);
upgrades.addAppender(stdout);
upgrades.addAppender(upgrade);
upgrades.setAdditive(false);
// add shutdown hook
DelayingShutdownHook shutdownHook = new DelayingShutdownHook();
shutdownHook.setContext(context);
shutdownHook.setDelay(Duration.buildByMilliseconds(SHUTDOWN_DELAY_MS));
}
}
}
@@ -0,0 +1,127 @@
package org.cryptomator.logging;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.helpers.NOPAppender;
import ch.qos.logback.core.hook.DelayingShutdownHook;
import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.util.Duration;
import dagger.Module;
import dagger.Provides;
import org.cryptomator.common.Environment;
import org.slf4j.ILoggerFactory;
import org.slf4j.LoggerFactory;
import javax.inject.Named;
import javax.inject.Singleton;
import java.nio.file.Path;
import java.util.Map;
@Module
public class LoggerModule {
private static final String UPGRADE_FILENAME = "upgrade.log";
private static final String LOGFILE_NAME = "cryptomator0.log";
private static final String LOGFILE_ROLLING_PATTERN = "cryptomator%i.log";
private static final int LOGFILE_ROLLING_MIN = 1;
private static final int LOGFILE_ROLLING_MAX = 9;
private static final String LOG_PATTERN = "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n";
static final Map<String, Level> DEFAULT_LOG_LEVELS = Map.of( //
Logger.ROOT_LOGGER_NAME, Level.INFO, //
"org.cryptomator", Level.INFO //
);
static final Map<String, Level> DEBUG_LOG_LEVELS = Map.of( //
Logger.ROOT_LOGGER_NAME, Level.INFO, //
"org.cryptomator", Level.TRACE //
);
@Provides
@Singleton
static LoggerContext provideLoggerContext() {
ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory();
if (loggerFactory instanceof LoggerContext) {
return (LoggerContext) loggerFactory;
} else {
throw new IllegalStateException("SLF4J not bound to Logback.");
}
}
@Provides
@Singleton
static PatternLayoutEncoder provideLayoutEncoder(LoggerContext context) {
PatternLayoutEncoder ple = new PatternLayoutEncoder();
ple.setPattern(LOG_PATTERN);
ple.setContext(context);
ple.start();
return ple;
}
@Provides
@Singleton
@Named("stdoutAppender")
static Appender<ILoggingEvent> provideStdoutAppender(LoggerContext context, PatternLayoutEncoder encoder) {
ConsoleAppender<ILoggingEvent> appender = new ConsoleAppender<>();
appender.setContext(context);
appender.setEncoder(encoder);
appender.start();
return appender;
}
@Provides
@Singleton
@Named("fileAppender")
static Appender<ILoggingEvent> provideFileAppender(LoggerContext context, PatternLayoutEncoder encoder, Environment environment) {
if (environment.getLogDir().isPresent()) {
Path logDir = environment.getLogDir().get();
RollingFileAppender<ILoggingEvent> appender = new RollingFileAppender<>();
appender.setContext(context);
appender.setFile(logDir.resolve(LOGFILE_NAME).toString());
appender.setEncoder(encoder);
LaunchBasedTriggeringPolicy triggeringPolicy = new LaunchBasedTriggeringPolicy();
triggeringPolicy.setContext(context);
triggeringPolicy.start();
appender.setTriggeringPolicy(triggeringPolicy);
FixedWindowRollingPolicy rollingPolicy = new FixedWindowRollingPolicy();
rollingPolicy.setContext(context);
rollingPolicy.setFileNamePattern(logDir.resolve(LOGFILE_ROLLING_PATTERN).toString());
rollingPolicy.setMinIndex(LOGFILE_ROLLING_MIN);
rollingPolicy.setMaxIndex(LOGFILE_ROLLING_MAX);
rollingPolicy.setParent(appender);
rollingPolicy.start();
appender.setRollingPolicy(rollingPolicy);
appender.start();
return appender;
} else {
NOPAppender appender = new NOPAppender<>();
appender.setContext(context);
return appender;
}
}
@Provides
@Singleton
@Named("upgradeAppender")
static Appender<ILoggingEvent> provideUpgradeAppender(LoggerContext context, PatternLayoutEncoder encoder, Environment environment) {
if (environment.getLogDir().isPresent()) {
FileAppender<ILoggingEvent> appender = new FileAppender<>();
appender.setFile(environment.getLogDir().get().resolve(UPGRADE_FILENAME).toString());
appender.setContext(context);
appender.setEncoder(encoder);
appender.start();
return appender;
} else {
NOPAppender appender = new NOPAppender<>();
appender.setContext(context);
return appender;
}
}
}
@@ -5,72 +5,69 @@
*******************************************************************************/
package org.cryptomator.launcher;
import org.cryptomator.ui.model.AppLaunchEvent;
import org.hamcrest.CoreMatchers;
import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.spi.FileSystemProvider;
import java.nio.file.Paths;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class FileOpenRequestHandlerTest {
@Test
public void testOpenArgsWithCorrectPaths() throws IOException {
Path p1 = Mockito.mock(Path.class);
Path p2 = Mockito.mock(Path.class);
FileSystem fs = Mockito.mock(FileSystem.class);
FileSystemProvider provider = Mockito.mock(FileSystemProvider.class);
BasicFileAttributes attrs = Mockito.mock(BasicFileAttributes.class);
Mockito.when(p1.getFileSystem()).thenReturn(fs);
Mockito.when(p2.getFileSystem()).thenReturn(fs);
Mockito.when(fs.provider()).thenReturn(provider);
Mockito.when(fs.getPath(Mockito.anyString())).thenReturn(p1, p2);
Mockito.when(provider.readAttributes(Mockito.any(), Mockito.eq(BasicFileAttributes.class))).thenReturn(attrs);
private FileOpenRequestHandler inTest;
private BlockingQueue<AppLaunchEvent> queue;
BlockingQueue<Path> queue = new ArrayBlockingQueue<>(10);
FileOpenRequestHandler handler = new FileOpenRequestHandler(queue);
handler.handleLaunchArgs(fs, new String[] {"foo", "bar"});
Assert.assertEquals(p1, queue.poll());
Assert.assertEquals(p2, queue.poll());
@BeforeEach
public void setup() {
queue = new ArrayBlockingQueue<>(1);
inTest = new FileOpenRequestHandler(queue);
}
@Test
@DisplayName("./cryptomator.exe foo bar")
public void testOpenArgsWithCorrectPaths() throws IOException {
inTest.handleLaunchArgs(new String[]{"foo", "bar"});
AppLaunchEvent evt = queue.poll();
Assertions.assertNotNull(evt);
List<Path> paths = evt.getPathsToOpen().collect(Collectors.toList());
MatcherAssert.assertThat(paths, CoreMatchers.hasItems(Paths.get("foo"), Paths.get("bar")));
}
@Test
@DisplayName("./cryptomator.exe foo (with 'foo' being an invalid path)")
public void testOpenArgsWithIncorrectPaths() throws IOException {
FileSystem fs = Mockito.mock(FileSystem.class);
Mockito.when(fs.getPath(Mockito.anyString())).thenThrow(new InvalidPathException("foo", "foo is not a path"));
Mockito.when(fs.getPath("foo")).thenThrow(new InvalidPathException("foo", "foo is not a path"));
inTest.handleLaunchArgs(fs, new String[]{"foo"});
@SuppressWarnings("unchecked")
BlockingQueue<Path> queue = Mockito.mock(BlockingQueue.class);
FileOpenRequestHandler handler = new FileOpenRequestHandler(queue);
handler.handleLaunchArgs(fs, new String[] {"foo"});
Mockito.verifyNoMoreInteractions(queue);
AppLaunchEvent evt = queue.poll();
Assertions.assertNotNull(evt);
List<Path> paths = evt.getPathsToOpen().collect(Collectors.toList());
Assertions.assertTrue(paths.isEmpty());
}
@Test
@DisplayName("./cryptomator.exe foo (with full event queue)")
public void testOpenArgsWithFullQueue() throws IOException {
Path p = Mockito.mock(Path.class);
FileSystem fs = Mockito.mock(FileSystem.class);
FileSystemProvider provider = Mockito.mock(FileSystemProvider.class);
BasicFileAttributes attrs = Mockito.mock(BasicFileAttributes.class);
Mockito.when(p.getFileSystem()).thenReturn(fs);
Mockito.when(fs.provider()).thenReturn(provider);
Mockito.when(fs.getPath(Mockito.anyString())).thenReturn(p);
Mockito.when(provider.readAttributes(Mockito.eq(p), Mockito.eq(BasicFileAttributes.class))).thenReturn(attrs);
Mockito.when(attrs.isRegularFile()).thenReturn(true);
queue.add(new AppLaunchEvent(Stream.empty()));
Assumptions.assumeTrue(queue.remainingCapacity() == 0);
@SuppressWarnings("unchecked")
BlockingQueue<Path> queue = Mockito.mock(BlockingQueue.class);
Mockito.when(queue.offer(Mockito.any())).thenReturn(false);
FileOpenRequestHandler handler = new FileOpenRequestHandler(queue);
handler.handleLaunchArgs(fs, new String[] {"foo"});
inTest.handleLaunchArgs(new String[]{"foo"});
}
}
@@ -1,100 +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.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.FileSystem;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.spi.FileSystemProvider;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
public class InterProcessCommunicatorTest {
Path portFilePath = Mockito.mock(Path.class);
Path portFileParentPath = Mockito.mock(Path.class);
BasicFileAttributes portFileParentPathAttrs = Mockito.mock(BasicFileAttributes.class);
FileSystem fs = Mockito.mock(FileSystem.class);
FileSystemProvider provider = Mockito.mock(FileSystemProvider.class);
SeekableByteChannel portFileChannel = Mockito.mock(SeekableByteChannel.class);
AtomicInteger port = new AtomicInteger(-1);
@Before
public void setup() throws IOException {
Mockito.when(portFilePath.getFileSystem()).thenReturn(fs);
Mockito.when(portFilePath.toAbsolutePath()).thenReturn(portFilePath);
Mockito.when(portFilePath.normalize()).thenReturn(portFilePath);
Mockito.when(portFilePath.getParent()).thenReturn(portFileParentPath);
Mockito.when(portFileParentPath.getFileSystem()).thenReturn(fs);
Mockito.when(fs.provider()).thenReturn(provider);
Mockito.when(provider.readAttributes(portFileParentPath, BasicFileAttributes.class)).thenReturn(portFileParentPathAttrs);
Mockito.when(portFileParentPathAttrs.isDirectory()).thenReturn(false, true); // Guava's MoreFiles will check if dir exists before attempting to create them.
Mockito.when(provider.newByteChannel(Mockito.eq(portFilePath), Mockito.any(), Mockito.any())).thenReturn(portFileChannel);
Mockito.when(portFileChannel.read(Mockito.any())).then(invocation -> {
ByteBuffer buf = invocation.getArgument(0);
buf.putInt(port.get());
return Integer.BYTES;
});
Mockito.when(portFileChannel.write(Mockito.any())).then(invocation -> {
ByteBuffer buf = invocation.getArgument(0);
port.set(buf.getInt());
return Integer.BYTES;
});
}
@Test(expected = UnsupportedOperationException.class)
public void testStartWithDummyPort1() throws IOException {
port.set(0);
InterProcessCommunicationProtocol protocol = Mockito.mock(InterProcessCommunicationProtocol.class);
try (InterProcessCommunicator result = InterProcessCommunicator.start(portFilePath, protocol)) {
Assert.assertTrue(result.isServer());
Mockito.verify(provider).createDirectory(portFileParentPath);
Mockito.verifyZeroInteractions(protocol);
result.handleLaunchArgs(new String[] {"foo"});
}
}
@Test
public void testStartWithDummyPort2() throws IOException {
Mockito.doThrow(new NoSuchFileException("port file")).when(provider).checkAccess(portFilePath);
InterProcessCommunicationProtocol protocol = Mockito.mock(InterProcessCommunicationProtocol.class);
try (InterProcessCommunicator result = InterProcessCommunicator.start(portFilePath, protocol)) {
Assert.assertTrue(result.isServer());
Mockito.verify(provider).createDirectory(portFileParentPath);
Mockito.verifyZeroInteractions(protocol);
}
}
@Test
public void testInterProcessCommunication() throws IOException, InterruptedException {
port.set(-1);
InterProcessCommunicationProtocol protocol = Mockito.mock(InterProcessCommunicationProtocol.class);
try (InterProcessCommunicator result1 = InterProcessCommunicator.start(portFilePath, protocol)) {
Assert.assertTrue(result1.isServer());
Mockito.verify(provider, Mockito.times(1)).createDirectory(portFileParentPath);
Mockito.verifyZeroInteractions(protocol);
try (InterProcessCommunicator result2 = InterProcessCommunicator.start(portFilePath, null)) {
Assert.assertFalse(result2.isServer());
Mockito.verify(provider, Mockito.times(1)).createDirectory(portFileParentPath);
Assert.assertNotSame(result1, result2);
result2.handleLaunchArgs(new String[] {"foo"});
Mockito.verify(protocol).handleLaunchArgs(new String[] {"foo"});
}
}
}
}
@@ -0,0 +1,71 @@
/*******************************************************************************
* 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 org.cryptomator.common.Environment;
import org.cryptomator.launcher.IpcFactory.IpcEndpoint;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mockito;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
public class IpcFactoryTest {
private Environment environment = Mockito.mock(Environment.class);
private IpcProtocolImpl protocolHandler = Mockito.mock(IpcProtocolImpl.class);
@Test
@DisplayName("Wihout IPC port files")
public void testNoIpcWithoutPortFile() throws IOException {
IpcFactory inTest = new IpcFactory(environment, protocolHandler);
Mockito.when(environment.getIpcPortPath()).thenReturn(Stream.empty());
try (IpcEndpoint endpoint1 = inTest.create()) {
Assertions.assertEquals(IpcFactory.SelfEndpoint.class, endpoint1.getClass());
Assertions.assertFalse(endpoint1.isConnectedToRemote());
Assertions.assertSame(protocolHandler, endpoint1.getRemote());
try (IpcEndpoint endpoint2 = inTest.create()) {
Assertions.assertEquals(IpcFactory.SelfEndpoint.class, endpoint2.getClass());
Assertions.assertNotSame(endpoint1, endpoint2);
Assertions.assertFalse(endpoint2.isConnectedToRemote());
Assertions.assertSame(protocolHandler, endpoint2.getRemote());
}
}
}
@Test
@DisplayName("Start server and client with port shared via file")
public void testInterProcessCommunication(@TempDir Path tmpDir) throws IOException {
Path portFile = tmpDir.resolve("testPortFile");
Mockito.when(environment.getIpcPortPath()).thenReturn(Stream.of(portFile));
IpcFactory inTest = new IpcFactory(environment, protocolHandler);
Assertions.assertFalse(Files.exists(portFile));
try (IpcEndpoint endpoint1 = inTest.create()) {
Assertions.assertEquals(IpcFactory.ServerEndpoint.class, endpoint1.getClass());
Assertions.assertFalse(endpoint1.isConnectedToRemote());
Assertions.assertTrue(Files.exists(portFile));
Assertions.assertSame(protocolHandler, endpoint1.getRemote());
Mockito.verifyZeroInteractions(protocolHandler);
try (IpcEndpoint endpoint2 = inTest.create()) {
Assertions.assertEquals(IpcFactory.ClientEndpoint.class, endpoint2.getClass());
Assertions.assertNotSame(endpoint1, endpoint2);
Assertions.assertTrue(endpoint2.isConnectedToRemote());
Assertions.assertNotSame(protocolHandler, endpoint2.getRemote());
Mockito.verifyZeroInteractions(protocolHandler);
endpoint2.getRemote().handleLaunchArgs(new String[] {"foo"});
Mockito.verify(protocolHandler).handleLaunchArgs(new String[] {"foo"});
}
}
}
}
@@ -5,12 +5,12 @@
*******************************************************************************/
package org.cryptomator.logging;
import java.io.File;
import org.junit.Assert;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.io.File;
public class LaunchBasedTriggeringPolicyTest {
@Test
@@ -21,15 +21,15 @@ public class LaunchBasedTriggeringPolicyTest {
// 1st invocation
boolean triggered = policy.isTriggeringEvent(activeFile, event);
Assert.assertTrue(triggered);
Assertions.assertTrue(triggered);
// 2nd invocation
triggered = policy.isTriggeringEvent(activeFile, event);
Assert.assertFalse(triggered);
Assertions.assertFalse(triggered);
// 3rd invocation
triggered = policy.isTriggeringEvent(activeFile, event);
Assert.assertFalse(triggered);
Assertions.assertFalse(triggered);
Mockito.verifyZeroInteractions(activeFile);
Mockito.verifyZeroInteractions(event);
@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE xml>
<!-- log config used during unit tests and starts from IDE. For production please specify -Dlogback.configurationFile=/path/to/config -->
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.cryptomator" level="DEBUG" />
<logger name="org.eclipse.jetty" level="INFO" />
<logger name="org.eclipse.jetty.server.Server" level="DEBUG" />
<logger name="org.apache" level="INFO" />
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
+25 -31
View File
@@ -27,9 +27,9 @@
<cryptomator.cryptolib.version>1.2.1</cryptomator.cryptolib.version>
<cryptomator.cryptofs.version>1.7.0</cryptomator.cryptofs.version>
<cryptomator.jni.version>2.0.0</cryptomator.jni.version>
<cryptomator.fuse.version>1.1.0</cryptomator.fuse.version>
<cryptomator.fuse.version>1.1.1</cryptomator.fuse.version>
<cryptomator.dokany.version>1.1.3</cryptomator.dokany.version>
<cryptomator.webdav.version>1.0.7</cryptomator.webdav.version>
<cryptomator.webdav.version>1.0.9</cryptomator.webdav.version>
<javafx.version>11.0.2</javafx.version>
@@ -45,10 +45,9 @@
<slf4j.version>1.7.25</slf4j.version>
<logback.version>1.2.3</logback.version>
<junit.version>4.12</junit.version>
<junit.hierarchicalrunner.version>4.12.1</junit.hierarchicalrunner.version>
<mockito.version>2.23.0</mockito.version>
<hamcrest.version>1.3</hamcrest.version> <!-- keep in sync with version required by JUnit -->
<junit.jupiter.version>5.4.0</junit.jupiter.version>
<mockito.version>2.24.0</mockito.version>
<hamcrest.version>1.3</hamcrest.version>
</properties>
<repositories>
@@ -131,9 +130,9 @@
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
@@ -201,20 +200,10 @@
<!-- JUnit / Mockito / Hamcrest -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<exclusions>
<exclusion>
<artifactId>hamcrest-core</artifactId>
<groupId>org.hamcrest</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>de.bechte.junit</groupId>
<artifactId>junit-hierarchicalcontextrunner</artifactId>
<version>${junit.hierarchicalrunner.version}</version>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
@@ -226,6 +215,12 @@
<artifactId>hamcrest-all</artifactId>
<version>${hamcrest.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-swing</artifactId>
<version>${javafx.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
@@ -236,9 +231,8 @@
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
@@ -250,11 +244,6 @@
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.bechte.junit</groupId>
<artifactId>junit-hierarchicalcontextrunner</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<modules>
@@ -345,6 +334,11 @@
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
</plugins>
</build>
+5
View File
@@ -109,5 +109,10 @@
<version>1.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-swing</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -9,6 +9,24 @@
*******************************************************************************/
package org.cryptomator.ui;
import javafx.application.Platform;
import javafx.stage.Stage;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.FxApplicationScoped;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.jni.JniException;
import org.cryptomator.jni.MacApplicationUiState;
import org.cryptomator.jni.MacFunctions;
import org.cryptomator.ui.l10n.Localization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.swing.SwingUtilities;
import java.awt.AWTException;
import java.awt.Image;
import java.awt.MenuItem;
@@ -24,27 +42,7 @@ import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.swing.SwingUtilities;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.jni.JniException;
import org.cryptomator.jni.MacApplicationUiState;
import org.cryptomator.jni.MacFunctions;
import org.cryptomator.ui.l10n.Localization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javafx.application.Platform;
import javafx.stage.Stage;
@Singleton
@FxApplicationScoped
public class ExitUtil {
private static final Logger LOG = LoggerFactory.getLogger(ExitUtil.class);
@@ -8,21 +8,11 @@
*******************************************************************************/
package org.cryptomator.ui;
import java.net.InetSocketAddress;
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;
import javax.inject.Named;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import javafx.beans.binding.Binding;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.CommonsModule;
import org.cryptomator.common.FxApplicationScoped;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.SettingsProvider;
import org.cryptomator.frontend.webdav.WebDavServer;
@@ -31,19 +21,21 @@ import org.cryptomator.ui.controllers.ViewControllerModule;
import org.cryptomator.ui.model.VaultComponent;
import org.fxmisc.easybind.EasyBind;
@Module(includes = {ViewControllerModule.class, CommonsModule.class, KeychainModule.class}, subcomponents = {VaultComponent.class})
import javax.inject.Named;
import java.net.InetSocketAddress;
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(includes = {ViewControllerModule.class, KeychainModule.class}, subcomponents = {VaultComponent.class})
public class UiModule {
private static final int NUM_SCHEDULER_THREADS = 4;
@Provides
@Singleton
Settings provideSettings(SettingsProvider settingsProvider) {
return settingsProvider.get();
}
@Provides
@Singleton
@FxApplicationScoped
ScheduledExecutorService provideScheduledExecutorService(@Named("shutdownTaskScheduler") Consumer<Runnable> shutdownTaskScheduler) {
final AtomicInteger threadNumber = new AtomicInteger(1);
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(NUM_SCHEDULER_THREADS, r -> {
@@ -57,7 +49,7 @@ public class UiModule {
}
@Provides
@Singleton
@FxApplicationScoped
ExecutorService provideExecutorService(@Named("shutdownTaskScheduler") Consumer<Runnable> shutdownTaskScheduler) {
final AtomicInteger threadNumber = new AtomicInteger(1);
ExecutorService executorService = Executors.newCachedThreadPool(r -> {
@@ -71,7 +63,7 @@ public class UiModule {
}
@Provides
@Singleton
@FxApplicationScoped
Binding<InetSocketAddress> provideServerSocketAddressBinding(Settings settings) {
return EasyBind.map(settings.port(), (Number port) -> {
String host = SystemUtils.IS_OS_WINDOWS ? "127.0.0.1" : "localhost";
@@ -80,7 +72,7 @@ public class UiModule {
}
@Provides
@Singleton
@FxApplicationScoped
WebDavServer provideWebDavServer(Binding<InetSocketAddress> serverSocketAddressBinding) {
WebDavServer server = WebDavServer.create();
// no need to unsubscribe eventually, because server is a singleton
@@ -9,24 +9,6 @@
******************************************************************************/
package org.cryptomator.ui.controllers;
import java.awt.Desktop;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.stream.Stream;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Binding;
@@ -61,27 +43,47 @@ import javafx.scene.layout.Pane;
import javafx.scene.text.Font;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.util.Duration;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.FxApplicationScoped;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.ui.ExitUtil;
import org.cryptomator.ui.controls.DirectoryListCell;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.model.AppLaunchEvent;
import org.cryptomator.ui.model.AutoUnlocker;
import org.cryptomator.ui.model.UpgradeStrategies;
import org.cryptomator.ui.model.UpgradeStrategy;
import org.cryptomator.ui.model.upgrade.UpgradeStrategies;
import org.cryptomator.ui.model.upgrade.UpgradeStrategy;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.model.VaultFactory;
import org.cryptomator.ui.model.VaultList;
import org.cryptomator.ui.util.DialogBuilderUtil;
import org.cryptomator.ui.util.Tasks;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import org.fxmisc.easybind.monadic.MonadicBinding;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import java.awt.Desktop;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.stream.Stream;
import static org.cryptomator.ui.util.DialogBuilderUtil.buildErrorDialog;
@Singleton
@FxApplicationScoped
public class MainController implements ViewController {
private static final Logger LOG = LoggerFactory.getLogger(MainController.class);
@@ -92,7 +94,7 @@ public class MainController implements ViewController {
private final ExitUtil exitUtil;
private final Localization localization;
private final ExecutorService executorService;
private final BlockingQueue<Path> fileOpenRequests;
private final BlockingQueue<AppLaunchEvent> launchEventQueue;
private final VaultFactory vaultFactoy;
private final ViewControllerLoader viewControllerLoader;
private final ObjectProperty<ViewController> activeController = new SimpleObjectProperty<>();
@@ -109,11 +111,11 @@ public class MainController implements ViewController {
private Subscription subs = Subscription.EMPTY;
@Inject
public MainController(@Named("mainWindow") Stage mainWindow, ExecutorService executorService, @Named("fileOpenRequests") BlockingQueue<Path> fileOpenRequests, ExitUtil exitUtil, Localization localization,
public MainController(@Named("mainWindow") Stage mainWindow, ExecutorService executorService, @Named("launchEventQueue") BlockingQueue<AppLaunchEvent> launchEventQueue, ExitUtil exitUtil, Localization localization,
VaultFactory vaultFactoy, ViewControllerLoader viewControllerLoader, UpgradeStrategies upgradeStrategies, VaultList vaults, AutoUnlocker autoUnlocker) {
this.mainWindow = mainWindow;
this.executorService = executorService;
this.fileOpenRequests = fileOpenRequests;
this.launchEventQueue = launchEventQueue;
this.exitUtil = exitUtil;
this.localization = localization;
this.vaultFactoy = vaultFactoy;
@@ -211,7 +213,7 @@ public class MainController implements ViewController {
stage.getIcons().add(new Image(getClass().getResourceAsStream("/window_icon_32.png")));
Application.setUserAgentStylesheet(getClass().getResource("/css/win_theme.css").toString());
}
exitUtil.initExitHandler(this::gracefulShutdown);
exitUtil.initExitHandler(() -> Platform.runLater(this::gracefulShutdown));
listenToFileOpenRequests(stage);
}
@@ -248,22 +250,13 @@ public class MainController implements ViewController {
}
private void listenToFileOpenRequests(Stage stage) {
executorService.submit(() -> {
while (!Thread.interrupted()) {
try {
final Path path = fileOpenRequests.take();
Platform.runLater(() -> {
addVault(path, true);
stage.setIconified(false);
stage.show();
stage.toFront();
stage.requestFocus();
});
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
Tasks.create(launchEventQueue::take).onSuccess(event -> {
stage.setIconified(false);
stage.show();
stage.toFront();
stage.requestFocus();
event.getPathsToOpen().forEach(path -> addVault(path, true));
}).schedulePeriodically(executorService, Duration.ZERO, Duration.ZERO);
}
private ListCell<Vault> createDirecoryListCell(ListView<Vault> param) {
@@ -5,14 +5,14 @@
*******************************************************************************/
package org.cryptomator.ui.controllers;
import javax.inject.Inject;
import javax.inject.Singleton;
import javafx.fxml.FXML;
import javafx.scene.Parent;
import javafx.scene.layout.VBox;
import org.cryptomator.common.FxApplicationScoped;
@Singleton
import javax.inject.Inject;
@FxApplicationScoped
public class NotFoundController implements ViewController {
@Inject
@@ -25,6 +25,7 @@ import javafx.scene.input.KeyEvent;
import javafx.scene.layout.VBox;
import javafx.util.StringConverter;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.FxApplicationScoped;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VolumeImpl;
import org.cryptomator.ui.l10n.Localization;
@@ -32,10 +33,9 @@ import org.cryptomator.ui.model.Volume;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.util.Optional;
@Singleton
@FxApplicationScoped
public class SettingsController implements ViewController {
private static final CharMatcher DIGITS_MATCHER = CharMatcher.inRange('0', '9');
@@ -106,7 +106,7 @@ public class UnlockController implements ViewController {
private Button unlockButton;
@FXML
private Label successMessage;
private Text messageText;
@FXML
private CheckBox savePassword;
@@ -136,7 +136,7 @@ public class UnlockController implements ViewController {
private ProgressIndicator progressIndicator;
@FXML
private Text messageText;
private Text progressText;
@FXML
private Hyperlink downloadsPageLink;
@@ -200,8 +200,8 @@ public class UnlockController implements ViewController {
advancedOptions.setVisible(false);
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.show"));
progressIndicator.setVisible(false);
successMessage.setVisible(state.successMessage().isPresent());
state.successMessage().map(localization::getString).ifPresent(successMessage::setText);
progressText.setText(null);
state.successMessage().map(localization::getString).ifPresent(messageText::setText);
if (SystemUtils.IS_OS_WINDOWS) {
winDriveLetter.valueProperty().removeListener(driveLetterChangeListener);
winDriveLetter.getItems().clear();
@@ -212,7 +212,6 @@ public class UnlockController implements ViewController {
chooseSelectedDriveLetter();
}
downloadsPageLink.setVisible(false);
messageText.setText(null);
mountName.setText(vault.getMountName());
savePassword.setSelected(false);
// auto-fill pw from keychain:
@@ -298,7 +297,7 @@ public class UnlockController implements ViewController {
@FXML
private void didClickAdvancedOptionsButton(ActionEvent event) {
successMessage.setVisible(false);
messageText.setText(null);
advancedOptions.setVisible(!advancedOptions.isVisible());
if (advancedOptions.isVisible()) {
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.hide"));
@@ -431,11 +430,12 @@ public class UnlockController implements ViewController {
private void didClickUnlockButton(ActionEvent event) {
advancedOptions.setDisable(true);
advancedOptions.setVisible(false);
advancedOptionsButton.setText(localization.getString("unlock.button.advancedOptions.show"));
progressIndicator.setVisible(true);
CharSequence password = passwordField.getCharacters();
Tasks.create(() -> {
messageText.setText(localization.getString("unlock.pendingMessage.unlocking"));
progressText.setText(localization.getString("unlock.pendingMessage.unlocking"));
vault.unlock(password);
if (keychainAccess.isPresent() && savePassword.isSelected()) {
keychainAccess.get().storePassphrase(vault.getId(), password);
@@ -478,6 +478,7 @@ public class UnlockController implements ViewController {
}
advancedOptions.setDisable(false);
progressIndicator.setVisible(false);
progressText.setText(null);
}).runOnce(executor);
}
@@ -22,9 +22,9 @@ import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.GridPane;
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.model.UpgradeStrategies;
import org.cryptomator.ui.model.UpgradeStrategy;
import org.cryptomator.ui.model.UpgradeStrategy.UpgradeFailedException;
import org.cryptomator.ui.model.upgrade.UpgradeStrategies;
import org.cryptomator.ui.model.upgrade.UpgradeStrategy;
import org.cryptomator.ui.model.upgrade.UpgradeStrategy.UpgradeFailedException;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.util.Tasks;
import org.fxmisc.easybind.EasyBind;
@@ -5,20 +5,18 @@
*******************************************************************************/
package org.cryptomator.ui.controllers;
import javafx.fxml.FXMLLoader;
import org.cryptomator.common.FxApplicationScoped;
import org.cryptomator.ui.l10n.Localization;
import javax.inject.Inject;
import javax.inject.Provider;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import org.cryptomator.ui.l10n.Localization;
import javafx.fxml.FXMLLoader;
@Singleton
@FxApplicationScoped
public class ViewControllerLoader {
private final Map<Class<? extends ViewController>, Provider<ViewController>> controllerProviders;
@@ -8,22 +8,6 @@
******************************************************************************/
package org.cryptomator.ui.controllers;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
@@ -39,15 +23,31 @@ import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.VBox;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.FxApplicationScoped;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.util.Tasks;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Named;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static org.cryptomator.ui.util.DialogBuilderUtil.buildYesNoDialog;
@Singleton
@FxApplicationScoped
public class WelcomeController implements ViewController {
private static final Logger LOG = LoggerFactory.getLogger(WelcomeController.class);
@@ -9,12 +9,27 @@
package org.cryptomator.ui.controls;
import com.google.common.base.Strings;
import javafx.beans.NamedArg;
import javafx.beans.Observable;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.OverrunStyle;
import javafx.scene.control.PasswordField;
import javafx.scene.control.Tooltip;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import java.awt.Toolkit;
import java.nio.CharBuffer;
import java.text.Normalizer;
import java.text.Normalizer.Form;
import java.util.Arrays;
/**
@@ -28,13 +43,44 @@ public class SecPasswordField extends PasswordField {
private static final int INITIAL_BUFFER_SIZE = 50;
private static final int GROW_BUFFER_SIZE = 50;
private static final String PLACEHOLDER = "*";
private static final double PADDING = 2.0;
private static final double INDICATOR_PADDING = 4.0;
private static final Color INDICATOR_COLOR = new Color(0.901, 0.494, 0.133, 1.0);
private final Tooltip tooltip = new Tooltip();
private final Label indicator = new Label();
private final String nonPrintableCharsWarning;
private final String capslockWarning;
private char[] content = new char[INITIAL_BUFFER_SIZE];
private int length = 0;
public SecPasswordField() {
this.onDragOverProperty().set(this::handleDragOver);
this.onDragDroppedProperty().set(this::handleDragDropped);
this("", "");
}
public SecPasswordField(@NamedArg("nonPrintableCharsWarning") String nonPrintableCharsWarning, @NamedArg("capslockWarning") String capslockWarning) {
this.nonPrintableCharsWarning = nonPrintableCharsWarning;
this.capslockWarning = capslockWarning;
indicator.setPadding(new Insets(PADDING, INDICATOR_PADDING, PADDING, INDICATOR_PADDING));
indicator.setAlignment(Pos.CENTER_RIGHT);
indicator.setMouseTransparent(true);
indicator.setTextOverrun(OverrunStyle.CLIP);
indicator.setTextFill(INDICATOR_COLOR);
indicator.setFont(Font.font(indicator.getFont().getFamily(), 15.0));
this.getChildren().add(indicator);
this.setTooltip(tooltip);
this.addEventHandler(DragEvent.DRAG_OVER, this::handleDragOver);
this.addEventHandler(DragEvent.DRAG_DROPPED, this::handleDragDropped);
this.addEventHandler(KeyEvent.ANY, this::handleKeyEvent);
this.focusedProperty().addListener(this::focusedChanged);
}
@Override
protected void layoutChildren() {
super.layoutChildren();
indicator.relocate(0.0, 0.0);
indicator.resize(getWidth(), getHeight());
}
private void handleDragOver(DragEvent event) {
@@ -53,23 +99,101 @@ public class SecPasswordField extends PasswordField {
event.consume();
}
private void handleKeyEvent(KeyEvent e) {
if (e.getCode() == KeyCode.CAPS) {
updateVisualHints(true);
}
}
private void focusedChanged(@SuppressWarnings("unused") Observable observable) {
updateVisualHints(isFocused());
}
private void updateVisualHints(boolean focused) {
StringBuilder tooltipSb = new StringBuilder();
StringBuilder indicatorSb = new StringBuilder();
if (containsNonPrintableCharacters()) {
indicatorSb.append('⚠');
tooltipSb.append("- ").append(nonPrintableCharsWarning).append('\n');
}
// AWT code needed until https://bugs.openjdk.java.net/browse/JDK-8090882 is closed:
if (focused && Toolkit.getDefaultToolkit().getLockingKeyState(java.awt.event.KeyEvent.VK_CAPS_LOCK)) {
indicatorSb.append('⇪');
tooltipSb.append("- ").append(capslockWarning).append('\n');
}
indicator.setText(indicatorSb.toString());
if (!indicator.getText().isEmpty()) {
setPadding(new Insets(PADDING, getIndicatorWidth(), PADDING, PADDING));
} else {
setPadding(new Insets(PADDING));
}
tooltip.setText(tooltipSb.toString());
if (tooltip.getText().isEmpty()) {
setTooltip(null);
} else {
setTooltip(tooltip);
}
}
private double getIndicatorWidth() {
return new Text(indicator.getText()).getLayoutBounds().getWidth() + INDICATOR_PADDING * 2.0;
}
/**
* @return <code>true</code> if any {@link Character#isISOControl(char) control character} is present in the current value of this password field.
* @implNote runs in O(n)
*/
boolean containsNonPrintableCharacters() {
for (int i = 0; i < length; i++) {
if (Character.isISOControl(content[i])) {
return true;
}
}
return false;
}
/**
* Replaces a range of characters with the given text.
* The text will be normalized to <a href="https://www.unicode.org/glossary/#normalization_form_c">NFC</a>.
*
* @param start The starting index in the range, inclusive. This must be &gt;= 0 and &lt; the end.
* @param end The ending index in the range, exclusive. This is one-past the last character to
* delete (consistent with the String manipulation methods). This must be &gt; the start,
* and &lt;= the length of the text.
* @param text The text that is to replace the range. This must not be null.
* @implNote Internally calls {@link PasswordField#replaceText(int, int, String)} with a dummy String for visual purposes.
*/
@Override
public void replaceText(int start, int end, String text) {
String normalizedText = Normalizer.normalize(text, Form.NFC);
int removed = end - start;
int added = text.length();
this.length += added - removed;
growContentIfNeeded();
text.getChars(0, text.length(), content, start);
int added = normalizedText.length();
int delta = added - removed;
String placeholderString = Strings.repeat(PLACEHOLDER, text.length());
// ensure sufficient content buffer size
int oldLength = length;
this.length += delta;
growContentIfNeeded();
// shift existing content
if (delta != 0 && start < oldLength) {
System.arraycopy(content, end, content, end + delta, oldLength - end);
}
// copy new text to content buffer
normalizedText.getChars(0, normalizedText.length(), content, start);
// trigger visual hints
updateVisualHints(true);
String placeholderString = Strings.repeat(PLACEHOLDER, normalizedText.length());
super.replaceText(start, end, placeholderString);
}
private void growContentIfNeeded() {
if (this.length > content.length) {
char[] newContent = new char[content.length + GROW_BUFFER_SIZE];
if (length > content.length) {
char[] newContent = new char[length + GROW_BUFFER_SIZE];
System.arraycopy(content, 0, newContent, 0, content.length);
swipe();
swipe(content);
this.content = newContent;
}
}
@@ -80,6 +204,7 @@ public class SecPasswordField extends PasswordField {
* @return A character sequence backed by the SecPasswordField's buffer (not a copy).
* @implNote The CharSequence will not copy the backing char[].
* Therefore any mutation to the SecPasswordField's content will mutate or eventually swipe the returned CharSequence.
* @implSpec The CharSequence is usually in <a href="https://www.unicode.org/glossary/#normalization_form_c">NFC</a> representation (unless NFD-encoded char[] is set via {@link #setPassword(char[])}).
* @see #swipe()
*/
@Override
@@ -87,6 +212,28 @@ public class SecPasswordField extends PasswordField {
return CharBuffer.wrap(content, 0, length);
}
/**
* Convenience method wrapper for {@link #setPassword(char[])}.
*
* @param password
* @see #setPassword(char[])
*/
public void setPassword(CharSequence password) {
char[] buf = new char[password.length()];
for (int i = 0; i < password.length(); i++) {
buf[i] = password.charAt(i);
}
setPassword(buf);
Arrays.fill(buf, SWIPE_CHAR);
}
/**
* Directly sets the content of this password field to a copy of the given password.
* No conversion whatsoever happens. If you want to normalize the unicode representation of the password,
* do it before calling this method.
*
* @param password
*/
public void setPassword(char[] password) {
swipe();
content = Arrays.copyOf(password, password.length);
@@ -100,7 +247,12 @@ public class SecPasswordField extends PasswordField {
* Destroys the stored password by overriding each character with a different character.
*/
public void swipe() {
Arrays.fill(content, SWIPE_CHAR);
swipe(content);
length = 0;
}
private void swipe(char[] buffer) {
Arrays.fill(buffer, SWIPE_CHAR);
}
}
@@ -5,6 +5,13 @@
*******************************************************************************/
package org.cryptomator.ui.l10n;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.StringUtils;
import org.cryptomator.common.FxApplicationScoped;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -19,16 +26,7 @@ import java.util.Objects;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Sets;
@Singleton
@FxApplicationScoped
public class Localization extends ResourceBundle {
private static final Logger LOG = LoggerFactory.getLogger(Localization.class);
@@ -0,0 +1,15 @@
package org.cryptomator.ui.model;
import java.nio.file.Path;
import java.util.stream.Stream;
public class AppLaunchEvent {
private final Stream<Path> pathsToOpen;
public AppLaunchEvent(Stream<Path> pathsToOpen) {this.pathsToOpen = pathsToOpen;}
public Stream<Path> getPathsToOpen() {
return pathsToOpen;
}
}
@@ -5,6 +5,13 @@
*******************************************************************************/
package org.cryptomator.ui.model;
import org.cryptomator.common.FxApplicationScoped;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.keychain.KeychainAccess;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.IOException;
import java.nio.CharBuffer;
import java.util.Arrays;
@@ -14,15 +21,7 @@ import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.cryptomator.cryptolib.api.CryptoException;
import org.cryptomator.keychain.KeychainAccess;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
@FxApplicationScoped
public class AutoUnlocker {
private static final Logger LOG = LoggerFactory.getLogger(AutoUnlocker.class);
@@ -1,6 +1,7 @@
package org.cryptomator.ui.model;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.Environment;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.cryptofs.CryptoFileSystem;
import org.cryptomator.frontend.fuse.mount.CommandFailedException;
@@ -25,21 +26,20 @@ import java.util.Optional;
public class FuseVolume implements Volume {
private static final Logger LOG = LoggerFactory.getLogger(FuseVolume.class);
// TODO: dont use fixed Strings and rather set them in some system environment variables in the cryptomator installer and load those!
private static final String DEFAULT_MOUNTROOTPATH_MAC = System.getProperty("user.home") + "/Library/Application Support/Cryptomator";
private static final String DEFAULT_MOUNTROOTPATH_LINUX = System.getProperty("user.home") + "/.Cryptomator";
private static final int MAX_TMPMOUNTPOINT_CREATION_RETRIES = 10;
private static final boolean IS_MAC = System.getProperty("os.name").toLowerCase().contains("mac");
private final VaultSettings vaultSettings;
private final Environment environment;
private Mount fuseMnt;
private Path mountPoint;
private boolean createdTemporaryMountPoint;
@Inject
public FuseVolume(VaultSettings vaultSettings) {
public FuseVolume(VaultSettings vaultSettings, Environment environment) {
this.vaultSettings = vaultSettings;
this.environment = environment;
}
@Override
@@ -49,11 +49,9 @@ public class FuseVolume implements Volume {
Path customMountPoint = Paths.get(optionalCustomMountPoint.get());
checkProvidedMountPoint(customMountPoint);
this.mountPoint = customMountPoint;
this.createdTemporaryMountPoint = false;
LOG.debug("Successfully checked custom mount point: {}", mountPoint);
} else {
this.mountPoint = createTemporaryMountPoint();
this.createdTemporaryMountPoint = true;
this.mountPoint = prepareTemporaryMountPoint();
LOG.debug("Successfully created mount point: {}", mountPoint);
}
mount(fs.getPath("/"));
@@ -70,20 +68,31 @@ public class FuseVolume implements Volume {
}
}
private Path createTemporaryMountPoint() throws IOException {
Path parent = Paths.get(SystemUtils.IS_OS_MAC ? DEFAULT_MOUNTROOTPATH_MAC : DEFAULT_MOUNTROOTPATH_LINUX);
private Path prepareTemporaryMountPoint() throws IOException, VolumeException {
Path mountPoint = chooseNonExistingTemporaryMountPoint();
// https://github.com/osxfuse/osxfuse/issues/306#issuecomment-245114592:
// In order to allow non-admin users to mount FUSE volumes in `/Volumes`,
// starting with version 3.5.0, FUSE will create non-existent mount points automatically.
if (IS_MAC && mountPoint.getParent().equals(Paths.get("/Volumes"))) {
return mountPoint;
} else {
Files.createDirectories(mountPoint);
this.createdTemporaryMountPoint = true;
return mountPoint;
}
}
private Path chooseNonExistingTemporaryMountPoint() throws VolumeException {
Path parent = environment.getMountPointsDir().orElseThrow();
String basename = vaultSettings.getId();
for (int i = 0; i < MAX_TMPMOUNTPOINT_CREATION_RETRIES; i++) {
try {
Path mountPath = parent.resolve(basename + "_" + i);
Files.createDirectory(mountPath);
return mountPath;
} catch (FileAlreadyExistsException e) {
continue;
Path mountPoint = parent.resolve(basename + "_" + i);
if (Files.notExists(mountPoint)) {
return mountPoint;
}
}
LOG.error("Failed to create mount path at {}/{}_x. Giving up after {} attempts.", parent, basename, MAX_TMPMOUNTPOINT_CREATION_RETRIES);
throw new FileAlreadyExistsException(parent.toString() + "/" + basename);
LOG.error("Failed to find feasible mountpoint at {}/{}_x. Giving up after {} attempts.", parent, basename, MAX_TMPMOUNTPOINT_CREATION_RETRIES);
throw new VolumeException("Did not find feasible mount point.");
}
private void mount(Path root) throws VolumeException {
@@ -121,7 +130,7 @@ public class FuseVolume implements Volume {
} catch (CommandFailedException e) {
throw new VolumeException(e);
}
deleteTemporaryMountPoint();
cleanupTemporaryMountPoint();
}
@Override
@@ -132,10 +141,10 @@ public class FuseVolume implements Volume {
} catch (CommandFailedException e) {
throw new VolumeException(e);
}
deleteTemporaryMountPoint();
cleanupTemporaryMountPoint();
}
private void deleteTemporaryMountPoint() {
private void cleanupTemporaryMountPoint() {
if (createdTemporaryMountPoint) {
try {
Files.delete(mountPoint);
@@ -5,6 +5,8 @@
*******************************************************************************/
package org.cryptomator.ui.model;
import dagger.BindsInstance;
import org.cryptomator.common.settings.VaultSettings;
import org.cryptomator.ui.model.VaultModule.PerVault;
import dagger.Subcomponent;
@@ -17,7 +19,9 @@ public interface VaultComponent {
@Subcomponent.Builder
interface Builder {
Builder vaultModule(VaultModule module);
@BindsInstance
Builder vaultSettings(VaultSettings vaultSettings);
VaultComponent build();
}
@@ -8,15 +8,14 @@
*******************************************************************************/
package org.cryptomator.ui.model;
import org.cryptomator.common.FxApplicationScoped;
import org.cryptomator.common.settings.VaultSettings;
import javax.inject.Inject;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.cryptomator.common.settings.VaultSettings;
@Singleton
@FxApplicationScoped
public class VaultFactory {
private final VaultComponent.Builder vaultComponentBuilder;
@@ -32,8 +31,7 @@ public class VaultFactory {
}
private Vault create(VaultSettings vaultSettings) {
VaultModule module = new VaultModule(vaultSettings);
VaultComponent comp = vaultComponentBuilder.vaultModule(module).build();
VaultComponent comp = vaultComponentBuilder.vaultSettings(vaultSettings).build();
return comp.vault();
}
@@ -5,22 +5,19 @@
*******************************************************************************/
package org.cryptomator.ui.model;
import java.util.List;
import java.util.stream.IntStream;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import com.google.common.collect.Lists;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.transformation.TransformationList;
import org.cryptomator.common.FxApplicationScoped;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
@Singleton
import javax.inject.Inject;
import java.util.List;
import java.util.stream.IntStream;
@FxApplicationScoped
public class VaultList extends TransformationList<Vault, VaultSettings> {
private final VaultFactory vaultFactory;
@@ -5,37 +5,22 @@
*******************************************************************************/
package org.cryptomator.ui.model;
import dagger.Module;
import dagger.Provides;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VolumeImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Scope;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import javax.inject.Scope;
import org.cryptomator.common.settings.VolumeImpl;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.common.settings.VaultSettings;
import dagger.Module;
import dagger.Provides;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Module
public class VaultModule {
private static final Logger LOG = LoggerFactory.getLogger(VaultModule.class);
private final VaultSettings vaultSettings;
public VaultModule(VaultSettings vaultSettings) {
this.vaultSettings = Objects.requireNonNull(vaultSettings);
}
@Provides
@PerVault
public VaultSettings provideVaultSettings() {
return vaultSettings;
}
@Scope
@Documented
@@ -5,6 +5,11 @@
*******************************************************************************/
package org.cryptomator.ui.model;
import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.common.FxApplicationScoped;
import javax.inject.Inject;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.Set;
@@ -13,13 +18,7 @@ import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.StreamSupport;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.SystemUtils;
@Singleton
@FxApplicationScoped
public final class WindowsDriveLetters {
private static final Set<Character> D_TO_Z;
@@ -3,17 +3,18 @@
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.model;
package org.cryptomator.ui.model.upgrade;
import org.cryptomator.common.FxApplicationScoped;
import org.cryptomator.ui.model.Vault;
import javax.inject.Inject;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import javax.inject.Inject;
import javax.inject.Singleton;
@Singleton
@FxApplicationScoped
public class UpgradeStrategies {
private final Collection<UpgradeStrategy> strategies;
@@ -3,7 +3,7 @@
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.model;
package org.cryptomator.ui.model.upgrade;
import java.io.IOException;
import java.nio.file.Files;
@@ -19,6 +19,7 @@ import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.cryptolib.api.KeyFile;
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.model.Vault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -3,25 +3,24 @@
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.model;
package org.cryptomator.ui.model.upgrade;
import javafx.application.Platform;
import org.apache.commons.lang3.StringUtils;
import org.cryptomator.common.FxApplicationScoped;
import org.cryptomator.cryptolib.Cryptors;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.model.Vault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.lang3.StringUtils;
import org.cryptomator.cryptolib.Cryptors;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.ui.l10n.Localization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javafx.application.Platform;
@Singleton
@FxApplicationScoped
class UpgradeVersion3DropBundleExtension extends UpgradeStrategy {
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion3DropBundleExtension.class);
@@ -3,10 +3,20 @@
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.model;
package org.cryptomator.ui.model.upgrade;
import com.google.common.io.BaseEncoding;
import org.apache.commons.lang3.StringUtils;
import org.cryptomator.common.FxApplicationScoped;
import org.cryptomator.cryptolib.Cryptors;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.common.MessageDigestSupplier;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.model.Vault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.IOException;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
@@ -19,22 +29,13 @@ import java.util.EnumSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.io.BaseEncoding;
import org.apache.commons.lang3.StringUtils;
import org.cryptomator.cryptolib.Cryptors;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.common.MessageDigestSupplier;
import org.cryptomator.ui.l10n.Localization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* Contains the collective knowledge of all creatures who were alive during the development of vault format 3.
* This class uses no external classes from the crypto or shortening layer by purpose, so we don't need legacy code inside these.
*/
@Singleton
@FxApplicationScoped
class UpgradeVersion3to4 extends UpgradeStrategy {
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion3to4.class);
@@ -3,8 +3,18 @@
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.model;
package org.cryptomator.ui.model.upgrade;
import org.cryptomator.common.FxApplicationScoped;
import org.cryptomator.cryptolib.Cryptors;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.api.FileHeader;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.model.Vault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
@@ -18,21 +28,11 @@ import java.nio.file.attribute.BasicFileAttributes;
import java.util.EnumSet;
import java.util.regex.Pattern;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.cryptomator.cryptolib.Cryptors;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.api.FileHeader;
import org.cryptomator.ui.l10n.Localization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Contains the collective knowledge of all creatures who were alive during the development of vault format 3.
* This class uses no external classes from the crypto or shortening layer by purpose, so we don't need legacy code inside these.
*/
@Singleton
@FxApplicationScoped
class UpgradeVersion4to5 extends UpgradeStrategy {
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion4to5.class);
@@ -3,23 +3,23 @@
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the accompanying LICENSE file.
*******************************************************************************/
package org.cryptomator.ui.model;
import java.io.IOException;
import javax.inject.Inject;
import javax.inject.Singleton;
package org.cryptomator.ui.model.upgrade;
import org.cryptomator.common.FxApplicationScoped;
import org.cryptomator.cryptofs.migration.Migrators;
import org.cryptomator.cryptofs.migration.api.NoApplicableMigratorException;
import org.cryptomator.cryptolib.Cryptors;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
import org.cryptomator.ui.l10n.Localization;
import org.cryptomator.ui.model.Vault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Singleton
import javax.inject.Inject;
import java.io.IOException;
@FxApplicationScoped
class UpgradeVersion5toX extends UpgradeStrategy {
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion5toX.class);
@@ -8,24 +8,21 @@
*******************************************************************************/
package org.cryptomator.ui.util;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.cryptomator.ui.l10n.Localization;
import com.google.common.base.Strings;
import com.nulabinc.zxcvbn.Zxcvbn;
import javafx.geometry.Insets;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.paint.Color;
import org.cryptomator.common.FxApplicationScoped;
import org.cryptomator.ui.l10n.Localization;
@Singleton
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
@FxApplicationScoped
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
@@ -38,15 +38,15 @@
<children>
<!-- Row 0 -->
<Label text="%changePassword.label.oldPassword" GridPane.rowIndex="0" GridPane.columnIndex="0" cacheShape="true" cache="true" />
<SecPasswordField fx:id="oldPasswordField" GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
<SecPasswordField fx:id="oldPasswordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
<!-- Row 1 -->
<Label text="%changePassword.label.newPassword" GridPane.rowIndex="1" GridPane.columnIndex="0" cacheShape="true" cache="true" />
<SecPasswordField fx:id="newPasswordField" GridPane.rowIndex="1" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
<SecPasswordField fx:id="newPasswordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" GridPane.rowIndex="1" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
<!-- Row 2 -->
<Label text="%changePassword.label.retypePassword" GridPane.rowIndex="2" GridPane.columnIndex="0" cacheShape="true" cache="true" />
<SecPasswordField fx:id="retypePasswordField" GridPane.rowIndex="2" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
<SecPasswordField fx:id="retypePasswordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" GridPane.rowIndex="2" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
<!-- Row 3 -->
<VBox GridPane.columnIndex="1" GridPane.rowIndex="3" spacing="6.0">
@@ -36,11 +36,11 @@
<children>
<!-- Row 0 -->
<Label GridPane.rowIndex="0" GridPane.columnIndex="0" text="%initialize.label.password" cacheShape="true" cache="true" />
<SecPasswordField fx:id="passwordField" GridPane.rowIndex="0" GridPane.columnIndex="1" cacheShape="true" cache="true" />
<SecPasswordField fx:id="passwordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" GridPane.rowIndex="0" GridPane.columnIndex="1" cacheShape="true" cache="true" />
<!-- Row 1 -->
<Label GridPane.rowIndex="1" GridPane.columnIndex="0" text="%initialize.label.retypePassword" cacheShape="true" cache="true" />
<SecPasswordField fx:id="retypePasswordField" GridPane.rowIndex="1" GridPane.columnIndex="1" cacheShape="true" cache="true" />
<SecPasswordField fx:id="retypePasswordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" GridPane.rowIndex="1" GridPane.columnIndex="1" cacheShape="true" cache="true" />
<!-- Row 2 -->
<VBox GridPane.columnIndex="1" GridPane.rowIndex="2" spacing="6.0">
@@ -56,5 +56,4 @@
</children>
</GridPane>
<Label VBox.vgrow="NEVER" text="%settings.requiresRestartLabel" alignment="CENTER" cacheShape="true" cache="true" />
</VBox>
+3 -3
View File
@@ -34,7 +34,7 @@
<children>
<!-- Row 0 -->
<Label text="%unlock.label.password" GridPane.rowIndex="0" GridPane.columnIndex="0" cacheShape="true" cache="true" />
<SecPasswordField fx:id="passwordField" GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
<SecPasswordField fx:id="passwordField" capslockWarning="%ctrl.secPasswordField.capsLocked" nonPrintableCharsWarning="%ctrl.secPasswordField.nonPrintableChars" GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" cacheShape="true" cache="true" />
<!-- Row 1 -->
<HBox GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" spacing="12.0" alignment="CENTER_RIGHT" cacheShape="true" cache="true">
@@ -43,7 +43,7 @@
</HBox>
<!-- Row 3 -->
<Label fx:id="successMessage" cacheShape="true" cache="true" visible="true" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2"/>
<Text fx:id="messageText" cache="true" visible="true" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2"/>
<!-- Row 3 -->
<GridPane fx:id="advancedOptions" vgap="12.0" hgap="12.0" prefWidth="400.0" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" visible="false" cacheShape="true" cache="true">
@@ -112,7 +112,7 @@
<!-- Row 5 -->
<VBox GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" spacing="12.0" alignment="CENTER" cacheShape="true" cache="true">
<ProgressIndicator progress="-1" fx:id="progressIndicator" cacheShape="true" cache="true" cacheHint="SPEED" />
<Text fx:id="messageText" cache="true" />
<Text fx:id="progressText" cache="true" />
</VBox>
</children>
</GridPane>
@@ -33,7 +33,6 @@ unlock.choicebox.winDriveLetter.auto = دخول تلقائي
unlock.errorMessage.wrongPassword = كلمة مرور خاطئة
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = محفظة غير مدعومة . هذه المحفظة تم انشاؤها بواسطة اصدار اقدم من كريبتوماتور
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = محفظة غير مدعومة . هذه المحفظة تم انشاؤها بواسطة اصدار احدث من كريبتوماتور
unlock.messageLabel.startServerFailed = Starting WebDAV server failed.
# change_password.fxml
changePassword.label.oldPassword = كلمة المرور القديمة
changePassword.label.newPassword = كلمة المرور الجديدة
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = Throughput (MiB/s)
# settings.fxml
settings.version.label = الاصدار %s
settings.checkForUpdates.label = افحص التحديثات
settings.requiresRestartLabel = * Cryptomator needs to restart
# tray icon
tray.menu.open = فتح
tray.menu.quit = اغلاق
@@ -75,11 +73,10 @@ upgrade.version3to4.err.io = Migration failed due to an I/O Exception. See log f
upgrade.confirmation.label = Yes, I've made sure that synchronization has finished
unlock.label.savePassword = حفظ كلمة المرور
unlock.errorMessage.unauthenticVersionMac = Could not authenticate version MAC.
unlocked.label.mountFailed = Connecting drive failed
unlock.savePassword.delete.confirmation.title = حذف كلمة المرور المحفوظة
unlock.savePassword.delete.confirmation.header = Do you really want to delete the saved password of this vault?
unlock.savePassword.delete.confirmation.content = The saved password of this vault will be immediately deleted from your system keychain. If you'd like to save your password again, you'd have to unlock your vault with the "Save Password" option enabled.
settings.debugMode.label = Debug Mode *
settings.debugMode.label = Debug Mode
upgrade.version3dropBundleExtension.title = Vault Version 3 Upgrade (Drop Bundle Extension)
upgrade.version3to4.title = Vault Version 3 to 4 Upgrade
upgrade.version4to5.title = Vault Version 4 to 5 Upgrade
@@ -105,7 +102,7 @@ settings.volume.webdav = WebDAV
settings.volume.fuse = FUSE
unlock.successLabel.vaultCreated = Vault was successfully created.
unlock.successLabel.passwordChanged = Password was successfully changed.
unlock.successLabel.upgraded = Cryptomator was successfully upgraded.
unlock.successLabel.upgraded = Vault was successfully upgraded.
unlock.label.useOwnMountPath = Use Custom Mount Point
welcome.askForUpdateCheck.dialog.title = Update check
welcome.askForUpdateCheck.dialog.header = Enable the integrated update check?
@@ -114,12 +111,14 @@ settings.volume.dokany = Dokany
main.gracefulShutdown.dialog.title = Locking vault(s) failed
main.gracefulShutdown.dialog.header = Vault(s) in use
main.gracefulShutdown.dialog.content = One or more vaults are still in use by other programs. Please close them to allow Cryptomator to shut down properly, then try again.\n\nIf this doesn't work, Cryptomator can shut down forcefully, but this can incur data loss and is not recommended.
main.gracefulShutdown.button.tryAgain = Try again
main.gracefulShutdown.button.forceShutdown = Force shutdown
main.gracefulShutdown.button.tryAgain = Try Again
main.gracefulShutdown.button.forceShutdown = Force Shutdown
unlock.pendingMessage.unlocking = Unlocking vault...
unlock.failedDialog.title = Unlock failed
unlock.failedDialog.header = Unlock failed
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
unlock.label.useReadOnlyMode = Read-Only
unlock.label.chooseMountPath = Choose empty directory…
unlock.label.chooseMountPath = Choose empty directory…
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
@@ -33,7 +33,6 @@ unlock.choicebox.winDriveLetter.auto = Автоматично наименова
unlock.errorMessage.wrongPassword = Неправилна парола
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Неподържана версия. Този сейф е бил създаден със стара версия на Криптоматор.
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Неподържана версия. Този сейф е бил създаден с по-нова версия на Криптоматор.
unlock.messageLabel.startServerFailed = Неуспешно стартиране на WebDAV сървъра.
# change_password.fxml
changePassword.label.oldPassword = Стара парола
changePassword.label.newPassword = Нова парола
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = Скорост (MB/s)
# settings.fxml
settings.version.label = Версия %s
settings.checkForUpdates.label = Проверка за обновления
settings.requiresRestartLabel = * Криптоматор трябва да се рестартира
# tray icon
tray.menu.open = Отворяне
tray.menu.quit = Изход
@@ -75,11 +73,10 @@ upgrade.version3to4.err.io = Преместването е отменено по
upgrade.confirmation.label = Да, сигурен съм, че сихронизацията е приключила
unlock.label.savePassword = Запазване на парола
unlock.errorMessage.unauthenticVersionMac = Неуспешна оторизация на MAC версията
unlocked.label.mountFailed = Връзката с диска неуспешна
unlock.savePassword.delete.confirmation.title = Изтриване на запазената парола
unlock.savePassword.delete.confirmation.header = Неистина ли искате да изтриете запазената парола за този сейф?
unlock.savePassword.delete.confirmation.content = Запазената парола за този сейф ще бъде незабавно премахната от Вашата система. Ако желаете да запазите паролата отново, трябва да отключите сейса с пусната опция "Запазване на павола".
settings.debugMode.label = Режим за отстраняване на грешки *
settings.debugMode.label = Режим за отстраняване на грешки
upgrade.version3dropBundleExtension.title = Обновяване до сейф версия 3
upgrade.version3to4.title = Обновяване на сейф от 3-та до 4-та версия
upgrade.version4to5.title = Обновяване на сейф от 4-та до 5-та версия
@@ -114,12 +111,14 @@ settings.volume.dokany = Dokany
main.gracefulShutdown.dialog.title = Locking vault(s) failed
main.gracefulShutdown.dialog.header = Vault(s) in use
main.gracefulShutdown.dialog.content = One or more vaults are still in use by other programs. Please close them to allow Cryptomator to shut down properly, then try again.\n\nIf this doesn't work, Cryptomator can shut down forcefully, but this can incur data loss and is not recommended.
main.gracefulShutdown.button.tryAgain = Try again
main.gracefulShutdown.button.forceShutdown = Force shutdown
main.gracefulShutdown.button.tryAgain = Try Again
main.gracefulShutdown.button.forceShutdown = Force Shutdown
unlock.pendingMessage.unlocking = Unlocking vault...
unlock.failedDialog.title = Unlock failed
unlock.failedDialog.header = Unlock failed
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
unlock.label.useReadOnlyMode = Read-Only
unlock.label.chooseMountPath = Choose empty directory…
unlock.label.chooseMountPath = Choose empty directory…
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
+9 -10
View File
@@ -33,7 +33,6 @@ unlock.choicebox.winDriveLetter.auto = Assigna automàticament
unlock.errorMessage.wrongPassword = Contrasenya incorrecta
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = La caixa forta no és compatible. Aquesta caixa forta s'ha creat amb una versió anterior de Cryptomator.
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = La caixa forta no és compatible. Aquesta caixa forta s'ha creat amb una versió més nova de Cryptomator.
unlock.messageLabel.startServerFailed = S'ha produït un error en iniciar el servidor WebDAV.
# change_password.fxml
changePassword.label.oldPassword = Contrasenya antiga
changePassword.label.newPassword = Contrasenya nova
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = Velocitat de transferència de dades (MiB/s)
# settings.fxml
settings.version.label = Versió %s
settings.checkForUpdates.label = Comprova si hi ha actualitzacions
settings.requiresRestartLabel = És necessari reiniciar * Cryptomator
# tray icon
tray.menu.open = Obri
tray.menu.quit = Surt
@@ -75,11 +73,10 @@ upgrade.version3to4.err.io = Error en la migració degut a una excepció de E/S.
upgrade.confirmation.label = Sí, m'he assegurat que la sincronització hagi acabat
unlock.label.savePassword = Desa la contrasenya
unlock.errorMessage.unauthenticVersionMac = No s'ha pogut autenticar la versió de MAC.
unlocked.label.mountFailed = Ha fallat el muntatge de la unitat
unlock.savePassword.delete.confirmation.title = Elimina la contrasenya desada
unlock.savePassword.delete.confirmation.header = Esteu segur que voleu eliminar la contrasenya desada d'aquesta unitat?
unlock.savePassword.delete.confirmation.content = La contrasenya desada d'aquesta caixa forta va a ser eliminada inmediatament del clauer del seu sistema. Si voleu tornar a desar la contrasenya haureu de tornar a desbloquejar la vostra caixa forta i activar l'opció "Desa la contrasenya".
settings.debugMode.label = Mode de depuració *
settings.debugMode.label = Mode de depuració
upgrade.version3dropBundleExtension.title = Actualitza la caixa forta a la versió 3 (Drop Bundle Extension)
upgrade.version3to4.title = Actualitza la caixa forta de la versió 3 a la 4
upgrade.version4to5.title = Actualitza la caixa forta de la versió 4 a la 5
@@ -117,9 +114,11 @@ main.gracefulShutdown.dialog.content = Hi ha programes encara estan utilitzant u
main.gracefulShutdown.button.tryAgain = Torna-ho a intentar
main.gracefulShutdown.button.forceShutdown = Força l'aturada
unlock.pendingMessage.unlocking = La caixa forta s'està desbloquejant...
unlock.failedDialog.title = Unlock failed
unlock.failedDialog.header = Unlock failed
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
unlock.label.useReadOnlyMode = Read-Only
unlock.label.chooseMountPath = Choose empty directory…
unlock.failedDialog.title = El desbloqueig ha fallat
unlock.failedDialog.header = El desbloqueig ha fallat
unlock.failedDialog.content.mountPathNonExisting = El punt de muntatge no existeix.
unlock.failedDialog.content.mountPathNotEmpty = El punt de muntatge no és buit
unlock.label.useReadOnlyMode = Només de lectura
unlock.label.chooseMountPath = Trieu un directori buit...
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
@@ -33,7 +33,6 @@ unlock.choicebox.winDriveLetter.auto = Přiřadit automaticky
unlock.errorMessage.wrongPassword = Chybné heslo
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Nepodporovaná verze trezoru. Byl vytvořen ve starším Cryptomator.
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Nepodporovaná verze trezoru. Byl vytvořen v novějším Cryptomator.
unlock.messageLabel.startServerFailed = Spuštění WebDAV serveru se nezdařílo.
# change_password.fxml
changePassword.label.oldPassword = Původní heslo
changePassword.label.newPassword = Nové heslo
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = Propustnost (MiB/s)
# settings.fxml
settings.version.label = Verze %s
settings.checkForUpdates.label = Zjistit případné aktualizace
settings.requiresRestartLabel = * Vyžaduje restart Cryptomator
# tray icon
tray.menu.open = Otevřít
tray.menu.quit = Ukončit
@@ -76,11 +74,10 @@ upgrade.version3to4.err.io = Převod se nezdařil kvůli výjimce na vst./výst.
upgrade.confirmation.label = Ano, je ověřeno, že synchronizace byla dokončena
unlock.label.savePassword = Uložit heslo
unlock.errorMessage.unauthenticVersionMac = Nedaří se ověřit MAC funkci verze.
unlocked.label.mountFailed = Připojení jednotky se nezdařilo
unlock.savePassword.delete.confirmation.title = Smazat uložené heslo
unlock.savePassword.delete.confirmation.header = Opravdu chcete smazat uložené heslo pro tento trezor?
unlock.savePassword.delete.confirmation.content = Uložené heslo k tomuto trezoru bude okamžitě vymazáno ze systémové klíčenky. Pokud ho tam budete chtít znovu uložit, bude třeba trezor odemknout se zapnutou volbou „Uložit heslo“.
settings.debugMode.label = Ladící režim *
settings.debugMode.label = Ladící režim
# Extension of what please? File, protocol, aplication extension for example? And bundle of what with what? Thanks :)
upgrade.version3dropBundleExtension.title = Přechod z verze 3 trezoru na novější (odebrat příp. .cryptomator a registraci bundle v macOS)
upgrade.version3to4.title = Aktualizace trezoru z verze 3 na 4
@@ -124,4 +121,6 @@ unlock.failedDialog.header = Unlock failed
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
unlock.label.useReadOnlyMode = Read-Only
unlock.label.chooseMountPath = Choose empty directory…
unlock.label.chooseMountPath = Choose empty directory…
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
@@ -33,7 +33,6 @@ unlock.choicebox.winDriveLetter.auto = Tildel automatisk
unlock.errorMessage.wrongPassword = Forkert adgangskode
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Ikke-understøttet Vault. Denne Vault er blevet oprettet med en ældre version af Cryptomator.
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Ikke-understøttet Vault. Denne Vault er blevet oprettet med en nyere version af Cryptomator.
unlock.messageLabel.startServerFailed = Kunne ikke starte WebDAV server.
# change_password.fxml
changePassword.label.oldPassword = Gammel adgangskode
changePassword.label.newPassword = Ny adgangskode
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = Throughput (MiB/s)
# settings.fxml
settings.version.label = Version %s
settings.checkForUpdates.label = Tjek for opdateringer
settings.requiresRestartLabel = * Cryptomator skal genstartes
# tray icon
tray.menu.open = Åbn
tray.menu.quit = Afslut
@@ -75,11 +73,10 @@ upgrade.version3to4.err.io = Migrering fejlede pga. en I/O fejl. Se logfilen for
upgrade.confirmation.label = Ja, jeg har sikret mig at al synkronisering er gennemført.
unlock.label.savePassword = Gem adgangskode
unlock.errorMessage.unauthenticVersionMac = Kunne ikke autentificere versions-MAC
unlocked.label.mountFailed = Montering af drev fejlede
unlock.savePassword.delete.confirmation.title = Slet gemt adgangskode
unlock.savePassword.delete.confirmation.header = Er du sikker på at du vil slette den til Vault'en gemte adgangskode?
unlock.savePassword.delete.confirmation.content = Den til Vault'en gemte adgangskode vil blive slettet fra dit systems keychain med øjeblikkelig virkning. Hvis du vil gemme din adgangskode på ny, skal du låse din Vault op med indstillingen "Gem adgangskode" slået til.
settings.debugMode.label = Debug Tilstand *
settings.debugMode.label = Debug Tilstand
upgrade.version3dropBundleExtension.title = Vault Version 3 Opgradering (Drop Bundle Extension)
upgrade.version3to4.title = Vault Version 3 til 4 Opgradering
upgrade.version4to5.title = Vault Version 4 til 5 Opgradering
@@ -105,7 +102,7 @@ settings.volume.webdav = WebDAV
settings.volume.fuse = FUSE
unlock.successLabel.vaultCreated = Vault was successfully created.
unlock.successLabel.passwordChanged = Password was successfully changed.
unlock.successLabel.upgraded = Cryptomator was successfully upgraded.
unlock.successLabel.upgraded = Vault was successfully upgraded.
unlock.label.useOwnMountPath = Use Custom Mount Point
welcome.askForUpdateCheck.dialog.title = Update check
welcome.askForUpdateCheck.dialog.header = Enable the integrated update check?
@@ -114,12 +111,14 @@ settings.volume.dokany = Dokany
main.gracefulShutdown.dialog.title = Locking vault(s) failed
main.gracefulShutdown.dialog.header = Vault(s) in use
main.gracefulShutdown.dialog.content = One or more vaults are still in use by other programs. Please close them to allow Cryptomator to shut down properly, then try again.\n\nIf this doesn't work, Cryptomator can shut down forcefully, but this can incur data loss and is not recommended.
main.gracefulShutdown.button.tryAgain = Try again
main.gracefulShutdown.button.forceShutdown = Force shutdown
main.gracefulShutdown.button.tryAgain = Try Again
main.gracefulShutdown.button.forceShutdown = Force Shutdown
unlock.pendingMessage.unlocking = Unlocking vault...
unlock.failedDialog.title = Unlock failed
unlock.failedDialog.header = Unlock failed
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
unlock.label.useReadOnlyMode = Read-Only
unlock.label.chooseMountPath = Choose empty directory…
unlock.label.chooseMountPath = Choose empty directory…
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
@@ -33,7 +33,6 @@ unlock.choicebox.winDriveLetter.auto = Automatisch ermitteln
unlock.errorMessage.wrongPassword = Falsches Passwort
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Tresor nicht unterstützt. Der Tresor wurde mit einer älteren Version von Cryptomator erstellt.
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Tresor nicht unterstützt. Der Tresor wurde mit einer neueren Version von Cryptomator erstellt.
unlock.messageLabel.startServerFailed = Starten des WebDAV-Servers fehlgeschlagen.
# change_password.fxml
changePassword.label.oldPassword = Altes Passwort
changePassword.label.newPassword = Neues Passwort
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = Durchsatz (MiB/s)
# settings.fxml
settings.version.label = Version %s
settings.checkForUpdates.label = Auf Updates prüfen
settings.requiresRestartLabel = * benötigt Neustart von Cryptomator
# tray icon
tray.menu.open = Öffnen
tray.menu.quit = Beenden
@@ -75,11 +73,10 @@ upgrade.version3to4.err.io = Migration aufgrund eines I/O-Fehlers fehlgeschlagen
upgrade.confirmation.label = Ja, die Synchronisation ist abgeschlossen
unlock.label.savePassword = Passwort speichern
unlock.errorMessage.unauthenticVersionMac = Versions-MAC konnte nicht authentifiziert werden.
unlocked.label.mountFailed = Verbinden des Laufwerks fehlgeschlagen
unlock.savePassword.delete.confirmation.title = Gespeichertes Passwort löschen
unlock.savePassword.delete.confirmation.header = Möchten Sie das gespeicherte Passwort von diesem Tresor wirklich löschen?
unlock.savePassword.delete.confirmation.content = Das gespeicherte Passwort von diesem Tresor wird sofort aus Ihrem System-Schlüsselbund gelöscht. Falls Sie das Passwort erneut speichern möchten, müssen Sie den Tresor entsperren und dabei die "Passwort speichern"-Option aktiviert haben.
settings.debugMode.label = Debug-Modus *
settings.debugMode.label = Debug-Modus
upgrade.version3dropBundleExtension.title = Upgrade Tresor-Version 3 (Entfall der Bundle-Extension)
upgrade.version3to4.title = Upgrade Tresor-Version 3 zu 4
upgrade.version4to5.title = Upgrade Tresor-Version 4 zu 5
@@ -105,7 +102,7 @@ settings.volume.webdav = WebDAV
settings.volume.fuse = FUSE
unlock.successLabel.vaultCreated = Der Tresor wurde erfolgreich erstellt.
unlock.successLabel.passwordChanged = Das Passwort wurde erfolgreich geändert.
unlock.successLabel.upgraded = Das Cryptomator Upgrade wurde erfolgreich abgeschlossen.
unlock.successLabel.upgraded = Der Tresor wurde erfolgreich aktualisiert.
unlock.label.useOwnMountPath = Eigenes Laufwerksverzeichnis nutzen
welcome.askForUpdateCheck.dialog.title = Auf Updates prüfen
welcome.askForUpdateCheck.dialog.header = Eingebaute Update-Prüfung aktivieren?
@@ -122,4 +119,6 @@ unlock.failedDialog.header = Entsperren fehlgeschlagen
unlock.failedDialog.content.mountPathNonExisting = Laufwerksverzeichnis existiert nicht.
unlock.failedDialog.content.mountPathNotEmpty = Laufwerksverzeichnis ist nicht leer.
unlock.label.useReadOnlyMode = Nur lesend
unlock.label.chooseMountPath = Leeren Ordner auswählen…
unlock.label.chooseMountPath = Leeren Ordner auswählen…
ctrl.secPasswordField.nonPrintableChars = Das Passwort enthält Steuerzeichen.\nEmpfehlung\: Entfernen Sie diese, um die Kompatibilität mit anderen Clients sicherzustellen.
ctrl.secPasswordField.capsLocked = Die Feststelltaste ist aktiviert.
@@ -7,6 +7,9 @@
app.name=Cryptomator
ctrl.secPasswordField.nonPrintableChars=Password contains control characters.\nRecommendation: Remove them to ensure compatibility with other clients.
ctrl.secPasswordField.capsLocked=Caps Lock is activated.
# main.fxml
main.emptyListInstructions=Click here to add a vault
main.directoryList.contextMenu.remove=Remove from List
@@ -22,8 +25,8 @@ main.createVault.nonEmptyDir.content=The selected directory already contains fil
main.gracefulShutdown.dialog.title=Locking vault(s) failed
main.gracefulShutdown.dialog.header=Vault(s) in use
main.gracefulShutdown.dialog.content=One or more vaults are still in use by other programs. Please close them to allow Cryptomator to shut down properly, then try again.\n\nIf this doesn't work, Cryptomator can shut down forcefully, but this can incur data loss and is not recommended.
main.gracefulShutdown.button.tryAgain=Try again
main.gracefulShutdown.button.forceShutdown=Force shutdown
main.gracefulShutdown.button.tryAgain=Try Again
main.gracefulShutdown.button.forceShutdown=Force Shutdown
# welcome.fxml
welcome.checkForUpdates.label.currentlyChecking=Checking for Updates...
@@ -92,10 +95,9 @@ unlock.errorMessage.unlockFailed=Unlock failed. See log file for details.
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware=Unsupported vault. This vault has been created with an older version of Cryptomator.
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault=Unsupported vault. This vault has been created with a newer version of Cryptomator.
unlock.errorMessage.unauthenticVersionMac=Could not authenticate version MAC.
unlock.messageLabel.startServerFailed=Starting WebDAV server failed.
unlock.successLabel.vaultCreated=Vault was successfully created.
unlock.successLabel.passwordChanged=Password was successfully changed.
unlock.successLabel.upgraded=Cryptomator was successfully upgraded.
unlock.successLabel.upgraded=Vault was successfully upgraded.
unlock.failedDialog.title=Unlock failed
unlock.failedDialog.header=Unlock failed
@@ -115,7 +117,6 @@ changePassword.errorMessage.decryptionFailed=Decryption failed
# unlocked.fxml
unlocked.button.lock=Lock Vault
unlocked.moreOptions.reveal=Reveal Drive
unlocked.label.mountFailed=Connecting drive failed
unlocked.label.revealFailed=Command failed
unlocked.label.unmountFailed=Ejecting drive failed
unlocked.label.statsEncrypted=encrypted
@@ -132,8 +133,7 @@ settings.webdav.port.label=WebDAV Port
settings.webdav.port.prompt=0 = Choose automatically
settings.webdav.port.apply=Apply
settings.webdav.prefGvfsScheme.label=WebDAV Scheme
settings.debugMode.label=Debug Mode *
settings.requiresRestartLabel=* Cryptomator needs to restart
settings.debugMode.label=Debug Mode
settings.volume.label=Preferred Volume Type
settings.volume.webdav=WebDAV
settings.volume.fuse=FUSE
@@ -35,7 +35,6 @@ unlock.choicebox.winDriveLetter.auto = Asignar automáticamente
unlock.errorMessage.wrongPassword = Contraseña incorrecta
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Caja fuerte no soportada. Esta caja se ha creado con una versión anterior de Cryptomator.
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Caja fuerte no soportada. Esta caja se ha creado con una versión más moderna de Cryptomator.
unlock.messageLabel.startServerFailed = Error al iniciar el servidor de WebDAV.
# change_password.fxml
changePassword.label.oldPassword = Contraseña antigua
# Can also use "current password" = "contraseña actual"
@@ -56,7 +55,6 @@ unlocked.ioGraph.yAxis.label = Rendimiento (MiB/s)
# settings.fxml
settings.version.label = Versión %s
settings.checkForUpdates.label = Comprobar actualizaciones
settings.requiresRestartLabel = * Cryptomator necesita reiniciarse
# tray icon
tray.menu.open = Abrir
tray.menu.quit = Salir
@@ -78,11 +76,10 @@ upgrade.version3to4.err.io = Error en la migración debido a una excepción de E
upgrade.confirmation.label = Sí, me he asegurado de que la sincronización ha terminado
unlock.label.savePassword = Guardar contraseña
unlock.errorMessage.unauthenticVersionMac = No se pudo autentificar la versión de MAC.
unlocked.label.mountFailed = Error al montar la unidad
unlock.savePassword.delete.confirmation.title = Borrar contraseña guardada
unlock.savePassword.delete.confirmation.header = ¿Quiere realmente borrar la contraseña guardada de esta unidad?
unlock.savePassword.delete.confirmation.content = La contraseña guardada de esta caja fuerte, será borrada inmediatamente del sistema de claves. Si quiere guardar su contraseña de nuevo, tiene que volver a desbloquear la caja fuerte marcando la opción de "Guardar contraseña".
settings.debugMode.label = Modo depuración *
settings.debugMode.label = Modo depuración
upgrade.version3dropBundleExtension.title = Actualizar caja fuerte a la versión 3 (Drop Bundle Extension)
upgrade.version3to4.title = Actualizar caja fuerte de versión 3 a 4
upgrade.version4to5.title = Actualizar caja fuerte de versión 4 a 5
@@ -125,4 +122,6 @@ unlock.failedDialog.header = Unlock failed
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
unlock.label.useReadOnlyMode = Read-Only
unlock.label.chooseMountPath = Choose empty directory…
unlock.label.chooseMountPath = Choose empty directory…
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
@@ -33,7 +33,6 @@ unlock.choicebox.winDriveLetter.auto = Assigner automatiquement
unlock.errorMessage.wrongPassword = Mot de passe incorrect
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Coffre-fort non supporté. Ce coffre a été créé avec une ancienne version de Cryptomator.
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Coffre-fort non supporté. Ce coffre a été créé avec une version de Cryptomator plus récente.
unlock.messageLabel.startServerFailed = Echec de démarrage du serveur WebDAV.
# change_password.fxml
changePassword.label.oldPassword = Ancien mot de passe
changePassword.label.newPassword = Nouveau mot de passe
@@ -56,7 +55,6 @@ unlocked.ioGraph.yAxis.label = Débit (MiB/s)
# settings.fxml
settings.version.label = Version %s
settings.checkForUpdates.label = Vérifier les mises à jour
settings.requiresRestartLabel = * Redémarrage requis
# tray icon
tray.menu.open = Ouvrir
tray.menu.quit = Quitter
@@ -78,11 +76,10 @@ upgrade.version3to4.err.io = La migration a échoué à cause d'une erreur d'ent
upgrade.confirmation.label = Oui, je suis certain que la synchronisation est terminée
unlock.label.savePassword = Se souvenir du mot de passe
unlock.errorMessage.unauthenticVersionMac = Impossible d'authentifier la version MAC
unlocked.label.mountFailed = Echec de connexion au lecteur
unlock.savePassword.delete.confirmation.title = Supprimer le mot de passe sauvegardé
unlock.savePassword.delete.confirmation.header = Voulez vous vraiment oublier le mot de passe de ce coffre-fort ?
unlock.savePassword.delete.confirmation.content = Le mot de passe de ce coffre sera supprimé immédiatement du trousseau. Si vous voulez le sauvegarder à nouveau, vous devrez cocher la case "Se souvenir du mot de passe" lors du déverrouillage du coffre.
settings.debugMode.label = Mode Débug *
settings.debugMode.label = Mode Débug
upgrade.version3dropBundleExtension.title = Mise à jour du coffre-fort (en version 3 extension "Drop Bundle")
upgrade.version3to4.title = Mise à jour de la version du coffre-fort (v3 à v4)
upgrade.version4to5.title = Mise à jour de la version du coffre-fort (v4 à v5)
@@ -125,4 +122,6 @@ unlock.failedDialog.header = Unlock failed
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
unlock.label.useReadOnlyMode = Read-Only
unlock.label.chooseMountPath = Choose empty directory…
unlock.label.chooseMountPath = Choose empty directory…
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
@@ -33,7 +33,6 @@ unlock.choicebox.winDriveLetter.auto = Automatikus hozzárendelés
unlock.errorMessage.wrongPassword = Hibás jelszó
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Nem támogatott széf. Ez a széf a Cryptomator egy korábbi verziójával került létrehozásra.
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Nem támogatott széf. Ez a széf a Cryptomator egy újabb verziójával került létrehozásra.
unlock.messageLabel.startServerFailed = WebDAV szerver indítása sikertelen.
# change_password.fxml
changePassword.label.oldPassword = Régi jelszó
changePassword.label.newPassword = Új jelszó
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = Teljesítmény (MiB/s)
# settings.fxml
settings.version.label = Verzió\: %s
settings.checkForUpdates.label = Frissítések keresése
settings.requiresRestartLabel = * Cryptomator újraindítása szükséges
# tray icon
tray.menu.open = Megnyit
tray.menu.quit = Kilépés
@@ -75,11 +73,10 @@ upgrade.version3to4.err.io = Migráció meghíusúlt egy I/O kivétel miatt. Ké
upgrade.confirmation.label = Igen, meggyőződtem a szinkronizáció befejeztéről
unlock.label.savePassword = Jelszó mentése
unlock.errorMessage.unauthenticVersionMac = Nem lehet a MAC verziót azonosítani.
unlocked.label.mountFailed = Meghajtó csatlakoztatása sikertelen
unlock.savePassword.delete.confirmation.title = Mentett jelszó törlése
unlock.savePassword.delete.confirmation.header = Biztosan el akarod távolítani a széfhez tartozó mentett jelszót?
unlock.savePassword.delete.confirmation.content = A széf mentett jelszava rögtön törlése kerül. Ha újra el szeretnéd menteni a jelszavad, a széfet a "Jelszó mentése" opció engedélyezése mellett kell feloldani.
settings.debugMode.label = Hibakeresési mód *
settings.debugMode.label = Hibakeresési mód
upgrade.version3dropBundleExtension.title = Vault Version 3 Upgrade (Drop Bundle Extension)
upgrade.version3to4.title = Széf verziófrissítése 3-ról 4-re
upgrade.version4to5.title = Széf verziófrissítése 4-ről 5-re
@@ -115,11 +112,13 @@ main.gracefulShutdown.dialog.title = Locking vault(s) failed
main.gracefulShutdown.dialog.header = Vault(s) in use
main.gracefulShutdown.dialog.content = One or more vaults are still in use by other programs. Please close them to allow Cryptomator to shut down properly, then try again.\n\nIf this doesn't work, Cryptomator can shut down forcefully, but this can incur data loss and is not recommended.
main.gracefulShutdown.button.tryAgain = Próbáld újra
main.gracefulShutdown.button.forceShutdown = Force shutdown
main.gracefulShutdown.button.forceShutdown = Force Shutdown
unlock.pendingMessage.unlocking = Unlocking vault...
unlock.failedDialog.title = Unlock failed
unlock.failedDialog.header = Unlock failed
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
unlock.label.useReadOnlyMode = Read-Only
unlock.label.chooseMountPath = Choose empty directory…
unlock.label.chooseMountPath = Choose empty directory…
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
@@ -33,7 +33,6 @@ unlock.choicebox.winDriveLetter.auto = Assegna automaticamente
unlock.errorMessage.wrongPassword = Password errata
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Vault non supportato. Questo vault è stato creato con una versione di Cryptomator più vecchia.
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Vault non supportato. Questo vault è stato creato con una versione di Cryptomator più recente.
unlock.messageLabel.startServerFailed = Avvio del server WebDAV fallito
# change_password.fxml
changePassword.label.oldPassword = Vecchia password
changePassword.label.newPassword = Nuova password
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = Volume dati (MiB/s)
# settings.fxml
settings.version.label = Versione %s
settings.checkForUpdates.label = Verifica aggiornamenti
settings.requiresRestartLabel = * Cryptomator deve essere riavviato
# tray icon
tray.menu.open = Apri
tray.menu.quit = Chiudi
@@ -75,11 +73,10 @@ upgrade.version3to4.err.io = Migrazione fallita a causa di una eccezione I/O. Ve
upgrade.confirmation.label = Si, sono sicuro che la sincronizzazione e' terminata
unlock.label.savePassword = Salva Password
unlock.errorMessage.unauthenticVersionMac = Non riesco ad autenticare la versione MAC.
unlocked.label.mountFailed = Tentativo di connessione drive fallito
unlock.savePassword.delete.confirmation.title = Cancella la password salvata
unlock.savePassword.delete.confirmation.header = Vuoi veramente cancellare le password salvate in questo vault?
unlock.savePassword.delete.confirmation.content = Le password salvate in questo vault saranno immediatamente cancellate dal sistema di chiavi. Se vuoi salvare nuovamente la tua password, devi sbloccare il tuo vault con l'opzione "Salva password" abilitata.
settings.debugMode.label = Modalita' debug *
settings.debugMode.label = Modalita' debug
upgrade.version3dropBundleExtension.title = Aggiornamento Vault versione 3 ( Estensione Drop bundle )
upgrade.version3to4.title = Aggiornamento Vault da versione 3 a 4
upgrade.version4to5.title = Aggiornamento da versione 4 a 5
@@ -114,12 +111,14 @@ settings.volume.dokany = Dokany
main.gracefulShutdown.dialog.title = Locking vault(s) failed
main.gracefulShutdown.dialog.header = Vault(s) in use
main.gracefulShutdown.dialog.content = One or more vaults are still in use by other programs. Please close them to allow Cryptomator to shut down properly, then try again.\n\nIf this doesn't work, Cryptomator can shut down forcefully, but this can incur data loss and is not recommended.
main.gracefulShutdown.button.tryAgain = Try again
main.gracefulShutdown.button.forceShutdown = Force shutdown
main.gracefulShutdown.button.tryAgain = Try Again
main.gracefulShutdown.button.forceShutdown = Force Shutdown
unlock.pendingMessage.unlocking = Unlocking vault...
unlock.failedDialog.title = Unlock failed
unlock.failedDialog.header = Unlock failed
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
unlock.label.useReadOnlyMode = Read-Only
unlock.label.chooseMountPath = Choose empty directory…
unlock.label.chooseMountPath = Choose empty directory…
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
@@ -33,7 +33,6 @@ unlock.choicebox.winDriveLetter.auto = 自動的に割り当てる
unlock.errorMessage.wrongPassword = パスワードが無効です
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = サポートされない金庫です。この金庫は古いバージョンの Cryptomator を使用して作成されました。
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = サポートされない金庫です。この金庫は新しいバージョンの Cryptomator を使用して作成されました。
unlock.messageLabel.startServerFailed = WebDAV サーバーの起動に失敗しました。
# change_password.fxml
changePassword.label.oldPassword = 古いパスワード
changePassword.label.newPassword = 新しいパスワード
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = スループット (MiB/s)
# settings.fxml
settings.version.label = バージョン %s
settings.checkForUpdates.label = 最新版のチェック
settings.requiresRestartLabel = *Cryptomatorの再起動が必要
# tray icon
tray.menu.open = 開く
tray.menu.quit = 閉じる
@@ -75,11 +73,10 @@ upgrade.version3to4.err.io = I/O の例外で移行に失敗しました。詳
upgrade.confirmation.label = はい、同期が完了していることを確認しました。
unlock.label.savePassword = パスワードを保存
unlock.errorMessage.unauthenticVersionMac = MAC バージョンを認証できません。
unlocked.label.mountFailed = ドライブの接続に失敗
unlock.savePassword.delete.confirmation.title = 保存済みのパスワードを削除
unlock.savePassword.delete.confirmation.header = 本当にこの金庫の保存済みパスワードを削除しますか?
unlock.savePassword.delete.confirmation.content = この金庫の保存済みパスワードは、直ちにシステムのキーチェーンから削除されます。もう一度パスワードを保存するには、"Save Password" オプションを有効にして金庫を解錠する必要があります。
settings.debugMode.label = デバッグモード *
settings.debugMode.label = デバッグモード
upgrade.version3dropBundleExtension.title = 金庫をバージョン 3 にアップグレード(Drop Bundle Extension)
upgrade.version3to4.title = 金庫をバージョン 3 から 4 にアップグレード
upgrade.version4to5.title = 金庫をバージョン 4 から 5 にアップグレード
@@ -122,4 +119,6 @@ unlock.failedDialog.header = 施錠に失敗しました
unlock.failedDialog.content.mountPathNonExisting = マウントポイントが存在しません。
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
unlock.label.useReadOnlyMode = Read-Only
unlock.label.chooseMountPath = Choose empty directory…
unlock.label.chooseMountPath = Choose empty directory…
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
@@ -33,7 +33,6 @@ unlock.choicebox.winDriveLetter.auto = 자동으로 할당
unlock.errorMessage.wrongPassword = 잘못된 비밀번호
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = 지원되지 않는 보관함. 이 보관함은 이전 버전의 Cryptomator에서 생성되었습니다.
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = 지원되지 않는 보관함. 이 보관함은 상위 버전의 Cryptomator에서 생성되었습니다.
unlock.messageLabel.startServerFailed = WedDAV 서버 시작 실패
# change_password.fxml
changePassword.label.oldPassword = 이전 비밀번호
changePassword.label.newPassword = 새로운 비밀번호
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = 처리량 (MiB/s)
# settings.fxml
settings.version.label = 버전 %s
settings.checkForUpdates.label = 업데이트 확인
settings.requiresRestartLabel = * Cryptomator 재시작 필요
# tray icon
tray.menu.open = 열기
tray.menu.quit = 종료
@@ -75,11 +73,10 @@ upgrade.version3to4.err.io = I/O 예외 문제로 마이그레이션이 실패
upgrade.confirmation.label = 네. 동기화가 완료되었음을 확인하였습니다.
unlock.label.savePassword = 비밀번호 저장
unlock.errorMessage.unauthenticVersionMac = 인증할 수 없는 버전의 MAC(Message Authentication Code)입니다
unlocked.label.mountFailed = 드라이브 연결 실패
unlock.savePassword.delete.confirmation.title = 저장된 비밀번호 삭제
unlock.savePassword.delete.confirmation.header = 정말로 이 보관함의 저장된 비밀번호를 지우시겠습니까?
unlock.savePassword.delete.confirmation.content = 이 보관함의 저장된 비밀번호는 당신의 시스템 키체인에서 즉시 삭제될 것입니다. 다시 비밀번호를 저장하고 싶으시다면, 보관함을 열 때 "비밀번호 저장" 옵션을 활성화해야 합니다
settings.debugMode.label = 디버그 모드 *
settings.debugMode.label = 디버그 모드
upgrade.version3dropBundleExtension.title = 버전 3 보관함 업그레이드 (Drop Bundle Extension)
upgrade.version3to4.title = 보관함 버전 3에서 4로 업그레이드
upgrade.version4to5.title = 보관함 버전 4에서 5로 업그레이드
@@ -122,4 +119,6 @@ unlock.failedDialog.header = 잠금 해제 실패
unlock.failedDialog.content.mountPathNonExisting = 마운트 지점이 존재하지 않습니다.
unlock.failedDialog.content.mountPathNotEmpty = 마운트 지점이 비어있지 않습니다.
unlock.label.useReadOnlyMode = Read-Only
unlock.label.chooseMountPath = Choose empty directory…
unlock.label.chooseMountPath = Choose empty directory…
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
@@ -33,7 +33,6 @@ unlock.choicebox.winDriveLetter.auto = Piešķirt automātiski
unlock.errorMessage.wrongPassword = Nepareiza parole
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Neatbastīta glabātuve. Šī glabātuve ir veidota ar vecāku Cryptomator versiju.
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Neatbastīta glabātuve. Šī glabātuve ir veidota ar jaunāku Cryptomator versiju.
unlock.messageLabel.startServerFailed = WebDAV servera palaišana neizdevās.
# change_password.fxml
changePassword.label.oldPassword = Vecā parole
changePassword.label.newPassword = Jaunā parole
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = Caurlaidība (MiB/s)
# settings.fxml
settings.version.label = Versija %s
settings.checkForUpdates.label = Meklēt atjauninājumus
settings.requiresRestartLabel = * Cryptomator nepieciešams restarts
# tray icon
tray.menu.open = Atvērt
tray.menu.quit = Iziet
@@ -76,11 +74,10 @@ upgrade.version3to4.err.io = Migrācija neizdevās dēļ I/O izņēmuma. Sīkāk
upgrade.confirmation.label = Jā, esmu pārliecinājies, ka sinhronizācija ir pabeigta.
unlock.label.savePassword = Saglabāt paroli
unlock.errorMessage.unauthenticVersionMac = Nevar autentificēt versijas MAC
unlocked.label.mountFailed = Diska pievienošana neizdevās
unlock.savePassword.delete.confirmation.title = Dzēst saglabāto paroli
unlock.savePassword.delete.confirmation.header = Vai tiešām vēlies dzēst saglabāto paroli šai glabātuvei?
unlock.savePassword.delete.confirmation.content = Saglabātā parole nekavējoties tiks izdzēsta. Ja vēlēsies paroli atkārtoti saglabāt, tev būs jāatslēdz glabātuve ar ieslēgtu opciju "Saglabāt paroli".
settings.debugMode.label = Atkļūdošanas režīms *
settings.debugMode.label = Atkļūdošanas režīms
upgrade.version3dropBundleExtension.title = Glabātuves 3 versijas atjauninājums (atmet paplašinājumu)
upgrade.version3to4.title = Glabātuves versijas 3 uz 4 atjauninājums
upgrade.version4to5.title = Glabātuves versijas 4 uz 5 atjauninājums
@@ -106,7 +103,7 @@ settings.volume.webdav = WebDAV
settings.volume.fuse = FUSE
unlock.successLabel.vaultCreated = Vault was successfully created.
unlock.successLabel.passwordChanged = Password was successfully changed.
unlock.successLabel.upgraded = Cryptomator was successfully upgraded.
unlock.successLabel.upgraded = Vault was successfully upgraded.
unlock.label.useOwnMountPath = Use Custom Mount Point
welcome.askForUpdateCheck.dialog.title = Update check
welcome.askForUpdateCheck.dialog.header = Enable the integrated update check?
@@ -115,12 +112,14 @@ settings.volume.dokany = Dokany
main.gracefulShutdown.dialog.title = Locking vault(s) failed
main.gracefulShutdown.dialog.header = Vault(s) in use
main.gracefulShutdown.dialog.content = One or more vaults are still in use by other programs. Please close them to allow Cryptomator to shut down properly, then try again.\n\nIf this doesn't work, Cryptomator can shut down forcefully, but this can incur data loss and is not recommended.
main.gracefulShutdown.button.tryAgain = Try again
main.gracefulShutdown.button.forceShutdown = Force shutdown
main.gracefulShutdown.button.tryAgain = Try Again
main.gracefulShutdown.button.forceShutdown = Force Shutdown
unlock.pendingMessage.unlocking = Unlocking vault...
unlock.failedDialog.title = Unlock failed
unlock.failedDialog.header = Unlock failed
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
unlock.label.useReadOnlyMode = Read-Only
unlock.label.chooseMountPath = Choose empty directory…
unlock.label.chooseMountPath = Choose empty directory…
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
@@ -35,7 +35,6 @@ unlock.choicebox.winDriveLetter.auto = Automatisch toekennen
unlock.errorMessage.wrongPassword = Verkeerd wachtwoord
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Niet ondersteunde kluis. Deze kluis is gemaakt met een nieuwere versie van Cryptomator.
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Niet ondersteunde kluis. Deze kluis is gemaakt met een nieuwere versie van Cryptomator.
unlock.messageLabel.startServerFailed = WebDAV server starten mislukt.
# change_password.fxml
changePassword.label.oldPassword = Huidig Wachtwoord
changePassword.label.newPassword = Nieuw Wachtwoord
@@ -55,7 +54,6 @@ unlocked.ioGraph.yAxis.label = Doorvoer (MiB/s)
# settings.fxml
settings.version.label = Versie %s
settings.checkForUpdates.label = Controleer op updates
settings.requiresRestartLabel = * Cryptomator dient te worden herstart
# tray icon
tray.menu.open = Open
tray.menu.quit = Afsluiten
@@ -77,11 +75,10 @@ upgrade.version3to4.err.io = I/O Exception\: migratie mislukt. Zie logbestand vo
upgrade.confirmation.label = Ja, ik heb geverifieerd dat de synchronisatie voltooid is
unlock.label.savePassword = Wachtwoord Opslaan
unlock.errorMessage.unauthenticVersionMac = MAC authenticatie mislukt
unlocked.label.mountFailed = Verbinden van schijf mislukt
unlock.savePassword.delete.confirmation.title = Verwijder Opgeslagen Wachtwoord
unlock.savePassword.delete.confirmation.header = Ben je zeker dat je het opgeslagen wachtwoord van deze kluis wilt verwijderen?
unlock.savePassword.delete.confirmation.content = Het opgeslagen wachtwoord van deze kluis zal onmiddellijk verwijderd worden van je systeem sleutelhanger. Als je opnieuw je wachtwoord wilt opslaan, zal je je kluis moeten ontgrendelen met de optie "Sla wachtwoord op" aan.
settings.debugMode.label = Debug Mode *
settings.debugMode.label = Debug Mode
upgrade.version3dropBundleExtension.title = Kluis Versie 3 Upgrade (Drop Bundel Extensie)
upgrade.version3to4.title = Kluis Versie 3 naar 4 Upgrade
upgrade.version4to5.title = Kluis Versie 4 naar 5 Upgrade
@@ -124,4 +121,6 @@ unlock.failedDialog.header = Unlock failed
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
unlock.label.useReadOnlyMode = Read-Only
unlock.label.chooseMountPath = Choose empty directory…
unlock.label.chooseMountPath = Choose empty directory…
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
@@ -33,7 +33,6 @@ unlock.choicebox.winDriveLetter.auto = Przydziel automatycznie
unlock.errorMessage.wrongPassword = Nieprawidłowe hasło
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Nieobsługiwana wersja portfela. Ten portfel został utworzony przez starszą wersję Cryptomatora.
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Nieobsługiwana wersja portfela. Ten portfel został utworzony przez nowszą wersję Cryptomatora.
unlock.messageLabel.startServerFailed = Nie udało się uruchomić serwera WebDAV.
# change_password.fxml
changePassword.label.oldPassword = Stare Hasło
changePassword.label.newPassword = Nowe Hasło
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = Wydajność (MiB/s)
# settings.fxml
settings.version.label = Wersja %s
settings.checkForUpdates.label = Sprawdź aktualizacje
settings.requiresRestartLabel = * Cryptomator wymaga restartu
# tray icon
tray.menu.open = Otwórz
tray.menu.quit = Wyjdź
@@ -75,11 +73,10 @@ upgrade.version3to4.err.io = Migracja nie powiodła się z powodu wyjątku I/O.
upgrade.confirmation.label = Tak, upewniłem się że synchronizacja plików została ukończona.
unlock.label.savePassword = Zapisz Hasło
unlock.errorMessage.unauthenticVersionMac = Nie udało się uwierzytelnić wersji MAC
unlocked.label.mountFailed = Nie udało się połączyć z napędem
unlock.savePassword.delete.confirmation.title = Usuń Zapisane Hasło
unlock.savePassword.delete.confirmation.header = Czy na pewno chcesz usunąć zapisane hasło do tego portfela?
unlock.savePassword.delete.confirmation.content = Zapisane hasło do tego portfela zostanie natychmiast usunięte z systemowego pęku kluczy. Jeśli chcesz ponownie zapisać hasło, musisz odblokować portfel z włączoną opcją "Zapisz hasło".
settings.debugMode.label = Tryb Debugowania *
settings.debugMode.label = Tryb Debugowania
upgrade.version3dropBundleExtension.title = Aktualizacja portfela do wersji 3 (Drop Bundle Extension)
upgrade.version3to4.title = Aktualizacja portfela z wersji 3 do 4
upgrade.version4to5.title = Aktualizacja portfela z wersji 4 do 5
@@ -122,4 +119,6 @@ unlock.failedDialog.header = Unlock failed
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
unlock.label.useReadOnlyMode = Read-Only
unlock.label.chooseMountPath = Choose empty directory…
unlock.label.chooseMountPath = Choose empty directory…
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
@@ -33,7 +33,6 @@ unlock.choicebox.winDriveLetter.auto = Atribuir automaticamente
unlock.errorMessage.wrongPassword = Senha incorreta
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Cofre não suportado. Esse cofre foi criado por uma versão mais antiga do Cryptomator.
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Cofre não suportado. Esse cofre foi criado por uma versão mais nova do Cryptomator.
unlock.messageLabel.startServerFailed = Falha ao iniciar servidor WebDAV.
# change_password.fxml
changePassword.label.oldPassword = Senha antiga
changePassword.label.newPassword = Senha Nova
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = Taxa de transferência (Mbps)
# settings.fxml
settings.version.label = Versão %s
settings.checkForUpdates.label = Verificar por atualizações
settings.requiresRestartLabel = * Cryptomator precisa ser reiniciado
# tray icon
tray.menu.open = Abrir
tray.menu.quit = Sair
@@ -75,11 +73,10 @@ upgrade.version3to4.err.io = Falha na migração devido a um erro de E/S. Veja o
upgrade.confirmation.label = Sim, tenho certeza de que a sincronização foi concluída
unlock.label.savePassword = Salvar senha
unlock.errorMessage.unauthenticVersionMac = Não foi possível autenticar a versão do MAC.
unlocked.label.mountFailed = Falha ao conectar o drive
unlock.savePassword.delete.confirmation.title = Apagar a senha armazenada
unlock.savePassword.delete.confirmation.header = Você realmente quer apagar a senha armazenada deste cofre?
unlock.savePassword.delete.confirmation.content = A senha deste cofre que se encontra armazenada será imediatamente apagada de seu sistema. Se você quiser novamente armazenar sua senha, abra o seu cofre com a opção "Armazenar Senha" habilitada.
settings.debugMode.label = Modo de Debug *
settings.debugMode.label = Modo de Debug
upgrade.version3dropBundleExtension.title = Atualização para a versão 3 do cofre (Drop Bundle Extension)
upgrade.version3to4.title = Atualização do cofre da versão 3 para 4
upgrade.version4to5.title = Atualização do cofre da versão 4 para 5
@@ -122,4 +119,6 @@ unlock.failedDialog.header = Unlock failed
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
unlock.label.useReadOnlyMode = Read-Only
unlock.label.chooseMountPath = Choose empty directory…
unlock.label.chooseMountPath = Choose empty directory…
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
@@ -33,7 +33,6 @@ unlock.choicebox.winDriveLetter.auto = Atribuir automaticamente
unlock.errorMessage.wrongPassword = Senha errada
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Cofre não suportado. Esse Cofre foi criado em uma versão antiga do Cryptomator.
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Cofre não suportado. Esse Cofre foi criado em uma versão mais recente do Cryptomator.
unlock.messageLabel.startServerFailed = A inicialização do servidor WebDAV falhou.
# change_password.fxml
changePassword.label.oldPassword = Senha antiga
changePassword.label.newPassword = Nova senha
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = Transferência (Mbps)
# settings.fxml
settings.version.label = Versão %s
settings.checkForUpdates.label = Procurar por atualizações
settings.requiresRestartLabel = * O Cryptomator precisa ser reiniciado
# tray icon
tray.menu.open = Abrir
tray.menu.quit = Sair
@@ -75,11 +73,10 @@ upgrade.version3to4.err.io = A migração falhou devido a uma falha de entrada e
upgrade.confirmation.label = Sim, tenho certeza que a sincronização terminou
unlock.label.savePassword = Salvar senha
unlock.errorMessage.unauthenticVersionMac = Não foi possível autenticar a versão MAC
unlocked.label.mountFailed = Conexão do volume falhou
unlock.savePassword.delete.confirmation.title = Apaga senha salva
unlock.savePassword.delete.confirmation.header = Você realmente quer apagar a senha salva para esse Cofre?
unlock.savePassword.delete.confirmation.content = A senha salva para esse Cofre será imediatamente apagada. Para salvar a senha novamente destrave o Cofre com a opção "Salvar senha" ativada.
settings.debugMode.label = Modo Debug *
settings.debugMode.label = Modo Debug
upgrade.version3dropBundleExtension.title = Atualização do Cofre v3 (Drop Bundle Extension)
upgrade.version3to4.title = Atualização do Cofre v3 para v4
upgrade.version4to5.title = Atualização do Cofre v4 para v5
@@ -122,4 +119,6 @@ unlock.failedDialog.header = Unlock failed
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
unlock.label.useReadOnlyMode = Read-Only
unlock.label.chooseMountPath = Choose empty directory…
unlock.label.chooseMountPath = Choose empty directory…
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
@@ -35,7 +35,6 @@ unlock.choicebox.winDriveLetter.auto = Автоназначение
unlock.errorMessage.wrongPassword = Неверный пароль
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Неподдерживаемое хранилище. Оно было создано в более старой версии Cryptomator.
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Неподдерживаемое хранилище. Оно было создано в более новой версии Cryptomator.
unlock.messageLabel.startServerFailed = Ошибка запуска сервера WebDAV.
# change_password.fxml
changePassword.label.oldPassword = Старый пароль
changePassword.label.newPassword = Новый пароль
@@ -56,7 +55,6 @@ unlocked.ioGraph.yAxis.label = Пропускная способность (Ми
# settings.fxml
settings.version.label = Версия %s
settings.checkForUpdates.label = Проверка обновлений
settings.requiresRestartLabel = * Требуется перезапуск Cryptomator
# tray icon
tray.menu.open = Открыть
tray.menu.quit = Выход
@@ -79,12 +77,11 @@ upgrade.version3to4.err.io = Преобразование не выполнен
upgrade.confirmation.label = Да, синхронизация завершена
unlock.label.savePassword = Сохранить пароль
unlock.errorMessage.unauthenticVersionMac = Не удалось проверить подлинность версии MAC.
unlocked.label.mountFailed = Ошибка подключения к диску
# А как назвать эту правку? Пожалуйста, перестаньте портить перевод.
unlock.savePassword.delete.confirmation.title = Удалить сохранённый пароль
unlock.savePassword.delete.confirmation.header = Вы действительно хотите удалить сохранённый пароль этого хранилища?
unlock.savePassword.delete.confirmation.content = Сохранённый пароль этого хранилища будет немедленно удалён из вашей системной связки ключей. Если вы снова захотите сохранить пароль, вам придётся разблокировать хранилище с включённым параметром "Сохранить пароль".
settings.debugMode.label = Режим отладки *
settings.debugMode.label = Режим отладки
upgrade.version3dropBundleExtension.title = Обновление хранилища версии 3 (расширение пакета)
upgrade.version3to4.title = Обновление хранилища с версии 3 на 4
upgrade.version4to5.title = Обновление хранилища с версии 4 на 5
@@ -129,4 +126,6 @@ unlock.failedDialog.header = Разблокировка не удалась\n
unlock.failedDialog.content.mountPathNonExisting = Точка монтирования не существует
unlock.failedDialog.content.mountPathNotEmpty = Точка монтирования не пуста
unlock.label.useReadOnlyMode = Read-Only
unlock.label.chooseMountPath = Choose empty directory…
unlock.label.chooseMountPath = Choose empty directory…
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
@@ -36,7 +36,6 @@ unlock.choicebox.winDriveLetter.auto = Priradiť automaticky
unlock.errorMessage.wrongPassword = Nesprávne heslo
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Nepodporovaný trezor. Tento trezor bol vytvorený staršou verziou Cryptromatoru.
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Nepodporovaný trezor. Bol vytvorený z novšou verziou Cryptomatoru.
unlock.messageLabel.startServerFailed = Spustenie WebDAv servera zlyhalo.
# change_password.fxml
changePassword.label.oldPassword = Staré heslo
changePassword.label.newPassword = Nové heslo
@@ -56,7 +55,6 @@ unlocked.ioGraph.yAxis.label = Priepustnosť (MiB/s)
# settings.fxml
settings.version.label = Verzia %s
settings.checkForUpdates.label = Skontrolovať aktualizácie
settings.requiresRestartLabel = * Cryptomator vyžaduje reštart
# tray icon
tray.menu.open = Otvoriť
tray.menu.quit = Vypnúť
@@ -78,11 +76,10 @@ upgrade.version3to4.err.io = Migrácia zlyhala kvôli I/O Exception. Skontrolujt
upgrade.confirmation.label = Áno, som si istý že synchronizácia je hotová
unlock.label.savePassword = Uložiť heslo
unlock.errorMessage.unauthenticVersionMac = Could not authenticate version MAC.
unlocked.label.mountFailed = Connecting drive failed
unlock.savePassword.delete.confirmation.title = Zmazať uložené heslo
unlock.savePassword.delete.confirmation.header = Naozaj chcete zmazať uložené heslo pre tento trezor?
unlock.savePassword.delete.confirmation.content = The saved password of this vault will be immediately deleted from your system keychain. If you'd like to save your password again, you'd have to unlock your vault with the "Save Password" option enabled.
settings.debugMode.label = Debug Mode *
settings.debugMode.label = Debug Mode
upgrade.version3dropBundleExtension.title = Vault Version 3 Upgrade (Drop Bundle Extension)
upgrade.version3to4.title = Vault Version 3 to 4 Upgrade
upgrade.version4to5.title = Vault Version 4 to 5 Upgrade
@@ -108,7 +105,7 @@ settings.volume.webdav = WebDAV
settings.volume.fuse = FUSE
unlock.successLabel.vaultCreated = Vault was successfully created.
unlock.successLabel.passwordChanged = Password was successfully changed.
unlock.successLabel.upgraded = Cryptomator was successfully upgraded.
unlock.successLabel.upgraded = Vault was successfully upgraded.
unlock.label.useOwnMountPath = Use Custom Mount Point
welcome.askForUpdateCheck.dialog.title = Update check
welcome.askForUpdateCheck.dialog.header = Enable the integrated update check?
@@ -117,12 +114,14 @@ settings.volume.dokany = Dokany
main.gracefulShutdown.dialog.title = Locking vault(s) failed
main.gracefulShutdown.dialog.header = Vault(s) in use
main.gracefulShutdown.dialog.content = One or more vaults are still in use by other programs. Please close them to allow Cryptomator to shut down properly, then try again.\n\nIf this doesn't work, Cryptomator can shut down forcefully, but this can incur data loss and is not recommended.
main.gracefulShutdown.button.tryAgain = Try again
main.gracefulShutdown.button.forceShutdown = Force shutdown
main.gracefulShutdown.button.tryAgain = Try Again
main.gracefulShutdown.button.forceShutdown = Force Shutdown
unlock.pendingMessage.unlocking = Unlocking vault...
unlock.failedDialog.title = Unlock failed
unlock.failedDialog.header = Unlock failed
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
unlock.label.useReadOnlyMode = Read-Only
unlock.label.chooseMountPath = Choose empty directory…
unlock.label.chooseMountPath = Choose empty directory…
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
@@ -33,7 +33,6 @@ unlock.choicebox.winDriveLetter.auto = เลือกไดรฟ์อัต
unlock.errorMessage.wrongPassword = รหัสผ่านไม่ถูกต้อง
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = ไม่รองรับกล่องข้อมูลนี้ อาจเป็นกล่องข้อมูลของเวอชั่นเก่า
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = ไม่รองรับกล่องข้อมูลนี้ อาจเป็นกล่องข้อมูลของเวอชั่นใหม่กว่า
unlock.messageLabel.startServerFailed = WebDAV เซฟเวอร์เริ่มต้นล้มเหลว
# change_password.fxml
changePassword.label.oldPassword = รหัสผ่านเดิม
changePassword.label.newPassword = รหัสผ่านใหม่
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = ความเร็วอ่านเขีย
# settings.fxml
settings.version.label = เวอร์ชั่น %s
settings.checkForUpdates.label = ค้นหาอัพเดท
settings.requiresRestartLabel = * ต้องรีสตาร์ท Cryptomator ใหม่
# tray icon
tray.menu.open = เปิด
tray.menu.quit = ออก
@@ -75,11 +73,10 @@ upgrade.version3to4.err.io = อัพเกรดล้มเหลวเนื
upgrade.confirmation.label = ใช่ ฉันมั่นใจว่าอัพเกรดเสร็จเรียบร้อยแล้ว
unlock.label.savePassword = เซฟรหัสผ่าน
unlock.errorMessage.unauthenticVersionMac = ไม่ตรวจพบเวอร์ชั่นของ MAC
unlocked.label.mountFailed = เชื่อมต่อไดรฟ์ล้มเหลว
unlock.savePassword.delete.confirmation.title = ลบรหัสผ่านที่เซฟ
unlock.savePassword.delete.confirmation.header = คุณต้องการลบลบรหัสผ่านที่เซฟไว้กับกล่องข้อมูลใช่หรือไม่ ?
unlock.savePassword.delete.confirmation.content = รหัสผ่านที่เซฟถูกลบจาก system keychain แล้ว ถ้าคุณต้องการเซฟรหัสผ่านใหม่อีกครั้ง ให้คุณคลิ้กเลือก "เซฟรหัสผ่าน"
settings.debugMode.label = โหมด Debug *
settings.debugMode.label = โหมด Debug
upgrade.version3dropBundleExtension.title = อัพเกรดกล่องข้อมูลเวอร์ชั่น 3 (Drop Bundle Extension)
upgrade.version3to4.title = อัพเกรดกล่องข้อมูลเวอร์ชั่น 3 ไปยัง 4
upgrade.version4to5.title = อัพเกรดกล่องข้อมูลเวอร์ชั่น 4 ไปยัง 5
@@ -105,7 +102,7 @@ settings.volume.webdav = WebDAV
settings.volume.fuse = FUSE
unlock.successLabel.vaultCreated = Vault was successfully created.
unlock.successLabel.passwordChanged = Password was successfully changed.
unlock.successLabel.upgraded = Cryptomator was successfully upgraded.
unlock.successLabel.upgraded = Vault was successfully upgraded.
unlock.label.useOwnMountPath = Use Custom Mount Point
welcome.askForUpdateCheck.dialog.title = Update check
welcome.askForUpdateCheck.dialog.header = Enable the integrated update check?
@@ -114,12 +111,14 @@ settings.volume.dokany = Dokany
main.gracefulShutdown.dialog.title = Locking vault(s) failed
main.gracefulShutdown.dialog.header = Vault(s) in use
main.gracefulShutdown.dialog.content = One or more vaults are still in use by other programs. Please close them to allow Cryptomator to shut down properly, then try again.\n\nIf this doesn't work, Cryptomator can shut down forcefully, but this can incur data loss and is not recommended.
main.gracefulShutdown.button.tryAgain = Try again
main.gracefulShutdown.button.forceShutdown = Force shutdown
main.gracefulShutdown.button.tryAgain = Try Again
main.gracefulShutdown.button.forceShutdown = Force Shutdown
unlock.pendingMessage.unlocking = Unlocking vault...
unlock.failedDialog.title = Unlock failed
unlock.failedDialog.header = Unlock failed
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
unlock.label.useReadOnlyMode = Read-Only
unlock.label.chooseMountPath = Choose empty directory…
unlock.label.chooseMountPath = Choose empty directory…
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
@@ -33,7 +33,6 @@ unlock.choicebox.winDriveLetter.auto = Otomatik ata
unlock.errorMessage.wrongPassword = Yanlış şifre
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Desteklenmeyen kasa. Bu kasa daha eski bir Cryptomator sürümü ile oluşturulmuş.
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Desteklenmeyen kasa. Bu kasa daha yeni bir Cryptomator sürümü ile oluşturulmuş.
unlock.messageLabel.startServerFailed = WebDAV sunucu başlatma başarısız.
# change_password.fxml
changePassword.label.oldPassword = Eski şifre
changePassword.label.newPassword = Yeni şifre
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = Veri hacmi (MiB/s)
# settings.fxml
settings.version.label = Sürüm %s
settings.checkForUpdates.label = Güncellemeleri denetle
settings.requiresRestartLabel = * Cryptomator yeniden başlatılması gerek
# tray icon
tray.menu.open = Aç
tray.menu.quit = Çıkış
@@ -75,11 +73,10 @@ upgrade.version3to4.err.io = Format değiştirme işlemi I/O Hatası dolayısı
upgrade.confirmation.label = Yes, I've made sure that synchronization has finished
unlock.label.savePassword = Save Password
unlock.errorMessage.unauthenticVersionMac = Could not authenticate version MAC.
unlocked.label.mountFailed = Connecting drive failed
unlock.savePassword.delete.confirmation.title = Delete Saved Password
unlock.savePassword.delete.confirmation.header = Do you really want to delete the saved password of this vault?
unlock.savePassword.delete.confirmation.content = The saved password of this vault will be immediately deleted from your system keychain. If you'd like to save your password again, you'd have to unlock your vault with the "Save Password" option enabled.
settings.debugMode.label = Debug Mode *
settings.debugMode.label = Debug Mode
upgrade.version3dropBundleExtension.title = Vault Version 3 Upgrade (Drop Bundle Extension)
upgrade.version3to4.title = Vault Version 3 to 4 Upgrade
upgrade.version4to5.title = Vault Version 4 to 5 Upgrade
@@ -105,7 +102,7 @@ settings.volume.webdav = WebDAV
settings.volume.fuse = FUSE
unlock.successLabel.vaultCreated = Vault was successfully created.
unlock.successLabel.passwordChanged = Password was successfully changed.
unlock.successLabel.upgraded = Cryptomator was successfully upgraded.
unlock.successLabel.upgraded = Vault was successfully upgraded.
unlock.label.useOwnMountPath = Use Custom Mount Point
welcome.askForUpdateCheck.dialog.title = Update check
welcome.askForUpdateCheck.dialog.header = Enable the integrated update check?
@@ -114,12 +111,14 @@ settings.volume.dokany = Dokany
main.gracefulShutdown.dialog.title = Locking vault(s) failed
main.gracefulShutdown.dialog.header = Vault(s) in use
main.gracefulShutdown.dialog.content = One or more vaults are still in use by other programs. Please close them to allow Cryptomator to shut down properly, then try again.\n\nIf this doesn't work, Cryptomator can shut down forcefully, but this can incur data loss and is not recommended.
main.gracefulShutdown.button.tryAgain = Try again
main.gracefulShutdown.button.forceShutdown = Force shutdown
main.gracefulShutdown.button.tryAgain = Try Again
main.gracefulShutdown.button.forceShutdown = Force Shutdown
unlock.pendingMessage.unlocking = Unlocking vault...
unlock.failedDialog.title = Unlock failed
unlock.failedDialog.header = Unlock failed
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
unlock.label.useReadOnlyMode = Read-Only
unlock.label.chooseMountPath = Choose empty directory…
unlock.label.chooseMountPath = Choose empty directory…
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
@@ -33,7 +33,6 @@ unlock.choicebox.winDriveLetter.auto = Призначити автоматичн
unlock.errorMessage.wrongPassword = Пароль невірний
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Сховище не підтримується. Воно було створене в старішій версії Cryptomator.
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Сховище не підтримується. Воно було створене в новішій версії Cryptomator.
unlock.messageLabel.startServerFailed = Помилка запуску сервера WebDAV.
# change_password.fxml
changePassword.label.oldPassword = Старий пароль
changePassword.label.newPassword = Новий пароль
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = Пропускна спроможність (Мі
# settings.fxml
settings.version.label = Версія %s
settings.checkForUpdates.label = Перевірити оновлення
settings.requiresRestartLabel = * Необхідне перезавантаження Cryptomator
# tray icon
tray.menu.open = Відкрити
tray.menu.quit = Вихід
@@ -75,11 +73,10 @@ upgrade.version3to4.err.io = Перетворення невдале через
upgrade.confirmation.label = Так, я переконався, що синхронізація завершена
unlock.label.savePassword = Зберегти пароль
unlock.errorMessage.unauthenticVersionMac = Неможливо перевірити справжність версії MAC.
unlocked.label.mountFailed = Не вдалося підключити диск
unlock.savePassword.delete.confirmation.title = Видалити збережений пароль
unlock.savePassword.delete.confirmation.header = Ви дійсно хочете видалити збережений пароль цього сховища?
unlock.savePassword.delete.confirmation.content = Збережений пароль цього сховища буде негайно видалено з вашого системної в'язки ключів. Якщо ви хочете знову зберегти свій пароль, вам потрібно буде розблокувати свої сховище з опцією "Зберегти пароль".
settings.debugMode.label = Режим налагодження *
settings.debugMode.label = Режим налагодження
upgrade.version3dropBundleExtension.title = Оновлення 3 версії сховища (Розширення пакету)
upgrade.version3to4.title = Оновлення сховища з версії 3 на 4
upgrade.version4to5.title = Оновлення сховища з версії 4 на 5
@@ -122,4 +119,6 @@ unlock.failedDialog.header = Unlock failed
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
unlock.label.useReadOnlyMode = Read-Only
unlock.label.chooseMountPath = Choose empty directory…
unlock.label.chooseMountPath = Choose empty directory…
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
+32 -33
View File
@@ -1,21 +1,21 @@
app.name = Cryptomator
# main.fxml
main.emptyListInstructions = 点按这里添加资料库
main.emptyListInstructions = 单击此处添加资料库
main.directoryList.contextMenu.remove = 从列表中移除
main.directoryList.contextMenu.changePassword = 变更密码
main.addDirectory.contextMenu.new = 新资料库
main.addDirectory.contextMenu.open = 使用现有资料库
main.addDirectory.contextMenu.new = 创建新资料库
main.addDirectory.contextMenu.open = 打开现有资料库
# welcome.fxml
welcome.checkForUpdates.label.currentlyChecking = 检查更新中……
welcome.newVersionMessage = 新版本 %1$s 已发布\n当前版本%2$s
# initialize.fxml
initialize.label.password = 密码
initialize.label.retypePassword = 重新输入密码
initialize.button.ok = 建资料库
initialize.messageLabel.alreadyInitialized = 已新建资料库
initialize.messageLabel.initializationFailed = 无法创建资料库。请查看日志来获详细信息。
initialize.button.ok = 建资料库
initialize.messageLabel.alreadyInitialized = 资料库已创建
initialize.messageLabel.initializationFailed = 无法创建资料库。请查看日志来获详细信息。
# notfound.fxml
notfound.label = 找不到这个资料库,是否已移动到别的地方?
notfound.label = 找不到资料库,是否已移动到其他地方?
# upgrade.fxml
upgrade.button = 升级资料库
upgrade.version3dropBundleExtension.msg = 此资料库需要升级至最新版本,\n"%1$s" 将重命名为 "%2$s"。\n请确保同步完成后再继续操作。
@@ -23,7 +23,7 @@ upgrade.version3dropBundleExtension.err.alreadyExists = 自动迁移失败。\n
# unlock.fxml
unlock.label.password = 密码
unlock.label.mountName = 驱动器名称
unlock.label.winDriveLetter = 驱动器
unlock.label.winDriveLetter = 驱动器盘符
unlock.label.downloadsPageLink = 所有 Cryptomator 版本
unlock.label.advancedHeading = 高级选项
unlock.button.unlock = 解锁资料库
@@ -33,7 +33,6 @@ unlock.choicebox.winDriveLetter.auto = 自动分配
unlock.errorMessage.wrongPassword = 密码错误
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = 此资料库属于过时版本的 Cryptomator,无法在此版本中使用。
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = 此资料库属于较新版本的 Cryptomator,无法在此版本中使用。
unlock.messageLabel.startServerFailed = 无法启动 WebDAV 服务器。
# change_password.fxml
changePassword.label.oldPassword = 原密码
changePassword.label.newPassword = 新密码
@@ -45,26 +44,25 @@ changePassword.errorMessage.decryptionFailed = 解密失败
# unlocked.fxml
unlocked.button.lock = 锁定资料库
unlocked.moreOptions.reveal = 打开驱动器
unlocked.label.revealFailed = 令无法执行
unlocked.label.revealFailed = 令无法执行
unlocked.label.unmountFailed = 无法弹出驱动器
unlocked.label.statsEncrypted = 已加密
unlocked.label.statsDecrypted = 已解密
unlocked.ioGraph.yAxis.label = 吞吐量 (Mib/s)
unlocked.ioGraph.yAxis.label = 吞吐量 (MB/s)
# settings.fxml
settings.version.label = 版本 %s
settings.checkForUpdates.label = 检查更新
settings.requiresRestartLabel = * Cryptomator 需要重启
# tray icon
tray.menu.open = 打开
tray.menu.quit = 退出
tray.infoMsg.title = 仍在运行
tray.infoMsg.msg = Cryptomator 仍在运行。 从托盘图标中退出。
tray.infoMsg.msg.osx = Cryptomator 仍在运行。 从菜单栏图标中退出。
initialize.messageLabel.passwordStrength.0 = 非常
initialize.messageLabel.passwordStrength.1 =
initialize.messageLabel.passwordStrength.2 = 可接受
tray.infoMsg.msg = Cryptomator 仍在运行。 从托盘图标中退出。
tray.infoMsg.msg.osx = Cryptomator 仍在运行。 从菜单栏图标中退出。
initialize.messageLabel.passwordStrength.0 =
initialize.messageLabel.passwordStrength.1 =
initialize.messageLabel.passwordStrength.2 = 一般
initialize.messageLabel.passwordStrength.3 = 较强
initialize.messageLabel.passwordStrength.4 = 非常
initialize.messageLabel.passwordStrength.4 =
initialize.label.doNotForget = 重要:若忘记此密码,数据将无法找回。
main.directoryList.remove.confirmation.title = 移除资料库
main.directoryList.remove.confirmation.header = 确定要移除此资料库吗?
@@ -72,15 +70,14 @@ main.directoryList.remove.confirmation.content = 资料库将仅从列表中移
upgrade.version3to4.msg = 此资料库需要升级至最新版本,\n已加密的文件夹名称将更新。\n请确保同步完成后再继续操作。
upgrade.version3to4.err.io = 由于 I/O 异常,迁移失败。请查看日志来获得详细信息。
# upgrade.fxml
upgrade.confirmation.label = 没错,同步已完成。
upgrade.confirmation.label = 没错,我确定同步已完成。
unlock.label.savePassword = 保存密码
# This Mac means Mac(Apple) or Mac address?
unlock.errorMessage.unauthenticVersionMac = 无法确认消息认证码版本。
unlocked.label.mountFailed = 无法连接到驱动器
unlock.savePassword.delete.confirmation.title = 删除已储存的密码
unlock.savePassword.delete.confirmation.header = 真的要删除为此资料库储存的密码吗?
unlock.savePassword.delete.confirmation.content = 此资料库储存的密码将立即从系统钥匙串中删除。如果您想再次保存密码,则必须通过启用“保存密码”选项启用您的密码库。
settings.debugMode.label = 调试模式 *
settings.debugMode.label = 调试模式
upgrade.version3dropBundleExtension.title = 升级资料库到第三版(移除 Bundle Extension
upgrade.version3to4.title = 升级资料库到第四版
upgrade.version4to5.title = 升级资料库到第五版
@@ -112,15 +109,17 @@ welcome.askForUpdateCheck.dialog.title = 检查更新
welcome.askForUpdateCheck.dialog.header = 启用更新检查?
welcome.askForUpdateCheck.dialog.content = 启用检查更新,Cryptomator将从Cryptomator服务器获取当前版本,并在有新版本时提示。\n我们建议您启用更新检查,以确保安装了最新版的Cryptomator,并安装了所有安全补丁。如果您未启用更新检查,则需要您自己到https\://cryptomator.org/downloads/ 检查并下载最新版本。\n你可以随时在设置中更改此设置。
settings.volume.dokany = Dokany
main.gracefulShutdown.dialog.title = Locking vault(s) failed
main.gracefulShutdown.dialog.header = Vault(s) in use
main.gracefulShutdown.dialog.content = One or more vaults are still in use by other programs. Please close them to allow Cryptomator to shut down properly, then try again.\n\nIf this doesn't work, Cryptomator can shut down forcefully, but this can incur data loss and is not recommended.
main.gracefulShutdown.button.tryAgain = Try again
main.gracefulShutdown.button.forceShutdown = Force shutdown
unlock.pendingMessage.unlocking = Unlocking vault...
unlock.failedDialog.title = Unlock failed
unlock.failedDialog.header = Unlock failed
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
unlock.label.useReadOnlyMode = Read-Only
unlock.label.chooseMountPath = Choose empty directory…
main.gracefulShutdown.dialog.title = 资料库锁定失败
main.gracefulShutdown.dialog.header = 资料库正在使用
main.gracefulShutdown.dialog.content = 一个或多个资料库正在被其他应用程序调用。若要正常退出 Cryptomator 请先关闭这些应用,然后重试。\n\n如果上述操作无效,也可以强制退出 Cryptomator,但这将导致数据丢失,请尽量避免如此退出。
main.gracefulShutdown.button.tryAgain = 重试
main.gracefulShutdown.button.forceShutdown = 强制退出
unlock.pendingMessage.unlocking = 解锁资料库
unlock.failedDialog.title = 解锁失败
unlock.failedDialog.header = 解锁失败
unlock.failedDialog.content.mountPathNonExisting = 挂载点不存在。
unlock.failedDialog.content.mountPathNotEmpty = 挂载点非空。
unlock.label.useReadOnlyMode = 只读
unlock.label.chooseMountPath = 选择空目录...
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
ctrl.secPasswordField.capsLocked = Caps Lock is activated.
@@ -33,7 +33,6 @@ unlock.choicebox.winDriveLetter.auto = 自動指定
unlock.errorMessage.wrongPassword = 密碼錯誤
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = 呢個檔案夾萬係由舊版Cryptomator創建嘅,並唔支援。
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = 呢個檔案夾萬係由新版Cryptomator創建嘅,並唔支援。
unlock.messageLabel.startServerFailed = 啓動唔到WebDAV嘅伺服器。
# change_password.fxml
changePassword.label.oldPassword = 舊密碼
changePassword.label.newPassword = 新密碼
@@ -53,7 +52,6 @@ unlocked.ioGraph.yAxis.label = 傳輸量(MIB / S
# settings.fxml
settings.version.label = 版本%s
settings.checkForUpdates.label = 檢查更新
settings.requiresRestartLabel = * Cryptomator需要更新
# tray icon
tray.menu.open = 開啓
tray.menu.quit = 離開
@@ -75,11 +73,10 @@ upgrade.version3to4.err.io = 由於I/O的例外,轉移失敗。取得詳細資
upgrade.confirmation.label = 是的,請確認同步已完成。
unlock.label.savePassword = 儲存密碼
unlock.errorMessage.unauthenticVersionMac = 無法認證消息驗證碼版本。
unlocked.label.mountFailed = 連接唔到磁碟
unlock.savePassword.delete.confirmation.title = 刪除儲存咗嘅密碼
unlock.savePassword.delete.confirmation.header = Do you really want to delete the saved password of this vault?
unlock.savePassword.delete.confirmation.content = The saved password of this vault will be immediately deleted from your system keychain. If you'd like to save your password again, you'd have to unlock your vault with the "Save Password" option enabled.
settings.debugMode.label = Debug Mode *
settings.debugMode.label = Debug Mode
upgrade.version3dropBundleExtension.title = Vault Version 3 Upgrade (Drop Bundle Extension)
upgrade.version3to4.title = Vault Version 3 to 4 Upgrade
upgrade.version4to5.title = Vault Version 4 to 5 Upgrade
@@ -105,7 +102,7 @@ settings.volume.webdav = WebDAV
settings.volume.fuse = FUSE
unlock.successLabel.vaultCreated = Vault was successfully created.
unlock.successLabel.passwordChanged = Password was successfully changed.
unlock.successLabel.upgraded = Cryptomator was successfully upgraded.
unlock.successLabel.upgraded = Vault was successfully upgraded.
unlock.label.useOwnMountPath = Use Custom Mount Point
welcome.askForUpdateCheck.dialog.title = Update check
welcome.askForUpdateCheck.dialog.header = Enable the integrated update check?
@@ -114,12 +111,14 @@ settings.volume.dokany = Dokany
main.gracefulShutdown.dialog.title = Locking vault(s) failed
main.gracefulShutdown.dialog.header = Vault(s) in use
main.gracefulShutdown.dialog.content = One or more vaults are still in use by other programs. Please close them to allow Cryptomator to shut down properly, then try again.\n\nIf this doesn't work, Cryptomator can shut down forcefully, but this can incur data loss and is not recommended.
main.gracefulShutdown.button.tryAgain = Try again
main.gracefulShutdown.button.forceShutdown = Force shutdown
main.gracefulShutdown.button.tryAgain = Try Again
main.gracefulShutdown.button.forceShutdown = Force Shutdown
unlock.pendingMessage.unlocking = Unlocking vault...
unlock.failedDialog.title = Unlock failed
unlock.failedDialog.header = Unlock failed
unlock.failedDialog.content.mountPathNonExisting = Mount point does not exist.
unlock.failedDialog.content.mountPathNotEmpty = Mount point is not empty.
unlock.label.useReadOnlyMode = Read-Only
unlock.label.chooseMountPath = Choose empty directory…
unlock.label.chooseMountPath = Choose empty directory…
ctrl.secPasswordField.nonPrintableChars = Password contains control characters.\nRecommendation\: Remove them to ensure compatibility with other clients.
ctrl.secPasswordField.capsLocked = Caps Lock is activated.

Some files were not shown because too many files have changed in this diff Show More