mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-15 17:21:27 +00:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9ba4935b6 | ||
|
|
b6ee29789e | ||
|
|
5ee82271f5 | ||
|
|
2eb4d87dd1 | ||
|
|
d0afeab74b | ||
|
|
cc74c2c05b | ||
|
|
8865cf0e4b | ||
|
|
65550ce70f | ||
|
|
78300f8bf1 | ||
|
|
32c65a7dda | ||
|
|
6d31ed7ea4 | ||
|
|
c3e5d3f38e | ||
|
|
e3900231aa | ||
|
|
06f13c57d4 | ||
|
|
fc1a5be85f | ||
|
|
a30b310c04 | ||
|
|
956dd855f9 | ||
|
|
67ba7cac40 | ||
|
|
9117b6bc0e | ||
|
|
bae826be28 | ||
|
|
d845e8d97a | ||
|
|
b37b2e4fb7 | ||
|
|
69f6a9927d | ||
|
|
addc9533eb | ||
|
|
8b717993ed | ||
|
|
f70d486462 | ||
|
|
293ac0ea3c | ||
|
|
e99a615b09 | ||
|
|
6da3fde864 | ||
|
|
3a725e4a16 | ||
|
|
e3256a747f | ||
|
|
adc20ea2f2 | ||
|
|
997f841662 | ||
|
|
e57b60f04e | ||
|
|
d5b4fb4fe9 | ||
|
|
edf92adfec | ||
|
|
718bacafa6 | ||
|
|
7122bdf199 |
11
.travis.yml
11
.travis.yml
@@ -5,7 +5,8 @@ jdk:
|
||||
|
||||
env:
|
||||
global:
|
||||
- secure: "Lgj042RD0X3rB8VZVZLWP1GetLhjd3PqI5JbJMlzgHJpDI6RkFIBLN9SWAGmkLPCehIp2zA5tu9+UVy0NNMxm9xz6SyjMCaxS28/fnYEXaNmwwDSF6O6gLUbdxyzoYIFPYOPmFxpzhebqnNIsxaM29oZpgRgUGqosCczQxiB+Ng="
|
||||
- secure: "Lgj042RD0X3rB8VZVZLWP1GetLhjd3PqI5JbJMlzgHJpDI6RkFIBLN9SWAGmkLPCehIp2zA5tu9+UVy0NNMxm9xz6SyjMCaxS28/fnYEXaNmwwDSF6O6gLUbdxyzoYIFPYOPmFxpzhebqnNIsxaM29oZpgRgUGqosCczQxiB+Ng=" #coveralls
|
||||
- secure: "IfYURwZaDWuBDvyn47n0k1Zod/IQw1FF+CS5nnV08Q+NfC3vGGJMwV8m59XnbfwnWGxwvCaAbk4qP6s6+ijgZNKkvgfFMo3rfTok5zt43bIqgaFOANYV+OC/1c59gYD6ZUxhW5iNgMgU3qdsRtJuwSmfkVv/jKyLGfAbS4kN8BA=" #coverity
|
||||
|
||||
before_install: "curl -L --cookie 'oraclelicense=accept-securebackup-cookie;' http://download.oracle.com/otn-pub/java/jce/8/jce_policy-8.zip -o /tmp/policy.zip && sudo unzip -j -o /tmp/policy.zip *.jar -d `jdk_switcher home oraclejdk8`/jre/lib/security && rm /tmp/policy.zip"
|
||||
|
||||
@@ -28,6 +29,14 @@ notifications:
|
||||
|
||||
before_deploy: mvn -fmain/pom.xml -Puber-jar clean package -DskipTests
|
||||
|
||||
addons:
|
||||
coverity_scan:
|
||||
project:
|
||||
name: "cryptomator/cryptomator"
|
||||
notification_email: sebastian.stenzel@cryptomator.org
|
||||
build_command: "mvn -fmain/pom.xml clean test -DskipTests"
|
||||
branch_pattern: coverity_scan
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
prerelease: true
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<artifactId>commons-test</artifactId>
|
||||
<name>Cryptomator common test dependencies</name>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<artifactId>commons</artifactId>
|
||||
<name>Cryptomator common</name>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<artifactId>filesystem-api</artifactId>
|
||||
<name>Cryptomator filesystem: API</name>
|
||||
|
||||
@@ -12,7 +12,7 @@ import static java.lang.String.format;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class FileSystemVisitor {
|
||||
public class FolderVisitor {
|
||||
|
||||
private final Consumer<Folder> beforeFolderVisitor;
|
||||
private final Consumer<Folder> afterFolderVisitor;
|
||||
@@ -20,7 +20,7 @@ public class FileSystemVisitor {
|
||||
private final Consumer<Node> nodeVisitor;
|
||||
private final int maxDepth;
|
||||
|
||||
public FileSystemVisitor(FileSystemVisitorBuilder builder) {
|
||||
public FolderVisitor(FolderVisitorBuilder builder) {
|
||||
this.beforeFolderVisitor = builder.beforeFolderVisitor;
|
||||
this.afterFolderVisitor = builder.afterFolderVisitor;
|
||||
this.fileVisitor = builder.fileVisitor;
|
||||
@@ -28,19 +28,19 @@ public class FileSystemVisitor {
|
||||
this.maxDepth = builder.maxDepth;
|
||||
}
|
||||
|
||||
public static FileSystemVisitorBuilder fileSystemVisitor() {
|
||||
return new FileSystemVisitorBuilder();
|
||||
public static FolderVisitorBuilder folderVisitor() {
|
||||
return new FolderVisitorBuilder();
|
||||
}
|
||||
|
||||
public FileSystemVisitor visit(Folder folder) {
|
||||
public FolderVisitor visit(Folder folder) {
|
||||
return visit(folder, 0);
|
||||
}
|
||||
|
||||
public FileSystemVisitor visit(File file) {
|
||||
public FolderVisitor visit(File file) {
|
||||
return visit(file, 0);
|
||||
}
|
||||
|
||||
private FileSystemVisitor visit(Folder folder, int depth) {
|
||||
private FolderVisitor visit(Folder folder, int depth) {
|
||||
beforeFolderVisitor.accept(folder);
|
||||
nodeVisitor.accept(folder);
|
||||
final int childDepth = depth + 1;
|
||||
@@ -52,13 +52,13 @@ public class FileSystemVisitor {
|
||||
return this;
|
||||
}
|
||||
|
||||
private FileSystemVisitor visit(File file, int depth) {
|
||||
private FolderVisitor visit(File file, int depth) {
|
||||
nodeVisitor.accept(file);
|
||||
fileVisitor.accept(file);
|
||||
return this;
|
||||
}
|
||||
|
||||
public static class FileSystemVisitorBuilder {
|
||||
public static class FolderVisitorBuilder {
|
||||
|
||||
private Consumer<Folder> beforeFolderVisitor = noOp();
|
||||
private Consumer<Folder> afterFolderVisitor = noOp();
|
||||
@@ -66,10 +66,10 @@ public class FileSystemVisitor {
|
||||
private Consumer<Node> nodeVisitor = noOp();
|
||||
private int maxDepth = Integer.MAX_VALUE;
|
||||
|
||||
private FileSystemVisitorBuilder() {
|
||||
private FolderVisitorBuilder() {
|
||||
}
|
||||
|
||||
public FileSystemVisitorBuilder beforeFolder(Consumer<Folder> beforeFolderVisitor) {
|
||||
public FolderVisitorBuilder beforeFolder(Consumer<Folder> beforeFolderVisitor) {
|
||||
if (beforeFolderVisitor == null) {
|
||||
throw new IllegalArgumentException("Vistior may not be null");
|
||||
}
|
||||
@@ -77,7 +77,7 @@ public class FileSystemVisitor {
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileSystemVisitorBuilder afterFolder(Consumer<Folder> afterFolderVisitor) {
|
||||
public FolderVisitorBuilder afterFolder(Consumer<Folder> afterFolderVisitor) {
|
||||
if (afterFolderVisitor == null) {
|
||||
throw new IllegalArgumentException("Vistior may not be null");
|
||||
}
|
||||
@@ -85,7 +85,7 @@ public class FileSystemVisitor {
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileSystemVisitorBuilder forEachFile(Consumer<File> fileVisitor) {
|
||||
public FolderVisitorBuilder forEachFile(Consumer<File> fileVisitor) {
|
||||
if (fileVisitor == null) {
|
||||
throw new IllegalArgumentException("Vistior may not be null");
|
||||
}
|
||||
@@ -93,7 +93,7 @@ public class FileSystemVisitor {
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileSystemVisitorBuilder forEachNode(Consumer<Node> nodeVisitor) {
|
||||
public FolderVisitorBuilder forEachNode(Consumer<Node> nodeVisitor) {
|
||||
if (nodeVisitor == null) {
|
||||
throw new IllegalArgumentException("Vistior may not be null");
|
||||
}
|
||||
@@ -101,7 +101,7 @@ public class FileSystemVisitor {
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileSystemVisitorBuilder withMaxDepth(int maxDepth) {
|
||||
public FolderVisitorBuilder withMaxDepth(int maxDepth) {
|
||||
if (maxDepth < 0) {
|
||||
throw new IllegalArgumentException(format("maxDepth must not be smaller 0 but was %d", maxDepth));
|
||||
}
|
||||
@@ -109,16 +109,16 @@ public class FileSystemVisitor {
|
||||
return this;
|
||||
}
|
||||
|
||||
public FileSystemVisitor visit(Folder folder) {
|
||||
public FolderVisitor visit(Folder folder) {
|
||||
return build().visit(folder);
|
||||
}
|
||||
|
||||
public FileSystemVisitor visit(File file) {
|
||||
public FolderVisitor visit(File file) {
|
||||
return build().visit(file);
|
||||
}
|
||||
|
||||
public FileSystemVisitor build() {
|
||||
return new FileSystemVisitor(this);
|
||||
public FolderVisitor build() {
|
||||
return new FolderVisitor(this);
|
||||
}
|
||||
|
||||
private static <T> Consumer<T> noOp() {
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<artifactId>filesystem-crypto-integration-tests</artifactId>
|
||||
<name>Cryptomator filesystem: Encryption layer tests</name>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<artifactId>filesystem-crypto</artifactId>
|
||||
<name>Cryptomator filesystem: Encryption layer</name>
|
||||
|
||||
@@ -13,7 +13,6 @@ import static org.cryptomator.crypto.engine.impl.Constants.CURRENT_VAULT_VERSION
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
@@ -25,7 +24,6 @@ import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.security.auth.DestroyFailedException;
|
||||
import javax.security.auth.Destroyable;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.cryptomator.common.LazyInitializer;
|
||||
import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.crypto.engine.FileContentCryptor;
|
||||
@@ -35,6 +33,7 @@ import org.cryptomator.crypto.engine.UnsupportedVaultFormatException;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
|
||||
|
||||
class CryptorImpl implements Cryptor {
|
||||
|
||||
@@ -99,12 +98,16 @@ class CryptorImpl implements Cryptor {
|
||||
try {
|
||||
final ObjectMapper om = new ObjectMapper();
|
||||
keyFile = om.readValue(masterkeyFileContents, KeyFile.class);
|
||||
if (keyFile == null) {
|
||||
throw new InvalidFormatException("Could not read masterkey file", null, KeyFile.class);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("Unable to parse masterkeyFileContents", e);
|
||||
}
|
||||
assert keyFile != null;
|
||||
|
||||
// check version
|
||||
if (keyFile.getVersion() != CURRENT_VAULT_VERSION || ArrayUtils.isEmpty(keyFile.getVersionMac())) {
|
||||
if (!CURRENT_VAULT_VERSION.equals(keyFile.getVersion())) {
|
||||
throw new UnsupportedVaultFormatException(keyFile.getVersion(), CURRENT_VAULT_VERSION);
|
||||
}
|
||||
|
||||
@@ -112,12 +115,13 @@ class CryptorImpl implements Cryptor {
|
||||
try {
|
||||
final SecretKey kek = new SecretKeySpec(kekBytes, ENCRYPTION_ALG);
|
||||
this.macKey = AesKeyWrap.unwrap(kek, keyFile.getMacMasterKey(), MAC_ALG);
|
||||
final Mac mac = new ThreadLocalMac(macKey, MAC_ALG).get();
|
||||
final byte[] versionMac = mac.doFinal(ByteBuffer.allocate(Integer.BYTES).putInt(CURRENT_VAULT_VERSION).array());
|
||||
if (!MessageDigest.isEqual(versionMac, keyFile.getVersionMac())) {
|
||||
destroyQuietly(macKey);
|
||||
throw new UnsupportedVaultFormatException(Integer.MAX_VALUE, CURRENT_VAULT_VERSION);
|
||||
}
|
||||
// future use (as soon as we need to prevent downgrade attacks):
|
||||
// final Mac mac = new ThreadLocalMac(macKey, MAC_ALG).get();
|
||||
// final byte[] versionMac = mac.doFinal(ByteBuffer.allocate(Integer.BYTES).putInt(CURRENT_VAULT_VERSION).array());
|
||||
// if (!MessageDigest.isEqual(versionMac, keyFile.getVersionMac())) {
|
||||
// destroyQuietly(macKey);
|
||||
// throw new UnsupportedVaultFormatException(Integer.MAX_VALUE, CURRENT_VAULT_VERSION);
|
||||
// }
|
||||
this.encryptionKey = AesKeyWrap.unwrap(kek, keyFile.getEncryptionMasterKey(), ENCRYPTION_ALG);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new InvalidPassphraseException();
|
||||
|
||||
@@ -111,7 +111,8 @@ class BlockAlignedWritableFile implements WritableFile {
|
||||
currentBlockBuffer.clear();
|
||||
try (ReadableFile r = openReadable.get()) {
|
||||
r.position(physicalPosition);
|
||||
r.read(currentBlockBuffer);
|
||||
int numRead = r.read(currentBlockBuffer);
|
||||
assert numRead == currentBlockBuffer.position();
|
||||
}
|
||||
int advance = (int) (logicalPosition - physicalPosition);
|
||||
currentBlockBuffer.position(advance);
|
||||
|
||||
@@ -47,10 +47,10 @@ class CiphertextReader implements Callable<Void> {
|
||||
int bytesRead = -1;
|
||||
do {
|
||||
ByteBuffer ciphertext = ByteBuffer.allocate(READ_BUFFER_SIZE);
|
||||
file.read(ciphertext);
|
||||
ciphertext.flip();
|
||||
bytesRead = ciphertext.remaining();
|
||||
bytesRead = file.read(ciphertext);
|
||||
if (bytesRead > 0) {
|
||||
ciphertext.flip();
|
||||
assert bytesRead == ciphertext.remaining();
|
||||
decryptor.append(ciphertext);
|
||||
}
|
||||
} while (bytesRead > 0);
|
||||
|
||||
@@ -35,7 +35,17 @@ class CryptoFile extends CryptoNode implements File {
|
||||
@Override
|
||||
public ReadableFile openReadable() {
|
||||
boolean authenticate = !fileSystem().delegate().shouldSkipAuthentication(toString());
|
||||
return new CryptoReadableFile(cryptor.getFileContentCryptor(), forceGetPhysicalFile().openReadable(), authenticate, this::reportAuthError);
|
||||
ReadableFile physicalReadable = forceGetPhysicalFile().openReadable();
|
||||
boolean success = false;
|
||||
try {
|
||||
final ReadableFile result = new CryptoReadableFile(cryptor.getFileContentCryptor(), physicalReadable, authenticate, this::reportAuthError);
|
||||
success = true;
|
||||
return result;
|
||||
} finally {
|
||||
if (!success) {
|
||||
physicalReadable.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void reportAuthError() {
|
||||
@@ -47,7 +57,17 @@ class CryptoFile extends CryptoNode implements File {
|
||||
if (parent.folder(name).exists()) {
|
||||
throw new UncheckedIOException(new FileAlreadyExistsException(toString()));
|
||||
}
|
||||
return new CryptoWritableFile(cryptor.getFileContentCryptor(), forceGetPhysicalFile().openWritable());
|
||||
WritableFile physicalWrtiable = forceGetPhysicalFile().openWritable();
|
||||
boolean success = false;
|
||||
try {
|
||||
final WritableFile result = new CryptoWritableFile(cryptor.getFileContentCryptor(), physicalWrtiable);
|
||||
success = true;
|
||||
return result;
|
||||
} finally {
|
||||
if (!success) {
|
||||
physicalWrtiable.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -160,8 +160,8 @@ class CryptoFolder extends CryptoNode implements Folder {
|
||||
assert target.parent().isPresent() : "Target can not be root, thus has a parent";
|
||||
|
||||
// prepare target:
|
||||
target.parent().get().create();
|
||||
target.delete();
|
||||
target.parent().get().create();
|
||||
|
||||
// perform the actual move:
|
||||
final File dirFile = forceGetPhysicalFile();
|
||||
@@ -186,7 +186,12 @@ class CryptoFolder extends CryptoNode implements Folder {
|
||||
return;
|
||||
}
|
||||
Deleter.deleteContent(this);
|
||||
forceGetPhysicalFolder().delete();
|
||||
Folder physicalFolder = forceGetPhysicalFolder();
|
||||
physicalFolder.delete();
|
||||
Folder physicalFolderParent = physicalFolder.parent().get();
|
||||
if (physicalFolderParent.folders().count() == 0) {
|
||||
physicalFolderParent.delete();
|
||||
}
|
||||
forceGetPhysicalFile().delete();
|
||||
invalidateDirectoryIdsRecursively();
|
||||
}
|
||||
|
||||
@@ -104,8 +104,8 @@ abstract class CryptoNode implements Node {
|
||||
if (obj instanceof CryptoNode) {
|
||||
CryptoNode other = (CryptoNode) obj;
|
||||
return this.getClass() == other.getClass() //
|
||||
&& (this.parent == null && other.parent == null || this.parent.equals(other.parent)) //
|
||||
&& (this.name == null && other.name == null || this.name.equals(other.name));
|
||||
&& (this.parent == null && other.parent == null || this.parent != null && this.parent.equals(other.parent)) //
|
||||
&& (this.name == null && other.name == null || this.name != null && this.name.equals(other.name));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,10 @@ class CryptoReadableFile implements ReadableFile {
|
||||
this.authenticate = authenticate;
|
||||
this.onAuthError = onAuthError;
|
||||
file.position(0);
|
||||
file.read(header);
|
||||
int headerBytesRead = file.read(header);
|
||||
if (headerBytesRead != header.capacity()) {
|
||||
throw new IllegalArgumentException("File too short to contain a header.");
|
||||
}
|
||||
header.flip();
|
||||
this.position(0);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.crypto.engine.InvalidPassphraseException;
|
||||
import org.cryptomator.crypto.engine.UnsupportedVaultFormatException;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
public class CryptorImplTest {
|
||||
@@ -48,6 +49,7 @@ public class CryptorImplTest {
|
||||
cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "asd");
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test(expected = UnsupportedVaultFormatException.class)
|
||||
public void testMasterkeyDecryptionWithMissingVersionMac() throws IOException {
|
||||
final String testMasterKey = "{\"version\":3,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
|
||||
@@ -57,6 +59,7 @@ public class CryptorImplTest {
|
||||
cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "asd");
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test(expected = UnsupportedVaultFormatException.class)
|
||||
public void testMasterkeyDecryptionWithWrongVersionMac() throws IOException {
|
||||
final String testMasterKey = "{\"version\":3,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
|
||||
|
||||
@@ -8,14 +8,15 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem.crypto;
|
||||
|
||||
import static org.cryptomator.filesystem.FileSystemVisitor.fileSystemVisitor;
|
||||
import static org.hamcrest.Matchers.both;
|
||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||
import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.crypto.engine.NoCryptor;
|
||||
@@ -37,7 +38,6 @@ public class CryptoFileSystemTest {
|
||||
final FileSystem physicalFs = new InMemoryFileSystem();
|
||||
final Folder physicalDataRoot = physicalFs.folder("d");
|
||||
final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, Mockito.mock(CryptoFileSystemDelegate.class), "foo");
|
||||
fs.create();
|
||||
|
||||
// add another encrypted folder:
|
||||
final Folder fooFolder = fs.folder("foo");
|
||||
@@ -47,8 +47,24 @@ public class CryptoFileSystemTest {
|
||||
fooBarFolder.create();
|
||||
Assert.assertTrue(fooFolder.exists());
|
||||
Assert.assertTrue(fooBarFolder.exists());
|
||||
Assert.assertEquals(3, countDataFolders(physicalDataRoot)); // parent +
|
||||
// foo + bar
|
||||
Assert.assertEquals(3, countDataFolders(physicalDataRoot)); // parent + foo + bar
|
||||
}
|
||||
|
||||
@Test(timeout = 1000)
|
||||
public void testDirectoryDeletion() throws UncheckedIOException, IOException {
|
||||
// mock stuff and prepare crypto FS:
|
||||
final Cryptor cryptor = new NoCryptor();
|
||||
final FileSystem physicalFs = new InMemoryFileSystem();
|
||||
final Folder physicalDataRoot = physicalFs.folder("d");
|
||||
final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, Mockito.mock(CryptoFileSystemDelegate.class), "foo");
|
||||
|
||||
// create and delete folders:
|
||||
fs.folder("foo").folder("bar").folder("baz").create();
|
||||
Assert.assertEquals(4, countDataFolders(physicalDataRoot)); // root + foo + bar + baz
|
||||
Assert.assertThat(physicalDataRoot.folders().count(), both(greaterThanOrEqualTo(1l)).and(lessThanOrEqualTo(4l))); // parent folders of the 4 folders
|
||||
fs.folder("foo").delete();
|
||||
Assert.assertEquals(1, countDataFolders(physicalDataRoot)); // just root
|
||||
Assert.assertEquals(1, physicalDataRoot.folders().count()); // just the parent of root
|
||||
}
|
||||
|
||||
@Test(timeout = 2000)
|
||||
@@ -204,22 +220,10 @@ public class CryptoFileSystemTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return number of folders on second level inside the given dataRoot
|
||||
* folder.
|
||||
* @return number of folders on second level inside the given dataRoot folder.
|
||||
*/
|
||||
private static int countDataFolders(Folder dataRoot) {
|
||||
final AtomicInteger num = new AtomicInteger();
|
||||
fileSystemVisitor() //
|
||||
.afterFolder(folder -> {
|
||||
final Folder parent = folder.parent().get();
|
||||
final Folder parentOfParent = parent.parent().orElse(null);
|
||||
if (parentOfParent != null && parentOfParent.equals(dataRoot)) {
|
||||
num.incrementAndGet();
|
||||
}
|
||||
}) //
|
||||
.withMaxDepth(2) //
|
||||
.visit(dataRoot);
|
||||
return num.get();
|
||||
return (int) dataRoot.folders().flatMap(Folder::folders).count();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<artifactId>filesystem-inmemory</artifactId>
|
||||
<name>Cryptomator filesystem: In-memory mock</name>
|
||||
|
||||
@@ -13,6 +13,7 @@ import java.io.UncheckedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
|
||||
@@ -30,11 +31,16 @@ class InMemoryFile extends InMemoryNode implements File {
|
||||
static final double GROWTH_RATE = 1.4;
|
||||
|
||||
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
private volatile ByteBuffer content = ByteBuffer.allocate(INITIAL_SIZE);
|
||||
private final AtomicReference<ByteBuffer> content = new AtomicReference<>(createNewEmptyByteBuffer());
|
||||
|
||||
public InMemoryFile(InMemoryFolder parent, String name, Instant lastModified, Instant creationTime) {
|
||||
super(parent, name, lastModified, creationTime);
|
||||
content.flip();
|
||||
}
|
||||
|
||||
static ByteBuffer createNewEmptyByteBuffer() {
|
||||
final ByteBuffer buf = ByteBuffer.allocate(INITIAL_SIZE);
|
||||
buf.flip();
|
||||
return buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,9 +53,9 @@ class InMemoryFile extends InMemoryNode implements File {
|
||||
}
|
||||
|
||||
private void internalMoveTo(InMemoryFile destination) {
|
||||
this.content.rewind();
|
||||
this.content.get().rewind();
|
||||
destination.create();
|
||||
destination.content = this.content;
|
||||
destination.content.set(this.content.getAndSet(createNewEmptyByteBuffer()));
|
||||
this.delete();
|
||||
}
|
||||
|
||||
@@ -62,7 +68,7 @@ class InMemoryFile extends InMemoryNode implements File {
|
||||
final ReadLock readLock = lock.readLock();
|
||||
readLock.lock();
|
||||
try {
|
||||
final ReadableFile result = new InMemoryReadableFile(this::getContent, readLock);
|
||||
final ReadableFile result = new InMemoryReadableFile(content::get, readLock);
|
||||
success = true;
|
||||
return result;
|
||||
} finally {
|
||||
@@ -79,7 +85,7 @@ class InMemoryFile extends InMemoryNode implements File {
|
||||
writeLock.lock();
|
||||
try {
|
||||
create();
|
||||
final WritableFile result = new InMemoryWritableFile(this::getContent, this::setContent, writeLock);
|
||||
final WritableFile result = new InMemoryWritableFile(content::get, content::set, writeLock);
|
||||
success = true;
|
||||
return result;
|
||||
} finally {
|
||||
@@ -97,7 +103,7 @@ class InMemoryFile extends InMemoryNode implements File {
|
||||
throw new UncheckedIOException(new FileAlreadyExistsException(k));
|
||||
} else {
|
||||
if (v == null) {
|
||||
assert!content.hasRemaining();
|
||||
assert!content.get().hasRemaining();
|
||||
this.creationTime = Instant.now();
|
||||
}
|
||||
this.lastModified = Instant.now();
|
||||
@@ -106,18 +112,9 @@ class InMemoryFile extends InMemoryNode implements File {
|
||||
});
|
||||
}
|
||||
|
||||
private ByteBuffer getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
private void setContent(ByteBuffer content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
content = ByteBuffer.allocate(INITIAL_SIZE);
|
||||
content.flip();
|
||||
content.set(createNewEmptyByteBuffer());
|
||||
final InMemoryFolder parent = parent().get();
|
||||
parent.existingChildren.computeIfPresent(this.name(), (k, v) -> {
|
||||
// returning null removes the entry.
|
||||
|
||||
@@ -86,8 +86,8 @@ abstract class InMemoryNode implements Node {
|
||||
if (obj instanceof InMemoryNode) {
|
||||
InMemoryNode other = (InMemoryNode) obj;
|
||||
return this.getClass() == other.getClass() //
|
||||
&& (this.parent == null && other.parent == null || this.parent.equals(other.parent)) //
|
||||
&& (this.name == null && other.name == null || this.name.equals(other.name));
|
||||
&& (this.parent == null && other.parent == null || this.parent != null && this.parent.equals(other.parent)) //
|
||||
&& (this.name == null && other.name == null || this.name != null && this.name.equals(other.name));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ package org.cryptomator.filesystem.inmem;
|
||||
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -20,8 +22,8 @@ class InMemoryReadableFile implements ReadableFile {
|
||||
|
||||
private final Supplier<ByteBuffer> contentGetter;
|
||||
private final ReadLock readLock;
|
||||
private boolean open = true;
|
||||
private volatile int position = 0;
|
||||
private final AtomicInteger position = new AtomicInteger();
|
||||
private final AtomicBoolean open = new AtomicBoolean(true);
|
||||
|
||||
public InMemoryReadableFile(Supplier<ByteBuffer> contentGetter, ReadLock readLock) {
|
||||
this.contentGetter = contentGetter;
|
||||
@@ -30,19 +32,21 @@ class InMemoryReadableFile implements ReadableFile {
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return open;
|
||||
return open.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(ByteBuffer destination) throws UncheckedIOException {
|
||||
ByteBuffer source = contentGetter.get().asReadOnlyBuffer();
|
||||
if (position >= source.limit()) {
|
||||
int toBeCopied = destination.remaining();
|
||||
int pos = position.getAndAdd(toBeCopied);
|
||||
if (pos >= source.limit()) {
|
||||
return -1;
|
||||
} else {
|
||||
source.position(position);
|
||||
source.position(pos);
|
||||
assert source.hasRemaining();
|
||||
int numRead = ByteBuffers.copy(source, destination);
|
||||
this.position += numRead;
|
||||
assert numRead <= toBeCopied;
|
||||
return numRead;
|
||||
}
|
||||
}
|
||||
@@ -55,12 +59,12 @@ class InMemoryReadableFile implements ReadableFile {
|
||||
@Override
|
||||
public void position(long position) throws UncheckedIOException {
|
||||
assert position < Integer.MAX_VALUE : "Can not use that big in-memory files.";
|
||||
this.position = (int) position;
|
||||
this.position.set((int) position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws UncheckedIOException {
|
||||
open = false;
|
||||
open.set(false);
|
||||
readLock.unlock();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@ package org.cryptomator.filesystem.inmem;
|
||||
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
@@ -22,9 +25,9 @@ public class InMemoryWritableFile implements WritableFile {
|
||||
private final Supplier<ByteBuffer> contentGetter;
|
||||
private final Consumer<ByteBuffer> contentSetter;
|
||||
private final WriteLock writeLock;
|
||||
|
||||
private boolean open = true;
|
||||
private volatile int position = 0;
|
||||
private final AtomicInteger position = new AtomicInteger();
|
||||
private final AtomicBoolean open = new AtomicBoolean(true);
|
||||
private final ReentrantLock writingLock = new ReentrantLock();
|
||||
|
||||
public InMemoryWritableFile(Supplier<ByteBuffer> contentGetter, Consumer<ByteBuffer> contentSetter, WriteLock writeLock) {
|
||||
this.contentGetter = contentGetter;
|
||||
@@ -34,44 +37,65 @@ public class InMemoryWritableFile implements WritableFile {
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return open;
|
||||
return open.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void truncate() throws UncheckedIOException {
|
||||
contentSetter.accept(ByteBuffer.allocate(0));
|
||||
writingLock.lock();
|
||||
try {
|
||||
contentSetter.accept(InMemoryFile.createNewEmptyByteBuffer());
|
||||
position.set(0);
|
||||
} finally {
|
||||
writingLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int write(ByteBuffer source) throws UncheckedIOException {
|
||||
ByteBuffer destination = contentGetter.get();
|
||||
int oldFileSize = destination.limit();
|
||||
int requiredSize = position + source.remaining();
|
||||
int newFileSize = Math.max(oldFileSize, requiredSize);
|
||||
if (destination.capacity() < requiredSize) {
|
||||
ByteBuffer old = destination;
|
||||
old.clear();
|
||||
int newBufferSize = Math.max(requiredSize, (int) (destination.capacity() * InMemoryFile.GROWTH_RATE));
|
||||
destination = ByteBuffer.allocate(newBufferSize);
|
||||
ByteBuffers.copy(old, destination);
|
||||
contentSetter.accept(destination);
|
||||
writingLock.lock();
|
||||
try {
|
||||
ByteBuffer content = contentGetter.get();
|
||||
int prevLimit = content.limit();
|
||||
|
||||
int toBeCopied = source.remaining();
|
||||
int pos = position.getAndAdd(toBeCopied);
|
||||
int ourLimit = pos + toBeCopied;
|
||||
int newLimit = Math.max(prevLimit, ourLimit);
|
||||
|
||||
ByteBuffer destination = ensureCapacity(content, newLimit);
|
||||
destination.limit(newLimit).position(pos);
|
||||
return ByteBuffers.copy(source, destination);
|
||||
} finally {
|
||||
writingLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private ByteBuffer ensureCapacity(ByteBuffer buf, int limit) {
|
||||
assert writingLock.isHeldByCurrentThread();
|
||||
if (buf.capacity() < limit) {
|
||||
int oldPos = buf.position();
|
||||
buf.clear();
|
||||
int newBufferSize = Math.max(limit, (int) (buf.capacity() * InMemoryFile.GROWTH_RATE));
|
||||
ByteBuffer newBuf = ByteBuffer.allocate(newBufferSize);
|
||||
ByteBuffers.copy(buf, newBuf);
|
||||
newBuf.limit(limit).position(oldPos);
|
||||
contentSetter.accept(newBuf);
|
||||
return newBuf;
|
||||
} else {
|
||||
return buf;
|
||||
}
|
||||
destination.limit(newFileSize);
|
||||
destination.position(position);
|
||||
int numWritten = ByteBuffers.copy(source, destination);
|
||||
this.position += numWritten;
|
||||
return numWritten;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void position(long position) throws UncheckedIOException {
|
||||
assert position < Integer.MAX_VALUE : "Can not use that big in-memory files.";
|
||||
this.position = (int) position;
|
||||
this.position.set((int) position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws UncheckedIOException {
|
||||
open = false;
|
||||
open.set(false);
|
||||
writeLock.unlock();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<artifactId>filesystem-invariants-tests</artifactId>
|
||||
<name>Cryptomator filesystem: Invariants tests</name>
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.cryptomator.filesystem.inmem.InMemoryFileSystem;
|
||||
import org.cryptomator.filesystem.invariants.FileSystemFactories.FileSystemFactory;
|
||||
import org.cryptomator.filesystem.nio.NioFileSystem;
|
||||
import org.cryptomator.filesystem.shortening.ShorteningFileSystem;
|
||||
import org.cryptomator.filesystem.stats.StatsFileSystem;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
class FileSystemFactories implements Iterable<FileSystemFactory> {
|
||||
@@ -28,10 +29,46 @@ class FileSystemFactories implements Iterable<FileSystemFactory> {
|
||||
public FileSystemFactories() {
|
||||
add("NioFileSystem", this::createNioFileSystem);
|
||||
add("InMemoryFileSystem", this::createInMemoryFileSystem);
|
||||
add("CryptoFileSystem(NioFileSystem)", this::createCryptoFileSystemNio);
|
||||
add("CryptoFileSystem(InMemoryFileSystem)", this::createCryptoFileSystemInMemory);
|
||||
add("ShorteningFileSystem(NioFileSystem)", this::createShorteningFileSystemNio);
|
||||
add("ShorteningFileSystem(InMemoryFileSystem)", this::createShorteningFileSystemInMemory);
|
||||
add("CryptoFileSystem > NioFileSystem", this::createCryptoFileSystemNio);
|
||||
add("CryptoFileSystem > InMemoryFileSystem", this::createCryptoFileSystemInMemory);
|
||||
add("ShorteningFileSystem > NioFileSystem", this::createShorteningFileSystemNio);
|
||||
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);
|
||||
}
|
||||
|
||||
private FileSystem createCryptoFileSystemInMemory() {
|
||||
return createCryptoFileSystem(createInMemoryFileSystem());
|
||||
}
|
||||
|
||||
private FileSystem createCryptoFileSystemNio() {
|
||||
return createCryptoFileSystem(createNioFileSystem());
|
||||
}
|
||||
|
||||
private FileSystem createShorteningFileSystemNio() {
|
||||
return createShorteningFileSystem(createNioFileSystem());
|
||||
}
|
||||
|
||||
private FileSystem createShorteningFileSystemInMemory() {
|
||||
return createShorteningFileSystem(createInMemoryFileSystem());
|
||||
}
|
||||
|
||||
private FileSystem createStatsFileSystemNio() {
|
||||
return createStatsFileSystem(createNioFileSystem());
|
||||
}
|
||||
|
||||
private FileSystem createStatsFileSystemInMemory() {
|
||||
return createStatsFileSystem(createInMemoryFileSystem());
|
||||
}
|
||||
|
||||
private FileSystem createCompoundFileSystemNio() {
|
||||
return createCompoundFileSystem(createNioFileSystem());
|
||||
}
|
||||
|
||||
private FileSystem createCompoundFileSystemInMemory() {
|
||||
return createCompoundFileSystem(createInMemoryFileSystem());
|
||||
}
|
||||
|
||||
private FileSystem createNioFileSystem() {
|
||||
@@ -46,25 +83,20 @@ class FileSystemFactories implements Iterable<FileSystemFactory> {
|
||||
return new InMemoryFileSystem();
|
||||
}
|
||||
|
||||
private FileSystem createCryptoFileSystemInMemory() {
|
||||
FileSystem delegate = createInMemoryFileSystem();
|
||||
private FileSystem createCompoundFileSystem(FileSystem delegate) {
|
||||
return createStatsFileSystem(createCryptoFileSystem(createShorteningFileSystem(delegate)));
|
||||
}
|
||||
|
||||
private FileSystem createStatsFileSystem(FileSystem delegate) {
|
||||
return new StatsFileSystem(delegate);
|
||||
}
|
||||
|
||||
private FileSystem createCryptoFileSystem(FileSystem delegate) {
|
||||
CRYPTO_FS_COMP.cryptoFileSystemFactory().initializeNew(delegate, "aPassphrase");
|
||||
return CRYPTO_FS_COMP.cryptoFileSystemFactory().unlockExisting(delegate, "aPassphrase", Mockito.mock(CryptoFileSystemDelegate.class));
|
||||
}
|
||||
|
||||
private FileSystem createCryptoFileSystemNio() {
|
||||
FileSystem delegate = createNioFileSystem();
|
||||
CRYPTO_FS_COMP.cryptoFileSystemFactory().initializeNew(delegate, "aPassphrase");
|
||||
return CRYPTO_FS_COMP.cryptoFileSystemFactory().unlockExisting(delegate, "aPassphrase", Mockito.mock(CryptoFileSystemDelegate.class));
|
||||
}
|
||||
|
||||
private FileSystem createShorteningFileSystemNio() {
|
||||
FileSystem delegate = createNioFileSystem();
|
||||
return new ShorteningFileSystem(delegate, "m", 3);
|
||||
}
|
||||
|
||||
private FileSystem createShorteningFileSystemInMemory() {
|
||||
FileSystem delegate = createInMemoryFileSystem();
|
||||
private FileSystem createShorteningFileSystem(FileSystem delegate) {
|
||||
return new ShorteningFileSystem(delegate, "m", 3);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<artifactId>filesystem-nameshortening</artifactId>
|
||||
<name>Cryptomator filesystem: Name shortening layer</name>
|
||||
|
||||
@@ -10,6 +10,7 @@ package org.cryptomator.filesystem.shortening;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
@@ -44,7 +45,7 @@ class FilenameShortener {
|
||||
if (longName.length() < threshold) {
|
||||
return longName;
|
||||
} else {
|
||||
final byte[] hashBytes = SHA1.get().digest(longName.getBytes());
|
||||
final byte[] hashBytes = SHA1.get().digest(longName.getBytes(StandardCharsets.UTF_8));
|
||||
final String hash = BASE32.encodeAsString(hashBytes);
|
||||
return hash + LONG_NAME_FILE_EXT;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<artifactId>filesystem-nio</artifactId>
|
||||
<name>Cryptomator filesystem: NIO-based physical layer</name>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<artifactId>filesystem-stats</artifactId>
|
||||
<name>Cryptomator filesystem: Throughput statistics</name>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<artifactId>frontend-api</artifactId>
|
||||
<name>Cryptomator frontend: API</name>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<artifactId>frontend-webdav</artifactId>
|
||||
<name>Cryptomator frontend: WebDAV frontend</name>
|
||||
|
||||
@@ -32,11 +32,15 @@ import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
import org.cryptomator.filesystem.ReadableFile;
|
||||
import org.cryptomator.filesystem.jackrabbit.FileLocator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
class DavFile extends DavNode<FileLocator> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DavFile.class);
|
||||
|
||||
public DavFile(FilesystemResourceFactory factory, LockManager lockManager, DavSession session, FileLocator node) {
|
||||
super(factory, lockManager, session, node);
|
||||
}
|
||||
@@ -128,7 +132,7 @@ class DavFile extends DavNode<FileLocator> {
|
||||
@Override
|
||||
public DavProperty<?> getProperty(DavPropertyName name) {
|
||||
if (DavPropertyName.GETCONTENTLENGTH.equals(name)) {
|
||||
return sizeProperty().get();
|
||||
return sizeProperty().orElse(null);
|
||||
} else {
|
||||
return super.getProperty(name);
|
||||
}
|
||||
@@ -147,6 +151,9 @@ class DavFile extends DavNode<FileLocator> {
|
||||
if (node.exists()) {
|
||||
try (ReadableFile src = node.openReadable()) {
|
||||
return Optional.of(new DefaultDavProperty<Long>(DavPropertyName.GETCONTENTLENGTH, src.size()));
|
||||
} catch (RuntimeException e) {
|
||||
LOG.warn("Could not determine file size of " + getResourcePath(), e);
|
||||
return Optional.empty();
|
||||
}
|
||||
} else {
|
||||
return Optional.empty();
|
||||
@@ -167,7 +174,9 @@ class DavFile extends DavNode<FileLocator> {
|
||||
public ActiveLock lock(LockInfo reqLockInfo) throws DavException {
|
||||
ActiveLock lock = super.lock(reqLockInfo);
|
||||
if (!exists()) {
|
||||
getCollection().addMember(this, new NullInputContext());
|
||||
DavFolder parentFolder = getCollection();
|
||||
assert parentFolder != null : "File always has a folder.";
|
||||
parentFolder.addMember(this, new NullInputContext());
|
||||
}
|
||||
return lock;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,9 @@ class DavFileWithRange extends DavFile {
|
||||
try {
|
||||
final Long lower = requestRange.getLeft().isEmpty() ? null : Long.valueOf(requestRange.getLeft());
|
||||
final Long upper = requestRange.getRight().isEmpty() ? null : Long.valueOf(requestRange.getRight());
|
||||
if (lower == null) {
|
||||
if (lower == null && upper == null) {
|
||||
return new ImmutablePair<Long, Long>(0l, contentLength - 1);
|
||||
} else if (lower == null) {
|
||||
return new ImmutablePair<Long, Long>(contentLength - upper, contentLength - 1);
|
||||
} else if (upper == null) {
|
||||
return new ImmutablePair<Long, Long>(lower, contentLength - 1);
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.apache.jackrabbit.webdav.property.DavPropertySet;
|
||||
import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
|
||||
import org.apache.jackrabbit.webdav.property.PropEntry;
|
||||
import org.cryptomator.filesystem.jackrabbit.FileSystemResourceLocator;
|
||||
import org.cryptomator.filesystem.jackrabbit.FolderLocator;
|
||||
|
||||
abstract class DavNode<T extends FileSystemResourceLocator> implements DavResource {
|
||||
|
||||
@@ -187,18 +188,14 @@ abstract class DavNode<T extends FileSystemResourceLocator> implements DavResour
|
||||
}
|
||||
|
||||
@Override
|
||||
public DavResource getCollection() {
|
||||
public DavFolder getCollection() {
|
||||
if (node.isRootLocation()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
assert node.parent().isPresent() : "as my mom always sais: if it's not root, it has a parent";
|
||||
final FileSystemResourceLocator parentPath = node.parent().get();
|
||||
try {
|
||||
return factory.createResource(parentPath, session);
|
||||
} catch (DavException e) {
|
||||
throw new IllegalStateException("Unable to get parent resource with path " + parentPath, e);
|
||||
}
|
||||
final FolderLocator parentPath = node.parent().get();
|
||||
return factory.createFolder(parentPath, session);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -158,7 +158,7 @@ class ExclusiveSharedLockManager implements LockManager {
|
||||
}
|
||||
// or otherwise look for parent locks:
|
||||
if (locator.parent().isPresent()) {
|
||||
return getLockInternal(type, scope, locator.parent().get(), depth++);
|
||||
return getLockInternal(type, scope, locator.parent().get(), depth + 1);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ class FilesystemResourceFactory implements DavResourceFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DavResource createResource(DavResourceLocator locator, DavSession session) throws DavException {
|
||||
public DavResource createResource(DavResourceLocator locator, DavSession session) {
|
||||
if (locator instanceof FolderLocator) {
|
||||
FolderLocator folder = (FolderLocator) locator;
|
||||
return createFolder(folder, session);
|
||||
|
||||
@@ -30,6 +30,7 @@ public final class CommandResult {
|
||||
|
||||
/**
|
||||
* Constructs a CommandResult from a terminated process and closes all its streams.
|
||||
*
|
||||
* @param process An <strong>already finished</strong> process.
|
||||
*/
|
||||
CommandResult(Process process) {
|
||||
@@ -52,7 +53,7 @@ public final class CommandResult {
|
||||
logDebugInfo();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Data written to STDOUT
|
||||
*/
|
||||
@@ -94,10 +95,10 @@ public final class CommandResult {
|
||||
assertNoException();
|
||||
int exitValue = getExitValue();
|
||||
if (exitValue != 0) {
|
||||
throw new CommandFailedException(format("Command execution failed. Exit code: %d\n" + "# Output:\n" + "%s\n" + "# Error:\n" + "%s", exitValue, stdout, stderr));
|
||||
throw new CommandFailedException(format("Command execution failed. Exit code: %d %n# Output:%n%s %n# Error: %n%s", exitValue, stdout, stderr));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void assertNoException() throws CommandFailedException {
|
||||
if (exception != null) {
|
||||
throw exception;
|
||||
|
||||
@@ -20,15 +20,15 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
import org.cryptomator.frontend.CommandFailedException;
|
||||
|
||||
final class FutureCommandResult implements Future<CommandResult>, Runnable {
|
||||
|
||||
|
||||
private final Process process;
|
||||
private final AtomicBoolean canceled = new AtomicBoolean();
|
||||
private final AtomicBoolean done = new AtomicBoolean();
|
||||
private final Lock lock = new ReentrantLock();
|
||||
private final Condition doneCondition = lock.newCondition();
|
||||
|
||||
|
||||
private CommandFailedException exception;
|
||||
|
||||
|
||||
FutureCommandResult(Process process) {
|
||||
this.process = process;
|
||||
}
|
||||
@@ -49,7 +49,7 @@ final class FutureCommandResult implements Future<CommandResult>, Runnable {
|
||||
public boolean isCancelled() {
|
||||
return canceled.get();
|
||||
}
|
||||
|
||||
|
||||
private void setDone() {
|
||||
lock.lock();
|
||||
try {
|
||||
@@ -69,7 +69,7 @@ final class FutureCommandResult implements Future<CommandResult>, Runnable {
|
||||
public CommandResult get() throws InterruptedException, ExecutionException {
|
||||
lock.lock();
|
||||
try {
|
||||
while(!done.get()) {
|
||||
while (!done.get()) {
|
||||
doneCondition.await();
|
||||
}
|
||||
} finally {
|
||||
@@ -85,8 +85,12 @@ final class FutureCommandResult implements Future<CommandResult>, Runnable {
|
||||
public CommandResult get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
|
||||
lock.lock();
|
||||
try {
|
||||
while(!done.get()) {
|
||||
doneCondition.await(timeout, unit);
|
||||
boolean doneInTime = true;
|
||||
if (!done.get()) {
|
||||
doneInTime = doneCondition.await(timeout, unit);
|
||||
}
|
||||
if (!doneInTime) {
|
||||
throw new TimeoutException();
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.0.0</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Cryptomator</name>
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<artifactId>uber-jar</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<parent>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>main</artifactId>
|
||||
<version>0.11.0</version>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
<artifactId>ui</artifactId>
|
||||
<name>Cryptomator GUI</name>
|
||||
|
||||
@@ -109,7 +109,7 @@ public class MacWarningsController extends AbstractFXMLViewController {
|
||||
|
||||
@FXML
|
||||
private void didClickMoreInformationButton(ActionEvent event) {
|
||||
application.getHostServices().showDocument("https://cryptomator.org/help.html#macWarning");
|
||||
application.getHostServices().showDocument("https://cryptomator.org/faq/#macWarning");
|
||||
}
|
||||
|
||||
private void unauthenticatedResourcesDidChange(Change<? extends String> change) {
|
||||
|
||||
@@ -36,6 +36,7 @@ import org.slf4j.LoggerFactory;
|
||||
import dagger.Lazy;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Binding;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
@@ -120,6 +121,9 @@ public class MainController extends AbstractFXMLViewController {
|
||||
@FXML
|
||||
private Pane contentPane;
|
||||
|
||||
@FXML
|
||||
private Pane emptyListInstructions;
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
vaultList.setItems(vaults);
|
||||
@@ -127,6 +131,7 @@ public class MainController extends AbstractFXMLViewController {
|
||||
activeController.set(welcomeController.get());
|
||||
selectedVault.bind(vaultList.getSelectionModel().selectedItemProperty());
|
||||
removeVaultButton.disableProperty().bind(canEditSelectedVault);
|
||||
emptyListInstructions.visibleProperty().bind(Bindings.isEmpty(vaults));
|
||||
|
||||
EasyBind.subscribe(activeController, this::activeControllerDidChange);
|
||||
EasyBind.subscribe(selectedVault, this::selectedVaultDidChange);
|
||||
@@ -231,6 +236,9 @@ public class MainController extends AbstractFXMLViewController {
|
||||
@FXML
|
||||
private void didClickRemoveSelectedEntry(ActionEvent e) {
|
||||
vaults.remove(selectedVault.get());
|
||||
if (vaults.isEmpty()) {
|
||||
activeController.set(welcomeController.get());
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
package org.cryptomator.ui.controllers;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Optional;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -21,6 +22,7 @@ import org.fxmisc.easybind.EasyBind;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
|
||||
@@ -42,12 +44,16 @@ public class SettingsController extends AbstractFXMLViewController {
|
||||
@FXML
|
||||
private TextField portField;
|
||||
|
||||
@FXML
|
||||
private Label versionLabel;
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
checkForUpdatesCheckbox.setDisable(areUpdatesManagedExternally());
|
||||
checkForUpdatesCheckbox.setSelected(settings.isCheckForUpdatesEnabled() && !areUpdatesManagedExternally());
|
||||
portField.setText(String.valueOf(settings.getPort()));
|
||||
portField.addEventFilter(KeyEvent.KEY_TYPED, this::filterNumericKeyEvents);
|
||||
versionLabel.setText(String.format(localization.getString("settings.version.label"), applicationVersion().orElse("SNAPSHOT")));
|
||||
|
||||
EasyBind.subscribe(portField.textProperty(), this::portDidChange);
|
||||
EasyBind.subscribe(checkForUpdatesCheckbox.selectedProperty(), settings::setCheckForUpdatesEnabled);
|
||||
@@ -63,6 +69,10 @@ public class SettingsController extends AbstractFXMLViewController {
|
||||
return localization;
|
||||
}
|
||||
|
||||
private Optional<String> applicationVersion() {
|
||||
return Optional.ofNullable(getClass().getPackage().getImplementationVersion());
|
||||
}
|
||||
|
||||
private void portDidChange(String newValue) {
|
||||
try {
|
||||
int port = Integer.parseInt(newValue);
|
||||
|
||||
@@ -164,6 +164,7 @@ public class UnlockedController extends AbstractFXMLViewController {
|
||||
private void didClickCopyUrl(ActionEvent event) {
|
||||
ClipboardContent clipboardContent = new ClipboardContent();
|
||||
clipboardContent.putUrl(vault.get().getWebDavUrl());
|
||||
clipboardContent.putString(vault.get().getWebDavUrl());
|
||||
Clipboard.getSystemClipboard().setContent(clipboardContent);
|
||||
}
|
||||
|
||||
@@ -185,9 +186,9 @@ public class UnlockedController extends AbstractFXMLViewController {
|
||||
|
||||
private void startIoSampling() {
|
||||
final Series<Number, Number> decryptedBytes = new Series<>();
|
||||
decryptedBytes.setName("decrypted");
|
||||
decryptedBytes.setName(localization.getString("unlocked.label.statsDecrypted"));
|
||||
final Series<Number, Number> encryptedBytes = new Series<>();
|
||||
encryptedBytes.setName("encrypted");
|
||||
encryptedBytes.setName(localization.getString("unlocked.label.statsEncrypted"));
|
||||
|
||||
ioGraph.getData().add(decryptedBytes);
|
||||
ioGraph.getData().add(encryptedBytes);
|
||||
|
||||
@@ -124,7 +124,9 @@ public class WelcomeController extends AbstractFXMLViewController {
|
||||
final ObjectMapper mapper = new ObjectMapper();
|
||||
final Map<String, String> map = mapper.readValue(responseData, new TypeReference<HashMap<String, String>>() {
|
||||
});
|
||||
this.compareVersions(map);
|
||||
if (map != null) {
|
||||
this.compareVersions(map);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// no error handling required. Maybe next time the version check is successful.
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Path;
|
||||
@@ -51,9 +50,7 @@ import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
public class Vault implements Serializable, CryptoFileSystemDelegate {
|
||||
|
||||
private static final long serialVersionUID = 3754487289683599469L;
|
||||
public class Vault implements CryptoFileSystemDelegate {
|
||||
|
||||
public static final String VAULT_FILE_EXTENSION = ".cryptomator";
|
||||
|
||||
@@ -169,7 +166,7 @@ public class Vault implements Serializable, CryptoFileSystemDelegate {
|
||||
|
||||
@Override
|
||||
public boolean shouldSkipAuthentication(String cleartextPath) {
|
||||
return namesOfResourcesWithInvalidMac.contains(cleartextPath);
|
||||
return whitelistedResourcesWithInvalidMac.contains(cleartextPath);
|
||||
}
|
||||
|
||||
// ******************************************************************************
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
|
||||
@Singleton
|
||||
@@ -49,7 +50,7 @@ public class VaultObjectMapperProvider implements Provider<ObjectMapper> {
|
||||
return om;
|
||||
}
|
||||
|
||||
private class VaultSerializer extends JsonSerializer<Vault> {
|
||||
private static class VaultSerializer extends JsonSerializer<Vault> {
|
||||
|
||||
@Override
|
||||
public void serialize(Vault value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
|
||||
@@ -70,6 +71,9 @@ public class VaultObjectMapperProvider implements Provider<ObjectMapper> {
|
||||
@Override
|
||||
public Vault deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
|
||||
final JsonNode node = jp.readValueAsTree();
|
||||
if (node == null || !node.has("path")) {
|
||||
throw new InvalidFormatException("Node is null or doesn't contain a path.", node, Vault.class);
|
||||
}
|
||||
final String pathStr = node.get("path").asText();
|
||||
final Path path = FileSystems.getDefault().getPath(pathStr);
|
||||
final Vault vault = vaultFactoy.createVault(path);
|
||||
|
||||
@@ -24,7 +24,7 @@ public class Settings implements Serializable {
|
||||
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_NUM_TRAY_NOTIFICATIONS = 3;
|
||||
public static final Integer DEFAULT_NUM_TRAY_NOTIFICATIONS = 3;
|
||||
|
||||
@JsonProperty("directories")
|
||||
private List<Vault> directories;
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.nio.channels.Selector;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
@@ -68,7 +69,7 @@ public class SingleInstanceManager {
|
||||
*/
|
||||
public boolean sendMessage(String string, long timeout) throws IOException {
|
||||
Objects.requireNonNull(string);
|
||||
byte[] message = string.getBytes();
|
||||
byte[] message = string.getBytes(StandardCharsets.UTF_8);
|
||||
if (message.length >= 256 * 256) {
|
||||
throw new IOException("Message too long.");
|
||||
}
|
||||
@@ -84,7 +85,7 @@ public class SingleInstanceManager {
|
||||
return true;
|
||||
}
|
||||
return !buf.hasRemaining();
|
||||
}, timeout, 10);
|
||||
} , timeout, 10);
|
||||
return !buf.hasRemaining();
|
||||
}
|
||||
|
||||
@@ -107,7 +108,7 @@ public class SingleInstanceManager {
|
||||
*/
|
||||
public static class LocalInstance implements Closeable {
|
||||
private class ChannelState {
|
||||
ByteBuffer write = ByteBuffer.wrap(applicationKey.getBytes());
|
||||
ByteBuffer write = ByteBuffer.wrap(applicationKey.getBytes(StandardCharsets.UTF_8));
|
||||
ByteBuffer readLength = ByteBuffer.allocate(2);
|
||||
ByteBuffer readMessage = null;
|
||||
}
|
||||
@@ -139,10 +140,17 @@ public class SingleInstanceManager {
|
||||
void handleSelection(SelectionKey key) throws IOException {
|
||||
if (key.isAcceptable()) {
|
||||
final SocketChannel accepted = channel.accept();
|
||||
if (accepted != null) {
|
||||
LOG.debug("accepted incoming connection");
|
||||
accepted.configureBlocking(false);
|
||||
accepted.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
|
||||
SelectionKey keyOfAcceptedConnection = null;
|
||||
try {
|
||||
if (accepted != null) {
|
||||
LOG.debug("accepted incoming connection");
|
||||
accepted.configureBlocking(false);
|
||||
keyOfAcceptedConnection = accepted.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
|
||||
}
|
||||
} finally {
|
||||
if (keyOfAcceptedConnection == null) {
|
||||
accepted.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +197,7 @@ public class SingleInstanceManager {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
IOUtils.closeQuietly(selector);
|
||||
IOUtils.closeQuietly(channel);
|
||||
@@ -257,7 +266,7 @@ public class SingleInstanceManager {
|
||||
|
||||
LOG.debug("connected to instance {}", port.get());
|
||||
|
||||
final byte[] bytes = applicationKey.getBytes();
|
||||
final byte[] bytes = applicationKey.getBytes(StandardCharsets.UTF_8);
|
||||
ByteBuffer buf = ByteBuffer.allocate(bytes.length);
|
||||
tryFill(channel, buf, 1000);
|
||||
if (buf.hasRemaining()) {
|
||||
@@ -359,7 +368,7 @@ public class SingleInstanceManager {
|
||||
}
|
||||
}
|
||||
return !buf.hasRemaining();
|
||||
}, timeout, 1);
|
||||
} , timeout, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,7 @@
|
||||
|
||||
.button,
|
||||
.toggle-button {
|
||||
-fx-pref-height: 25px;
|
||||
-fx-pref-height: 27px;
|
||||
-fx-background-color: COLOR_BORDER, linear-gradient(to bottom, #F0F0F0 0%, #E5E5E5 100%);
|
||||
-fx-background-insets: 0, 1;
|
||||
-fx-padding: 2px 12px 2px 12px;
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Separator?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<?import javafx.scene.shape.Arc?>
|
||||
|
||||
<HBox fx:id="rootPane" prefHeight="440.0" prefWidth="652.0" spacing="12.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
|
||||
|
||||
@@ -49,7 +52,15 @@
|
||||
</fx:define>
|
||||
|
||||
<VBox prefWidth="200.0" cacheShape="true" cache="true">
|
||||
<ListView fx:id="vaultList" VBox.vgrow="ALWAYS" focusTraversable="false" cacheShape="true" cache="true"/>
|
||||
<StackPane VBox.vgrow="ALWAYS" cacheShape="true" cache="true">
|
||||
<ListView fx:id="vaultList" focusTraversable="false" cacheShape="true" cache="true"/>
|
||||
<AnchorPane fx:id="emptyListInstructions" cacheShape="true" cache="true">
|
||||
<HBox AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.bottomAnchor="100.0" alignment="CENTER" cacheShape="true" cache="true">
|
||||
<Label textAlignment="CENTER" text="%main.emptyListInstructions" wrapText="true" cacheShape="true" cache="true"/>
|
||||
</HBox>
|
||||
<Arc AnchorPane.bottomAnchor="5.0" type="OPEN" centerX="-10.0" centerY="0.0" radiusY="100.0" radiusX="60.0" startAngle="0" length="-60.0" strokeWidth="1" stroke="BLACK" fill="TRANSPARENT"/>
|
||||
</AnchorPane>
|
||||
</StackPane>
|
||||
<ToolBar VBox.vgrow="NEVER" styleClass="list-related-toolbar" cacheShape="true" cache="true">
|
||||
<items>
|
||||
<ToggleButton text="" styleClass="ionicons" fx:id="addVaultButton" onAction="#didClickAddVault" focusTraversable="false" cacheShape="true" cache="true"/>
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
|
||||
<VBox prefWidth="400.0" alignment="TOP_CENTER" spacing="12.0" cacheShape="true" cache="true">
|
||||
<GridPane VBox.vgrow="ALWAYS" vgap="12.0" hgap="12.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
|
||||
<VBox prefWidth="400.0" alignment="TOP_CENTER" spacing="12.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
|
||||
<Label VBox.vgrow="NEVER" fx:id="versionLabel" alignment="CENTER" cacheShape="true" cache="true" />
|
||||
<GridPane VBox.vgrow="ALWAYS" vgap="12.0" hgap="12.0" cacheShape="true" cache="true">
|
||||
<padding>
|
||||
<Insets top="24.0" right="12.0" bottom="24.0" left="12.0" />
|
||||
</padding>
|
||||
|
||||
@@ -28,9 +28,12 @@
|
||||
<fx:define>
|
||||
<ContextMenu fx:id="moreOptionsMenu">
|
||||
<items>
|
||||
<MenuItem text="%unlocked.moreOptions.reveal" onAction="#didClickRevealVault"/>
|
||||
<!-- Future use: -->
|
||||
<MenuItem text="%unlocked.moreOptions.copyUrl" onAction="#didClickCopyUrl"/>
|
||||
<MenuItem text="%unlocked.moreOptions.reveal" onAction="#didClickRevealVault">
|
||||
<graphic><Label text="" styleClass="ionicons"/></graphic>
|
||||
</MenuItem>
|
||||
<MenuItem text="%unlocked.moreOptions.copyUrl" onAction="#didClickCopyUrl">
|
||||
<graphic><Label text="" styleClass="ionicons"/></graphic>
|
||||
</MenuItem>
|
||||
</items>
|
||||
</ContextMenu>
|
||||
</fx:define>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<VBox prefWidth="400.0" prefHeight="400.0" spacing="24.0" alignment="CENTER" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
|
||||
|
||||
<VBox fx:id="checkForUpdatesContainer" spacing="6.0" alignment="CENTER" cacheShape="true" cache="true">
|
||||
<VBox fx:id="checkForUpdatesContainer" spacing="6.0" alignment="CENTER" cacheShape="true" cache="true" prefHeight="50.0">
|
||||
<HBox alignment="CENTER" spacing="5.0" cacheShape="true" cache="true">
|
||||
<Label fx:id="checkForUpdatesStatus" cacheShape="true" cache="true" />
|
||||
<ProgressIndicator fx:id="checkForUpdatesIndicator" progress="-1" prefWidth="15.0" prefHeight="15.0" cacheShape="true" cache="true" cacheHint="SPEED" />
|
||||
@@ -28,8 +28,10 @@
|
||||
<Hyperlink alignment="CENTER" fx:id="updateLink" onAction="#didClickUpdateLink" cacheShape="true" cache="true" />
|
||||
</VBox>
|
||||
|
||||
<ImageView fitHeight="200.0" preserveRatio="true" smooth="false" cache="true">
|
||||
<ImageView fitHeight="200.0" preserveRatio="true" smooth="false" cache="true" style="-fx-background-color: green;">
|
||||
<Image url="/bot_welcome.png"/>
|
||||
</ImageView>
|
||||
|
||||
<VBox prefHeight="50.0"/>
|
||||
|
||||
</VBox>
|
||||
@@ -10,6 +10,7 @@
|
||||
app.name=Cryptomator
|
||||
|
||||
# main.fxml
|
||||
main.emptyListInstructions=Click here to add a vault
|
||||
main.directoryList.contextMenu.remove=Remove from list
|
||||
main.directoryList.contextMenu.changePassword=Change password
|
||||
main.addDirectory.contextMenu.new=Create new vault
|
||||
@@ -36,8 +37,8 @@ unlock.button.unlock=Unlock vault
|
||||
unlock.button.advancedOptions.show=More options
|
||||
unlock.button.advancedOptions.hide=Less options
|
||||
unlock.choicebox.winDriveLetter.auto=Assign automatically
|
||||
unlock.errorMessage.wrongPassword=Wrong password.
|
||||
unlock.errorMessage.mountingFailed=Mounting failed.
|
||||
unlock.errorMessage.wrongPassword=Wrong password
|
||||
unlock.errorMessage.mountingFailed=Mounting failed. See logfile for details.
|
||||
unlock.errorMessage.unsupportedKeyLengthInstallJCE=Decryption failed. Please install Oracle JCE Unlimited Strength Policy.
|
||||
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware=Unsupported vault. This vault has been created with an older version of Cryptomator.
|
||||
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault=Unsupported vault. This vault has been created with a newer version of Cryptomator.
|
||||
@@ -49,19 +50,21 @@ changePassword.label.newPassword=New password
|
||||
changePassword.label.retypePassword=Retype password
|
||||
changePassword.label.downloadsPageLink=All Cryptomator versions
|
||||
changePassword.button.change=Change password
|
||||
changePassword.errorMessage.wrongPassword=Wrong password.
|
||||
changePassword.errorMessage.decryptionFailed=Decryption failed.
|
||||
changePassword.errorMessage.wrongPassword=Wrong password
|
||||
changePassword.errorMessage.decryptionFailed=Decryption failed
|
||||
changePassword.errorMessage.unsupportedKeyLengthInstallJCE=Decryption failed. Please install Oracle JCE Unlimited Strength Policy.
|
||||
changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware=Unsupported vault. This vault has been created with an older version of Cryptomator.
|
||||
changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault=Unsupported vault. This vault has been created with a newer version of Cryptomator.
|
||||
changePassword.infoMessage.success=Password changed.
|
||||
changePassword.infoMessage.success=Password changed
|
||||
|
||||
# unlocked.fxml
|
||||
unlocked.button.lock=Lock vault
|
||||
unlocked.moreOptions.reveal=Reveal drive
|
||||
unlocked.moreOptions.copyUrl=Copy WebDAV URL
|
||||
unlocked.label.revealFailed=Command failed.
|
||||
unlocked.label.unmountFailed=Ejecting drive failed.
|
||||
unlocked.label.revealFailed=Command failed
|
||||
unlocked.label.unmountFailed=Ejecting drive failed
|
||||
unlocked.label.statsEncrypted=encrypted
|
||||
unlocked.label.statsDecrypted=decrypted
|
||||
unlocked.ioGraph.yAxis.label=Throughput (MiB/s)
|
||||
|
||||
# mac_warnings.fxml
|
||||
@@ -71,6 +74,7 @@ macWarnings.moreInformationButton=Learn more
|
||||
macWarnings.whitelistButton=Decrypt selected anyway
|
||||
|
||||
# settings.fxml
|
||||
settings.version.label=Version %s
|
||||
settings.checkForUpdates.label=Check for updates
|
||||
settings.port.label=WebDAV Port *
|
||||
settings.port.prompt=0 = Choose automatically
|
||||
|
||||
88
main/ui/src/main/resources/localization_de.properties
Normal file
88
main/ui/src/main/resources/localization_de.properties
Normal file
@@ -0,0 +1,88 @@
|
||||
#-------------------------------------------------------------------------------
|
||||
# Copyright (c) 2016 Markus Kreusch
|
||||
# This file is licensed under the terms of the MIT license.
|
||||
# See the LICENSE.txt file for more info.
|
||||
#
|
||||
# Contributors:
|
||||
# Markus Kreusch - initial API and implementation
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
app.name=Cryptomator
|
||||
|
||||
# main.fxml
|
||||
main.emptyListInstructions=Klicken Sie hier, um neue Tresore hinzuzufügen
|
||||
main.directoryList.contextMenu.remove=Aus Liste entfernen
|
||||
main.directoryList.contextMenu.changePassword=Passwort ändern
|
||||
main.addDirectory.contextMenu.new=Tresor erstellen
|
||||
main.addDirectory.contextMenu.open=Tresor öffnen
|
||||
|
||||
# welcome.fxml
|
||||
welcome.checkForUpdates.label.currentlyChecking=Auf Updates prüfen...
|
||||
welcome.newVersionMessage=Version %s kann heruntergeladen werden. Aktuelle Version %s.
|
||||
|
||||
# initialize.fxml
|
||||
initialize.label.password=Passwort
|
||||
initialize.label.retypePassword=Passwort bestätigen
|
||||
initialize.button.ok=Tresor erstellen
|
||||
initialize.messageLabel.alreadyInitialized=Tresor bereits vorhanden
|
||||
initialize.messageLabel.initializationFailed=Fehler beim Initialisieren. Details in der Log-Datei.
|
||||
|
||||
# unlock.fxml
|
||||
unlock.label.password=Passwort
|
||||
unlock.label.mountName=Laufwerksname
|
||||
unlock.label.winDriveLetter=Laufwerksbuchstabe
|
||||
unlock.label.downloadsPageLink=Alle Cryptomator Versionen
|
||||
unlock.label.advancedHeading=Erweiterte Optionen
|
||||
unlock.button.unlock=Tresor entsperren
|
||||
unlock.button.advancedOptions.show=Weitere Optionen
|
||||
unlock.button.advancedOptions.hide=Weniger Optionen
|
||||
unlock.choicebox.winDriveLetter.auto=Automatisch ermitteln
|
||||
unlock.errorMessage.wrongPassword=Falsches Passwort
|
||||
unlock.errorMessage.mountingFailed=Verbindung fehlgeschlagen. Details in der Log-Datei.
|
||||
unlock.errorMessage.unsupportedKeyLengthInstallJCE=Entschlüsselung fehlgeschlagen. Bitte die Oracle JCE Unlimited Strength Policy installieren.
|
||||
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware=Tresor nicht unterstützt. Der Tresor wurde mit einer älteren Version von Cryptomator erstellt.
|
||||
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault=Tresor nicht unterstützt. Der Tresor wurde mit einer neueren Version von Cryptomator erstellt.
|
||||
unlock.messageLabel.startServerFailed=Starten des WebDAV-Servers fehlgeschlagen.
|
||||
|
||||
# change_password.fxml
|
||||
changePassword.label.oldPassword=Altes Passwort
|
||||
changePassword.label.newPassword=Neues Passwort
|
||||
changePassword.label.retypePassword=Passwort bestätigen
|
||||
changePassword.label.downloadsPageLink=Alle Cryptomator Versionen
|
||||
changePassword.button.change=Passwort ändern
|
||||
changePassword.errorMessage.wrongPassword=Falsches Passwort
|
||||
changePassword.errorMessage.decryptionFailed=Entschlüsselung fehlgeschlagen
|
||||
changePassword.errorMessage.unsupportedKeyLengthInstallJCE=Entschlüsselung fehlgeschlagen. Bitte die Oracle JCE Unlimited Strength Policy installieren.
|
||||
changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware=Tresor nicht unterstützt. Der Tresor wurde mit einer älteren Version von Cryptomator erstellt.
|
||||
changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault=Tresor nicht unterstützt. Der Tresor wurde mit einer neueren Version von Cryptomator erstellt.
|
||||
changePassword.infoMessage.success=Passwort geändert
|
||||
|
||||
# unlocked.fxml
|
||||
unlocked.button.lock=Tresor sperren
|
||||
unlocked.moreOptions.reveal=Laufwerk anzeigen
|
||||
unlocked.moreOptions.copyUrl=WebDAV-URL kopieren
|
||||
unlocked.label.revealFailed=Befehl fehlgeschlagen
|
||||
unlocked.label.unmountFailed=Trennen des Laufwerks fehlgeschlagen
|
||||
unlocked.label.statsEncrypted=verschlüsselt
|
||||
unlocked.label.statsDecrypted=entschlüsselt
|
||||
unlocked.ioGraph.yAxis.label=Durchsatz (MiB/s)
|
||||
|
||||
# mac_warnings.fxml
|
||||
macWarnings.windowTitle=Achtung - Kompromittierte Datei in %s
|
||||
macWarnings.message=Cryptomator hat möglicherweise unerlaubte Veränderungen in den folgenden Dateien erkannt:
|
||||
macWarnings.moreInformationButton=Mehr erfahren
|
||||
macWarnings.whitelistButton=Trotzdem entschlüsseln
|
||||
|
||||
# settings.fxml
|
||||
settings.version.label=Version %s
|
||||
settings.checkForUpdates.label=Auf Updates prüfen
|
||||
settings.port.label=WebDAV Port *
|
||||
settings.port.prompt=0 = Automatisch wählen
|
||||
settings.requiresRestartLabel=* benötigt Neustart von Cryptomator
|
||||
|
||||
# tray icon
|
||||
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.
|
||||
Reference in New Issue
Block a user