diff --git a/.travis.yml b/.travis.yml
index ff30b781b..611045f47 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -16,7 +16,7 @@ before_install: "curl -L --cookie 'oraclelicense=accept-securebackup-cookie;' ht
script: mvn -fmain/pom.xml clean test
-after_success: mvn -fmain/pom.xml clean test jacoco:report coveralls:report
+after_success: mvn -fmain/pom.xml -Ptest-coverage clean test jacoco:report-aggregate coveralls:report
notifications:
webhooks:
diff --git a/main/ant-kit/src/main/resources/package/linux/Cryptomator.png b/main/ant-kit/src/main/resources/package/linux/Cryptomator.png
index 99d9fb876..1e0832c42 100644
Binary files a/main/ant-kit/src/main/resources/package/linux/Cryptomator.png and b/main/ant-kit/src/main/resources/package/linux/Cryptomator.png differ
diff --git a/main/commons/pom.xml b/main/commons/pom.xml
index 81ef3d6ef..5603f36b5 100644
--- a/main/commons/pom.xml
+++ b/main/commons/pom.xml
@@ -17,11 +17,28 @@
Shared utilities
+
com.google.guava
guava
-
+
+ org.apache.commons
+ commons-lang3
+
+
+
+
+ com.google.dagger
+ dagger
+
+
+ com.google.dagger
+ dagger-compiler
+ provided
+
+
+
junit
junit
@@ -43,4 +60,13 @@
test
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+
+
+
diff --git a/main/commons/src/main/java/org/cryptomator/common/CommonsModule.java b/main/commons/src/main/java/org/cryptomator/common/CommonsModule.java
new file mode 100644
index 000000000..d56d22f80
--- /dev/null
+++ b/main/commons/src/main/java/org/cryptomator/common/CommonsModule.java
@@ -0,0 +1,21 @@
+package org.cryptomator.common;
+
+import java.util.Comparator;
+
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+@Module
+public class CommonsModule {
+
+ @Provides
+ @Singleton
+ @Named("SemVer")
+ Comparator providesSemVerComparator() {
+ return new SemVerComparator();
+ }
+
+}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/SemVerComparator.java b/main/commons/src/main/java/org/cryptomator/common/SemVerComparator.java
similarity index 97%
rename from main/ui/src/main/java/org/cryptomator/ui/util/SemVerComparator.java
rename to main/commons/src/main/java/org/cryptomator/common/SemVerComparator.java
index b9031b471..930e2e93d 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/util/SemVerComparator.java
+++ b/main/commons/src/main/java/org/cryptomator/common/SemVerComparator.java
@@ -6,7 +6,7 @@
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
-package org.cryptomator.ui.util;
+package org.cryptomator.common;
import java.util.Comparator;
diff --git a/main/ui/src/test/java/org/cryptomator/ui/util/SemVerComparatorTest.java b/main/commons/src/test/java/org/cryptomator/common/SemVerComparatorTest.java
similarity index 95%
rename from main/ui/src/test/java/org/cryptomator/ui/util/SemVerComparatorTest.java
rename to main/commons/src/test/java/org/cryptomator/common/SemVerComparatorTest.java
index a505d0af4..859eb9471 100644
--- a/main/ui/src/test/java/org/cryptomator/ui/util/SemVerComparatorTest.java
+++ b/main/commons/src/test/java/org/cryptomator/common/SemVerComparatorTest.java
@@ -6,10 +6,11 @@
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
-package org.cryptomator.ui.util;
+package org.cryptomator.common;
import java.util.Comparator;
+import org.cryptomator.common.SemVerComparator;
import org.junit.Assert;
import org.junit.Test;
diff --git a/main/filesystem-charsets/pom.xml b/main/filesystem-charsets/pom.xml
new file mode 100644
index 000000000..b5eba0555
--- /dev/null
+++ b/main/filesystem-charsets/pom.xml
@@ -0,0 +1,45 @@
+
+
+
+ 4.0.0
+
+ org.cryptomator
+ main
+ 1.1.0-SNAPSHOT
+
+ filesystem-charsets
+ Cryptomator filesystem: Filename charset compatibility layer
+
+
+
+ org.cryptomator
+ filesystem-api
+
+
+
+
+ org.cryptomator
+ commons-test
+
+
+ org.cryptomator
+ filesystem-inmemory
+
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+
+
+
+
\ No newline at end of file
diff --git a/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFile.java b/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFile.java
new file mode 100644
index 000000000..af73d59c1
--- /dev/null
+++ b/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFile.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Sebastian Stenzel and others.
+ * This file is licensed under the terms of the MIT license.
+ * See the LICENSE.txt file for more info.
+ *
+ * Contributors:
+ * Sebastian Stenzel - initial API and implementation
+ *******************************************************************************/
+package org.cryptomator.filesystem.charsets;
+
+import java.io.UncheckedIOException;
+import java.text.Normalizer;
+import java.text.Normalizer.Form;
+
+import org.cryptomator.filesystem.File;
+import org.cryptomator.filesystem.delegating.DelegatingFile;
+
+class NormalizedNameFile extends DelegatingFile {
+
+ private final Form displayForm;
+
+ public NormalizedNameFile(NormalizedNameFolder parent, File delegate, Form displayForm) {
+ super(parent, delegate);
+ this.displayForm = displayForm;
+ }
+
+ @Override
+ public String name() throws UncheckedIOException {
+ return Normalizer.normalize(super.name(), displayForm);
+ }
+
+}
diff --git a/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFileSystem.java b/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFileSystem.java
new file mode 100644
index 000000000..a69c572d6
--- /dev/null
+++ b/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFileSystem.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Sebastian Stenzel and others.
+ * This file is licensed under the terms of the MIT license.
+ * See the LICENSE.txt file for more info.
+ *
+ * Contributors:
+ * Sebastian Stenzel - initial API and implementation
+ *******************************************************************************/
+package org.cryptomator.filesystem.charsets;
+
+import java.text.Normalizer.Form;
+
+import org.cryptomator.filesystem.Folder;
+import org.cryptomator.filesystem.delegating.DelegatingFileSystem;
+
+public class NormalizedNameFileSystem extends NormalizedNameFolder implements DelegatingFileSystem {
+
+ public NormalizedNameFileSystem(Folder delegate, Form displayForm) {
+ super(null, delegate, displayForm);
+ }
+
+ @Override
+ public Folder getDelegate() {
+ return delegate;
+ }
+
+}
diff --git a/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFolder.java b/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFolder.java
new file mode 100644
index 000000000..e2762059b
--- /dev/null
+++ b/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/NormalizedNameFolder.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Sebastian Stenzel and others.
+ * This file is licensed under the terms of the MIT license.
+ * See the LICENSE.txt file for more info.
+ *
+ * Contributors:
+ * Sebastian Stenzel - initial API and implementation
+ *******************************************************************************/
+package org.cryptomator.filesystem.charsets;
+
+import java.io.UncheckedIOException;
+import java.text.Normalizer;
+import java.text.Normalizer.Form;
+
+import org.cryptomator.filesystem.File;
+import org.cryptomator.filesystem.Folder;
+import org.cryptomator.filesystem.delegating.DelegatingFolder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class NormalizedNameFolder extends DelegatingFolder {
+
+ private static final Logger LOG = LoggerFactory.getLogger(NormalizedNameFolder.class);
+ private final Form displayForm;
+
+ public NormalizedNameFolder(NormalizedNameFolder parent, Folder delegate, Form displayForm) {
+ super(parent, delegate);
+ this.displayForm = displayForm;
+ }
+
+ @Override
+ public String name() throws UncheckedIOException {
+ return Normalizer.normalize(super.name(), displayForm);
+ }
+
+ @Override
+ public NormalizedNameFile file(String name) throws UncheckedIOException {
+ String nfcName = Normalizer.normalize(name, Form.NFC);
+ String nfdName = Normalizer.normalize(name, Form.NFD);
+ NormalizedNameFile nfcFile = super.file(nfcName);
+ NormalizedNameFile nfdFile = super.file(nfdName);
+ if (!nfcName.equals(nfdName) && nfcFile.exists() && nfdFile.exists()) {
+ LOG.warn("Ambiguous file names \"" + nfcName + "\" (NFC) vs. \"" + nfdName + "\" (NFD). Both files exist. Using \"" + nfcName + "\" (NFC).");
+ } else if (!nfcName.equals(nfdName) && !nfcFile.exists() && nfdFile.exists()) {
+ LOG.info("Moving file from \"" + nfcName + "\" (NFD) to \"" + nfdName + "\" (NFC).");
+ nfdFile.moveTo(nfcFile);
+ }
+ return nfcFile;
+ }
+
+ @Override
+ protected NormalizedNameFile newFile(File delegate) {
+ return new NormalizedNameFile(this, delegate, displayForm);
+ }
+
+ @Override
+ public NormalizedNameFolder folder(String name) throws UncheckedIOException {
+ String nfcName = Normalizer.normalize(name, Form.NFC);
+ String nfdName = Normalizer.normalize(name, Form.NFD);
+ NormalizedNameFolder nfcFolder = super.folder(nfcName);
+ NormalizedNameFolder nfdFolder = super.folder(nfdName);
+ if (!nfcName.equals(nfdName) && nfcFolder.exists() && nfdFolder.exists()) {
+ LOG.warn("Ambiguous folder names \"" + nfcName + "\" (NFC) vs. \"" + nfdName + "\" (NFD). Both files exist. Using \"" + nfcName + "\" (NFC).");
+ } else if (!nfcName.equals(nfdName) && !nfcFolder.exists() && nfdFolder.exists()) {
+ LOG.info("Moving folder from \"" + nfcName + "\" (NFD) to \"" + nfdName + "\" (NFC).");
+ nfdFolder.moveTo(nfcFolder);
+ }
+ return nfcFolder;
+ }
+
+ @Override
+ protected NormalizedNameFolder newFolder(Folder delegate) {
+ return new NormalizedNameFolder(this, delegate, displayForm);
+ }
+
+}
diff --git a/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/package-info.java b/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/package-info.java
new file mode 100644
index 000000000..3ee4f6a17
--- /dev/null
+++ b/main/filesystem-charsets/src/main/java/org/cryptomator/filesystem/charsets/package-info.java
@@ -0,0 +1,16 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Sebastian Stenzel and others.
+ * This file is licensed under the terms of the MIT license.
+ * See the LICENSE.txt file for more info.
+ *
+ * Contributors:
+ * Sebastian Stenzel - initial API and implementation
+ *******************************************************************************/
+/**
+ * Makes sure, the filesystems wrapped by this filesystem work only on UTF-8 encoded file paths using Normalization Form C.
+ * Filesystems wrapping this file system, on the other hand, will get filenames reported in a specified Normalization Form.
+ * It is recommended to use NFD for OS X and NFC for other operating systems.
+ * When looking for a file or folder with a name given in either form, both possibilities are considered
+ * and files/folders stored in NFD are automatically migrated to NFC.
+ */
+package org.cryptomator.filesystem.charsets;
\ No newline at end of file
diff --git a/main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFileSystemTest.java b/main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFileSystemTest.java
new file mode 100644
index 000000000..e662838a8
--- /dev/null
+++ b/main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFileSystemTest.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Sebastian Stenzel and others.
+ * This file is licensed under the terms of the MIT license.
+ * See the LICENSE.txt file for more info.
+ *
+ * Contributors:
+ * Sebastian Stenzel - initial API and implementation
+ *******************************************************************************/
+package org.cryptomator.filesystem.charsets;
+
+import java.nio.ByteBuffer;
+import java.text.Normalizer.Form;
+
+import org.cryptomator.filesystem.FileSystem;
+import org.cryptomator.filesystem.WritableFile;
+import org.cryptomator.filesystem.inmem.InMemoryFileSystem;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class NormalizedNameFileSystemTest {
+
+ @Test
+ public void testFileMigration() {
+ FileSystem inMemoryFs = new InMemoryFileSystem();
+ try (WritableFile writable = inMemoryFs.file("\u006F\u0302").openWritable()) {
+ writable.write(ByteBuffer.allocate(0));
+ }
+ FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFC);
+ Assert.assertTrue(normalizationFs.file("\u00F4").exists());
+ Assert.assertTrue(normalizationFs.file("\u006F\u0302").exists());
+ Assert.assertFalse(inMemoryFs.file("\u006F\u0302").exists());
+ Assert.assertTrue(inMemoryFs.file("\u00F4").exists());
+ }
+
+ @Test
+ public void testNoFileMigration() {
+ FileSystem inMemoryFs = new InMemoryFileSystem();
+ try (WritableFile writable = inMemoryFs.file("\u00F4").openWritable()) {
+ writable.write(ByteBuffer.allocate(0));
+ }
+ FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFC);
+ Assert.assertTrue(normalizationFs.file("\u00F4").exists());
+ Assert.assertTrue(normalizationFs.file("\u006F\u0302").exists());
+ Assert.assertFalse(inMemoryFs.file("\u006F\u0302").exists());
+ Assert.assertTrue(inMemoryFs.file("\u00F4").exists());
+ }
+
+ @Test
+ public void testFolderMigration() {
+ FileSystem inMemoryFs = new InMemoryFileSystem();
+ inMemoryFs.folder("\u006F\u0302").create();
+ FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFC);
+ Assert.assertTrue(normalizationFs.folder("\u00F4").exists());
+ Assert.assertTrue(normalizationFs.folder("\u006F\u0302").exists());
+ Assert.assertFalse(inMemoryFs.folder("\u006F\u0302").exists());
+ Assert.assertTrue(inMemoryFs.folder("\u00F4").exists());
+ }
+
+ @Test
+ public void testNoFolderMigration() {
+ FileSystem inMemoryFs = new InMemoryFileSystem();
+ inMemoryFs.folder("\u00F4").create();
+ FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFC);
+ Assert.assertTrue(normalizationFs.folder("\u00F4").exists());
+ Assert.assertTrue(normalizationFs.folder("\u006F\u0302").exists());
+ Assert.assertFalse(inMemoryFs.folder("\u006F\u0302").exists());
+ Assert.assertTrue(inMemoryFs.folder("\u00F4").exists());
+ }
+
+ @Test
+ public void testNfcDisplayNames() {
+ FileSystem inMemoryFs = new InMemoryFileSystem();
+ inMemoryFs.folder("a\u00F4").create();
+ inMemoryFs.folder("b\u006F\u0302").create();
+ FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFC);
+ Assert.assertEquals("a\u00F4", normalizationFs.folder("a\u00F4").name());
+ Assert.assertEquals("b\u00F4", normalizationFs.folder("b\u006F\u0302").name());
+ }
+
+ @Test
+ public void testNfdDisplayNames() {
+ FileSystem inMemoryFs = new InMemoryFileSystem();
+ inMemoryFs.folder("a\u00F4").create();
+ inMemoryFs.folder("b\u006F\u0302").create();
+ FileSystem normalizationFs = new NormalizedNameFileSystem(inMemoryFs, Form.NFD);
+ Assert.assertEquals("a\u006F\u0302", normalizationFs.folder("a\u00F4").name());
+ Assert.assertEquals("b\u006F\u0302", normalizationFs.folder("b\u006F\u0302").name());
+ }
+
+}
diff --git a/main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFileTest.java b/main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFileTest.java
new file mode 100644
index 000000000..3a3f1fe64
--- /dev/null
+++ b/main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFileTest.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Sebastian Stenzel and others.
+ * This file is licensed under the terms of the MIT license.
+ * See the LICENSE.txt file for more info.
+ *
+ * Contributors:
+ * Sebastian Stenzel - initial API and implementation
+ *******************************************************************************/
+package org.cryptomator.filesystem.charsets;
+
+import java.text.Normalizer.Form;
+
+import org.cryptomator.filesystem.File;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class NormalizedNameFileTest {
+
+ private File delegateNfc;
+ private File delegateNfd;
+
+ @Before
+ public void setup() {
+ delegateNfc = Mockito.mock(File.class);
+ delegateNfd = Mockito.mock(File.class);
+ Mockito.when(delegateNfc.name()).thenReturn("\u00C5");
+ Mockito.when(delegateNfd.name()).thenReturn("\u0041\u030A");
+ }
+
+ @Test
+ public void testDisplayNameInNfc() {
+ File file1 = new NormalizedNameFile(null, delegateNfc, Form.NFC);
+ File file2 = new NormalizedNameFile(null, delegateNfd, Form.NFC);
+ Assert.assertEquals("\u00C5", file1.name());
+ Assert.assertEquals("\u00C5", file2.name());
+ }
+
+ @Test
+ public void testDisplayNameInNfd() {
+ File file1 = new NormalizedNameFile(null, delegateNfc, Form.NFD);
+ File file2 = new NormalizedNameFile(null, delegateNfd, Form.NFD);
+ Assert.assertEquals("\u0041\u030A", file1.name());
+ Assert.assertEquals("\u0041\u030A", file2.name());
+ }
+
+}
diff --git a/main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFolderTest.java b/main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFolderTest.java
new file mode 100644
index 000000000..7bf017652
--- /dev/null
+++ b/main/filesystem-charsets/src/test/java/org/cryptomator/filesystem/charsets/NormalizedNameFolderTest.java
@@ -0,0 +1,149 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Sebastian Stenzel and others.
+ * This file is licensed under the terms of the MIT license.
+ * See the LICENSE.txt file for more info.
+ *
+ * Contributors:
+ * Sebastian Stenzel - initial API and implementation
+ *******************************************************************************/
+package org.cryptomator.filesystem.charsets;
+
+import java.text.Normalizer.Form;
+
+import org.cryptomator.filesystem.File;
+import org.cryptomator.filesystem.Folder;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class NormalizedNameFolderTest {
+
+ private Folder delegate;
+ private File delegateSubFileNfc;
+ private File delegateSubFileNfd;
+ private Folder delegateSubFolderNfc;
+ private Folder delegateSubFolderNfd;
+
+ @Before
+ public void setup() {
+ delegate = Mockito.mock(Folder.class);
+ delegateSubFileNfc = Mockito.mock(File.class);
+ delegateSubFileNfd = Mockito.mock(File.class);
+ Mockito.when(delegate.file("\u00C5")).thenReturn(delegateSubFileNfc);
+ Mockito.when(delegateSubFileNfc.name()).thenReturn("\u00C5");
+ Mockito.when(delegate.file("\u0041\u030A")).thenReturn(delegateSubFileNfd);
+ Mockito.when(delegateSubFileNfd.name()).thenReturn("\u0041\u030A");
+ delegateSubFolderNfc = Mockito.mock(Folder.class);
+ delegateSubFolderNfd = Mockito.mock(Folder.class);
+ Mockito.when(delegate.folder("\u00F4")).thenReturn(delegateSubFolderNfc);
+ Mockito.when(delegateSubFolderNfc.name()).thenReturn("\u00F4");
+ Mockito.when(delegate.folder("\u006F\u0302")).thenReturn(delegateSubFolderNfd);
+ Mockito.when(delegateSubFolderNfd.name()).thenReturn("\u006F\u0302");
+ }
+
+ @Test
+ public void testDisplayNameInNfc() {
+ Folder folder1 = new NormalizedNameFolder(null, delegateSubFolderNfc, Form.NFC);
+ Folder folder2 = new NormalizedNameFolder(null, delegateSubFolderNfd, Form.NFC);
+ Assert.assertEquals("\u00F4", folder1.name());
+ Assert.assertEquals("\u00F4", folder2.name());
+ }
+
+ @Test
+ public void testDisplayNameInNfd() {
+ Folder folder1 = new NormalizedNameFolder(null, delegateSubFolderNfc, Form.NFD);
+ Folder folder2 = new NormalizedNameFolder(null, delegateSubFolderNfd, Form.NFD);
+ Assert.assertEquals("\u006F\u0302", folder1.name());
+ Assert.assertEquals("\u006F\u0302", folder2.name());
+ }
+
+ @Test
+ public void testNoFolderMigration1() {
+ Mockito.when(delegateSubFolderNfc.exists()).thenReturn(true);
+ Mockito.when(delegateSubFolderNfd.exists()).thenReturn(false);
+ Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC);
+ Folder sub1 = folder.folder("\u00F4");
+ Folder sub2 = folder.folder("\u006F\u0302");
+ Mockito.verify(delegateSubFolderNfd, Mockito.never()).moveTo(Mockito.any());
+ Assert.assertSame(sub1, sub2);
+ }
+
+ @Test
+ public void testNoFolderMigration2() {
+ Mockito.when(delegateSubFolderNfc.exists()).thenReturn(true);
+ Mockito.when(delegateSubFolderNfd.exists()).thenReturn(true);
+ Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC);
+ Folder sub1 = folder.folder("\u00F4");
+ Folder sub2 = folder.folder("\u006F\u0302");
+ Mockito.verify(delegateSubFolderNfd, Mockito.never()).moveTo(Mockito.any());
+ Assert.assertSame(sub1, sub2);
+ }
+
+ @Test
+ public void testNoFolderMigration3() {
+ Mockito.when(delegateSubFolderNfc.exists()).thenReturn(false);
+ Mockito.when(delegateSubFolderNfd.exists()).thenReturn(false);
+ Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC);
+ Folder sub1 = folder.folder("\u00F4");
+ Folder sub2 = folder.folder("\u006F\u0302");
+ Mockito.verify(delegateSubFolderNfd, Mockito.never()).moveTo(Mockito.any());
+ Assert.assertSame(sub1, sub2);
+ }
+
+ @Test
+ public void testFolderMigration() {
+ Mockito.when(delegateSubFolderNfc.exists()).thenReturn(false);
+ Mockito.when(delegateSubFolderNfd.exists()).thenReturn(true);
+ Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC);
+ Folder sub1 = folder.folder("\u00F4");
+ Mockito.verify(delegateSubFolderNfd).moveTo(delegateSubFolderNfc);
+ Folder sub2 = folder.folder("\u006F\u0302");
+ Assert.assertSame(sub1, sub2);
+ }
+
+ @Test
+ public void testNoFileMigration1() {
+ Mockito.when(delegateSubFileNfc.exists()).thenReturn(true);
+ Mockito.when(delegateSubFileNfd.exists()).thenReturn(false);
+ Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC);
+ File sub1 = folder.file("\u00C5");
+ File sub2 = folder.file("\u0041\u030A");
+ Mockito.verify(delegateSubFileNfd, Mockito.never()).moveTo(Mockito.any());
+ Assert.assertSame(sub1, sub2);
+ }
+
+ @Test
+ public void testNoFileMigration2() {
+ Mockito.when(delegateSubFileNfc.exists()).thenReturn(true);
+ Mockito.when(delegateSubFileNfd.exists()).thenReturn(true);
+ Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC);
+ File sub1 = folder.file("\u00C5");
+ File sub2 = folder.file("\u0041\u030A");
+ Mockito.verify(delegateSubFileNfd, Mockito.never()).moveTo(Mockito.any());
+ Assert.assertSame(sub1, sub2);
+ }
+
+ @Test
+ public void testNoFileMigration3() {
+ Mockito.when(delegateSubFileNfc.exists()).thenReturn(false);
+ Mockito.when(delegateSubFileNfd.exists()).thenReturn(false);
+ Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC);
+ File sub1 = folder.file("\u00C5");
+ File sub2 = folder.file("\u0041\u030A");
+ Mockito.verify(delegateSubFileNfd, Mockito.never()).moveTo(Mockito.any());
+ Assert.assertSame(sub1, sub2);
+ }
+
+ @Test
+ public void testFileMigration() {
+ Mockito.when(delegateSubFileNfc.exists()).thenReturn(false);
+ Mockito.when(delegateSubFileNfd.exists()).thenReturn(true);
+ Folder folder = new NormalizedNameFolder(null, delegate, Form.NFC);
+ File sub1 = folder.file("\u00C5");
+ Mockito.verify(delegateSubFileNfd).moveTo(delegateSubFileNfc);
+ File sub2 = folder.file("\u0041\u030A");
+ Assert.assertSame(sub1, sub2);
+ }
+
+}
diff --git a/main/filesystem-charsets/src/test/resources/log4j2.xml b/main/filesystem-charsets/src/test/resources/log4j2.xml
new file mode 100644
index 000000000..9b4889392
--- /dev/null
+++ b/main/filesystem-charsets/src/test/resources/log4j2.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/main/filesystem-invariants-tests/pom.xml b/main/filesystem-invariants-tests/pom.xml
index aa13917ee..054464338 100644
--- a/main/filesystem-invariants-tests/pom.xml
+++ b/main/filesystem-invariants-tests/pom.xml
@@ -20,6 +20,10 @@
org.cryptomator
filesystem-api
+
+ org.cryptomator
+ filesystem-charsets
+
org.cryptomator
filesystem-crypto
diff --git a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileSystemFactories.java b/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileSystemFactories.java
index 5f9a423f5..8f1eed8ca 100644
--- a/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileSystemFactories.java
+++ b/main/filesystem-invariants-tests/src/test/java/org/cryptomator/filesystem/invariants/FileSystemFactories.java
@@ -4,11 +4,13 @@ import static org.cryptomator.common.test.TempFilesRemovedOnShutdown.createTempD
import java.io.IOException;
import java.io.UncheckedIOException;
+import java.text.Normalizer.Form;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.cryptomator.filesystem.FileSystem;
+import org.cryptomator.filesystem.charsets.NormalizedNameFileSystem;
import org.cryptomator.filesystem.crypto.CryptoEngineTestModule;
import org.cryptomator.filesystem.crypto.CryptoFileSystemDelegate;
import org.cryptomator.filesystem.crypto.CryptoFileSystemTestComponent;
@@ -35,8 +37,10 @@ class FileSystemFactories implements Iterable {
add("ShorteningFileSystem > InMemoryFileSystem", this::createShorteningFileSystemInMemory);
add("StatsFileSystem > NioFileSystem", this::createStatsFileSystemNio);
add("StatsFileSystem > InMemoryFileSystem", this::createStatsFileSystemInMemory);
- add("StatsFileSystem > CryptoFileSystem > ShorteningFileSystem > InMemoryFileSystem", this::createCompoundFileSystemInMemory);
- add("StatsFileSystem > CryptoFileSystem > ShorteningFileSystem > NioFileSystem", this::createCompoundFileSystemNio);
+ add("NormalizingFileSystem > NioFileSystem", this::createNormalizingFileSystemNio);
+ add("NormalizingFileSystem > InMemoryFileSystem", this::createNormalizingFileSystemInMemory);
+ add("StatsFileSystem > NormalizingFileSystem > CryptoFileSystem > ShorteningFileSystem > InMemoryFileSystem", this::createCompoundFileSystemInMemory);
+ add("StatsFileSystem > NormalizingFileSystem > CryptoFileSystem > ShorteningFileSystem > NioFileSystem", this::createCompoundFileSystemNio);
}
private FileSystem createCryptoFileSystemInMemory() {
@@ -63,6 +67,14 @@ class FileSystemFactories implements Iterable {
return createStatsFileSystem(createInMemoryFileSystem());
}
+ private FileSystem createNormalizingFileSystemNio() {
+ return createNormalizingFileSystem(createInMemoryFileSystem());
+ }
+
+ private FileSystem createNormalizingFileSystemInMemory() {
+ return createNormalizingFileSystem(createInMemoryFileSystem());
+ }
+
private FileSystem createCompoundFileSystemNio() {
return createCompoundFileSystem(createNioFileSystem());
}
@@ -84,13 +96,17 @@ class FileSystemFactories implements Iterable {
}
private FileSystem createCompoundFileSystem(FileSystem delegate) {
- return createStatsFileSystem(createCryptoFileSystem(createShorteningFileSystem(delegate)));
+ return createStatsFileSystem(createNormalizingFileSystem(createCryptoFileSystem(createShorteningFileSystem(delegate))));
}
private FileSystem createStatsFileSystem(FileSystem delegate) {
return new StatsFileSystem(delegate);
}
+ private FileSystem createNormalizingFileSystem(FileSystem delegate) {
+ return new NormalizedNameFileSystem(delegate, Form.NFC);
+ }
+
private FileSystem createCryptoFileSystem(FileSystem delegate) {
CRYPTO_FS_COMP.cryptoFileSystemFactory().initializeNew(delegate, "aPassphrase");
return CRYPTO_FS_COMP.cryptoFileSystemFactory().unlockExisting(delegate, "aPassphrase", Mockito.mock(CryptoFileSystemDelegate.class));
diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavComponent.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavComponent.java
index 9f6c5dbff..68f1a43e7 100644
--- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavComponent.java
+++ b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/WebDavComponent.java
@@ -10,10 +10,12 @@ package org.cryptomator.frontend.webdav;
import javax.inject.Singleton;
+import org.cryptomator.common.CommonsModule;
+
import dagger.Component;
@Singleton
-@Component
+@Component(modules = {CommonsModule.class})
public interface WebDavComponent {
WebDavServer server();
diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MacOsXWebDavMounter.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MacOsXAppleScriptWebDavMounter.java
similarity index 91%
rename from main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MacOsXWebDavMounter.java
rename to main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MacOsXAppleScriptWebDavMounter.java
index 0acf2ddee..f381e2e6f 100644
--- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MacOsXWebDavMounter.java
+++ b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MacOsXAppleScriptWebDavMounter.java
@@ -12,11 +12,13 @@ package org.cryptomator.frontend.webdav.mount;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
+import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
+import javax.inject.Named;
import javax.inject.Singleton;
import org.apache.commons.io.IOUtils;
@@ -26,15 +28,18 @@ import org.cryptomator.frontend.CommandFailedException;
import org.cryptomator.frontend.Frontend.MountParam;
@Singleton
-final class MacOsXWebDavMounter implements WebDavMounterStrategy {
+final class MacOsXAppleScriptWebDavMounter implements WebDavMounterStrategy {
+
+ private final Comparator semVerComparator;
@Inject
- MacOsXWebDavMounter() {
+ MacOsXAppleScriptWebDavMounter(@Named("SemVer") Comparator semVerComparator) {
+ this.semVerComparator = semVerComparator;
}
@Override
public boolean shouldWork() {
- return SystemUtils.IS_OS_MAC_OSX;
+ return SystemUtils.IS_OS_MAC_OSX && semVerComparator.compare(SystemUtils.OS_VERSION, "10.10") >= 0;
}
@Override
diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MacOsXShellScriptWebDavMounter.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MacOsXShellScriptWebDavMounter.java
new file mode 100644
index 000000000..89e8a86a2
--- /dev/null
+++ b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MacOsXShellScriptWebDavMounter.java
@@ -0,0 +1,89 @@
+/*******************************************************************************
+ * Copyright (c) 2014, 2016 Sebastian Stenzel, Markus Kreusch
+ * This file is licensed under the terms of the MIT license.
+ * See the LICENSE.txt file for more info.
+ *
+ * Contributors:
+ * Sebastian Stenzel - initial API and implementation, strategy fine tuning
+ * Markus Kreusch - Refactored WebDavMounter to use strategy pattern
+ ******************************************************************************/
+package org.cryptomator.frontend.webdav.mount;
+
+import java.net.URI;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.apache.commons.lang3.SystemUtils;
+import org.cryptomator.frontend.CommandFailedException;
+import org.cryptomator.frontend.Frontend.MountParam;
+import org.cryptomator.frontend.webdav.mount.command.Script;
+
+@Singleton
+final class MacOsXShellScriptWebDavMounter implements WebDavMounterStrategy {
+
+ private final Comparator semVerComparator;
+
+ @Inject
+ MacOsXShellScriptWebDavMounter(@Named("SemVer") Comparator semVerComparator) {
+ this.semVerComparator = semVerComparator;
+ }
+
+ @Override
+ public boolean shouldWork() {
+ return SystemUtils.IS_OS_MAC_OSX && semVerComparator.compare(SystemUtils.OS_VERSION, "10.10") < 0;
+ }
+
+ @Override
+ public void warmUp(int serverPort) {
+ // no-op
+ }
+
+ @Override
+ public WebDavMount mount(URI uri, Map> mountParams) throws CommandFailedException {
+ final String mountName = mountParams.getOrDefault(MountParam.MOUNT_NAME, Optional.empty()).orElseThrow(() -> {
+ return new IllegalArgumentException("Missing mount parameter MOUNT_NAME.");
+ });
+
+ // we don't use the uri to derive a path, as it *could* be longer than 255 chars.
+ final String path = "/Volumes/Cryptomator_" + UUID.randomUUID().toString();
+ final Script mountScript = Script.fromLines("mkdir \"$MOUNT_PATH\"", "mount_webdav -S -v $MOUNT_NAME \"$DAV_AUTHORITY$DAV_PATH\" \"$MOUNT_PATH\"").addEnv("DAV_AUTHORITY", uri.getRawAuthority())
+ .addEnv("DAV_PATH", uri.getRawPath()).addEnv("MOUNT_PATH", path).addEnv("MOUNT_NAME", mountName);
+ mountScript.execute();
+ return new MacWebDavMount(path);
+ }
+
+ private static class MacWebDavMount extends AbstractWebDavMount {
+ private final String mountPath;
+ private final Script revealScript;
+ private final Script unmountScript;
+
+ private MacWebDavMount(String mountPath) {
+ this.mountPath = mountPath;
+ this.revealScript = Script.fromLines("open \"$MOUNT_PATH\"").addEnv("MOUNT_PATH", mountPath);
+ this.unmountScript = Script.fromLines("diskutil umount $MOUNT_PATH").addEnv("MOUNT_PATH", mountPath);
+ }
+
+ @Override
+ public void unmount() throws CommandFailedException {
+ // only attempt unmount if user didn't unmount manually:
+ if (Files.exists(FileSystems.getDefault().getPath(mountPath))) {
+ unmountScript.execute();
+ }
+ }
+
+ @Override
+ public void reveal() throws CommandFailedException {
+ revealScript.execute();
+ }
+
+ }
+
+}
diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MountStrategies.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MountStrategies.java
index edb5645fa..3cd1b2a08 100644
--- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MountStrategies.java
+++ b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MountStrategies.java
@@ -19,74 +19,87 @@ import javax.inject.Singleton;
@Singleton
class MountStrategies implements Collection {
-
+
private final Collection delegate;
-
+
@Inject
- MountStrategies(LinuxGvfsWebDavMounter linuxMounter, MacOsXWebDavMounter osxMounter, WindowsWebDavMounter winMounter) {
- delegate = unmodifiableList(asList(linuxMounter, osxMounter, winMounter));
+ MountStrategies(LinuxGvfsWebDavMounter linuxMounter, MacOsXAppleScriptWebDavMounter osxAppleScriptMounter, MacOsXShellScriptWebDavMounter osxShellScriptMounter, WindowsWebDavMounter winMounter) {
+ delegate = unmodifiableList(asList(linuxMounter, osxAppleScriptMounter, osxShellScriptMounter, winMounter));
}
+ @Override
public int size() {
return delegate.size();
}
+ @Override
public boolean isEmpty() {
return delegate.isEmpty();
}
+ @Override
public boolean contains(Object o) {
return delegate.contains(o);
}
+ @Override
public Iterator iterator() {
return delegate.iterator();
}
+ @Override
public Object[] toArray() {
return delegate.toArray();
}
+ @Override
public T[] toArray(T[] a) {
return delegate.toArray(a);
}
+ @Override
public boolean add(WebDavMounterStrategy e) {
return delegate.add(e);
}
+ @Override
public boolean remove(Object o) {
return delegate.remove(o);
}
+ @Override
public boolean containsAll(Collection> c) {
return delegate.containsAll(c);
}
+ @Override
public boolean addAll(Collection extends WebDavMounterStrategy> c) {
return delegate.addAll(c);
}
+ @Override
public boolean removeAll(Collection> c) {
return delegate.removeAll(c);
}
+ @Override
public boolean retainAll(Collection> c) {
return delegate.retainAll(c);
}
+ @Override
public void clear() {
delegate.clear();
}
+ @Override
public boolean equals(Object o) {
return delegate.equals(o);
}
+ @Override
public int hashCode() {
return delegate.hashCode();
}
-
-
}
diff --git a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WindowsWebDavMounter.java b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WindowsWebDavMounter.java
index a2a257a61..ee8fd5b96 100644
--- a/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WindowsWebDavMounter.java
+++ b/main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/WindowsWebDavMounter.java
@@ -11,10 +11,16 @@ package org.cryptomator.frontend.webdav.mount;
import static org.cryptomator.frontend.webdav.mount.command.Script.fromLines;
+import java.io.IOException;
+import java.io.InterruptedIOException;
import java.net.URI;
import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -22,12 +28,16 @@ import java.util.regex.Pattern;
import javax.inject.Inject;
import javax.inject.Singleton;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.CharUtils;
+import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.frontend.CommandFailedException;
import org.cryptomator.frontend.Frontend.MountParam;
import org.cryptomator.frontend.webdav.mount.command.CommandResult;
import org.cryptomator.frontend.webdav.mount.command.Script;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* A {@link WebDavMounterStrategy} utilizing the "net use" command.
@@ -37,7 +47,9 @@ import org.cryptomator.frontend.webdav.mount.command.Script;
@Singleton
final class WindowsWebDavMounter implements WebDavMounterStrategy {
+ private static final Logger LOG = LoggerFactory.getLogger(WindowsWebDavMounter.class);
private static final Pattern WIN_MOUNT_DRIVELETTER_PATTERN = Pattern.compile("\\s*([A-Z]):\\s*");
+ private static final Pattern REG_QUERY_PROXY_OVERRIDES_PATTERN = Pattern.compile("\\s*ProxyOverride\\s+REG_SZ\\s+(.*)\\s*");
private static final String AUTO_ASSIGN_DRIVE_LETTER = "*";
private static final String LOCALHOST = "localhost";
private static final int MOUNT_TIMEOUT_SECONDS = 60;
@@ -60,12 +72,12 @@ final class WindowsWebDavMounter implements WebDavMounterStrategy {
@Override
public WebDavMount mount(URI uri, Map> mountParams) throws CommandFailedException {
- final String driveLetter = mountParams.getOrDefault(MountParam.WIN_DRIVE_LETTER, Optional.of(AUTO_ASSIGN_DRIVE_LETTER)).orElse(AUTO_ASSIGN_DRIVE_LETTER);
+ final String driveLetter = mountParams.getOrDefault(MountParam.WIN_DRIVE_LETTER, Optional.empty()).orElse(AUTO_ASSIGN_DRIVE_LETTER);
if (driveLetters.getOccupiedDriveLetters().contains(CharUtils.toChar(driveLetter))) {
throw new CommandFailedException("Drive letter occupied.");
}
-
- final String hostname = mountParams.getOrDefault(MountParam.HOSTNAME, Optional.of(LOCALHOST)).orElse(LOCALHOST);
+
+ final String hostname = mountParams.getOrDefault(MountParam.HOSTNAME, Optional.empty()).orElse(LOCALHOST);
try {
final URI adjustedUri = new URI(uri.getScheme(), uri.getUserInfo(), hostname, uri.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment());
CommandResult mountResult = mount(adjustedUri, driveLetter);
@@ -74,14 +86,14 @@ final class WindowsWebDavMounter implements WebDavMounterStrategy {
throw new IllegalArgumentException("Invalid host: " + hostname);
}
}
-
+
private CommandResult mount(URI uri, String driveLetter) throws CommandFailedException {
- final Script proxyBypassScript = fromLines(
- "reg add \"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\" /v \"ProxyOverride\" /d \";%DAV_HOST%;%DAV_HOST%:%DAV_PORT%\" /f");
- proxyBypassScript.addEnv("DAV_HOST", uri.getHost());
- proxyBypassScript.addEnv("DAV_PORT", String.valueOf(uri.getPort()));
- proxyBypassScript.execute();
-
+ try {
+ addProxyOverrides(uri);
+ } catch (IOException e) {
+ throw new CommandFailedException(e);
+ }
+
final String driveLetterStr = AUTO_ASSIGN_DRIVE_LETTER.equals(driveLetter) ? AUTO_ASSIGN_DRIVE_LETTER : driveLetter + ":";
final Script mountScript = fromLines("net use %DRIVE_LETTER% \\\\%DAV_HOST%@%DAV_PORT%\\DavWWWRoot%DAV_UNC_PATH% /persistent:no");
mountScript.addEnv("DRIVE_LETTER", driveLetterStr);
@@ -90,6 +102,44 @@ final class WindowsWebDavMounter implements WebDavMounterStrategy {
mountScript.addEnv("DAV_UNC_PATH", uri.getRawPath().replace('/', '\\'));
return mountScript.execute(MOUNT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
}
+
+ private void addProxyOverrides(URI uri) throws IOException, CommandFailedException {
+ try {
+ // get existing value for ProxyOverride key from reqistry:
+ ProcessBuilder query = new ProcessBuilder("reg", "query", "\"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\"", "/v", "ProxyOverride");
+ Process queryCmd = query.start();
+ String queryStdOut = IOUtils.toString(queryCmd.getInputStream(), StandardCharsets.UTF_8);
+ int queryResult = queryCmd.waitFor();
+
+ // determine new value for ProxyOverride key:
+ Set overrides = new HashSet<>();
+ Matcher matcher = REG_QUERY_PROXY_OVERRIDES_PATTERN.matcher(queryStdOut);
+ if (queryResult == 0 && matcher.find()) {
+ String[] existingOverrides = StringUtils.split(matcher.group(1), ';');
+ overrides.addAll(Arrays.asList(existingOverrides));
+ }
+ overrides.removeIf(s -> s.startsWith(uri.getHost() + ":"));
+ overrides.add("");
+ overrides.add(uri.getHost());
+ overrides.add(uri.getHost() + ":" + uri.getPort());
+
+ // set new value:
+ String overridesStr = StringUtils.join(overrides, ';');
+ ProcessBuilder add = new ProcessBuilder("reg", "add", "\"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\"", "/v", "ProxyOverride", "/d", "\"" + overridesStr + "\"", "/f");
+ LOG.debug("Invoking command: " + StringUtils.join(add.command(), ' '));
+ Process addCmd = add.start();
+ int addResult = addCmd.waitFor();
+ if (addResult != 0) {
+ String addStdErr = IOUtils.toString(addCmd.getErrorStream(), StandardCharsets.UTF_8);
+ throw new CommandFailedException(addStdErr);
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ InterruptedIOException ioException = new InterruptedIOException();
+ ioException.initCause(e);
+ throw ioException;
+ }
+ }
private String getDriveLetter(String result) throws CommandFailedException {
final Matcher matcher = WIN_MOUNT_DRIVELETTER_PATTERN.matcher(result);
diff --git a/main/jacoco-report/.gitignore b/main/jacoco-report/.gitignore
new file mode 100644
index 000000000..b83d22266
--- /dev/null
+++ b/main/jacoco-report/.gitignore
@@ -0,0 +1 @@
+/target/
diff --git a/main/jacoco-report/pom.xml b/main/jacoco-report/pom.xml
new file mode 100644
index 000000000..20de8d2bf
--- /dev/null
+++ b/main/jacoco-report/pom.xml
@@ -0,0 +1,86 @@
+
+
+
+ 4.0.0
+
+ org.cryptomator
+ main
+ 1.1.0-SNAPSHOT
+
+ jacoco-report
+ Cryptomator Code Coverage Report
+
+
+
+
+ org.cryptomator
+ commons
+
+
+ org.cryptomator
+ commons-test
+
+
+
+
+ org.cryptomator
+ filesystem-api
+
+
+ org.cryptomator
+ filesystem-charsets
+
+
+ org.cryptomator
+ filesystem-crypto
+
+
+ org.cryptomator
+ filesystem-crypto-integration-tests
+
+
+ org.cryptomator
+ filesystem-inmemory
+
+
+ org.cryptomator
+ filesystem-nameshortening
+
+
+ org.cryptomator
+ filesystem-nio
+
+
+ org.cryptomator
+ filesystem-stats
+
+
+
+
+ org.cryptomator
+ frontend-api
+
+
+ org.cryptomator
+ frontend-webdav
+
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+
+
+ report-aggregate
+ verify
+
+ report-aggregate
+
+
+
+
+
+
+
diff --git a/main/pom.xml b/main/pom.xml
index aebabe97b..75ba59663 100644
--- a/main/pom.xml
+++ b/main/pom.xml
@@ -35,12 +35,12 @@
1.3
2.4
4.0
- 3.3.2
+ 3.4
1.10
3.1
2.4.4
1.10.19
- 2.0.2
+ 2.4
@@ -49,6 +49,16 @@
https://jitpack.io
+
+
+
+ jacoco-snapshots
+ https://oss.sonatype.org/content/repositories/snapshots
+
+ true
+
+
+
@@ -70,6 +80,11 @@
filesystem-api
${project.version}
+
+ org.cryptomator
+ filesystem-charsets
+ ${project.version}
+
org.cryptomator
filesystem-nio
@@ -81,6 +96,12 @@
${project.version}
test
+
+ org.cryptomator
+ filesystem-invariants-tests
+ ${project.version}
+ test
+
org.cryptomator
filesystem-nameshortening
@@ -238,7 +259,6 @@
hamcrest-all
${hamcrest.version}
-
@@ -271,6 +291,7 @@
frontend-api
frontend-webdav
ui
+ filesystem-charsets
@@ -281,6 +302,12 @@
ant-kit
+
+ test-coverage
+
+ jacoco-report
+
+
@@ -305,7 +332,7 @@
org.jacoco
jacoco-maven-plugin
- 0.7.5.201505241946
+ 0.7.7-SNAPSHOT
prepare-agent
@@ -314,6 +341,12 @@
+
+
+ **/*_*
+ **/Dagger*
+
+
@@ -332,6 +365,9 @@
coveralls-maven-plugin
4.0.0
+
+ jacoco-report/target/site/jacoco-aggregate/jacoco.xml
+
${env.COVERALLS_REPO_TOKEN}
diff --git a/main/ui/pom.xml b/main/ui/pom.xml
index 6808f3887..873c1af38 100644
--- a/main/ui/pom.xml
+++ b/main/ui/pom.xml
@@ -38,6 +38,10 @@
org.cryptomator
filesystem-crypto
+
+ org.cryptomator
+ filesystem-charsets
+
org.cryptomator
filesystem-stats
diff --git a/main/ui/src/main/java/org/cryptomator/ui/Cryptomator.java b/main/ui/src/main/java/org/cryptomator/ui/Cryptomator.java
index f4fbdb1a0..3150ddc4d 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/Cryptomator.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/Cryptomator.java
@@ -36,6 +36,8 @@ public class Cryptomator {
private static final CleanShutdownPerformer CLEAN_SHUTDOWN_PERFORMER = new CleanShutdownPerformer();
public static void main(String[] args) {
+ String cryptomatorVersion = Optional.ofNullable(Cryptomator.class.getPackage().getImplementationVersion()).orElse("SNAPSHOT");
+ LOG.info("Starting Cryptomator {} on {} {} ({})", cryptomatorVersion, SystemUtils.OS_NAME, SystemUtils.OS_VERSION, SystemUtils.OS_ARCH);
if (SystemUtils.IS_OS_MAC_OSX) {
/*
* On OSX we're in an awkward position. We need to register a handler in the main thread of this application. However, we can't
diff --git a/main/ui/src/main/java/org/cryptomator/ui/CryptomatorModule.java b/main/ui/src/main/java/org/cryptomator/ui/CryptomatorModule.java
index fa432cc9d..3740b566d 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/CryptomatorModule.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/CryptomatorModule.java
@@ -8,13 +8,13 @@
*******************************************************************************/
package org.cryptomator.ui;
-import java.util.Comparator;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.inject.Named;
import javax.inject.Singleton;
+import org.cryptomator.common.CommonsModule;
import org.cryptomator.crypto.engine.impl.CryptoEngineModule;
import org.cryptomator.frontend.FrontendFactory;
import org.cryptomator.frontend.webdav.WebDavServer;
@@ -24,7 +24,6 @@ import org.cryptomator.ui.model.VaultObjectMapperProvider;
import org.cryptomator.ui.settings.Settings;
import org.cryptomator.ui.settings.SettingsProvider;
import org.cryptomator.ui.util.DeferredCloser;
-import org.cryptomator.ui.util.SemVerComparator;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -33,7 +32,7 @@ import dagger.Provides;
import javafx.application.Application;
import javafx.stage.Stage;
-@Module(includes = CryptoEngineModule.class)
+@Module(includes = {CryptoEngineModule.class, CommonsModule.class})
class CryptomatorModule {
private final Application application;
@@ -65,13 +64,6 @@ class CryptomatorModule {
return closer;
}
- @Provides
- @Singleton
- @Named("SemVer")
- Comparator provideSemVerComparator() {
- return new SemVerComparator();
- }
-
@Provides
@Singleton
@Named("VaultJsonMapper")
diff --git a/main/ui/src/main/java/org/cryptomator/ui/ExitUtil.java b/main/ui/src/main/java/org/cryptomator/ui/ExitUtil.java
index 39cb090e3..3c4976cf4 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/ExitUtil.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/ExitUtil.java
@@ -136,6 +136,7 @@ class ExitUtil {
return;
} else {
settings.setNumTrayNotifications(settings.getNumTrayNotifications() - 1);
+ settings.save();
}
final Runnable notificationCmd;
if (SystemUtils.IS_OS_MAC_OSX) {
diff --git a/main/ui/src/main/java/org/cryptomator/ui/MainApplication.java b/main/ui/src/main/java/org/cryptomator/ui/MainApplication.java
index ed01184b3..86c16261b 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/MainApplication.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/MainApplication.java
@@ -38,6 +38,7 @@ public class MainApplication extends Application {
@Override
public void start(Stage primaryStage) throws IOException {
+ LOG.info("JavaFX application started");
final CryptomatorComponent comp = DaggerCryptomatorComponent.builder().cryptomatorModule(new CryptomatorModule(this, primaryStage)).build();
final MainController mainCtrl = comp.mainController();
closer = comp.deferredCloser();
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java
index 2b5e644c9..5944cf999 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java
@@ -12,21 +12,11 @@ package org.cryptomator.ui.controllers;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URL;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Singleton;
-import com.nulabinc.zxcvbn.Strength;
-import com.nulabinc.zxcvbn.Zxcvbn;
-import javafx.beans.property.IntegerProperty;
-import javafx.beans.property.SimpleIntegerProperty;
-import javafx.scene.control.Label;
-import javafx.scene.paint.Color;
-import javafx.scene.shape.Rectangle;
-import org.apache.commons.lang3.StringUtils;
import org.cryptomator.crypto.engine.InvalidPassphraseException;
import org.cryptomator.crypto.engine.UnsupportedVaultFormatException;
import org.cryptomator.ui.controls.SecPasswordField;
@@ -40,12 +30,16 @@ import org.slf4j.LoggerFactory;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.BooleanBinding;
+import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Hyperlink;
+import javafx.scene.control.Label;
+import javafx.scene.layout.Region;
import javafx.scene.text.Text;
@Singleton
@@ -54,19 +48,18 @@ public class ChangePasswordController extends LocalizedFXMLViewController {
private static final Logger LOG = LoggerFactory.getLogger(ChangePasswordController.class);
private final Application app;
+ private final PasswordStrengthUtil strengthRater;
final ObjectProperty vault = new SimpleObjectProperty<>();
private Optional listener = Optional.empty();
- final IntegerProperty passwordStrength = new SimpleIntegerProperty(); // 0-4
+ private final IntegerProperty passwordStrength = new SimpleIntegerProperty(); // 0-4
@Inject
- public ChangePasswordController(Application app, Localization localization) {
+ public ChangePasswordController(Application app, PasswordStrengthUtil strengthRater, Localization localization) {
super(localization);
this.app = app;
+ this.strengthRater = strengthRater;
}
- @Inject
- PasswordStrengthUtil strengthRater;
-
@FXML
private SecPasswordField oldPasswordField;
@@ -89,7 +82,19 @@ public class ChangePasswordController extends LocalizedFXMLViewController {
private Label passwordStrengthLabel;
@FXML
- private Rectangle passwordStrengthShape;
+ private Region passwordStrengthLevel0;
+
+ @FXML
+ private Region passwordStrengthLevel1;
+
+ @FXML
+ private Region passwordStrengthLevel2;
+
+ @FXML
+ private Region passwordStrengthLevel3;
+
+ @FXML
+ private Region passwordStrengthLevel4;
@Override
public void initialize() {
@@ -99,9 +104,16 @@ public class ChangePasswordController extends LocalizedFXMLViewController {
changePasswordButton.disableProperty().bind(oldPasswordIsEmpty.or(newPasswordIsEmpty.or(passwordsDiffer)));
passwordStrength.bind(EasyBind.map(newPasswordField.textProperty(), strengthRater::computeRate));
- passwordStrengthShape.widthProperty().bind(EasyBind.map(passwordStrength, strengthRater::getWidth));
- passwordStrengthShape.fillProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthColor));
- passwordStrengthShape.strokeWidthProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrokeWidth));
+ passwordStrengthLevel0.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(0));
+ passwordStrengthLevel1.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(1));
+ passwordStrengthLevel2.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(2));
+ passwordStrengthLevel3.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(3));
+ passwordStrengthLevel4.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(4));
+ passwordStrengthLevel0.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor));
+ passwordStrengthLevel1.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor));
+ passwordStrengthLevel2.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor));
+ passwordStrengthLevel3.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor));
+ passwordStrengthLevel4.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor));
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/InitializeController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/InitializeController.java
index 0a87c4fd6..96016aa55 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controllers/InitializeController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/InitializeController.java
@@ -9,16 +9,15 @@
******************************************************************************/
package org.cryptomator.ui.controllers;
-import javafx.application.Platform;
-import javafx.beans.binding.BooleanBinding;
-import javafx.beans.property.*;
-import javafx.event.ActionEvent;
-import javafx.fxml.FXML;
-import javafx.scene.control.Button;
-import javafx.scene.control.Label;
-import javafx.scene.paint.Color;
-import javafx.scene.shape.Rectangle;
-import org.apache.commons.lang3.StringUtils;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.URL;
+import java.nio.file.FileAlreadyExistsException;
+import java.util.Optional;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.settings.Localization;
@@ -27,35 +26,34 @@ import org.fxmisc.easybind.EasyBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.net.URL;
-import java.nio.file.FileAlreadyExistsException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-
-import com.nulabinc.zxcvbn.*;
+import javafx.application.Platform;
+import javafx.beans.binding.BooleanBinding;
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleIntegerProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.layout.Region;
@Singleton
public class InitializeController extends LocalizedFXMLViewController {
private static final Logger LOG = LoggerFactory.getLogger(InitializeController.class);
+ private final PasswordStrengthUtil strengthRater;
final ObjectProperty vault = new SimpleObjectProperty<>();
private Optional listener = Optional.empty();
- final IntegerProperty passwordStrength = new SimpleIntegerProperty(); // 0-4
+ private final IntegerProperty passwordStrength = new SimpleIntegerProperty(); // 0-4
@Inject
- public InitializeController(Localization localization) {
+ public InitializeController(Localization localization, PasswordStrengthUtil strengthRater) {
super(localization);
+ this.strengthRater = strengthRater;
}
- @Inject
- PasswordStrengthUtil strengthRater;
-
@FXML
private SecPasswordField passwordField;
@@ -72,7 +70,19 @@ public class InitializeController extends LocalizedFXMLViewController {
private Label passwordStrengthLabel;
@FXML
- private Rectangle passwordStrengthShape;
+ private Region passwordStrengthLevel0;
+
+ @FXML
+ private Region passwordStrengthLevel1;
+
+ @FXML
+ private Region passwordStrengthLevel2;
+
+ @FXML
+ private Region passwordStrengthLevel3;
+
+ @FXML
+ private Region passwordStrengthLevel4;
@Override
public void initialize() {
@@ -81,9 +91,16 @@ public class InitializeController extends LocalizedFXMLViewController {
okButton.disableProperty().bind(passwordIsEmpty.or(passwordsDiffer));
passwordStrength.bind(EasyBind.map(passwordField.textProperty(), strengthRater::computeRate));
- passwordStrengthShape.widthProperty().bind(EasyBind.map(passwordStrength, strengthRater::getWidth));
- passwordStrengthShape.fillProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthColor));
- passwordStrengthShape.strokeWidthProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrokeWidth));
+ passwordStrengthLevel0.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(0));
+ passwordStrengthLevel1.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(1));
+ passwordStrengthLevel2.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(2));
+ passwordStrengthLevel3.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(3));
+ passwordStrengthLevel4.visibleProperty().bind(passwordStrength.greaterThanOrEqualTo(4));
+ passwordStrengthLevel0.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor));
+ passwordStrengthLevel1.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor));
+ passwordStrengthLevel2.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor));
+ passwordStrengthLevel3.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor));
+ passwordStrengthLevel4.backgroundProperty().bind(EasyBind.map(passwordStrength, strengthRater::getBackgroundWithStrengthColor));
passwordStrengthLabel.textProperty().bind(EasyBind.map(passwordStrength, strengthRater::getStrengthDescription));
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java
index 5c262f6de..f316fe2c2 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java
@@ -40,6 +40,7 @@ import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
+import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
@@ -94,6 +95,9 @@ public class MainController extends LocalizedFXMLViewController {
this.changePasswordController = changePasswordController;
this.settingsController = settingsController;
this.vaults = FXCollections.observableList(settings.getDirectories());
+ this.vaults.addListener((Change extends Vault> c) -> {
+ settings.save();
+ });
// derived bindings:
this.isShowingSettings = activeController.isEqualTo(settingsController.get());
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java
index 7af0e8269..80cd60cab 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java
@@ -59,9 +59,9 @@ public class SettingsController extends LocalizedFXMLViewController {
useIpv6Checkbox.setSelected(SystemUtils.IS_OS_WINDOWS && settings.shouldUseIpv6());
versionLabel.setText(String.format(localization.getString("settings.version.label"), applicationVersion().orElse("SNAPSHOT")));
- EasyBind.subscribe(checkForUpdatesCheckbox.selectedProperty(), settings::setCheckForUpdatesEnabled);
+ EasyBind.subscribe(checkForUpdatesCheckbox.selectedProperty(), this::checkForUpdateDidChange);
EasyBind.subscribe(portField.textProperty(), this::portDidChange);
- EasyBind.subscribe(useIpv6Checkbox.selectedProperty(), settings::setUseIpv6);
+ EasyBind.subscribe(useIpv6Checkbox.selectedProperty(), this::useIpv6DidChange);
}
@Override
@@ -73,21 +73,30 @@ public class SettingsController extends LocalizedFXMLViewController {
return Optional.ofNullable(getClass().getPackage().getImplementationVersion());
}
+ private void checkForUpdateDidChange(Boolean newValue) {
+ settings.setCheckForUpdatesEnabled(newValue);
+ settings.save();
+ }
+
private void portDidChange(String newValue) {
try {
int port = Integer.parseInt(newValue);
- if (port < Settings.MIN_PORT) {
+ if (port < Settings.MIN_PORT || port > Settings.MAX_PORT) {
settings.setPort(Settings.DEFAULT_PORT);
- } else if (port < Settings.MAX_PORT) {
- settings.setPort(port);
} else {
- portField.setText(String.valueOf(Settings.MAX_PORT));
+ settings.setPort(port);
+ settings.save();
}
} catch (NumberFormatException e) {
portField.setText(String.valueOf(Settings.DEFAULT_PORT));
}
}
+ private void useIpv6DidChange(Boolean newValue) {
+ settings.setUseIpv6(newValue);
+ settings.save();
+ }
+
private void filterNumericKeyEvents(KeyEvent t) {
if (t.getCharacter() == null || t.getCharacter().length() == 0) {
return;
diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java
index 376e56d09..3cbc4bbc1 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/model/Vault.java
@@ -30,6 +30,7 @@ import org.cryptomator.common.LazyInitializer;
import org.cryptomator.common.Optionals;
import org.cryptomator.crypto.engine.InvalidPassphraseException;
import org.cryptomator.filesystem.FileSystem;
+import org.cryptomator.filesystem.charsets.NormalizedNameFileSystem;
import org.cryptomator.filesystem.crypto.CryptoFileSystemDelegate;
import org.cryptomator.filesystem.crypto.CryptoFileSystemFactory;
import org.cryptomator.filesystem.nio.NioFileSystem;
@@ -126,7 +127,8 @@ public class Vault implements CryptoFileSystemDelegate {
FileSystem fs = getNioFileSystem();
FileSystem shorteningFs = shorteningFileSystemFactory.get(fs);
FileSystem cryptoFs = cryptoFileSystemFactory.unlockExisting(shorteningFs, passphrase, this);
- StatsFileSystem statsFs = new StatsFileSystem(cryptoFs);
+ FileSystem normalizingFs = new NormalizedNameFileSystem(cryptoFs, SystemUtils.IS_OS_MAC_OSX ? Form.NFD : Form.NFC);
+ StatsFileSystem statsFs = new StatsFileSystem(normalizingFs);
statsFileSystem = Optional.of(statsFs);
String contextPath = StringUtils.prependIfMissing(mountName, "/");
Frontend frontend = frontendFactory.create(statsFs, contextPath);
diff --git a/main/ui/src/main/java/org/cryptomator/ui/settings/Localization.java b/main/ui/src/main/java/org/cryptomator/ui/settings/Localization.java
index d12082ef5..1c2fbf8d5 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/settings/Localization.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/settings/Localization.java
@@ -1,27 +1,70 @@
package org.cryptomator.ui.settings;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.UncheckedIOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.Collections;
import java.util.Enumeration;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import javax.inject.Inject;
import javax.inject.Singleton;
+import org.apache.commons.collections4.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
@Singleton
public class Localization extends ResourceBundle {
+ private static final Logger LOG = LoggerFactory.getLogger(Localization.class);
+
+ private static final String LOCALIZATION_DEFAULT_FILE = "/localization/en.txt";
+ private static final String LOCALIZATION_FILENAME_FMT = "/localization/%s.txt";
+ private static final String LOCALIZATION_FILE = String.format(LOCALIZATION_FILENAME_FMT, Locale.getDefault().getLanguage());
+
+ private final ResourceBundle fallback;
+ private final ResourceBundle localized;
+
@Inject
public Localization() {
- this.parent = ResourceBundle.getBundle("localization");
+ try (InputStream in = getClass().getResourceAsStream(LOCALIZATION_DEFAULT_FILE)) {
+ Objects.requireNonNull(in);
+ Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8);
+ this.fallback = new PropertyResourceBundle(reader);
+ LOG.info("Loaded localization from {}", LOCALIZATION_FILE);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+
+ try (InputStream in = getClass().getResourceAsStream(LOCALIZATION_FILE)) {
+ if (in != null) {
+ Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8);
+ this.localized = new PropertyResourceBundle(reader);
+ } else {
+ this.localized = this.fallback;
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
}
@Override
protected Object handleGetObject(String key) {
- return parent.getObject(key);
+ return localized.containsKey(key) ? localized.getObject(key) : fallback.getObject(key);
}
@Override
public Enumeration getKeys() {
- return parent.getKeys();
+ Collection keys = CollectionUtils.union(localized.keySet(), fallback.keySet());
+ return Collections.enumeration(keys);
}
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/settings/Settings.java b/main/ui/src/main/java/org/cryptomator/ui/settings/Settings.java
index 85c8fefdc..17f7c5b25 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/settings/Settings.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/settings/Settings.java
@@ -11,6 +11,7 @@ package org.cryptomator.ui.settings;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
import org.cryptomator.ui.model.Vault;
@@ -23,10 +24,12 @@ public class Settings implements Serializable {
private static final long serialVersionUID = 7609959894417878744L;
public static final int MIN_PORT = 1024;
public static final int MAX_PORT = 65535;
- public static final int DEFAULT_PORT = 0;
+ public static final int DEFAULT_PORT = 42427;
public static final boolean DEFAULT_USE_IPV6 = false;
public static final Integer DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
+ private final Consumer saveCmd;
+
@JsonProperty("directories")
private List directories;
@@ -35,7 +38,7 @@ public class Settings implements Serializable {
@JsonProperty("port")
private Integer port;
-
+
@JsonProperty("useIpv6")
private Boolean useIpv6;
@@ -45,8 +48,12 @@ public class Settings implements Serializable {
/**
* Package-private constructor; use {@link SettingsProvider}.
*/
- Settings() {
+ Settings(Consumer saveCmd) {
+ this.saveCmd = saveCmd;
+ }
+ public void save() {
+ saveCmd.accept(this);
}
/* Getter/Setter */
diff --git a/main/ui/src/main/java/org/cryptomator/ui/settings/SettingsProvider.java b/main/ui/src/main/java/org/cryptomator/ui/settings/SettingsProvider.java
index 9d40ac09d..417b6c369 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/settings/SettingsProvider.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/settings/SettingsProvider.java
@@ -16,6 +16,12 @@ import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
+import java.util.Objects;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Inject;
import javax.inject.Named;
@@ -23,7 +29,6 @@ import javax.inject.Provider;
import javax.inject.Singleton;
import org.apache.commons.lang3.SystemUtils;
-import org.cryptomator.ui.util.DeferredCloser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -32,9 +37,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
@Singleton
public class SettingsProvider implements Provider {
- private static final Logger LOG = LoggerFactory.getLogger(Settings.class);
+ private static final Logger LOG = LoggerFactory.getLogger(SettingsProvider.class);
private static final Path SETTINGS_DIR;
private static final String SETTINGS_FILE = "settings.json";
+ private static final long SAVE_DELAY_MS = 1000;
static {
final String appdata = System.getenv("APPDATA");
@@ -52,12 +58,12 @@ public class SettingsProvider implements Provider {
}
}
- private final DeferredCloser deferredCloser;
private final ObjectMapper objectMapper;
+ private final ScheduledExecutorService saveScheduler = Executors.newSingleThreadScheduledExecutor();
+ private final AtomicReference> scheduledSaveCmd = new AtomicReference<>();
@Inject
- public SettingsProvider(DeferredCloser deferredCloser, @Named("VaultJsonMapper") ObjectMapper objectMapper) {
- this.deferredCloser = deferredCloser;
+ public SettingsProvider(@Named("VaultJsonMapper") ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@@ -72,28 +78,39 @@ public class SettingsProvider implements Provider {
@Override
public Settings get() {
- Settings settings = null;
+ final Settings settings = new Settings(this::scheduleSave);
try {
final Path settingsPath = getSettingsPath();
final InputStream in = Files.newInputStream(settingsPath, StandardOpenOption.READ);
- settings = objectMapper.readValue(in, Settings.class);
+ objectMapper.readerForUpdating(settings).readValue(in);
+ LOG.info("Settings loaded from " + settingsPath);
} catch (IOException e) {
- LOG.warn("Failed to load settings, creating new one.");
- settings = new Settings();
+ LOG.info("Failed to load settings, creating new one.");
}
- deferredCloser.closeLater(settings, this::save);
return settings;
}
- private void save(Settings 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);
+ }
+ }
+
+ private void save(Settings settings) {
+ Objects.requireNonNull(settings);
try {
final Path settingsPath = getSettingsPath();
Files.createDirectories(settingsPath.getParent());
final OutputStream out = Files.newOutputStream(settingsPath, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
objectMapper.writeValue(out, settings);
+ LOG.info("Settings saved to " + settingsPath);
} catch (IOException e) {
LOG.error("Failed to save settings.", e);
}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/util/PasswordStrengthUtil.java b/main/ui/src/main/java/org/cryptomator/ui/util/PasswordStrengthUtil.java
index fe6af2f61..b2871a9b3 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/util/PasswordStrengthUtil.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/util/PasswordStrengthUtil.java
@@ -8,105 +8,76 @@
*******************************************************************************/
package org.cryptomator.ui.util;
-import com.nulabinc.zxcvbn.Zxcvbn;
-import javafx.beans.property.IntegerProperty;
-import javafx.scene.paint.Color;
-import org.apache.commons.lang3.StringUtils;
-import org.cryptomator.ui.settings.Localization;
+import java.util.ArrayList;
+import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
-import java.util.ArrayList;
-import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.cryptomator.ui.settings.Localization;
+
+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;
@Singleton
public class PasswordStrengthUtil {
- private final Zxcvbn zxcvbn;
- private final List sanitizedInputs;
- private final Localization localization;
+ private final Zxcvbn zxcvbn;
+ private final List sanitizedInputs;
+ private final Localization localization;
- @Inject
- public PasswordStrengthUtil(Localization localization){
- this.localization = localization;
- this.zxcvbn = new Zxcvbn();
- this.sanitizedInputs = new ArrayList<>();
- this.sanitizedInputs.add("cryptomator");
- }
+ @Inject
+ public PasswordStrengthUtil(Localization localization) {
+ this.localization = localization;
+ this.zxcvbn = new Zxcvbn();
+ this.sanitizedInputs = new ArrayList<>();
+ this.sanitizedInputs.add("cryptomator");
+ }
- public int computeRate(String password) {
- if (StringUtils.isEmpty(password)) {
- return -1;
- } else {
- return zxcvbn.measure(password, sanitizedInputs).getScore();
- }
- }
+ public int computeRate(String password) {
+ if (StringUtils.isEmpty(password)) {
+ return -1;
+ } else {
+ return zxcvbn.measure(password, sanitizedInputs).getScore();
+ }
+ }
- public Color getStrengthColor(Number score) {
- Color strengthColor = Color.web("#FF0000");
- switch (score.intValue()) {
- case 0:
- strengthColor = Color.web("#FF0000");
- break;
- case 1:
- strengthColor = Color.web("#FF8000");
- break;
- case 2:
- strengthColor = Color.web("#FFBF00");
- break;
- case 3:
- strengthColor = Color.web("#FFFF00");
- break;
- case 4:
- strengthColor = Color.web("#BFFF00");
- break;
- default:
- strengthColor = Color.web("#FF0000");
- break;
- }
- return strengthColor;
- }
+ public Color getStrengthColor(Number score) {
+ switch (score.intValue()) {
+ case 0:
+ return Color.web("#e74c3c");
+ case 1:
+ return Color.web("#e67e22");
+ case 2:
+ return Color.web("#f1c40f");
+ case 3:
+ return Color.web("#40d47e");
+ case 4:
+ return Color.web("#27ae60");
+ default:
+ return Color.TRANSPARENT;
+ }
+ }
- public int getWidth(Number score) {
- int width = 0;
- switch (score.intValue()) {
- case 0:
- width += 5;
- break;
- case 1:
- width += 25;
- break;
- case 2:
- width += 50;
- break;
- case 3:
- width += 75;
- break;
- case 4:
- width = 100;
- break;
- default:
- width = 0;
- break;
- }
- return Math.round(width*2.23f);
- }
+ public Background getBackgroundWithStrengthColor(Number score) {
+ Color c = this.getStrengthColor(score);
+ BackgroundFill fill = new BackgroundFill(c, CornerRadii.EMPTY, Insets.EMPTY);
+ return new Background(fill);
+ }
- public float getStrokeWidth(Number score) {
- if (score.intValue() >= 0) {
- return 0.5f;
- } else {
- return 0;
- }
- }
-
- public String getStrengthDescription(Number score) {
- if (score.intValue() >= 0) {
- return String.format(localization.getString("initialize.messageLabel.passwordStrength"),
- localization.getString("initialize.messageLabel.passwordStrength." + score.intValue()));
- } else {
- return "";
- }
- }
+ public String getStrengthDescription(Number score) {
+ String key = "initialize.messageLabel.passwordStrength." + score.intValue();
+ if (localization.containsKey(key)) {
+ return localization.getString(key);
+ } else {
+ return "";
+ }
+ }
}
diff --git a/main/ui/src/main/resources/bot_welcome.png b/main/ui/src/main/resources/bot_welcome.png
index 5daf92fd6..a429621d9 100644
Binary files a/main/ui/src/main/resources/bot_welcome.png and b/main/ui/src/main/resources/bot_welcome.png differ
diff --git a/main/ui/src/main/resources/bot_welcome@2x.png b/main/ui/src/main/resources/bot_welcome@2x.png
index 8c25a5e21..d6e201c20 100644
Binary files a/main/ui/src/main/resources/bot_welcome@2x.png and b/main/ui/src/main/resources/bot_welcome@2x.png differ
diff --git a/main/ui/src/main/resources/css/linux_theme.css b/main/ui/src/main/resources/css/linux_theme.css
index 4beabf9e8..9c8fc2f3a 100644
--- a/main/ui/src/main/resources/css/linux_theme.css
+++ b/main/ui/src/main/resources/css/linux_theme.css
@@ -50,6 +50,10 @@
-fx-font-family: Ionicons;
}
+.caption-label {
+ -fx-font-size: 0.9em;
+}
+
/****************************************************************************
* *
* Hyperlinks *
diff --git a/main/ui/src/main/resources/css/mac_theme.css b/main/ui/src/main/resources/css/mac_theme.css
index bac4a985d..33d1e223e 100644
--- a/main/ui/src/main/resources/css/mac_theme.css
+++ b/main/ui/src/main/resources/css/mac_theme.css
@@ -49,6 +49,10 @@
-fx-font-family: Ionicons;
}
+.caption-label {
+ -fx-font-size: 0.9em;
+}
+
/****************************************************************************
* *
* Hyperlinks *
diff --git a/main/ui/src/main/resources/css/win_theme.css b/main/ui/src/main/resources/css/win_theme.css
index f0109b004..bfd569b54 100644
--- a/main/ui/src/main/resources/css/win_theme.css
+++ b/main/ui/src/main/resources/css/win_theme.css
@@ -42,6 +42,10 @@
-fx-font-family: Ionicons;
}
+.caption-label {
+ -fx-font-size: 0.9em;
+}
+
/****************************************************************************
* *
* Hyperlinks *
diff --git a/main/ui/src/main/resources/fxml/change_password.fxml b/main/ui/src/main/resources/fxml/change_password.fxml
index 22c17ce9b..44d985526 100644
--- a/main/ui/src/main/resources/fxml/change_password.fxml
+++ b/main/ui/src/main/resources/fxml/change_password.fxml
@@ -21,9 +21,10 @@
+
+
+
-
-
@@ -46,18 +47,24 @@
-
+
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
-
-
+
+
diff --git a/main/ui/src/main/resources/fxml/initialize.fxml b/main/ui/src/main/resources/fxml/initialize.fxml
index e5fcd6deb..206843445 100644
--- a/main/ui/src/main/resources/fxml/initialize.fxml
+++ b/main/ui/src/main/resources/fxml/initialize.fxml
@@ -1,5 +1,4 @@
-
-
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
+
-
+
diff --git a/main/ui/src/main/resources/localization_de.properties b/main/ui/src/main/resources/localization/de.txt
similarity index 96%
rename from main/ui/src/main/resources/localization_de.properties
rename to main/ui/src/main/resources/localization/de.txt
index 12bee9c0f..d656e86c0 100644
--- a/main/ui/src/main/resources/localization_de.properties
+++ b/main/ui/src/main/resources/localization/de.txt
@@ -1,9 +1,13 @@
# Copyright (c) 2016 The Cryptomator Contributors
# This file is licensed under the terms of the MIT license.
# See the LICENSE.txt file for more info.
-#
+#
# Contributors:
-# Markus Kreusch - initial translation
+# Markus Kreusch
+# Michael Schmetter
+# Sebastian Wiesendahl
+# Tim Marius Wunderlich
+
app.name = Cryptomator
# main.fxml
main.emptyListInstructions = Klicken Sie hier, um neue Tresore hinzuzufügen
@@ -65,6 +69,7 @@ unlocked.label.statsDecrypted = entschlüsselt
unlocked.ioGraph.yAxis.label = Durchsatz (MiB/s)
# mac_warnings.fxml
macWarnings.windowTitle = Achtung - Kompromittierte Datei in %s
+# "potentially detected" or "potentially malicious"?
macWarnings.message = Cryptomator hat möglicherweise unerlaubte Veränderungen in den folgenden Dateien erkannt\:
macWarnings.moreInformationButton = Mehr erfahren
macWarnings.whitelistButton = Trotzdem entschlüsseln
@@ -80,4 +85,4 @@ tray.menu.open = Öffnen
tray.menu.quit = Beenden
tray.infoMsg.title = Cryptomator läuft noch
tray.infoMsg.msg = Cryptomator läuft noch. Mit dem Tray-Icon beenden.
-tray.infoMsg.msg.osx = Cryptomator läuft noch. Über die Menüleiste beenden.
\ No newline at end of file
+tray.infoMsg.msg.osx = Cryptomator läuft noch. Über die Menüleiste beenden.
diff --git a/main/ui/src/main/resources/localization.properties b/main/ui/src/main/resources/localization/en.txt
similarity index 95%
rename from main/ui/src/main/resources/localization.properties
rename to main/ui/src/main/resources/localization/en.txt
index 23d37051f..2541ad92f 100644
--- a/main/ui/src/main/resources/localization.properties
+++ b/main/ui/src/main/resources/localization/en.txt
@@ -24,10 +24,9 @@ initialize.label.retypePassword=Retype password
initialize.button.ok=Create vault
initialize.messageLabel.alreadyInitialized=Vault already initialized
initialize.messageLabel.initializationFailed=Could not initialize vault. See logfile for details.
-initialize.messageLabel.passwordStrength=Password Strength: %s
-initialize.messageLabel.passwordStrength.0=Weak
-initialize.messageLabel.passwordStrength.1=Fair
-initialize.messageLabel.passwordStrength.2=Good
+initialize.messageLabel.passwordStrength.0=Very weak
+initialize.messageLabel.passwordStrength.1=Weak
+initialize.messageLabel.passwordStrength.2=Fair
initialize.messageLabel.passwordStrength.3=Strong
initialize.messageLabel.passwordStrength.4=Very strong
diff --git a/main/ui/src/main/resources/localization/es.txt b/main/ui/src/main/resources/localization/es.txt
new file mode 100644
index 000000000..4433abb62
--- /dev/null
+++ b/main/ui/src/main/resources/localization/es.txt
@@ -0,0 +1,87 @@
+# Copyright (c) 2016 The Cryptomator Contributors
+# This file is licensed under the terms of the MIT license.
+# See the LICENSE.txt file for more info.
+#
+# Contributos:
+# Jay
+# Sebastian Wiesendahl
+
+app.name = Cryptomator
+# main.fxml
+main.emptyListInstructions = Click aquí para añadir una caja fuerte
+# should it be imperative?
+main.directoryList.contextMenu.remove = Eliminar de la lista
+main.directoryList.contextMenu.changePassword = Cambiar la contraseña
+main.addDirectory.contextMenu.new = Crear una nueva caja fuerte
+main.addDirectory.contextMenu.open = Abrir una caja fuerte existente
+# welcome.fxml
+welcome.checkForUpdates.label.currentlyChecking = Chequando por actualizaciónes...
+welcome.newVersionMessage = Se puede bajar version %s. Este es %s.
+# initialize.fxml
+initialize.label.password = Contraseña
+initialize.label.retypePassword = Reintroduzca contraseña
+initialize.button.ok = Crear caja fuerte
+initialize.messageLabel.alreadyInitialized = Caja fuerte ya está inicializado
+initialize.messageLabel.initializationFailed = No se pudo inicializar la caja fuerte. Ver archivo de registro para detalles.
+# notfound.fxml
+notfound.label = No se pudo encontrar la caja fuerte. Se movió a otro lugar?
+# upgrade.fxml
+upgrade.button = Actualizar caja fuerte
+upgrade.version3dropBundleExtension.msg = Este caja fuerte se debe actualizar a un nuevo formato.\n"%1$s" se renombra a "%2$s".\nPor favor aseguranse que la sincronización ya se terminó antes de continuar.
+upgrade.version3dropBundleExtension.err.alreadyExists = Migración automática ha fallado.\n"%s" ya existe.
+# unlock.fxml
+unlock.label.password = Contraseña
+unlock.label.mountName = Nombre del disco
+unlock.label.winDriveLetter = Letra del disco
+unlock.label.downloadsPageLink = Todas las versiones de Cryptomator
+unlock.label.advancedHeading = Opciones avanzadas
+unlock.button.unlock = Abrir caja fuerte
+unlock.button.advancedOptions.show = Más opciones
+unlock.button.advancedOptions.hide = Menos opciones
+unlock.choicebox.winDriveLetter.auto = Asignar automáticamente
+unlock.errorMessage.wrongPassword = Contraseña incorrecta
+unlock.errorMessage.mountingFailed = Monteo ha fallado. Ver archivo del registro para detalles.
+unlock.errorMessage.unsupportedKeyLengthInstallJCE = Decifración ha fallado. Por favor instala archivos de la Oracle JCE Unlimited Strength Policy.
+unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Caja fuerte insupportado. Este caja se ha creado con una versión pasada de Cryptomator.
+unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Caja fuerte insupportado. Este caja se ha creado con una versión nueva de Cryptomator.
+unlock.messageLabel.startServerFailed = Iniciación del servidor de WebDAV ha fallado.
+# change_password.fxml
+changePassword.label.oldPassword = Contraseña anterior
+# Can also use "current password" = "contraseña actual"
+changePassword.label.newPassword = Nueva contraseña
+changePassword.label.retypePassword = Reintroduzca contraseña
+changePassword.label.downloadsPageLink = Todas las versiones de Cryptomator
+changePassword.button.change = Cambiar contraseña
+changePassword.errorMessage.wrongPassword = Contraseña incorrecta
+changePassword.errorMessage.decryptionFailed = Decifración ha fallado
+changePassword.errorMessage.unsupportedKeyLengthInstallJCE = Decifración ha fallado. Por favor instala Oracle JCE Unlimited Strength Policy.
+changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Caja fuerte insupportado. Este caja se ha creado con una versión pasada de Cryptomator.
+changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault = Caja fuerte insupportado. Este caja se ha creado con una versión nueva de Cryptomator.
+changePassword.infoMessage.success = Contraseña se ha cambiado
+# unlocked.fxml
+unlocked.button.lock = Encerrar caja fuerte
+unlocked.moreOptions.reveal = Revelar disco
+unlocked.moreOptions.copyUrl = Copiar URL de WebDAV
+unlocked.label.revealFailed = Comando ha fallado
+unlocked.label.unmountFailed = Expulsar el disco ha fallado
+unlocked.label.statsEncrypted = cifrado
+unlocked.label.statsDecrypted = decifrado
+unlocked.ioGraph.yAxis.label = Procesamiento (MiB/s)
+# mac_warnings.fxml
+macWarnings.windowTitle = Peligro - archivo corrupto en %s
+macWarnings.message = Cryptomator ha detectado corrupciones malciosas en los seguientes archivos\:
+macWarnings.moreInformationButton = Aprende más
+macWarnings.whitelistButton = Decifrar selecionado de todos modos
+# settings.fxml
+settings.version.label = Version %s
+settings.checkForUpdates.label = Chequear por actualizaciones
+settings.port.label = Puerta de WebDAV *
+settings.port.prompt = 0 \= Elige automáticamente
+settings.useipv6.label = Usa literal de IPv6
+settings.requiresRestartLabel = * Cryptomator se necesita reiniciar
+# tray icon
+tray.menu.open = Abrir
+tray.menu.quit = Salir
+tray.infoMsg.title = Todavía en ejecución
+tray.infoMsg.msg = Cryptomator todavía esta en ejecución. Sale del icono del tray.
+tray.infoMsg.msg.osx = Cryptomator todavía esta en ejecución. Sale del icono de la barra del menú.
diff --git a/main/ui/src/main/resources/localization/fr.txt b/main/ui/src/main/resources/localization/fr.txt
new file mode 100644
index 000000000..17aec69f4
--- /dev/null
+++ b/main/ui/src/main/resources/localization/fr.txt
@@ -0,0 +1,86 @@
+# Copyright (c) 2016 The Cryptomator Contributors
+# This file is licensed under the terms of the MIT license.
+# See the LICENSE.txt file for more info.
+#
+# Contributors:
+# Jean-Noël Charon
+# Olivier Thomasson
+# vesparny
+
+app.name = Cryptomator
+# main.fxml
+main.emptyListInstructions = Cliquez ici pour ajouter un coffre
+main.directoryList.contextMenu.remove = Retirer de la liste
+main.directoryList.contextMenu.changePassword = Changer le mot de passe
+main.addDirectory.contextMenu.new = Créer un nouveau coffre
+main.addDirectory.contextMenu.open = Ouvrir un coffre existant
+# welcome.fxml
+welcome.checkForUpdates.label.currentlyChecking = Recherche de mise à jour...
+welcome.newVersionMessage = La version %s peut-être téléchargée. Il s'agit de %s.
+# initialize.fxml
+initialize.label.password = Mot de passe
+initialize.label.retypePassword = Confirmation
+initialize.button.ok = Créer le coffre
+initialize.messageLabel.alreadyInitialized = Coffre déjà initialisé
+initialize.messageLabel.initializationFailed = Impossible d'initialiser le coffre. Voir le fichier de log pour plus de détails.
+# notfound.fxml
+notfound.label = Coffre introuvable. A t'il été déplacé?
+# upgrade.fxml
+upgrade.button = Mettre à niveau
+upgrade.version3dropBundleExtension.msg = Ce coffre doit être converti dans un format plus récent.\n"%1$s" sera renommé en "%2$s".\nAssurez-vous que la synchronisation est terminée avant de continuer.
+upgrade.version3dropBundleExtension.err.alreadyExists = La conversion automatique a échoué.\n"%s" existe déjà.
+# unlock.fxml
+unlock.label.password = Mot de passe
+unlock.label.mountName = Nom du lecteur
+unlock.label.winDriveLetter = Lettre du lecteur
+unlock.label.downloadsPageLink = Toutes les versions de Cryptomator
+unlock.label.advancedHeading = Options avancées
+unlock.button.unlock = Déverrouiller le coffre
+unlock.button.advancedOptions.show = Plus d'options
+unlock.button.advancedOptions.hide = Moins d'options
+unlock.choicebox.winDriveLetter.auto = Assigner automatiquement
+unlock.errorMessage.wrongPassword = Mot de passe incorrect
+unlock.errorMessage.mountingFailed = Echec du montage. Voir le fichier de log pour plus de détails.
+unlock.errorMessage.unsupportedKeyLengthInstallJCE = Echec du décryptage. Veuillez installer la Policy Oracle "JCE Unlimited Strength Policy".
+unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Coffre non supporté. Ce coffre a été créé avec une ancienne version de Cryptomator.
+unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = Coffre non supporté. Ce coffre a été créé avec une version de Cryptomator plus récente.
+unlock.messageLabel.startServerFailed = Le serveur WebDAV n'a pas pu démarrer.
+# change_password.fxml
+changePassword.label.oldPassword = Ancien mot de passe
+changePassword.label.newPassword = Nouveau mot de passe
+changePassword.label.retypePassword = Vérification
+changePassword.label.downloadsPageLink = Toutes les versions de Cryptomator
+changePassword.button.change = Modification du mot de masse
+changePassword.errorMessage.wrongPassword = Mot de passe incorrect
+changePassword.errorMessage.decryptionFailed = Echec du décryptage
+changePassword.errorMessage.unsupportedKeyLengthInstallJCE = Echec du décryptage. Veuillez installer la Policy Oracle "JCE Unlimited Strength Policy".
+changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Coffre non supporté. Ce coffre a été créé avec une ancienne version de Cryptomator.
+changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault = Coffre non supporté. Ce coffre a été créé avec une version de Cryptomator plus récente.
+changePassword.infoMessage.success = Password changed
+# unlocked.fxml
+unlocked.button.lock = Verrouiller le coffre
+unlocked.moreOptions.reveal = Voir le lecteur
+unlocked.moreOptions.copyUrl = Copier l'URL WebDAV
+unlocked.label.revealFailed = Echec de la commande
+unlocked.label.unmountFailed = Echec de l'éjection du lecteur
+unlocked.label.statsEncrypted = crypté
+unlocked.label.statsDecrypted = décryptage
+unlocked.ioGraph.yAxis.label = Débit (MiB/s)
+# mac_warnings.fxml
+macWarnings.windowTitle = Attention - Fichier corrompu dans %s
+macWarnings.message = Cryptomator a détecté des corruptions de données dans les fichiers suivants\:
+macWarnings.moreInformationButton = En savoir plus
+macWarnings.whitelistButton = Décrypter tout de même
+# settings.fxml
+settings.version.label = Version %s
+settings.checkForUpdates.label = Vérif. des mises à jour
+settings.port.label = Port WebDAV *
+settings.port.prompt = 0 \= Choix automatique
+settings.useipv6.label = Utiliser un litéral IPv6
+settings.requiresRestartLabel = * Redémarrage requis
+# tray icon
+tray.menu.open = Ouvrir
+tray.menu.quit = Quitter
+tray.infoMsg.title = Toujours en fonctionnement
+tray.infoMsg.msg = Cryptomator est toujours en fonctionnement. Utiliser l'icône de la barre des tâches pour quitter.
+tray.infoMsg.msg.osx = Cryptomator est toujours en fonctionnement. Utilisez la barre de menu pour quitter.
diff --git a/main/ui/src/main/resources/localization/hu.txt b/main/ui/src/main/resources/localization/hu.txt
new file mode 100644
index 000000000..86deb76a0
--- /dev/null
+++ b/main/ui/src/main/resources/localization/hu.txt
@@ -0,0 +1,84 @@
+# Copyright (c) 2016 The Cryptomator Contributors
+# This file is licensed under the terms of the MIT license.
+# See the LICENSE.txt file for more info.
+#
+# Contributors:
+# Roland Burda
+
+app.name = Cryptomator
+# main.fxml
+main.emptyListInstructions = Kattints ide egy széf létrehozásához
+main.directoryList.contextMenu.remove = Eltávolítás listából
+main.directoryList.contextMenu.changePassword = Jelszó megváltoztatása
+main.addDirectory.contextMenu.new = Új széf létrehozása
+main.addDirectory.contextMenu.open = Létez\u0151 széf megnyitása
+# welcome.fxml
+welcome.checkForUpdates.label.currentlyChecking = Frissítések keresése...
+welcome.newVersionMessage = Új verzió érhet\u0151 el\: %s. Jelenlegi verzió\: %s.
+# initialize.fxml
+initialize.label.password = Jelszó
+initialize.label.retypePassword = Jelszó ismét
+initialize.button.ok = Széf létrehozása
+initialize.messageLabel.alreadyInitialized = A széf már meg van nyitva
+initialize.messageLabel.initializationFailed = Nem sikerült megnyitni a széfet. További információ a naplófájlban.
+# notfound.fxml
+notfound.label = Széf nem található. Lehetséges, hogy áthelyezésre került?
+# upgrade.fxml
+upgrade.button = Széf frissítése
+upgrade.version3dropBundleExtension.msg = A széf új verzióra történ\u0151 migrációja szükséges. "%1$s" a következ\u0151re lesz átnevezve\: "%2$s". Kérlek gy\u0151z\u0151dj meg a szinkronizáció befejeztér\u0151l, miel\u0151tt más m\u0171veletet végeznél.
+upgrade.version3dropBundleExtension.err.alreadyExists = Automatikus migráció meghíusúlt. "%s" már létezik.
+# unlock.fxml
+unlock.label.password = Jelszó
+unlock.label.mountName = Meghajtó neve
+unlock.label.winDriveLetter = Meghajtó bet\u0171jele
+unlock.label.downloadsPageLink = Összes Cryptomator verzió
+unlock.label.advancedHeading = Haladó beállítások
+unlock.button.unlock = Széf feloldása
+unlock.button.advancedOptions.show = További beállítások
+unlock.button.advancedOptions.hide = Alapbeállítások
+unlock.choicebox.winDriveLetter.auto = Automatikus hozzárendelés
+unlock.errorMessage.wrongPassword = Hibás jelszó
+unlock.errorMessage.mountingFailed = Meghajtó felcsatolása sikertelen. További információk a naplófájlban.
+unlock.errorMessage.unsupportedKeyLengthInstallJCE = A titkosítás feloldása sikertelen. Kérlek telepítsd a "Oracle JCE Unlimited Strength Policy Files"-t.
+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ó
+changePassword.label.retypePassword = Új jelszó ismét
+changePassword.label.downloadsPageLink = Összes Cryptomator verzió
+changePassword.button.change = Jelszó megváltoztatása
+changePassword.errorMessage.wrongPassword = Hibás jelszó
+changePassword.errorMessage.decryptionFailed = A titkosítás feloldása meghíusúlt
+changePassword.errorMessage.unsupportedKeyLengthInstallJCE = A titkosítás feloldása sikertelen. Kérlek telepítsd a "Oracle JCE Unlimited Strength Policy"-t.
+changePassword.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.
+changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault = Nem támogatott széf. Ez a széf a Cryptomator egy újabb verziójával került létrehozásra.
+changePassword.infoMessage.success = Jelszó megváltoztatva
+# unlocked.fxml
+unlocked.button.lock = Széf lezárása
+unlocked.moreOptions.reveal = Meghajtó felfedése
+unlocked.moreOptions.copyUrl = WebDAV URL másolása
+unlocked.label.revealFailed = Parancs meghíusúlt
+unlocked.label.unmountFailed = Meghajtó leválasztása sikertelen
+unlocked.label.statsEncrypted = titkosított
+unlocked.label.statsDecrypted = titkosítás feloldva
+unlocked.ioGraph.yAxis.label = Teljesítmény (MiB/s)
+# mac_warnings.fxml
+macWarnings.windowTitle = Veszély - Korrupt fájl a következ\u0151ben\: %s
+macWarnings.message = Cryptomator potenciálisan rosszindulatú hibákat fedezett fel a következ\u0151 fájlokban\:
+macWarnings.moreInformationButton = Tudj meg többet
+macWarnings.whitelistButton = Kiválasztottak titkosításának feloldása mindenképp
+# settings.fxml
+settings.version.label = Verzió\: %s
+settings.checkForUpdates.label = Frissítések keresése
+settings.port.label = WebDAV Port *
+settings.port.prompt = 0 \= Automatikus választás
+settings.useipv6.label = Literális IPv6 használata
+settings.requiresRestartLabel = * Cryptomator újraindítása szükséges
+# tray icon
+tray.menu.open = Megnyit
+tray.menu.quit = Kilépés
+tray.infoMsg.title = M\u0171velet folyamatban
+tray.infoMsg.msg = Cryptomator még fut. A tálcán található ikon segítségével bezárhatod.
+tray.infoMsg.msg.osx = Cryptomator még fut. A menüsávban található ikon segítségével bezárhatod.
diff --git a/main/ui/src/main/resources/localization/it.txt b/main/ui/src/main/resources/localization/it.txt
new file mode 100644
index 000000000..1448d8f56
--- /dev/null
+++ b/main/ui/src/main/resources/localization/it.txt
@@ -0,0 +1,84 @@
+# Copyright (c) 2016 The Cryptomator Contributors
+# This file is licensed under the terms of the MIT license.
+# See the LICENSE.txt file for more info.
+#
+# Contributors:
+# vesparny
+
+app.name = Cryptomator
+# main.fxml
+main.emptyListInstructions = Clicca qui per aggiungere un vault
+main.directoryList.contextMenu.remove = Rimuovi dalla lista
+main.directoryList.contextMenu.changePassword = Cambia la password
+main.addDirectory.contextMenu.new = Crea un nuovo vault
+main.addDirectory.contextMenu.open = Apri un vault
+# welcome.fxml
+welcome.checkForUpdates.label.currentlyChecking = Verifica aggiornamenti...
+welcome.newVersionMessage = La versione %s può essere scaricata. Questa è %s
+# initialize.fxml
+initialize.label.password = Password
+initialize.label.retypePassword = Conferma password
+initialize.button.ok = Crea un vault
+initialize.messageLabel.alreadyInitialized = Vault già inizializzato
+initialize.messageLabel.initializationFailed = Non è possibile inizializzare il vault. Controlla il file di log per dettagli.
+# notfound.fxml
+notfound.label = Il vault non può essere trovato. E' stato rimosso?
+# upgrade.fxml
+upgrade.button = Aggiorna vault
+upgrade.version3dropBundleExtension.msg = Questo vault deve essere migrato ad un nuovo formato.\n"%1$s" verrà rinominato in "%2$s".\nPer favore verifica che la sincronizzazione sia finita prima di procedere\n
+upgrade.version3dropBundleExtension.err.alreadyExists = Migrazione automatica fallita.\n"%s" esiste già.
+# unlock.fxml
+unlock.label.password = Password
+unlock.label.mountName = nome del drive
+unlock.label.winDriveLetter = lettera del drive
+unlock.label.downloadsPageLink = Tutte le versioni di Cryptomator
+unlock.label.advancedHeading = Opzioni avanzate
+unlock.button.unlock = Sblocca vault
+unlock.button.advancedOptions.show = Più opzioni
+unlock.button.advancedOptions.hide = Meno opzioni
+unlock.choicebox.winDriveLetter.auto = Assegna automaticamente
+unlock.errorMessage.wrongPassword = Password errata
+unlock.errorMessage.mountingFailed = Montaggio fallito. Controlla il file di log per dettagli.
+unlock.errorMessage.unsupportedKeyLengthInstallJCE = Decriptaggio fallito.
+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
+changePassword.label.retypePassword = Conferma password
+changePassword.label.downloadsPageLink = Tutte le versioni di Cryptomator
+changePassword.button.change = Cambia la password
+changePassword.errorMessage.wrongPassword = Password errata
+changePassword.errorMessage.decryptionFailed = Decriptaggio fallito
+changePassword.errorMessage.unsupportedKeyLengthInstallJCE = Decriptaggio fallito. Per favore installa Oracle JCE Unlimited Strength Policy
+changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Vault non supportato. Questo vault è stato creato con una versione di Cryptomator più recente.
+changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault = Vault non supportato. Questo vault è stato creato con una versione di Cryptomator più vecchia.
+changePassword.infoMessage.success = Password cambiata
+# unlocked.fxml
+unlocked.button.lock = Blocca vault
+unlocked.moreOptions.reveal = Apri il disco
+unlocked.moreOptions.copyUrl = Copia url WebDAV
+unlocked.label.revealFailed = Comando fallito
+unlocked.label.unmountFailed = Espulsione disco fallita
+unlocked.label.statsEncrypted = criptato
+unlocked.label.statsDecrypted = decriptato
+unlocked.ioGraph.yAxis.label = Volume dati (MiB/s)
+# mac_warnings.fxml
+macWarnings.windowTitle = Pericolo - File corroto in %s
+macWarnings.message = Cryptomator ha individuato potenziali pericolose corruzioni nei seguenti file\:
+macWarnings.moreInformationButton = Più informazioni
+macWarnings.whitelistButton = Decripta i selezionati comunque
+# settings.fxml
+settings.version.label = Versione %s
+settings.checkForUpdates.label = Verifica aggiornamenti
+settings.port.label = WebDAV Port *
+settings.port.prompt = 0 \= Scegli automaticamente
+settings.useipv6.label = Utilizza IPv6 literal
+settings.requiresRestartLabel = * Cryptomator deve essere riavviato
+# tray icon
+tray.menu.open = Apri
+tray.menu.quit = Chiudi
+tray.infoMsg.title = Ancora in esecuzione
+tray.infoMsg.msg = Cryptomator è ancora in esecuzione. Chiudilo utilizzando l'icona nel menù di stato.
+tray.infoMsg.msg.osx = Cryptomator è ancora in esecuzione. Chiudilo utilizzando l'icona nella barra del menù.
diff --git a/main/ui/src/main/resources/localization/kr.txt b/main/ui/src/main/resources/localization/kr.txt
new file mode 100644
index 000000000..1978f9093
--- /dev/null
+++ b/main/ui/src/main/resources/localization/kr.txt
@@ -0,0 +1,84 @@
+# Copyright (c) 2016 The Cryptomator Contributors
+# This file is licensed under the terms of the MIT license.
+# See the LICENSE.txt file for more info.
+#
+# Contributors:
+# ChangHwan Kim
+
+app.name = Cryptomator
+# main.fxml
+main.emptyListInstructions = 여기를 클릭하여 보관함 추가하기
+main.directoryList.contextMenu.remove = 목록에서 삭제
+main.directoryList.contextMenu.changePassword = 비밀번호 변경
+main.addDirectory.contextMenu.new = 새 보관함 만들기
+main.addDirectory.contextMenu.open = 기존 보관함 열기
+# welcome.fxml
+welcome.checkForUpdates.label.currentlyChecking = 업데이트 확인
+welcome.newVersionMessage = %s 버전이 새로 다운로드 가능합니다. 지금 버전은 %s 입니다.
+# initialize.fxml
+initialize.label.password = 비밀번호
+initialize.label.retypePassword = 비밀번호 재입력
+initialize.button.ok = 보관함 만들기
+initialize.messageLabel.alreadyInitialized = 이미 보관함이 초기화되었습니다.
+initialize.messageLabel.initializationFailed = 보관함을 초기화할 수 없습니다. 자세한 사항은 로그 파일을 참조하세요.
+# notfound.fxml
+notfound.label = 보관함을 찾을 수 없습니다. 옮겨진 것은 아닌가요?
+# upgrade.fxml
+upgrade.button = 보관함 업그레이드
+upgrade.version3dropBundleExtension.msg = 이 보관함은 새로운 형식으로 다시 바뀔 필요가 있습니다. "%1$s"의 이름은 "%2$s"로 바뀔 것입니다. 진행하기 전에 동기화가 완료되었는지 다시 한 번 확인해주시기 바랍니다.
+upgrade.version3dropBundleExtension.err.alreadyExists = 자동 마이그레이션 실패. "%s"가 이미 존재합니다.
+# unlock.fxml
+unlock.label.password = 비밀번호
+unlock.label.mountName = 드라이브 이름
+unlock.label.winDriveLetter = 드라이브 문자
+unlock.label.downloadsPageLink = 모든 Cryptomator 버전
+unlock.label.advancedHeading = 고급 옵션
+unlock.button.unlock = 보관함 해제
+unlock.button.advancedOptions.show = 더 많은 옵션
+unlock.button.advancedOptions.hide = 기본 옵션
+unlock.choicebox.winDriveLetter.auto = 자동으로 할당
+unlock.errorMessage.wrongPassword = 틀린 비밀번호
+unlock.errorMessage.mountingFailed = 마운트 실패. 자세한 사항은 로그 파일을 참조하세요.
+unlock.errorMessage.unsupportedKeyLengthInstallJCE = 복호화 실패. Oracle JCE Unlimited Strength Policy Files을 설치하세요.
+unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware = 지원되지 않는 보관함. 이 보관함은 이전 버전의 Cryptomator에서 생성되었습니다.
+unlock.errorMessage.unsupportedVersion.softwareOlderThanVault = 지원되지 않는 보관함. 이 보관함은 최신 버전의 Cryptomator에서 생성되었습니다.
+unlock.messageLabel.startServerFailed = WedDAV 서버 시작 실패
+# change_password.fxml
+changePassword.label.oldPassword = 이전 비밀번호
+changePassword.label.newPassword = 새로운 비밀번호
+changePassword.label.retypePassword = 비밀번호 재입력
+changePassword.label.downloadsPageLink = 모든 Cryptomator 버전
+changePassword.button.change = 비밀번호 변경
+changePassword.errorMessage.wrongPassword = 틀린 비밀번호
+changePassword.errorMessage.decryptionFailed = 복호화 실패
+changePassword.errorMessage.unsupportedKeyLengthInstallJCE = 복호화 실패. Oracle JCE Unlimited Strength Policy Files을 설치하세요.
+changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware = 지원되지 않는 보관함. 이 보관함은 이전 버전의 Cryptomator에서 생성되었습니다.
+changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault = 지원되지 않는 보관함. 이 보관함은 최신 버전의 Cryptomator에서 생성되었습니다.
+changePassword.infoMessage.success = 비밀번호 변경
+# unlocked.fxml
+unlocked.button.lock = 보관함 잠그기
+unlocked.moreOptions.reveal = 드라이브 표시
+unlocked.moreOptions.copyUrl = WebDAV 주소 복사
+unlocked.label.revealFailed = 명령 실패
+unlocked.label.unmountFailed = 드라이브 추출 실패
+unlocked.label.statsEncrypted = 암호화
+unlocked.label.statsDecrypted = 복호화
+unlocked.ioGraph.yAxis.label = 처리량 (MiB/s)
+# mac_warnings.fxml
+macWarnings.windowTitle = 위험 - %s에 손상된 파일
+macWarnings.message = Cryptomator가 다음 파일들에서 잠재적인 손상 위험을 감지했습니다.
+macWarnings.moreInformationButton = 더 알아보기
+macWarnings.whitelistButton = 선택 항목 강제 복호화
+# settings.fxml
+settings.version.label = 버전 %s
+settings.checkForUpdates.label = 업데이트 확인
+settings.port.label = WebDAV 포트 *
+settings.port.prompt = 0 \= 자동으로 선택
+settings.useipv6.label = IPv6 사용
+settings.requiresRestartLabel = * Cryptomator 재시작 필요
+# tray icon
+tray.menu.open = 열기
+tray.menu.quit = 종료
+tray.infoMsg.title = 계속 실행 중입니다.
+tray.infoMsg.msg = Cryptomator가 계속 실행 중입니다. 종료하실려면 트레이 아이콘에서 해주세요.
+tray.infoMsg.msg.osx = Cryptomator가 계속 실행중입니다. 종료하실려면 메뉴 바 아이콘에서 해주세요.
diff --git a/main/ui/src/main/resources/localization/pt.txt b/main/ui/src/main/resources/localization/pt.txt
new file mode 100644
index 000000000..ef8cdfbe3
--- /dev/null
+++ b/main/ui/src/main/resources/localization/pt.txt
@@ -0,0 +1,34 @@
+# Copyright (c) 2016 The Cryptomator Contributors
+# This file is licensed under the terms of the MIT license.
+# See the LICENSE.txt file for more info.
+#
+# Contributors:
+# Will
+# André Di Biasi
+
+app.name = Cryptomator
+main.directoryList.contextMenu.remove = Remover da lista
+main.directoryList.contextMenu.changePassword = Alterar senha
+# welcome.fxml
+welcome.checkForUpdates.label.currentlyChecking = Procurando atualizações...
+# initialize.fxml
+initialize.label.password = Senha
+initialize.label.retypePassword = Digite a senha novamente
+# unlock.fxml
+unlock.label.password = Senha
+unlock.label.advancedHeading = Opções avançadas
+unlock.button.advancedOptions.hide = Menos opções
+unlock.choicebox.winDriveLetter.auto = Atribuir automaticamente
+unlock.errorMessage.wrongPassword = Senha incorreta
+unlock.errorMessage.mountingFailed = Montagem falhou. Veja o arquivo de log para detalhes
+unlock.messageLabel.startServerFailed = Inicialização do servidor WEBDAV falhou
+# change_password.fxml
+changePassword.label.oldPassword = Senha antiga
+changePassword.label.newPassword = Nova senha
+changePassword.label.retypePassword = Digite a senha novamente
+changePassword.button.change = Alterar senha
+changePassword.infoMessage.success = Senha alterada
+unlocked.label.revealFailed = Falha no comando
+unlocked.label.unmountFailed = Falha ao ejetar o drive
+unlocked.label.statsEncrypted = encriptado
+macWarnings.moreInformationButton = Saiba mais
diff --git a/main/ui/src/main/resources/localization/ru.txt b/main/ui/src/main/resources/localization/ru.txt
new file mode 100644
index 000000000..31241b6ea
--- /dev/null
+++ b/main/ui/src/main/resources/localization/ru.txt
@@ -0,0 +1,55 @@
+# Copyright (c) 2016 The Cryptomator Contributors
+# This file is licensed under the terms of the MIT license.
+# See the LICENSE.txt file for more info.
+#
+# Contributors:
+# Garik
+# Konstantine
+
+app.name = Cryptomator
+# main.fxml
+main.emptyListInstructions = Нажмите здесь что добавить хранилище
+main.directoryList.contextMenu.remove = Удалить из списка
+main.directoryList.contextMenu.changePassword = Сменить пароль
+main.addDirectory.contextMenu.new = Создать новое хранилище
+main.addDirectory.contextMenu.open = Открыть существующее хранилище
+# welcome.fxml
+welcome.checkForUpdates.label.currentlyChecking = Проверка обновлений...
+welcome.newVersionMessage = Версия может быть скачена. Это.
+# initialize.fxml
+initialize.label.password = Пароль
+initialize.label.retypePassword = Повторите пароль
+initialize.button.ok = Создать хранилище
+initialize.messageLabel.alreadyInitialized = Хранилище уже инициализировано
+initialize.messageLabel.initializationFailed = Невозможно инициировать хранилище. Смотрите лог для деталей.
+# notfound.fxml
+notfound.label = Хранилище не найдено.Оно было удалено?
+# upgrade.fxml
+upgrade.button = Обновить хранилище
+upgrade.version3dropBundleExtension.msg = Этому хранилищу нужно мигрировать в новый формат.\n"%1$s" will be renamed to "%2$s".\nPlease make sure synchronization has finished before proceeding.
+upgrade.version3dropBundleExtension.err.alreadyExists = Автоматическая миграция не удалась.\n"%" уже существует.
+# unlock.fxml
+unlock.label.password = Пароль
+unlock.label.mountName = Имя носителя
+changePassword.errorMessage.wrongPassword = Неправильный пароль
+changePassword.errorMessage.decryptionFailed = Расшифровка провалилась
+changePassword.errorMessage.unsupportedKeyLengthInstallJCE = Расшифровка не удалась. Пожалуйста установите Oracle JCE Unlimited Strength Policy.
+changePassword.infoMessage.success = Пароль изменился
+# unlocked.fxml
+unlocked.button.lock = Заблокировать хранилище
+unlocked.moreOptions.copyUrl = Скопировать WebDAV URL
+unlocked.label.revealFailed = Команда не удалась
+unlocked.label.unmountFailed = Извлечение диска не удалось
+unlocked.label.statsEncrypted = зашифровано
+unlocked.label.statsDecrypted = расшифровано
+macWarnings.moreInformationButton = Изучить больше
+# 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 работает. Выйдите c помощью иконки в трее.
+tray.infoMsg.msg.osx = Cryptomator всё ещё работает.Выйдите с помощью иконки в меню баре.
diff --git a/main/ui/src/main/resources/localization/sk.txt b/main/ui/src/main/resources/localization/sk.txt
new file mode 100644
index 000000000..74ef6bf14
--- /dev/null
+++ b/main/ui/src/main/resources/localization/sk.txt
@@ -0,0 +1,88 @@
+# Copyright (c) 2016 The Cryptomator Contributors
+# This file is licensed under the terms of the MIT license.
+# See the LICENSE.txt file for more info.
+#
+# Contributors:
+# Filip Havrlent
+# Tatiana Chovancová
+
+# Copyright (c) 2016 The Cryptomator Contributors
+# This file is licensed under the terms of the MIT license.
+# See the LICENSE.txt file for more info.
+app.name = Cryptomator
+# main.fxml
+main.emptyListInstructions = Pridať trezor
+main.directoryList.contextMenu.remove = Odstrániť zo zoznamu
+main.directoryList.contextMenu.changePassword = Zmeniť heslo
+main.addDirectory.contextMenu.new = Vytvoriť nový trezor
+main.addDirectory.contextMenu.open = Otvoriť existujúci trezor
+# welcome.fxml
+welcome.checkForUpdates.label.currentlyChecking = Kontrolujú sa aktualizácie...
+welcome.newVersionMessage = Verzia %s je pripravená na stiahnutie. Toto je verzia %s.
+# initialize.fxml
+initialize.label.password = Heslo
+initialize.label.retypePassword = Zadajte heslo znova
+initialize.button.ok = Vytvoriť trezor
+initialize.messageLabel.alreadyInitialized = Trezor je už inicializovaný
+initialize.messageLabel.initializationFailed = Nepodarilo sa inicializovať trezor. Pozrite súbor záznamov pre viac detailov.
+# notfound.fxml
+notfound.label = Trezor nemohol byť nenájdený. Bol presunutý?
+# upgrade.fxml
+upgrade.button = Upgradnúť trezor
+upgrade.version3dropBundleExtension.msg = Tento trezor musí byť premigrovaný na nový formát. "%1$s" bude premenovaný na "%2$s". Prosím, uistite sa že je dokončená synchronizácia skôr než budete pokračovať.
+upgrade.version3dropBundleExtension.err.alreadyExists = Automatická migrácia zlyhala. "%s" už existuje.
+# unlock.fxml
+unlock.label.password = Heslo
+unlock.label.mountName = Názov jednotky
+unlock.label.winDriveLetter = Označenie jednotky
+unlock.label.downloadsPageLink = Všetky verzie Cryptomatoru
+unlock.label.advancedHeading = Pokročilé nastavenia
+unlock.button.unlock = Odomknúť trezor
+unlock.button.advancedOptions.show = Viac nastavení
+unlock.button.advancedOptions.hide = Menej nastavení
+unlock.choicebox.winDriveLetter.auto = Priradiť automaticky
+unlock.errorMessage.wrongPassword = Nesprávne heslo
+unlock.errorMessage.mountingFailed = Pripájanie zlyhalo. Viac informácii v logu.
+unlock.errorMessage.unsupportedKeyLengthInstallJCE = Dešifrovanie zlyhalo. Prosím nainštalujte Oracle JCE Unlimited Strength Policy Files.
+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
+changePassword.label.retypePassword = Znova zadajte heslo
+changePassword.label.downloadsPageLink = Všetky verzie Cryptomatoru.
+changePassword.button.change = Zmeniť heslo
+changePassword.errorMessage.wrongPassword = Nesprávne heslo
+changePassword.errorMessage.decryptionFailed = Dešifrovanie zlyhalo.
+changePassword.errorMessage.unsupportedKeyLengthInstallJCE = Dešifrovanie zlyhalo. Prosím nainštalujte Oracle JCE Unlimited Strength Policy.
+changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware = Nepodporovaný trezor. Bol vytvorený staršou verziou Cryptomatoru.
+changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault = Nepodporovaný trezor. Bol vytvorený novšou verziou Cryptomatoru.
+changePassword.infoMessage.success = Heslo zmenené
+# unlocked.fxml
+unlocked.button.lock = Zamknúť trezor
+unlocked.moreOptions.reveal = Odhaliť jednotku
+unlocked.moreOptions.copyUrl = Kopírovať WebDAV URL
+unlocked.label.revealFailed = Príkaz zlyhal
+unlocked.label.unmountFailed = Odpájanie jednotky zlyhalo
+unlocked.label.statsEncrypted = zašifrované
+unlocked.label.statsDecrypted = dešifrované
+unlocked.ioGraph.yAxis.label = Priepustnosť (MiB/s)
+# mac_warnings.fxml
+macWarnings.windowTitle = Upozornenie - Poškodený súbor v %s
+macWarnings.message = Cryptomator odhalil potencionálne škodlivé poškodenie v nasledujúcich súboroch\:
+macWarnings.moreInformationButton = Zistiť viac
+macWarnings.whitelistButton = Napriek tomu dešifrovať vybrané
+# settings.fxml
+settings.version.label = Verzia %s
+settings.checkForUpdates.label = Skontrolovať aktualizácie
+settings.port.label = WebDAV Port *
+settings.port.prompt = 0 \= Vybrať automaticky
+settings.useipv6.label = Použiť IPv6
+settings.requiresRestartLabel = * Cryptomator vyžaduje reštart
+# tray icon
+tray.menu.open = Otvoriť
+tray.menu.quit = Vypnúť
+tray.infoMsg.title = Stále beží
+tray.infoMsg.msg = Cryptomator je stále spustený. Vypnite ho pomocou ikony v systémovej lište.
+tray.infoMsg.msg.osx = Cryptomator je stále sputený. Ukončite ho pomocou ikony v menu.
diff --git a/main/ui/src/main/resources/localization_es.properties b/main/ui/src/main/resources/localization_es.properties
deleted file mode 100644
index 64cdce6f1..000000000
--- a/main/ui/src/main/resources/localization_es.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-# Copyright (c) 2016 The Cryptomator Contributors
-# This file is licensed under the terms of the MIT license.
-# See the LICENSE.txt file for more info.
-
-app.name=Cryptomator
diff --git a/main/ui/src/main/resources/localization_fr.properties b/main/ui/src/main/resources/localization_fr.properties
deleted file mode 100644
index 377f56331..000000000
--- a/main/ui/src/main/resources/localization_fr.properties
+++ /dev/null
@@ -1,96 +0,0 @@
-# Copyright (c) 2016 The Cryptomator Contributors
-# This file is licensed under the terms of the MIT license.
-# See the LICENSE.txt file for more info.
-#
-# Contributors:
-# Jean-Nol Charon - initial translation
-
-app.name=Cryptomator
-
-# main.fxml
-main.emptyListInstructions=Cliquez ici pour ajouter un coffre
-main.directoryList.contextMenu.remove=Retirer de la liste
-main.directoryList.contextMenu.changePassword=Changer le mot de passe
-main.addDirectory.contextMenu.new=Crer un nouveau coffre
-main.addDirectory.contextMenu.open=Ouvrir un coffre existant
-
-# welcome.fxml
-welcome.checkForUpdates.label.currentlyChecking=Recherche de mise jour...
-welcome.newVersionMessage=La version %s peut-tre tlcharge. Il s'agit de %s.
-
-# initialize.fxml
-initialize.label.password=Mot de passe
-initialize.label.retypePassword=Confirmation
-initialize.button.ok=Crer le coffre
-initialize.messageLabel.alreadyInitialized=Coffre dj initialis
-initialize.messageLabel.initializationFailed=Impossible d'initialiser le coffre. Voir le fichier de log pour plus de dtails.
-
-# notfound.fxml
-notfound.label=Coffre introuvable. A t'il t dplac?
-
-# upgrade.fxml
-upgrade.button=Mettre niveau
-
-upgrade.version3dropBundleExtension.msg=This vault needs to be migrated to a newer format.\n"%1$s" will be renamed to "%2$s".\nPlease make sure synchronization has finished before proceeding.
-upgrade.version3dropBundleExtension.err.alreadyExists=Automatic migration failed.\n"%s" already exists.
-
-# unlock.fxml
-unlock.label.password=Mot de passe
-unlock.label.mountName=Nom du lecteur
-unlock.label.winDriveLetter=Lettre du lecteur
-unlock.label.downloadsPageLink=Toutes les versions de Cryptomator
-unlock.label.advancedHeading=Options avances
-unlock.button.unlock=Dverrouiller le coffre
-unlock.button.advancedOptions.show=Plus d'options
-unlock.button.advancedOptions.hide=Moins d'options
-unlock.choicebox.winDriveLetter.auto=Assigner automatiquement
-unlock.errorMessage.wrongPassword=Mot de passe incorrect
-unlock.errorMessage.mountingFailed=Echec du montage. Voir le fichier de log pour plus de dtails.
-unlock.errorMessage.unsupportedKeyLengthInstallJCE=Echec du dcryptage. Veuillez installer la Policy Oracle "JCE Unlimited Strength Policy".
-unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware=Coffre non support. Ce coffre a t cr avec une ancienne version de Cryptomator.
-unlock.errorMessage.unsupportedVersion.softwareOlderThanVault=Coffre non support. Ce coffre a t cr avec une version de Cryptomator plus rcente.
-unlock.messageLabel.startServerFailed=Le serveur WebDAV n'a pas pu dmarrer.
-
-# change_password.fxml
-changePassword.label.oldPassword=Ancien mot de passe
-changePassword.label.newPassword=Nouveau mot de passe
-changePassword.label.retypePassword=Resaisissez votre mot de passe
-changePassword.label.downloadsPageLink=Toutes les versions de Cryptomator
-changePassword.button.change=Modification du mot de masse
-changePassword.errorMessage.wrongPassword=Mot de passe incorrect
-changePassword.errorMessage.decryptionFailed=Echec du dcryptage
-changePassword.errorMessage.unsupportedKeyLengthInstallJCE=Echec du dcryptage. Veuillez installer la Policy Oracle "JCE Unlimited Strength Policy".
-changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware=Coffre non support. Ce coffre a t cr avec une ancienne version de Cryptomator.
-changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault=Coffre non support. Ce coffre a t cr avec une version de Cryptomator plus rcente.
-changePassword.infoMessage.success=Password changed
-
-# unlocked.fxml
-unlocked.button.lock=Verrouiller le coffre
-unlocked.moreOptions.reveal=Voir le lecteur
-unlocked.moreOptions.copyUrl=Copier l'URL WebDAV
-unlocked.label.revealFailed=Echec de la commande
-unlocked.label.unmountFailed=Echec de l'jection du lecteur
-unlocked.label.statsEncrypted=cryptage
-unlocked.label.statsDecrypted=dcryptage
-unlocked.ioGraph.yAxis.label=Dbit (MiB/s)
-
-# mac_warnings.fxml
-macWarnings.windowTitle=Attention - Fichier corrompu dans %s
-macWarnings.message=Cryptomator a dtect des corruptions de donnes dans les fichiers suivants:
-macWarnings.moreInformationButton=En savoir plus
-macWarnings.whitelistButton=Dcrypter tout de mme
-
-# settings.fxml
-settings.version.label=Version %s
-settings.checkForUpdates.label=Vrif. des mises jour
-settings.port.label=Port WebDAV *
-settings.port.prompt=0 = Choix automatique
-settings.useipv6.label=Utiliser un litral IPv6
-settings.requiresRestartLabel=* Redmarrage requis
-
-# tray icon
-tray.menu.open=Ouvrir
-tray.menu.quit=Quitter
-tray.infoMsg.title=Toujours en fonctionnement
-tray.infoMsg.msg=Cryptomator est toujours en fonctionnement. Utiliser l'icne de la barre des tches pour quitter.
-tray.infoMsg.msg.osx=Cryptomator est toujours en fonctionnement. Utilisez la barre de menu pour quitter.