From 41585a2a39d5b10c8a0661af7d9a38118b968fa0 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 19 Sep 2016 12:58:35 +0200 Subject: [PATCH 01/10] removed debug output [ci skip] --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 61811198..78c66ac5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,7 +33,6 @@ addons: branch_pattern: release.* before_deploy: - "if ! gpg --list-secret-keys 34C80F11; then gpg --import 34C80F11.gpg; fi" -- "ls -la target" deploy: - provider: script # SNAPSHOTS script: mvn clean deploy -DskipTests -Prelease --settings settings.xml From 92efc42b077307f1fa3ef5644d457ed161ecd279 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Mon, 19 Sep 2016 13:38:28 +0200 Subject: [PATCH 02/10] mark release on github as pre-release [ci skip] --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 78c66ac5..beb1593a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,6 +55,7 @@ deploy: - "target/dependency-list.txt" - "target/*.jar" skip_cleanup: true + prerelease: true on: repo: cryptomator/cryptofs tags: true From 63a73d48ee90b51c7f65ed96ea2a90cf61fbe28a Mon Sep 17 00:00:00 2001 From: Markus Kreusch Date: Tue, 20 Sep 2016 11:57:45 +0200 Subject: [PATCH 03/10] Implemented readAttributes(Path,String,LinkOption[]) --- .../cryptofs/BiConsumerThrowingException.java | 7 + .../CryptoFileAttributeByNameProvider.java | 226 ++++++++++++++++++ .../cryptofs/CryptoFileSystem.java | 26 +- ...ileSystemFileAttributeIntegrationTest.java | 93 +++++++ .../cryptofs/CryptoFileSystemTest.java | 3 +- 5 files changed, 347 insertions(+), 8 deletions(-) create mode 100644 src/main/java/org/cryptomator/cryptofs/BiConsumerThrowingException.java create mode 100644 src/main/java/org/cryptomator/cryptofs/CryptoFileAttributeByNameProvider.java create mode 100644 src/test/java/org/cryptomator/cryptofs/CryptoFileSystemFileAttributeIntegrationTest.java diff --git a/src/main/java/org/cryptomator/cryptofs/BiConsumerThrowingException.java b/src/main/java/org/cryptomator/cryptofs/BiConsumerThrowingException.java new file mode 100644 index 00000000..ed8832f5 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/BiConsumerThrowingException.java @@ -0,0 +1,7 @@ +package org.cryptomator.cryptofs; + +interface BiConsumerThrowingException { + + void accept(A a, B b) throws E; + +} diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileAttributeByNameProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileAttributeByNameProvider.java new file mode 100644 index 00000000..5a394ef8 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileAttributeByNameProvider.java @@ -0,0 +1,226 @@ +/******************************************************************************* + * Copyright (c) 2016 Sebastian Stenzel and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the accompanying LICENSE.txt. + * + * Contributors: + * Sebastian Stenzel - initial API and implementation + *******************************************************************************/ +package org.cryptomator.cryptofs; + +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.DosFileAttributes; +import java.nio.file.attribute.FileAttributeView; +import java.nio.file.attribute.FileTime; +import java.nio.file.attribute.PosixFileAttributes; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.function.Function; + +import javax.inject.Inject; + +import com.google.common.base.Predicate; + +@PerFileSystem +class CryptoFileAttributeByNameProvider { + + private static final SortedMap> GETTERS = new TreeMap<>(); + { + attribute("basic:lastModifiedTime", BasicFileAttributes.class, BasicFileAttributes::lastModifiedTime); + attribute("basic:lastAccessTime", BasicFileAttributes.class, BasicFileAttributes::lastAccessTime); + attribute("basic:creationTime", BasicFileAttributes.class, BasicFileAttributes::creationTime); + attribute("basic:isRegularFile", BasicFileAttributes.class, BasicFileAttributes::isRegularFile); + attribute("basic:isDirectory", BasicFileAttributes.class, BasicFileAttributes::isDirectory); + attribute("basic:isSymbolicLink", BasicFileAttributes.class, BasicFileAttributes::isSymbolicLink); + attribute("basic:isOther", BasicFileAttributes.class, BasicFileAttributes::isOther); + attribute("basic:size", BasicFileAttributes.class, BasicFileAttributes::size); + attribute("basic:fileKey", BasicFileAttributes.class, BasicFileAttributes::fileKey); + + attribute("dos:isReadOnly", DosFileAttributes.class, DosFileAttributes::isReadOnly); + attribute("dos:isHidden", DosFileAttributes.class, DosFileAttributes::isHidden); + attribute("dos:isArchive", DosFileAttributes.class, DosFileAttributes::isArchive); + attribute("dos:isSystem", DosFileAttributes.class, DosFileAttributes::isSystem); + + attribute("posix:owner", PosixFileAttributes.class, PosixFileAttributes::owner); + attribute("posix:group", PosixFileAttributes.class, PosixFileAttributes::group); + attribute("posix:permissions", PosixFileAttributes.class, PosixFileAttributes::permissions); + } + + private static final SortedMap> SETTERS = new TreeMap<>(); + { + attribute("basic:lastModifiedTime", BasicFileAttributeView.class, FileTime.class, (view, lastModifiedTime) -> view.setTimes(lastModifiedTime, null, null)); + attribute("basic:lastAccessTime", BasicFileAttributeView.class, FileTime.class, (view, lastAccessTime) -> view.setTimes(null, lastAccessTime, null)); + attribute("basic:creationTime", BasicFileAttributeView.class, FileTime.class, (view, creationTime) -> view.setTimes(null, null, creationTime)); + + attribute("dos:isReadOnly", DosFileAttributes.class, DosFileAttributes::isReadOnly); + attribute("dos:isHidden", DosFileAttributes.class, DosFileAttributes::isHidden); + attribute("dos:isArchive", DosFileAttributes.class, DosFileAttributes::isArchive); + attribute("dos:isSystem", DosFileAttributes.class, DosFileAttributes::isSystem); + + attribute("posix:owner", PosixFileAttributes.class, PosixFileAttributes::owner); + attribute("posix:group", PosixFileAttributes.class, PosixFileAttributes::group); + attribute("posix:permissions", PosixFileAttributes.class, PosixFileAttributes::permissions); + } + + private void attribute(String name, Class type, Function getter) { + String plainName = name.substring(name.indexOf(':') + 1); + GETTERS.put(name, new AttributeGetter<>(plainName, type, getter)); + } + + private void attribute(String name, Class type, Class valueType, BiConsumerThrowingException setter) { + SETTERS.put(name, new AttributeSetter<>(type, valueType, setter)); + } + + private final CryptoFileAttributeProvider cryptoFileAttributeProvider; + private final CryptoFileAttributeViewProvider cryptoFileAttributeViewProvider; + + @Inject + public CryptoFileAttributeByNameProvider(CryptoFileAttributeProvider cryptoFileAttributeProvider, CryptoFileAttributeViewProvider cryptoFileAttributeViewProvider) { + this.cryptoFileAttributeProvider = cryptoFileAttributeProvider; + this.cryptoFileAttributeViewProvider = cryptoFileAttributeViewProvider; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + public void setAttribute(Path path, String attributeName, Object value) throws IOException { + String normalizedAttributeName = normalizedAttributeName(attributeName); + AttributeSetter setter = SETTERS.get(normalizedAttributeName); + if (setter == null) { + throw new IllegalArgumentException("Unrecognized attribute name: " + attributeName); + } + FileAttributeView view = cryptoFileAttributeViewProvider.getAttributeView(path, setter.type()); + setter.set(view, value); + } + + public Map readAttributes(Path path, String attributesString) throws IOException { + if (attributesString.isEmpty()) { + throw new IllegalArgumentException("No attributes specified"); + } + Predicate getterNameFilter = getterNameFilter(attributesString); + @SuppressWarnings("rawtypes") + Collection getters = GETTERS.entrySet().stream() // + .filter(entry -> getterNameFilter.apply(entry.getKey())) // + .map(Entry::getValue) // + .collect(toList()); + return readAttributes(path, getters); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private Map readAttributes(Path path, Collection getters) throws IOException { + Map result = new HashMap<>(); + BasicFileAttributes attributes = null; + for (AttributeGetter getter : getters) { + if (!getter.type().isInstance(attributes)) { + attributes = cryptoFileAttributeProvider.readAttributes(path, getter.type()); + } + String name = getter.name(); + result.put(name, getter.read(attributes)); + } + return result; + } + + private String normalizedAttributeName(String attributeName) { + if (attributeName.indexOf(':') == -1) { + return "basic:" + attributeName; + } else { + return attributeName; + } + } + + private Predicate getterNameFilter(String attributesString) { + String viewName = viewName(attributesString); + Set attributeNames = attributeNames(viewName, attributesString); + if (attributeNames.contains("*")) { + String prefix = viewName + ":"; + return value -> value.startsWith(prefix); + } else { + return attributeNames::contains; + } + } + + private String viewName(String attributes) { + int firstColon = attributes.indexOf(':'); + if (firstColon == -1) { + return "basic"; + } else { + return attributes.substring(0, firstColon); + } + } + + private Set attributeNames(String viewName, String attributeString) { + int firstColon = attributeString.indexOf(':'); + String attributeNames; + if (firstColon == -1) { + attributeNames = attributeString; + } else { + attributeNames = attributeString.substring(firstColon + 1); + } + return stream(attributeNames.split(",")).map(name -> { + if ("*".equals(name)) { + return "*"; + } else { + return viewName + ":" + name; + } + }).collect(toSet()); + } + + private static class AttributeSetter { + + private final Class type; + private final Class valueType; + private final BiConsumerThrowingException setter; + + private AttributeSetter(Class type, Class valueType, BiConsumerThrowingException setter) { + this.type = type; + this.valueType = valueType; + this.setter = setter; + } + + public Class type() { + return type; + } + + public void set(T attributes, Object value) throws IOException { + setter.accept(attributes, valueType.cast(value)); + } + + } + + private static class AttributeGetter { + + private final String name; + private final Class type; + private final Function getter; + + private AttributeGetter(String name, Class type, Function getter) { + this.name = name; + this.type = type; + this.getter = getter; + } + + public String name() { + return name; + } + + public Class type() { + return type; + } + + public Object read(T attributes) { + return getter.apply(attributes); + } + + } + +} diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystem.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystem.java index e139fd4e..f38500c0 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystem.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystem.java @@ -76,6 +76,7 @@ class CryptoFileSystem extends FileSystem { private final DirectoryIdProvider dirIdProvider; private final LongFileNameProvider longFileNameProvider; private final CryptoFileAttributeProvider fileAttributeProvider; + private final CryptoFileAttributeByNameProvider fileAttributeByNameProvider; private final CryptoFileAttributeViewProvider fileAttributeViewProvider; private final OpenCryptoFiles openCryptoFiles; private final CryptoFileStore fileStore; @@ -87,7 +88,7 @@ class CryptoFileSystem extends FileSystem { public CryptoFileSystem(@PathToVault Path pathToVault, CryptoFileSystemProperties properties, Cryptor cryptor, CryptoFileSystemProvider provider, CryptoFileSystems cryptoFileSystems, CryptoFileStore fileStore, OpenCryptoFiles openCryptoFiles, CryptoPathMapper cryptoPathMapper, DirectoryIdProvider dirIdProvider, LongFileNameProvider longFileNameProvider, CryptoFileAttributeProvider fileAttributeProvider, CryptoFileAttributeViewProvider fileAttributeViewProvider, PathMatcherFactory pathMatcherFactory, CryptoPathFactory cryptoPathFactory, CryptoFileSystemStats stats, - RootDirectoryInitializer rootDirectoryInitializer) { + RootDirectoryInitializer rootDirectoryInitializer, CryptoFileAttributeByNameProvider fileAttributeByNameProvider) { this.cryptor = cryptor; this.provider = provider; this.cryptoFileSystems = cryptoFileSystems; @@ -96,6 +97,7 @@ public CryptoFileSystem(@PathToVault Path pathToVault, CryptoFileSystemPropertie this.dirIdProvider = dirIdProvider; this.longFileNameProvider = longFileNameProvider; this.fileAttributeProvider = fileAttributeProvider; + this.fileAttributeByNameProvider = fileAttributeByNameProvider; this.fileAttributeViewProvider = fileAttributeViewProvider; this.openCryptoFiles = openCryptoFiles; this.fileStore = fileStore; @@ -176,14 +178,24 @@ public WatchService newWatchService() throws IOException { /* methods delegated to by CryptoFileSystemProvider */ - void setAttribute(Path cleartextPath, String attribute, Object value, LinkOption... options) { - // TODO + void setAttribute(CryptoPath cleartextPath, String attribute, Object value, LinkOption... options) throws IOException { + Path ciphertextDirPath = cryptoPathMapper.getCiphertextDirPath(cleartextPath); + if (Files.notExists(ciphertextDirPath) && cleartextPath.getNameCount() > 0) { + Path ciphertextFilePath = cryptoPathMapper.getCiphertextFilePath(cleartextPath, CiphertextFileType.FILE); + fileAttributeByNameProvider.setAttribute(ciphertextFilePath, attribute, value); + } else { + fileAttributeByNameProvider.setAttribute(ciphertextDirPath, attribute, value); + } } - Map readAttributes(Path cleartextPath, String attributes, LinkOption... options) { - // TODO - // TODO if we read attrs from Dir, everything is fine. If we read them from DirFile, we need to adjust CryptoBasicFileAttributes#isDirectory - return null; + Map readAttributes(CryptoPath cleartextPath, String attributes, LinkOption... options) throws IOException { + Path ciphertextDirPath = cryptoPathMapper.getCiphertextDirPath(cleartextPath); + if (Files.notExists(ciphertextDirPath) && cleartextPath.getNameCount() > 0) { + Path ciphertextFilePath = cryptoPathMapper.getCiphertextFilePath(cleartextPath, CiphertextFileType.FILE); + return fileAttributeByNameProvider.readAttributes(ciphertextFilePath, attributes); + } else { + return fileAttributeByNameProvider.readAttributes(ciphertextDirPath, attributes); + } } A readAttributes(CryptoPath cleartextPath, Class type, LinkOption... options) throws IOException { diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemFileAttributeIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemFileAttributeIntegrationTest.java new file mode 100644 index 00000000..73f4a588 --- /dev/null +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemFileAttributeIntegrationTest.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2016 Sebastian Stenzel and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the accompanying LICENSE.txt. + * + * Contributors: + * Sebastian Stenzel - initial API and implementation + *******************************************************************************/ +package org.cryptomator.cryptofs; + +import static java.lang.Boolean.FALSE; +import static java.lang.Boolean.TRUE; +import static java.lang.System.currentTimeMillis; +import static java.nio.file.Files.readAttributes; +import static org.cryptomator.cryptofs.CryptoFileSystemProperties.cryptoFileSystemProperties; +import static org.cryptomator.cryptofs.CryptoFileSystemUris.createUri; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.lessThan; +import static org.junit.Assert.assertThat; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; +import java.util.Map; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import com.google.common.jimfs.Jimfs; + +public class CryptoFileSystemFileAttributeIntegrationTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private static FileSystem inMemoryFs; + private static Path pathToVault; + private static FileSystem fileSystem; + + @BeforeClass + public static void setupClass() throws IOException { + inMemoryFs = Jimfs.newFileSystem(); + pathToVault = inMemoryFs.getRootDirectories().iterator().next().resolve("vault"); + fileSystem = new CryptoFileSystemProvider().newFileSystem(createUri(pathToVault), cryptoFileSystemProperties().withPassphrase("asd").build()); + } + + @AfterClass + public static void teardownClass() throws IOException { + inMemoryFs.close(); + } + + @Test + public void testReadAttributesOfNonExistingFile() throws IOException { + Path file = fileSystem.getPath("/nonExisting"); + + thrown.expect(NoSuchFileException.class); + + readAttributes(file, "size,lastModifiedTime,isDirectory"); + } + + @Test + public void testReadFileAttributesByName() throws IOException { + Path file = fileSystem.getPath("/a"); + Files.write(file, new byte[1]); + + Map result = Files.readAttributes(file, "size,lastModifiedTime,isDirectory"); + + assertThat((FileTime) result.get("lastModifiedTime"), is(greaterThan(FileTime.fromMillis(currentTimeMillis() - 10000)))); + assertThat((FileTime) result.get("lastModifiedTime"), is(lessThan(FileTime.fromMillis(currentTimeMillis() + 10000)))); + assertThat((Long) result.get("size"), is(1L)); + assertThat((Boolean) result.get("isDirectory"), is(FALSE)); + } + + @Test + public void testReadDirectoryAttributesByName() throws IOException { + Path file = fileSystem.getPath("/b"); + Files.createDirectory(file); + + Map result = Files.readAttributes(file, "lastModifiedTime,isDirectory"); + + assertThat((FileTime) result.get("lastModifiedTime"), is(greaterThan(FileTime.fromMillis(currentTimeMillis() - 10000)))); + assertThat((FileTime) result.get("lastModifiedTime"), is(lessThan(FileTime.fromMillis(currentTimeMillis() + 10000)))); + assertThat((Boolean) result.get("isDirectory"), is(TRUE)); + } + +} diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemTest.java index ad590835..9780c1d7 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemTest.java @@ -78,6 +78,7 @@ public class CryptoFileSystemTest { private final DirectoryIdProvider dirIdProvider = mock(DirectoryIdProvider.class); private final LongFileNameProvider longFileNameProvider = mock(LongFileNameProvider.class); private final CryptoFileAttributeProvider fileAttributeProvider = mock(CryptoFileAttributeProvider.class); + private final CryptoFileAttributeByNameProvider fileAttributeByNameProvider = mock(CryptoFileAttributeByNameProvider.class); private final CryptoFileAttributeViewProvider fileAttributeViewProvider = mock(CryptoFileAttributeViewProvider.class); private final PathMatcherFactory pathMatcherFactory = mock(PathMatcherFactory.class); private final CryptoPathFactory cryptoPathFactory = mock(CryptoPathFactory.class); @@ -95,7 +96,7 @@ public void setup() { when(cryptoPathFactory.emptyFor(any())).thenReturn(empty); inTest = new CryptoFileSystem(pathToVault, properties, cryptor, provider, cryptoFileSystems, fileStore, openCryptoFiles, cryptoPathMapper, dirIdProvider, longFileNameProvider, fileAttributeProvider, - fileAttributeViewProvider, pathMatcherFactory, cryptoPathFactory, stats, rootDirectoryInitializer); + fileAttributeViewProvider, pathMatcherFactory, cryptoPathFactory, stats, rootDirectoryInitializer, fileAttributeByNameProvider); } @Test From 7846494b4b0d30a99dec26e1d0f041bb25981b2e Mon Sep 17 00:00:00 2001 From: Markus Kreusch Date: Thu, 29 Sep 2016 10:37:47 +0200 Subject: [PATCH 04/10] Fixed NoSuchFileException during move and copy * Move and copy did not work when invoked with two CryptoPaths from different CryptoFileSystems * Added CopyAndMoveOperations to hold implementation for this cases --- .../cryptofs/CopyAndMoveOperations.java | 135 ++++++++++++++++++ .../cryptofs/CryptoFileSystemProvider.java | 21 +-- .../CryptoFileSystemProviderComponent.java | 2 + .../org/cryptomator/cryptofs/CryptoPath.java | 2 +- ...yptoFileSystemProviderIntegrationTest.java | 72 ++++++++++ .../CryptoFileSystemProviderTest.java | 18 ++- 6 files changed, 233 insertions(+), 17 deletions(-) create mode 100644 src/main/java/org/cryptomator/cryptofs/CopyAndMoveOperations.java diff --git a/src/main/java/org/cryptomator/cryptofs/CopyAndMoveOperations.java b/src/main/java/org/cryptomator/cryptofs/CopyAndMoveOperations.java new file mode 100644 index 00000000..d81b7465 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/CopyAndMoveOperations.java @@ -0,0 +1,135 @@ +package org.cryptomator.cryptofs; + +import static java.nio.file.StandardCopyOption.ATOMIC_MOVE; +import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; +import static java.nio.file.StandardOpenOption.CREATE_NEW; +import static java.nio.file.StandardOpenOption.READ; +import static java.nio.file.StandardOpenOption.WRITE; + +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.file.AtomicMoveNotSupportedException; +import java.nio.file.CopyOption; +import java.nio.file.DirectoryStream; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.spi.FileSystemProvider; +import java.util.EnumSet; +import java.util.Optional; + +import javax.inject.Inject; + +import org.apache.commons.lang3.ArrayUtils; + +@PerProvider +class CopyAndMoveOperations { + + @Inject + public CopyAndMoveOperations() { + } + + public void copy(CryptoPath source, CryptoPath target, CopyOption... options) throws IOException { + if (source.equals(target)) { + return; + } + if (pathsBelongToSameFileSystem(source, target)) { + source.getFileSystem().copy(source, target, options); + } else { + Optional sourceAttrs = attributes(source); + Optional targetAttrs = attributes(target); + if (!sourceAttrs.isPresent()) { + throw new NoSuchFileException(source.toUri().toString()); + } + if (targetAttrs.isPresent()) { + if (ArrayUtils.contains(options, REPLACE_EXISTING)) { + provider(target).delete(target); + } else { + throw new FileAlreadyExistsException(target.toUri().toString()); + } + } + if (sourceAttrs.get().isDirectory()) { + provider(target).createDirectory(target); + } else { + try (FileChannel sourceChannel = provider(source).newFileChannel(source, EnumSet.of(READ)); // + FileChannel targetChannel = provider(target).newFileChannel(target, EnumSet.of(CREATE_NEW, WRITE))) { + sourceChannel.transferTo(0, sourceChannel.size(), targetChannel); + } + } + if (ArrayUtils.contains(options, COPY_ATTRIBUTES)) { + BasicFileAttributeView targetAttrView = provider(target).getFileAttributeView(target, BasicFileAttributeView.class); + targetAttrView.setTimes(sourceAttrs.get().lastModifiedTime(), sourceAttrs.get().lastAccessTime(), sourceAttrs.get().creationTime()); + } + } + } + + private FileSystemProvider provider(CryptoPath path) { + return path.getFileSystem().provider(); + } + + public void move(CryptoPath source, CryptoPath target, CopyOption... options) throws IOException { + if (source.equals(target)) { + return; + } + if (pathsBelongToSameFileSystem(source, target)) { + source.getFileSystem().move(source, target, options); + } else { + if (ArrayUtils.contains(options, ATOMIC_MOVE)) { + throw new AtomicMoveNotSupportedException(source.toUri().toString(), target.toUri().toString(), "Move of encrypted file to different FileSystem"); + } + if (isNonEmptyDirectory(source)) { + throw new IOException("Can not move non empty directory to different FileSystem"); + } + boolean success = false; + try { + copy(source, target, addCopyAttributesTo(options)); + success = true; + } finally { + if (!success) { + // do a best effort to clean a partially copied file + try { + provider(target).deleteIfExists(target); + } catch (IOException e) { + // ignore + } + } + } + provider(source).deleteIfExists(source); + } + } + + private boolean pathsBelongToSameFileSystem(CryptoPath source, CryptoPath target) { + return source.getFileSystem() == target.getFileSystem(); + } + + private boolean isNonEmptyDirectory(CryptoPath source) throws IOException { + Optional sourceAttrs = attributes(source); + if (!sourceAttrs.map(BasicFileAttributes::isDirectory).orElse(false)) { + return false; + } + try (DirectoryStream contents = provider(source).newDirectoryStream(source, ignored -> true)) { + return contents.iterator().hasNext(); + } + } + + private Optional attributes(CryptoPath path) { + try { + return Optional.of(provider(path).readAttributes(path, BasicFileAttributes.class)); + } catch (IOException e) { + return Optional.empty(); + } + } + + private CopyOption[] addCopyAttributesTo(CopyOption[] options) { + CopyOption[] result = new CopyOption[options.length + 1]; + for (int i = 0; i < options.length; i++) { + result[i] = options[i]; + } + result[options.length] = COPY_ATTRIBUTES; + return result; + } + +} diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java index 751f6683..91b068da 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java @@ -80,19 +80,21 @@ public class CryptoFileSystemProvider extends FileSystemProvider { private final CryptoFileSystems fileSystems; + private final CopyAndMoveOperations copyAndMoveOperations; public static FileSystem newFileSystem(Path pathToVault, CryptoFileSystemProperties properties) throws IOException { return FileSystems.newFileSystem(createUri(pathToVault.toAbsolutePath()), properties); } public CryptoFileSystemProvider() { - this.fileSystems = DaggerCryptoFileSystemProviderComponent.builder() // + CryptoFileSystemProviderComponent component = DaggerCryptoFileSystemProviderComponent.builder() // .secureRandomModule(new SecureRandomModule(strongSecureRandom())) // .cryptoFileSystemProviderModule(CryptoFileSystemProviderModule.builder() // .withCrytpoFileSystemProvider(this) // .build()) // - .build() // - .fileSystems(); + .build(); + this.fileSystems = component.fileSystems(); + this.copyAndMoveOperations = component.copyAndMoveOperations(); } private static SecureRandom strongSecureRandom() { @@ -107,8 +109,9 @@ private static SecureRandom strongSecureRandom() { * @deprecated only for testing */ @Deprecated - CryptoFileSystemProvider(CryptoFileSystems fileSystems) { - this.fileSystems = fileSystems; + CryptoFileSystemProvider(CryptoFileSystemProviderComponent component) { + this.fileSystems = component.fileSystems(); + this.copyAndMoveOperations = component.copyAndMoveOperations(); } /** @@ -178,14 +181,12 @@ public void delete(Path cleartextPath) throws IOException { @Override public void copy(Path cleartextSource, Path cleartextTarget, CopyOption... options) throws IOException { - // from javadoc: "both the source and target paths must be associated with this provider" - fileSystem(cleartextSource).copy(CryptoPath.cast(cleartextSource), CryptoPath.cast(cleartextTarget), options); + copyAndMoveOperations.copy(CryptoPath.cast(cleartextSource), CryptoPath.cast(cleartextTarget), options); } @Override public void move(Path cleartextSource, Path cleartextTarget, CopyOption... options) throws IOException { - // from javadoc: "both the source and target paths must be associated with this provider" - fileSystem(cleartextSource).move(CryptoPath.cast(cleartextSource), CryptoPath.cast(cleartextTarget), options); + copyAndMoveOperations.move(CryptoPath.cast(cleartextSource), CryptoPath.cast(cleartextTarget), options); } @Override @@ -234,7 +235,7 @@ private CryptoFileSystem fileSystem(Path path) { if (fileSystem.provider() == this) { return (CryptoFileSystem) fileSystem; } else { - throw new ProviderMismatchException("Used a path from FileSystem:" + fileSystem.provider() + " with FileSystem:" + this); + throw new ProviderMismatchException("Used a path from provider " + fileSystem.provider() + " with provider " + this); } } diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProviderComponent.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProviderComponent.java index 4fba77fc..c6ae5aff 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProviderComponent.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProviderComponent.java @@ -10,4 +10,6 @@ interface CryptoFileSystemProviderComponent { CryptoFileSystemComponent newCryptoFileSystemComponent(CryptoFileSystemModule cryptoFileSystemModule); + CopyAndMoveOperations copyAndMoveOperations(); + } \ No newline at end of file diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoPath.java b/src/main/java/org/cryptomator/cryptofs/CryptoPath.java index e025ab50..ff289bf7 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoPath.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoPath.java @@ -44,7 +44,7 @@ public static CryptoPath cast(Path path) { if (path instanceof CryptoPath) { return (CryptoPath) path; } else { - throw new ProviderMismatchException(); + throw new ProviderMismatchException("Used a path from different provider: " + path); } } diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java index 8d144a33..38e7f7ad 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderIntegrationTest.java @@ -8,7 +8,11 @@ *******************************************************************************/ package org.cryptomator.cryptofs; +import static java.nio.file.Files.readAllBytes; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static org.cryptomator.cryptofs.CryptoFileSystemProperties.cryptoFileSystemProperties; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; import java.io.IOException; import java.net.URI; @@ -57,4 +61,72 @@ public void testOpenAndCloseFileChannel() throws IOException { } } + @Test + public void testCopyFileFromOneCryptoFileSystemToAnother() throws IOException { + byte[] data = new byte[] {1, 2, 3, 4, 5, 6, 7}; + + Path fs1Location = tmpPath.resolve("foo"); + Path fs2Location = tmpPath.resolve("bar"); + Files.createDirectories(fs1Location); + Files.createDirectories(fs2Location); + FileSystem fs1 = CryptoFileSystemProvider.newFileSystem(fs1Location, cryptoFileSystemProperties().withPassphrase("asd").build()); + FileSystem fs2 = CryptoFileSystemProvider.newFileSystem(fs2Location, cryptoFileSystemProperties().withPassphrase("qwe").build()); + Path file1 = fs1.getPath("/foo/bar"); + Path file2 = fs2.getPath("/bar/baz"); + Files.createDirectories(file1.getParent()); + Files.createDirectories(file2.getParent()); + Files.write(file1, data); + + Files.copy(file1, file2); + + assertThat(readAllBytes(file1), is(data)); + assertThat(readAllBytes(file2), is(data)); + } + + @Test + public void testCopyFileByRelacingExistingFromOneCryptoFileSystemToAnother() throws IOException { + byte[] data = new byte[] {1, 2, 3, 4, 5, 6, 7}; + byte[] data2 = new byte[] {10, 11, 12}; + + Path fs1Location = tmpPath.resolve("foo"); + Path fs2Location = tmpPath.resolve("bar"); + Files.createDirectories(fs1Location); + Files.createDirectories(fs2Location); + FileSystem fs1 = CryptoFileSystemProvider.newFileSystem(fs1Location, cryptoFileSystemProperties().withPassphrase("asd").build()); + FileSystem fs2 = CryptoFileSystemProvider.newFileSystem(fs2Location, cryptoFileSystemProperties().withPassphrase("qwe").build()); + Path file1 = fs1.getPath("/foo/bar"); + Path file2 = fs2.getPath("/bar/baz"); + Files.createDirectories(file1.getParent()); + Files.createDirectories(file2.getParent()); + Files.write(file1, data); + Files.write(file2, data2); + + Files.copy(file1, file2, REPLACE_EXISTING); + + assertThat(readAllBytes(file1), is(data)); + assertThat(readAllBytes(file2), is(data)); + } + + @Test + public void testMoveFileFromOneCryptoFileSystemToAnother() throws IOException { + byte[] data = new byte[] {1, 2, 3, 4, 5, 6, 7}; + + Path fs1Location = tmpPath.resolve("foo"); + Path fs2Location = tmpPath.resolve("bar"); + Files.createDirectories(fs1Location); + Files.createDirectories(fs2Location); + FileSystem fs1 = CryptoFileSystemProvider.newFileSystem(fs1Location, cryptoFileSystemProperties().withPassphrase("asd").build()); + FileSystem fs2 = CryptoFileSystemProvider.newFileSystem(fs2Location, cryptoFileSystemProperties().withPassphrase("qwe").build()); + Path file1 = fs1.getPath("/foo/bar"); + Path file2 = fs2.getPath("/bar/baz"); + Files.createDirectories(file1.getParent()); + Files.createDirectories(file2.getParent()); + Files.write(file1, data); + + Files.move(file1, file2); + + assertThat(Files.exists(file1), is(false)); + assertThat(readAllBytes(file2), is(data)); + } + } diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java index 668a075d..a91b3ea2 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java @@ -64,13 +64,13 @@ public class CryptoFileSystemProviderTest { private CryptoPath cryptoPath = mock(CryptoPath.class); private CryptoPath secondCryptoPath = mock(CryptoPath.class); private CryptoFileSystem cryptoFileSystem = mock(CryptoFileSystem.class); + private CopyAndMoveOperations copyAndMoveOperations = mock(CopyAndMoveOperations.class); private Path otherPath = mock(Path.class); private FileSystem otherFileSystem = mock(FileSystem.class); private FileSystemProvider otherProvider = mock(FileSystemProvider.class); - @SuppressWarnings("deprecation") - private CryptoFileSystemProvider inTest = new CryptoFileSystemProvider(fileSystems); + private CryptoFileSystemProvider inTest; @DataPoints @SuppressWarnings("unchecked") @@ -93,7 +93,13 @@ public class CryptoFileSystemProviderTest { ); @Before + @SuppressWarnings("deprecation") public void setup() { + CryptoFileSystemProviderComponent component = mock(CryptoFileSystemProviderComponent.class); + when(component.fileSystems()).thenReturn(fileSystems); + when(component.copyAndMoveOperations()).thenReturn(copyAndMoveOperations); + inTest = new CryptoFileSystemProvider(component); + when(cryptoPath.getFileSystem()).thenReturn(cryptoFileSystem); when(secondCryptoPath.getFileSystem()).thenReturn(cryptoFileSystem); when(cryptoFileSystem.provider()).thenReturn(inTest); @@ -237,21 +243,21 @@ public void testDeleteDelegatesToFileSystem() throws IOException { } @Test - public void testCopyDelegatesToFileSystem() throws IOException { + public void testCopyDelegatesToCopyAndMoveOperations() throws IOException { CopyOption option = mock(CopyOption.class); inTest.copy(cryptoPath, secondCryptoPath, option); - verify(cryptoFileSystem).copy(cryptoPath, secondCryptoPath, option); + verify(copyAndMoveOperations).copy(cryptoPath, secondCryptoPath, option); } @Test - public void testMoveDelegatesToFileSystem() throws IOException { + public void testMoveDelegatesToCopyAndMoveOperations() throws IOException { CopyOption option = mock(CopyOption.class); inTest.move(cryptoPath, secondCryptoPath, option); - verify(cryptoFileSystem).move(cryptoPath, secondCryptoPath, option); + verify(copyAndMoveOperations).move(cryptoPath, secondCryptoPath, option); } @Test From c82b1519c03c5df038b9ef3a20f6f3100de11874 Mon Sep 17 00:00:00 2001 From: Markus Kreusch Date: Thu, 29 Sep 2016 14:51:46 +0200 Subject: [PATCH 05/10] Implemented correct closing behaviour * Operations now throw ClosedFileSystemException if invoked on a closed CryptoFileSystem * DirectoryStreams are now closed when the CryptoFileSystem is closed * FileChannels are now closed when the CryptoFileSystem is closed --- .../cryptofs/CryptoDirectoryStream.java | 14 +- .../cryptofs/CryptoFileChannel.java | 11 +- .../cryptofs/CryptoFileChannelFactory.java | 24 +- .../cryptofs/CryptoFileSystem.java | 49 ++- .../cryptofs/CryptoFileSystemProvider.java | 4 +- .../org/cryptomator/cryptofs/CryptoPath.java | 29 +- .../cryptofs/DirectoryStreamFactory.java | 61 +++ .../cryptomator/cryptofs/FinallyUtils.java | 34 ++ .../cryptomator/cryptofs/OpenCryptoFile.java | 7 +- .../cryptomator/cryptofs/OpenCryptoFiles.java | 8 + .../cryptofs/CryptoDirectoryStreamTest.java | 10 +- .../cryptofs/CryptoFileChannelTest.java | 383 +++++++++--------- .../cryptofs/CryptoFileSystemTest.java | 30 +- .../org/cryptomator/cryptofs/TestHelper.java | 6 +- 14 files changed, 433 insertions(+), 237 deletions(-) create mode 100644 src/main/java/org/cryptomator/cryptofs/DirectoryStreamFactory.java create mode 100644 src/main/java/org/cryptomator/cryptofs/FinallyUtils.java diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoDirectoryStream.java b/src/main/java/org/cryptomator/cryptofs/CryptoDirectoryStream.java index f8a6c807..6fb488d6 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoDirectoryStream.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoDirectoryStream.java @@ -8,6 +8,8 @@ *******************************************************************************/ package org.cryptomator.cryptofs; +import static org.cryptomator.cryptofs.FinallyUtils.guaranteeInvocationOf; + import java.io.IOException; import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; @@ -16,6 +18,7 @@ import java.nio.file.Path; import java.util.Iterator; import java.util.Objects; +import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -38,8 +41,11 @@ class CryptoDirectoryStream implements DirectoryStream { private final FileNameCryptor filenameCryptor; private final LongFileNameProvider longFileNameProvider; private final DirectoryStream.Filter filter; + private final Consumer onClose; - public CryptoDirectoryStream(Directory ciphertextDir, Path cleartextDir, FileNameCryptor filenameCryptor, LongFileNameProvider longFileNameProvider, DirectoryStream.Filter filter) throws IOException { + public CryptoDirectoryStream(Directory ciphertextDir, Path cleartextDir, FileNameCryptor filenameCryptor, LongFileNameProvider longFileNameProvider, DirectoryStream.Filter filter, + Consumer onClose) throws IOException { + this.onClose = onClose; this.directoryId = ciphertextDir.dirId; this.ciphertextDirStream = Files.newDirectoryStream(ciphertextDir.path, p -> true); LOG.trace("OPEN " + directoryId); @@ -101,8 +107,10 @@ private boolean isAcceptableByFilter(Path path) { @Override public void close() throws IOException { - ciphertextDirStream.close(); - LOG.trace("CLOSE " + directoryId); + guaranteeInvocationOf( // + () -> ciphertextDirStream.close(), // + () -> onClose.accept(this), // + () -> LOG.trace("CLOSE " + directoryId)); } } diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileChannel.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileChannel.java index 401b995e..a27f1900 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileChannel.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileChannel.java @@ -9,6 +9,7 @@ package org.cryptomator.cryptofs; import static java.lang.Math.min; +import static org.cryptomator.cryptofs.FinallyUtils.guaranteeInvocationOf; import java.io.IOException; import java.nio.ByteBuffer; @@ -19,6 +20,7 @@ import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import java.util.Objects; +import java.util.function.Consumer; class CryptoFileChannel extends FileChannel { @@ -26,16 +28,19 @@ class CryptoFileChannel extends FileChannel { private final OpenCryptoFile openCryptoFile; private final EffectiveOpenOptions options; + private final Consumer onClose; + private long position = 0; /** * @throws IOException * @throws ClosedChannelException if the openCryptoFile has already been closed */ - public CryptoFileChannel(OpenCryptoFile openCryptoFile, EffectiveOpenOptions options) throws IOException { + public CryptoFileChannel(OpenCryptoFile openCryptoFile, EffectiveOpenOptions options, Consumer onClose) throws IOException { this.openCryptoFile = Objects.requireNonNull(openCryptoFile); this.options = Objects.requireNonNull(options); this.openCryptoFile.open(options); + this.onClose = onClose; } @Override @@ -250,7 +255,9 @@ public FileLock tryLock(long position, long size, boolean shared) throws IOExcep @Override protected void implCloseChannel() throws IOException { - openCryptoFile.close(options); + guaranteeInvocationOf( // + () -> onClose.accept(this), // + () -> openCryptoFile.close(options)); } private void assertWritable() throws IOException { diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileChannelFactory.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileChannelFactory.java index 4045b19d..2763ee04 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileChannelFactory.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileChannelFactory.java @@ -1,18 +1,40 @@ package org.cryptomator.cryptofs; +import static org.cryptomator.cryptofs.FinallyUtils.guaranteeInvocationOf; + import java.io.IOException; +import java.nio.file.ClosedFileSystemException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import javax.inject.Inject; @PerOpenFile class CryptoFileChannelFactory { + private final ConcurrentMap channels = new ConcurrentHashMap<>(); + private volatile boolean closed = false; + @Inject public CryptoFileChannelFactory() { } public CryptoFileChannel create(OpenCryptoFile openCryptoFile, EffectiveOpenOptions options) throws IOException { - return new CryptoFileChannel(openCryptoFile, options); + CryptoFileChannel channel = new CryptoFileChannel(openCryptoFile, options, closed -> channels.remove(closed)); + channels.put(channel, channel); + if (closed) { + channel.close(); + throw new ClosedFileSystemException(); + } + return channel; + } + + public void close() throws IOException { + closed = true; + guaranteeInvocationOf( // + channels.keySet().stream() // + .map(channel -> (RunnableThrowingException) () -> channel.close()) // + .iterator()); } } \ No newline at end of file diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystem.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystem.java index f38500c0..d33cb05b 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystem.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystem.java @@ -10,6 +10,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.cryptomator.cryptofs.Constants.SEPARATOR; +import static org.cryptomator.cryptofs.FinallyUtils.guaranteeInvocationOf; import java.io.IOException; import java.io.UncheckedIOException; @@ -18,6 +19,7 @@ import java.nio.file.AccessDeniedException; import java.nio.file.AccessMode; import java.nio.file.AtomicMoveNotSupportedException; +import java.nio.file.ClosedFileSystemException; import java.nio.file.CopyOption; import java.nio.file.DirectoryNotEmptyException; import java.nio.file.DirectoryStream; @@ -74,28 +76,29 @@ class CryptoFileSystem extends FileSystem { private final Cryptor cryptor; private final CryptoPathMapper cryptoPathMapper; private final DirectoryIdProvider dirIdProvider; - private final LongFileNameProvider longFileNameProvider; private final CryptoFileAttributeProvider fileAttributeProvider; private final CryptoFileAttributeByNameProvider fileAttributeByNameProvider; private final CryptoFileAttributeViewProvider fileAttributeViewProvider; + private final DirectoryStreamFactory directoryStreamFactory; private final OpenCryptoFiles openCryptoFiles; private final CryptoFileStore fileStore; private final PathMatcherFactory pathMatcherFactory; private final CryptoPathFactory cryptoPathFactory; private final CryptoFileSystemStats stats; + private volatile boolean open = true; + @Inject public CryptoFileSystem(@PathToVault Path pathToVault, CryptoFileSystemProperties properties, Cryptor cryptor, CryptoFileSystemProvider provider, CryptoFileSystems cryptoFileSystems, CryptoFileStore fileStore, - OpenCryptoFiles openCryptoFiles, CryptoPathMapper cryptoPathMapper, DirectoryIdProvider dirIdProvider, LongFileNameProvider longFileNameProvider, CryptoFileAttributeProvider fileAttributeProvider, + OpenCryptoFiles openCryptoFiles, CryptoPathMapper cryptoPathMapper, DirectoryIdProvider dirIdProvider, CryptoFileAttributeProvider fileAttributeProvider, CryptoFileAttributeViewProvider fileAttributeViewProvider, PathMatcherFactory pathMatcherFactory, CryptoPathFactory cryptoPathFactory, CryptoFileSystemStats stats, - RootDirectoryInitializer rootDirectoryInitializer, CryptoFileAttributeByNameProvider fileAttributeByNameProvider) { + RootDirectoryInitializer rootDirectoryInitializer, CryptoFileAttributeByNameProvider fileAttributeByNameProvider, DirectoryStreamFactory directoryStreamFactory) { this.cryptor = cryptor; this.provider = provider; this.cryptoFileSystems = cryptoFileSystems; this.pathToVault = pathToVault; this.cryptoPathMapper = cryptoPathMapper; this.dirIdProvider = dirIdProvider; - this.longFileNameProvider = longFileNameProvider; this.fileAttributeProvider = fileAttributeProvider; this.fileAttributeByNameProvider = fileAttributeByNameProvider; this.fileAttributeViewProvider = fileAttributeViewProvider; @@ -104,6 +107,7 @@ public CryptoFileSystem(@PathToVault Path pathToVault, CryptoFileSystemPropertie this.pathMatcherFactory = pathMatcherFactory; this.cryptoPathFactory = cryptoPathFactory; this.stats = stats; + this.directoryStreamFactory = directoryStreamFactory; this.rootPath = cryptoPathFactory.rootFor(this); this.emptyPath = cryptoPathFactory.emptyFor(this); @@ -114,65 +118,81 @@ public CryptoFileSystem(@PathToVault Path pathToVault, CryptoFileSystemPropertie @Override public FileSystemProvider provider() { + assertOpen(); return provider; } @Override public boolean isReadOnly() { + assertOpen(); + // TODO return false; } @Override public String getSeparator() { + assertOpen(); return SEPARATOR; } @Override public Iterable getRootDirectories() { + assertOpen(); return Collections.singleton(getRootPath()); } @Override public Iterable getFileStores() { + assertOpen(); return Collections.singleton(fileStore); } @Override - public void close() { + public void close() throws IOException { // TODO implement correct closing behavior: - // * close all streams, channels, watch services - // * let further access to all paths etc. fail with ClosedFileSystemException - cryptoFileSystems.remove(this); - cryptor.destroy(); + // * close all directory streams and watch services + if (open) { + open = false; + guaranteeInvocationOf( // + () -> cryptoFileSystems.remove(this), // + () -> openCryptoFiles.close(), // + () -> directoryStreamFactory.close(), // + () -> cryptor.destroy()); + } } @Override public boolean isOpen() { - return cryptoFileSystems.contains(this); + return open; } @Override public Set supportedFileAttributeViews() { + assertOpen(); return fileStore.supportedFileAttributeViewNames(); } @Override public CryptoPath getPath(String first, String... more) { + assertOpen(); return cryptoPathFactory.getPath(this, first, more); } @Override public PathMatcher getPathMatcher(String syntaxAndPattern) { + assertOpen(); return pathMatcherFactory.pathMatcherFrom(syntaxAndPattern); } @Override public UserPrincipalLookupService getUserPrincipalLookupService() { + assertOpen(); throw new UnsupportedOperationException(); } @Override public WatchService newWatchService() throws IOException { + assertOpen(); throw new UnsupportedOperationException(); } @@ -295,8 +315,7 @@ void createDirectory(CryptoPath cleartextDir, FileAttribute... attrs) throws } DirectoryStream newDirectoryStream(CryptoPath cleartextDir, Filter filter) throws IOException { - Directory ciphertextDir = cryptoPathMapper.getCiphertextDir(cleartextDir); - return new CryptoDirectoryStream(ciphertextDir, cleartextDir, cryptor.fileNameCryptor(), longFileNameProvider, filter); + return directoryStreamFactory.newDirectoryStream(cleartextDir, filter); } FileChannel newFileChannel(CryptoPath cleartextPath, Set optionsSet, FileAttribute... attrs) throws IOException { @@ -458,4 +477,10 @@ CryptoFileSystemStats getStats() { return stats; } + void assertOpen() { + if (!open) { + throw new ClosedFileSystemException(); + } + } + } diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java index 91b068da..99526030 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java @@ -233,7 +233,9 @@ public void setAttribute(Path cleartextPath, String attribute, Object value, Lin private CryptoFileSystem fileSystem(Path path) { FileSystem fileSystem = path.getFileSystem(); if (fileSystem.provider() == this) { - return (CryptoFileSystem) fileSystem; + CryptoFileSystem cryptoFileSystem = (CryptoFileSystem) fileSystem; + cryptoFileSystem.assertOpen(); + return cryptoFileSystem; } else { throw new ProviderMismatchException("Used a path from provider " + fileSystem.provider() + " with provider " + this); } diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoPath.java b/src/main/java/org/cryptomator/cryptofs/CryptoPath.java index ff289bf7..b524aa8b 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoPath.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoPath.java @@ -35,6 +35,7 @@ class CryptoPath implements Path { private final boolean absolute; public CryptoPath(CryptoFileSystem fileSystem, List elements, boolean absolute) { + fileSystem.assertOpen(); this.fileSystem = fileSystem; this.elements = Collections.unmodifiableList(elements); this.absolute = absolute; @@ -42,7 +43,9 @@ public CryptoPath(CryptoFileSystem fileSystem, List elements, boolean ab public static CryptoPath cast(Path path) { if (path instanceof CryptoPath) { - return (CryptoPath) path; + CryptoPath cryptoPath = (CryptoPath) path; + cryptoPath.getFileSystem().assertOpen(); + return cryptoPath; } else { throw new ProviderMismatchException("Used a path from different provider: " + path); } @@ -55,16 +58,19 @@ public CryptoFileSystem getFileSystem() { @Override public boolean isAbsolute() { + fileSystem.assertOpen(); return absolute; } @Override public CryptoPath getRoot() { + fileSystem.assertOpen(); return absolute ? fileSystem.getRootPath() : null; } @Override public Path getFileName() { + fileSystem.assertOpen(); int elementCount = getNameCount(); if (elementCount == 0) { return null; @@ -75,6 +81,7 @@ public Path getFileName() { @Override public CryptoPath getParent() { + fileSystem.assertOpen(); int elementCount = getNameCount(); if (elementCount > 1) { List elems = elements.subList(0, elementCount - 1); @@ -86,21 +93,25 @@ public CryptoPath getParent() { @Override public int getNameCount() { + fileSystem.assertOpen(); return elements.size(); } @Override public Path getName(int index) { + fileSystem.assertOpen(); return subpath(index, index + 1); } @Override public CryptoPath subpath(int beginIndex, int endIndex) { + fileSystem.assertOpen(); return new CryptoPath(fileSystem, elements.subList(beginIndex, endIndex), false); } @Override public boolean startsWith(Path path) { + fileSystem.assertOpen(); CryptoPath other = cast(path); boolean matchesAbsolute = this.isAbsolute() == other.isAbsolute(); if (matchesAbsolute && other.elements.size() <= this.elements.size()) { @@ -112,11 +123,13 @@ public boolean startsWith(Path path) { @Override public boolean startsWith(String other) { + fileSystem.assertOpen(); return startsWith(fileSystem.getPath(other)); } @Override public boolean endsWith(Path path) { + fileSystem.assertOpen(); CryptoPath other = cast(path); if (other.elements.size() <= this.elements.size()) { return this.elements.subList(this.elements.size() - other.elements.size(), this.elements.size()).equals(other.elements); @@ -127,11 +140,13 @@ public boolean endsWith(Path path) { @Override public boolean endsWith(String other) { + fileSystem.assertOpen(); return endsWith(fileSystem.getPath(other)); } @Override public CryptoPath normalize() { + fileSystem.assertOpen(); LinkedList normalized = new LinkedList<>(); for (String elem : elements) { String lastElem = normalized.peekLast(); @@ -148,6 +163,7 @@ public CryptoPath normalize() { @Override public Path resolve(Path path) { + fileSystem.assertOpen(); CryptoPath other = cast(path); if (other.isAbsolute()) { return other; @@ -161,11 +177,13 @@ public Path resolve(Path path) { @Override public Path resolve(String other) { + fileSystem.assertOpen(); return resolve(fileSystem.getPath(other)); } @Override public Path resolveSibling(Path other) { + fileSystem.assertOpen(); final Path parent = getParent(); if (parent == null || other.isAbsolute()) { return other; @@ -176,11 +194,13 @@ public Path resolveSibling(Path other) { @Override public Path resolveSibling(String other) { + fileSystem.assertOpen(); return resolveSibling(fileSystem.getPath(other)); } @Override public Path relativize(Path path) { + fileSystem.assertOpen(); CryptoPath normalized = this.normalize(); CryptoPath other = cast(path).normalize(); if (normalized.isAbsolute() == other.isAbsolute()) { @@ -207,11 +227,13 @@ private int countCommonPrefixElements(CryptoPath p1, CryptoPath p2) { @Override public URI toUri() { + fileSystem.assertOpen(); return CryptoFileSystemUris.createUri(fileSystem.getPathToVault(), elements.toArray(new String[elements.size()])); } @Override public Path toAbsolutePath() { + fileSystem.assertOpen(); if (isAbsolute()) { return this; } else { @@ -221,26 +243,31 @@ public Path toAbsolutePath() { @Override public Path toRealPath(LinkOption... options) throws IOException { + fileSystem.assertOpen(); return normalize().toAbsolutePath(); } @Override public File toFile() { + fileSystem.assertOpen(); throw new UnsupportedOperationException(); } @Override public WatchKey register(WatchService watcher, WatchEvent.Kind[] events, WatchEvent.Modifier... modifiers) throws IOException { + fileSystem.assertOpen(); throw new UnsupportedOperationException("Method not implemented."); } @Override public WatchKey register(WatchService watcher, WatchEvent.Kind... events) throws IOException { + fileSystem.assertOpen(); throw new UnsupportedOperationException("Method not implemented."); } @Override public Iterator iterator() { + fileSystem.assertOpen(); return new Iterator() { private int idx = 0; diff --git a/src/main/java/org/cryptomator/cryptofs/DirectoryStreamFactory.java b/src/main/java/org/cryptomator/cryptofs/DirectoryStreamFactory.java new file mode 100644 index 00000000..8824f727 --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/DirectoryStreamFactory.java @@ -0,0 +1,61 @@ +package org.cryptomator.cryptofs; + +import static org.cryptomator.cryptofs.FinallyUtils.guaranteeInvocationOf; + +import java.io.IOException; +import java.nio.file.ClosedFileSystemException; +import java.nio.file.DirectoryStream; +import java.nio.file.DirectoryStream.Filter; +import java.nio.file.Path; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import javax.inject.Inject; + +import org.cryptomator.cryptofs.CryptoPathMapper.Directory; +import org.cryptomator.cryptolib.api.Cryptor; + +@PerFileSystem +class DirectoryStreamFactory { + + private final Cryptor cryptor; + private final LongFileNameProvider longFileNameProvider; + private final CryptoPathMapper cryptoPathMapper; + + private final ConcurrentMap streams = new ConcurrentHashMap<>(); + + private volatile boolean closed = false; + + @Inject + public DirectoryStreamFactory(Cryptor cryptor, LongFileNameProvider longFileNameProvider, CryptoPathMapper cryptoPathMapper) { + this.cryptor = cryptor; + this.longFileNameProvider = longFileNameProvider; + this.cryptoPathMapper = cryptoPathMapper; + } + + public DirectoryStream newDirectoryStream(CryptoPath cleartextDir, Filter filter) throws IOException { + Directory ciphertextDir = cryptoPathMapper.getCiphertextDir(cleartextDir); + CryptoDirectoryStream stream = new CryptoDirectoryStream( // + ciphertextDir, // + cleartextDir, // + cryptor.fileNameCryptor(), // + longFileNameProvider, // + filter, // + closed -> streams.remove(closed)); + streams.put(stream, stream); + if (closed) { + stream.close(); + throw new ClosedFileSystemException(); + } + return stream; + } + + public void close() throws IOException { + closed = true; + guaranteeInvocationOf( // + streams.keySet().stream() // + .map(stream -> (RunnableThrowingException) () -> stream.close()) // + .iterator()); + } + +} diff --git a/src/main/java/org/cryptomator/cryptofs/FinallyUtils.java b/src/main/java/org/cryptomator/cryptofs/FinallyUtils.java new file mode 100644 index 00000000..a183e24f --- /dev/null +++ b/src/main/java/org/cryptomator/cryptofs/FinallyUtils.java @@ -0,0 +1,34 @@ +package org.cryptomator.cryptofs; + +import static java.util.Arrays.asList; + +import java.util.Iterator; + +class FinallyUtils { + + @SafeVarargs + @SuppressWarnings({"rawtypes", "unchecked"}) + public static void guaranteeInvocationOf(RunnableThrowingException... tasks) throws E { + guaranteeInvocationOf((Iterator) asList(tasks).iterator()); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public static void guaranteeInvocationOf(Iterable> tasks) throws E { + guaranteeInvocationOf((Iterator) tasks.iterator()); + } + + @SuppressWarnings("unchecked") + public static void guaranteeInvocationOf(Iterator> tasks) throws E { + if (tasks.hasNext()) { + RunnableThrowingException next = tasks.next(); + try { + next.run(); + } catch (Exception e) { + throw (E) e; + } finally { + guaranteeInvocationOf(tasks); + } + } + } + +} diff --git a/src/main/java/org/cryptomator/cryptofs/OpenCryptoFile.java b/src/main/java/org/cryptomator/cryptofs/OpenCryptoFile.java index e281a26b..1fd7413c 100644 --- a/src/main/java/org/cryptomator/cryptofs/OpenCryptoFile.java +++ b/src/main/java/org/cryptomator/cryptofs/OpenCryptoFile.java @@ -123,7 +123,6 @@ private void write(ByteSource source, long position) throws IOException { chunkCache.set(chunkIndex, chunkData); } else { ChunkData chunkData = chunkCache.get(chunkIndex); - // TODO locking or similar to prevent removal of chunkData from cache while writing chunkData.copyDataStartingAt(offsetInChunk).from(source); } written += len; @@ -149,12 +148,10 @@ public synchronized void force(boolean metaData, EffectiveOpenOptions options) t } public FileLock lock(long position, long size, boolean shared) throws IOException { - // TODO compute correct position / size return channel.lock(position, size, shared); } public FileLock tryLock(long position, long size, boolean shared) throws IOException { - // TODO compute correct position / size return channel.tryLock(position, size, shared); } @@ -167,6 +164,10 @@ public void open(EffectiveOpenOptions openOptions) throws IOException { } } + public void close() throws IOException { + cryptoFileChannelFactory.close(); + } + public void close(EffectiveOpenOptions options) throws IOException { force(true, options); if (openCounter.countClose()) { diff --git a/src/main/java/org/cryptomator/cryptofs/OpenCryptoFiles.java b/src/main/java/org/cryptomator/cryptofs/OpenCryptoFiles.java index 3f5207a5..6915086e 100644 --- a/src/main/java/org/cryptomator/cryptofs/OpenCryptoFiles.java +++ b/src/main/java/org/cryptomator/cryptofs/OpenCryptoFiles.java @@ -8,6 +8,7 @@ *******************************************************************************/ package org.cryptomator.cryptofs; +import static org.cryptomator.cryptofs.FinallyUtils.guaranteeInvocationOf; import static org.cryptomator.cryptofs.OpenCryptoFileModule.openCryptoFileModule; import static org.cryptomator.cryptofs.UncheckedThrows.allowUncheckedThrowsOf; @@ -37,6 +38,13 @@ public OpenCryptoFile get(Path path, EffectiveOpenOptions options) throws IOExce }); } + public void close() throws IOException { + guaranteeInvocationOf( // + openCryptoFiles.values().stream() // + .map(openCryptoFile -> (RunnableThrowingException) () -> openCryptoFile.close()) // + .iterator()); + } + private OpenCryptoFile create(Path normalizedPath, EffectiveOpenOptions options) { return component .newOpenCryptoFileComponent(openCryptoFileModule() // diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoDirectoryStreamTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoDirectoryStreamTest.java index 20a98ad5..0ca6ada0 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoDirectoryStreamTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoDirectoryStreamTest.java @@ -10,6 +10,7 @@ import java.io.IOException; import java.nio.file.DirectoryStream; +import java.nio.file.DirectoryStream.Filter; import java.nio.file.FileSystem; import java.nio.file.Path; import java.nio.file.Paths; @@ -19,6 +20,7 @@ import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; +import java.util.function.Consumer; import org.apache.commons.lang3.StringUtils; import org.cryptomator.cryptofs.CryptoPathMapper.Directory; @@ -37,6 +39,10 @@ public class CryptoDirectoryStreamTest { + private static final Consumer DO_NOTHING_ON_CLOSE = ignored -> { + }; + private static final Filter ACCEPT_ALL = ignored -> true; + private static CryptorProvider cryptorProvider; @BeforeClass @@ -94,7 +100,7 @@ public void testDirListing() throws IOException { ciphertextFileNames.add("alsoInvalid"); Mockito.when(dirStream.iterator()).thenReturn(Iterators.transform(ciphertextFileNames.iterator(), cleartextPath::resolve)); - try (CryptoDirectoryStream stream = new CryptoDirectoryStream(new Directory("foo", ciphertextDirPath), cleartextPath, filenameCryptor, longFileNameProvider, p -> true)) { + try (CryptoDirectoryStream stream = new CryptoDirectoryStream(new Directory("foo", ciphertextDirPath), cleartextPath, filenameCryptor, longFileNameProvider, ACCEPT_ALL, DO_NOTHING_ON_CLOSE)) { Iterator iter = stream.iterator(); Assert.assertTrue(iter.hasNext()); Assert.assertEquals(cleartextPath.resolve("one"), iter.next()); @@ -116,7 +122,7 @@ public void testDirListingForEmptyDir() throws IOException { Mockito.when(dirStream.iterator()).thenReturn(Collections.emptyIterator()); - try (CryptoDirectoryStream stream = new CryptoDirectoryStream(new Directory("foo", ciphertextDirPath), cleartextPath, filenameCryptor, longFileNameProvider, p -> true)) { + try (CryptoDirectoryStream stream = new CryptoDirectoryStream(new Directory("foo", ciphertextDirPath), cleartextPath, filenameCryptor, longFileNameProvider, ACCEPT_ALL, DO_NOTHING_ON_CLOSE)) { Iterator iter = stream.iterator(); Assert.assertFalse(iter.hasNext()); iter.next(); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelTest.java index 1c8e7c9b..3e5d0027 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileChannelTest.java @@ -21,6 +21,7 @@ import java.nio.channels.FileLock; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; +import java.util.function.Consumer; import org.junit.Before; import org.junit.Rule; @@ -40,164 +41,167 @@ public class CryptoFileChannelTest { private static final long ANY_POSITIVE_LONG = 3727L; private static final long ANY_NEGATIVE_LONG = -32L; - + private static final int ANY_POSITIVE_INT = 58; - + private static final long EOF = -1; - + @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); - + @Rule public ExpectedException thrown = ExpectedException.none(); - + private OpenCryptoFile openCryptoFile = mock(OpenCryptoFile.class); - + private EffectiveOpenOptions options = mock(EffectiveOpenOptions.class); - + + @SuppressWarnings("unchecked") + private Consumer onClose = mock(Consumer.class); + private CryptoFileChannel inTest; - + @Before public void setUp() throws IOException { - inTest = new CryptoFileChannel(openCryptoFile, options); + inTest = new CryptoFileChannel(openCryptoFile, options, onClose); } - + @Test public void testConstructorPassesOptionsToOpenCryptoFilesOpenMethod() throws IOException { verify(openCryptoFile).open(options); } - + @Test public void testFlyTroughFromBlockingIo() throws IOException { IOException e = new IOException(); ByteBuffer buffer = ByteBuffer.allocate(100); when(openCryptoFile.read(buffer, 0)).thenThrow(e); when(options.readable()).thenReturn(true); - + thrown.expect(sameInstance(e)); - + inTest.read(buffer); } - + public class Position { - + @Test public void testInitialPositionIsZero() throws IOException { assertThat(inTest.position(), is(0L)); } - + @Test public void testPositionCanBeSet() throws IOException { inTest.position(ANY_POSITIVE_LONG); - + assertThat(inTest.position(), is(ANY_POSITIVE_LONG)); } - + @Test public void testPositionCanNotBeSetToANegativeValue() throws IOException { thrown.expect(IllegalArgumentException.class); - + inTest.position(ANY_NEGATIVE_LONG); } - + } - + @Test public void testSizeDelegatesToOpenCryptoFile() throws ClosedChannelException { long expectedSize = 3823; - + when(openCryptoFile.size()).thenReturn(expectedSize); - + assertThat(inTest.size(), is(expectedSize)); } - + public class Truncate { - + @Test public void testTruncateFailsWithIOExceptionIfNotWritable() throws IOException { when(options.writable()).thenReturn(false); - + thrown.expect(IOException.class); thrown.expectMessage("not writable"); - + inTest.truncate(ANY_POSITIVE_LONG); } - + @Test public void testTruncateDelegatesToOpenCryptoFile() throws IOException { long expectedSize = 342; when(options.writable()).thenReturn(true); - + inTest.truncate(expectedSize); - + verify(openCryptoFile).truncate(expectedSize); } - + @Test public void testTruncateKeepsPositionIfNewSizeGreaterCurrentPosition() throws IOException { long newSize = 442; long currentPosition = 342; inTest.position(currentPosition); when(options.writable()).thenReturn(true); - + inTest.truncate(newSize); - + assertThat(inTest.position(), is(currentPosition)); } - + @Test public void testTruncateSetsPositionToNewSizeIfSmallerCurrentPosition() throws IOException { long newSize = 242; long currentPosition = 342; inTest.position(currentPosition); when(options.writable()).thenReturn(true); - + inTest.truncate(newSize); - + assertThat(inTest.position(), is(newSize)); } - + } - + public class Force { - + @Test public void testForceDelegatesToOpenCryptoFileWithTrue() throws IOException { inTest.force(true); - + verify(openCryptoFile).force(true, options); } - + @Test public void testForceDelegatesToOpenCryptoFileWithFalse() throws IOException { inTest.force(false); - + verify(openCryptoFile).force(false, options); } - + } - + @Test public void testMapThrowsUnsupportedOperationException() throws IOException { thrown.expect(UnsupportedOperationException.class); - + inTest.map(MapMode.PRIVATE, ANY_POSITIVE_LONG, ANY_POSITIVE_LONG); } - + public class Lock { - + @Test public void testTryLockReturnsNullIfDelegateReturnsNull() throws IOException { boolean shared = true; long position = 372L; long size = 3828L; when(openCryptoFile.tryLock(position, size, shared)).thenReturn(null); - + FileLock result = inTest.tryLock(position, size, shared); - + assertThat(result, is(nullValue())); } - + @Test public void testTryLockReturnsCryptoFileLockWrappingDelegate() throws IOException { boolean shared = true; @@ -205,18 +209,18 @@ public void testTryLockReturnsCryptoFileLockWrappingDelegate() throws IOExceptio long size = 3828L; FileLock delegate = mock(FileLock.class); when(openCryptoFile.tryLock(position, size, shared)).thenReturn(delegate); - + FileLock result = inTest.tryLock(position, size, shared); - + assertThat(result, is(instanceOf(CryptoFileLock.class))); - CryptoFileLock cryptoFileLock = (CryptoFileLock)result; + CryptoFileLock cryptoFileLock = (CryptoFileLock) result; assertThat(cryptoFileLock.acquiredBy(), is(inTest)); assertThat(cryptoFileLock.delegate(), is(delegate)); assertThat(cryptoFileLock.isShared(), is(shared)); assertThat(cryptoFileLock.position(), is(position)); assertThat(cryptoFileLock.size(), is(size)); } - + @Test public void tesLockReturnsCryptoFileLockWrappingDelegate() throws IOException { boolean shared = true; @@ -224,32 +228,32 @@ public void tesLockReturnsCryptoFileLockWrappingDelegate() throws IOException { long size = 3828L; FileLock delegate = mock(FileLock.class); when(openCryptoFile.lock(position, size, shared)).thenReturn(delegate); - + FileLock result = inTest.lock(position, size, shared); - + assertThat(result, is(instanceOf(CryptoFileLock.class))); - CryptoFileLock cryptoFileLock = (CryptoFileLock)result; + CryptoFileLock cryptoFileLock = (CryptoFileLock) result; assertThat(cryptoFileLock.acquiredBy(), is(inTest)); assertThat(cryptoFileLock.delegate(), is(delegate)); assertThat(cryptoFileLock.isShared(), is(shared)); assertThat(cryptoFileLock.position(), is(position)); assertThat(cryptoFileLock.size(), is(size)); } - + } - + public class Read { @Test public void testReadFailsIfNotReadable() throws IOException { when(options.readable()).thenReturn(false); - + thrown.expect(IOException.class); thrown.expectMessage("not readable"); - + inTest.read(ByteBuffer.allocate(10)); } - + @Test public void testReadDelegatesToOpenCryptoFile() throws IOException { int amountRead = 82; @@ -260,10 +264,10 @@ public void testReadDelegatesToOpenCryptoFile() throws IOException { when(openCryptoFile.read(buffer, initialPosition)).thenReturn(amountRead); int result = inTest.read(buffer); - + assertThat(result, is(amountRead)); } - + @Test public void testReadIncrementsPositionByAmountRead() throws IOException { int amountRead = 82; @@ -274,10 +278,10 @@ public void testReadIncrementsPositionByAmountRead() throws IOException { when(openCryptoFile.read(buffer, initialPosition)).thenReturn(amountRead); inTest.read(buffer); - + assertThat(inTest.position(), is(initialPosition + amountRead)); } - + @Test public void testReadWithPositionDelegatesToOpenCryptoFile() throws IOException { int amountRead = 82; @@ -288,10 +292,10 @@ public void testReadWithPositionDelegatesToOpenCryptoFile() throws IOException { when(openCryptoFile.read(buffer, position)).thenReturn(amountRead); int result = inTest.read(buffer, position); - + assertThat(result, is(amountRead)); } - + @Test public void testReadDoesNotChangePositionOnEof() throws IOException { int amountRead = -1; @@ -302,113 +306,100 @@ public void testReadDoesNotChangePositionOnEof() throws IOException { when(openCryptoFile.read(buffer, initialPosition)).thenReturn(amountRead); inTest.read(buffer); - + assertThat(inTest.position(), is(initialPosition)); } - + @Test public void testReadWithBuffersFailsIfChannelNotReadable() throws IOException { ByteBuffer[] irrelevant = null; when(options.readable()).thenReturn(false); - + thrown.expect(IOException.class); thrown.expectMessage("not readable"); - + inTest.read(irrelevant, 0, 0); } - + @Test public void testReadWithBuffersDoesReadNothingWhenLengthIsZero() throws IOException { ByteBuffer[] buffers = new ByteBuffer[10]; when(options.readable()).thenReturn(true); - + long read = inTest.read(buffers, 3, 0); - + assertThat(read, is(0L)); verify(openCryptoFile, never()).read(any(ByteBuffer.class), anyLong()); } - + @Test public void testReadWithBuffersReturnsEofIfHitOnFirstRead() throws IOException { - ByteBuffer[] buffers = { - ByteBuffer.allocate(10), - ByteBuffer.allocate(10), - ByteBuffer.allocate(10) - }; + ByteBuffer[] buffers = {ByteBuffer.allocate(10), ByteBuffer.allocate(10), ByteBuffer.allocate(10)}; when(options.readable()).thenReturn(true); long position = 349L; inTest.position(position); - when(openCryptoFile.read(buffers[0], position)).thenReturn((int)EOF); - + when(openCryptoFile.read(buffers[0], position)).thenReturn((int) EOF); + long read = inTest.read(buffers, 0, 3); - + assertThat(read, is(EOF)); verify(openCryptoFile).read(buffers[0], position); verify(openCryptoFile, never()).read(same(buffers[1]), anyLong()); verify(openCryptoFile, never()).read(same(buffers[2]), anyLong()); } - + @Test public void testReadWithBuffersAbortsIfEofIsHitAfterFirstRead() throws IOException { - ByteBuffer[] buffers = { - ByteBuffer.allocate(10), - ByteBuffer.allocate(10), - ByteBuffer.allocate(10) - }; + ByteBuffer[] buffers = {ByteBuffer.allocate(10), ByteBuffer.allocate(10), ByteBuffer.allocate(10)}; when(options.readable()).thenReturn(true); long position = 349L; long amountRead = 10; inTest.position(position); - when(openCryptoFile.read(buffers[0], position)).thenReturn((int)amountRead); - when(openCryptoFile.read(buffers[1], position + amountRead)).thenReturn((int)EOF); - + when(openCryptoFile.read(buffers[0], position)).thenReturn((int) amountRead); + when(openCryptoFile.read(buffers[1], position + amountRead)).thenReturn((int) EOF); + long read = inTest.read(buffers, 0, 3); - + assertThat(read, is(amountRead)); verify(openCryptoFile).read(buffers[0], position); verify(openCryptoFile).read(buffers[1], position + amountRead); verify(openCryptoFile, never()).read(same(buffers[2]), anyLong()); } - + @Test public void testReadWithBuffersReadsFromLengthBuffersIfEofIsNotHit() throws IOException { - ByteBuffer[] buffers = { - ByteBuffer.allocate(10), - ByteBuffer.allocate(10), - ByteBuffer.allocate(10), - ByteBuffer.allocate(10) - }; + ByteBuffer[] buffers = {ByteBuffer.allocate(10), ByteBuffer.allocate(10), ByteBuffer.allocate(10), ByteBuffer.allocate(10)}; when(options.readable()).thenReturn(true); long position = 349L; long firstAmountRead = 23; long secondAmountRead = 19; inTest.position(position); - when(openCryptoFile.read(buffers[1], position)).thenReturn((int)firstAmountRead); - when(openCryptoFile.read(buffers[2], position + firstAmountRead)).thenReturn((int)secondAmountRead); - + when(openCryptoFile.read(buffers[1], position)).thenReturn((int) firstAmountRead); + when(openCryptoFile.read(buffers[2], position + firstAmountRead)).thenReturn((int) secondAmountRead); + long read = inTest.read(buffers, 1, 2); - + assertThat(read, is(firstAmountRead + secondAmountRead)); verify(openCryptoFile, never()).read(same(buffers[0]), anyLong()); verify(openCryptoFile).read(buffers[1], position); verify(openCryptoFile).read(buffers[2], position + firstAmountRead); verify(openCryptoFile, never()).read(same(buffers[3]), anyLong()); } - + } - + public class Write { - + @Test public void testWriteFailsIfNotWritable() throws IOException { when(options.writable()).thenReturn(false); - + thrown.expect(IOException.class); thrown.expectMessage("not writable"); - + inTest.write(ByteBuffer.allocate(10)); } - + @Test public void testWriteDelegatesToOpenCryptoFile() throws IOException { int amountWritten = 82; @@ -419,23 +410,23 @@ public void testWriteDelegatesToOpenCryptoFile() throws IOException { when(openCryptoFile.write(options, buffer, initialPosition)).thenReturn(amountWritten); int result = inTest.write(buffer); - + assertThat(result, is(amountWritten)); } - + @Test public void testWriteAppendsIfInAppendMode() throws IOException { int amountWritten = 82; ByteBuffer buffer = ByteBuffer.allocate(ANY_POSITIVE_INT); when(options.writable()).thenReturn(true); when(options.append()).thenReturn(true); - when(openCryptoFile.append(options, buffer)).thenReturn((long)amountWritten); + when(openCryptoFile.append(options, buffer)).thenReturn((long) amountWritten); int result = inTest.write(buffer); - + assertThat(result, is(amountWritten)); } - + @Test public void testWriteWithPositionDelegatesToOpenCryptoFile() throws IOException { int amountWritten = 82; @@ -446,10 +437,10 @@ public void testWriteWithPositionDelegatesToOpenCryptoFile() throws IOException when(openCryptoFile.write(options, buffer, position)).thenReturn(amountWritten); int result = inTest.write(buffer, position); - + assertThat(result, is(amountWritten)); } - + @Test public void testWriteIncrementsPositionByAmountWritten() throws IOException { int amountWritten = 82; @@ -460,89 +451,79 @@ public void testWriteIncrementsPositionByAmountWritten() throws IOException { when(openCryptoFile.write(options, buffer, initialPosition)).thenReturn(amountWritten); inTest.write(buffer); - + assertThat(inTest.position(), is(initialPosition + amountWritten)); } - + @Test public void testWriteWithBuffersFailsIfNotWritable() throws IOException { ByteBuffer[] irrelevant = null; when(options.writable()).thenReturn(false); - + thrown.expect(IOException.class); thrown.expectMessage("not writable"); - + inTest.write(irrelevant, 0, 0); } - + @Test public void testWriteWithBuffersStartsWritingFromOffsetAndWritesLengthBuffers() throws IOException { - ByteBuffer[] buffers = { - ByteBuffer.allocate(10), - ByteBuffer.allocate(21), - ByteBuffer.allocate(19), - ByteBuffer.allocate(14) - }; + ByteBuffer[] buffers = {ByteBuffer.allocate(10), ByteBuffer.allocate(21), ByteBuffer.allocate(19), ByteBuffer.allocate(14)}; long position = 348L; long firstWritten = 21L; long secondWritten = 19L; inTest.position(position); - when(openCryptoFile.write(options, buffers[1], position)).thenReturn((int)firstWritten); - when(openCryptoFile.write(options, buffers[2], position + firstWritten)).thenReturn((int)secondWritten); + when(openCryptoFile.write(options, buffers[1], position)).thenReturn((int) firstWritten); + when(openCryptoFile.write(options, buffers[2], position + firstWritten)).thenReturn((int) secondWritten); when(options.writable()).thenReturn(true); - + inTest.write(buffers, 1, 2); - + verify(openCryptoFile, never()).write(same(options), same(buffers[0]), anyLong()); verify(openCryptoFile).write(options, buffers[1], position); verify(openCryptoFile).write(options, buffers[2], position + firstWritten); verify(openCryptoFile, never()).write(same(options), same(buffers[3]), anyLong()); } - + @Test public void testWriteWithBuffersAppendsIfInAppendMode() throws IOException { - ByteBuffer[] buffers = { - ByteBuffer.allocate(10), - ByteBuffer.allocate(21), - ByteBuffer.allocate(19), - ByteBuffer.allocate(14) - }; + ByteBuffer[] buffers = {ByteBuffer.allocate(10), ByteBuffer.allocate(21), ByteBuffer.allocate(19), ByteBuffer.allocate(14)}; long firstWritten = 21L; long secondWritten = 19L; when(openCryptoFile.append(options, buffers[1])).thenReturn(firstWritten); when(openCryptoFile.append(options, buffers[2])).thenReturn(secondWritten); when(options.writable()).thenReturn(true); when(options.append()).thenReturn(true); - + inTest.write(buffers, 1, 2); - + InOrder inOrder = inOrder(openCryptoFile); inOrder.verify(openCryptoFile).append(options, buffers[1]); inOrder.verify(openCryptoFile).append(options, buffers[2]); verify(openCryptoFile, never()).append(options, buffers[0]); verify(openCryptoFile, never()).append(options, buffers[3]); } - + } - + public class TransferTo { - + @Test public void testTransferToFailsIfChannelNotReadable() throws IOException { - WritableByteChannel irrelevant = null; + WritableByteChannel irrelevant = null; when(options.readable()).thenReturn(false); - + thrown.expect(IOException.class); thrown.expectMessage("not readable"); - + inTest.transferTo(0L, 0L, irrelevant); } - + @Test public void testTransferToTransfersCountDataStartingFromPosition() throws IOException { long startTransferFrom = 6042; long transferAmount = 23000; - ReadAndWritableBytes sourceData = ReadAndWritableBytes.random((int)(startTransferFrom + transferAmount)); + ReadAndWritableBytes sourceData = ReadAndWritableBytes.random((int) (startTransferFrom + transferAmount)); when(options.readable()).thenReturn(true); when(openCryptoFile.read(any(ByteBuffer.class), anyLong())).thenAnswer(new Answer() { @Override @@ -553,19 +534,19 @@ public Integer answer(InvocationOnMock invocation) throws Throwable { } }); ReadAndWritableBytes targetData = ReadAndWritableBytes.empty(); - + long amount = inTest.transferTo(startTransferFrom, transferAmount, targetData); - + assertThat(amount, is(transferAmount)); - assertThat(targetData.toArray(), is(sourceData.toArray((int)startTransferFrom, (int)transferAmount))); + assertThat(targetData.toArray(), is(sourceData.toArray((int) startTransferFrom, (int) transferAmount))); } - + @Test public void testTransferToTransfersAllRemainingBytesIfCountIsGreater() throws IOException { long startTransferFrom = 6042; long transferAmount = 23000; long remainingAmount = 10000; - ReadAndWritableBytes sourceData = ReadAndWritableBytes.random((int)(startTransferFrom + remainingAmount)); + ReadAndWritableBytes sourceData = ReadAndWritableBytes.random((int) (startTransferFrom + remainingAmount)); when(options.readable()).thenReturn(true); when(openCryptoFile.read(any(ByteBuffer.class), anyLong())).thenAnswer(new Answer() { @Override @@ -576,28 +557,28 @@ public Integer answer(InvocationOnMock invocation) throws Throwable { } }); ReadAndWritableBytes targetData = ReadAndWritableBytes.empty(); - + long amount = inTest.transferTo(startTransferFrom, transferAmount, targetData); - + assertThat(amount, is(remainingAmount)); - assertThat(targetData.toArray(), is(sourceData.toArray((int)startTransferFrom, (int)remainingAmount))); + assertThat(targetData.toArray(), is(sourceData.toArray((int) startTransferFrom, (int) remainingAmount))); } - + } - + public class TransferFrom { - + @Test public void testTransferFromFailsIfChannelNotWritable() throws IOException { - ReadableByteChannel irrelevant = null; + ReadableByteChannel irrelevant = null; when(options.writable()).thenReturn(false); - + thrown.expect(IOException.class); thrown.expectMessage("not writable"); - + inTest.transferFrom(irrelevant, 0L, 0L); } - + @Test public void testTransferFromTransfersNothingIfPositionGreaterSize() throws IOException { long position = 5000; @@ -605,17 +586,17 @@ public void testTransferFromTransfersNothingIfPositionGreaterSize() throws IOExc ReadAndWritableBytes sourceData = ReadAndWritableBytes.random(1000); when(options.writable()).thenReturn(true); when(openCryptoFile.size()).thenReturn(size); - + long amount = inTest.transferFrom(sourceData, position, 150); - + assertThat(amount, is(0L)); } - + @Test public void testTransferFromTransfersCountBytesStartingAtPosition() throws IOException { long startTransferAt = 6042; long transferAmount = 23000; - ReadAndWritableBytes sourceData = ReadAndWritableBytes.random((int)(transferAmount)); + ReadAndWritableBytes sourceData = ReadAndWritableBytes.random((int) (transferAmount)); ReadAndWritableBytes targetData = ReadAndWritableBytes.empty(); when(options.writable()).thenReturn(true); when(openCryptoFile.size()).thenReturn(startTransferAt); @@ -627,19 +608,19 @@ public Integer answer(InvocationOnMock invocation) throws Throwable { return targetData.write(source, position.intValue()); } }); - + long amount = inTest.transferFrom(sourceData, startTransferAt, transferAmount); - + assertThat(amount, is(transferAmount)); - assertThat(sourceData.toArray(), is(targetData.toArray((int)startTransferAt, (int)transferAmount))); + assertThat(sourceData.toArray(), is(targetData.toArray((int) startTransferAt, (int) transferAmount))); } - + @Test public void testTransferFromTransfersRemainingBytesIfLessThanCount() throws IOException { long startTransferAt = 6042; long transferAmount = 23000; long remainingAmount = 15000; - ReadAndWritableBytes sourceData = ReadAndWritableBytes.random((int)(remainingAmount)); + ReadAndWritableBytes sourceData = ReadAndWritableBytes.random((int) (remainingAmount)); ReadAndWritableBytes targetData = ReadAndWritableBytes.empty(); when(options.writable()).thenReturn(true); when(openCryptoFile.size()).thenReturn(startTransferAt); @@ -651,104 +632,104 @@ public Integer answer(InvocationOnMock invocation) throws Throwable { return targetData.write(source, position.intValue()); } }); - + long amount = inTest.transferFrom(sourceData, startTransferAt, transferAmount); - + assertThat(amount, is(remainingAmount)); - assertThat(sourceData.toArray(), is(targetData.toArray((int)startTransferAt, (int)remainingAmount))); + assertThat(sourceData.toArray(), is(targetData.toArray((int) startTransferAt, (int) remainingAmount))); } - + } - + public class OperationsOnClosedChannelThrowClosedChannelException { - + @Before public void setUp() throws IOException { inTest.close(); thrown.expect(ClosedChannelException.class); } - + @Test public void testGetPosition() throws IOException { inTest.position(); } - + @Test public void testSetPosition() throws IOException { inTest.position(ANY_POSITIVE_LONG); } - + @Test public void testRead() throws IOException { when(options.readable()).thenReturn(true); - + inTest.read(ByteBuffer.allocate(10)); } - + @Test public void testWrite() throws IOException { when(options.writable()).thenReturn(true); - + inTest.write(ByteBuffer.allocate(10)); } - + @Test public void testReadWithPosition() throws IOException { when(options.readable()).thenReturn(true); - + inTest.read(ByteBuffer.allocate(10), ANY_POSITIVE_LONG); } - + @Test public void testWriteWithPosition() throws IOException { when(options.writable()).thenReturn(true); - + inTest.write(ByteBuffer.allocate(10), ANY_POSITIVE_LONG); } - + @Test public void testTransferFrom() throws IOException { when(options.readable()).thenReturn(true); ReadableByteChannel source = mock(ReadableByteChannel.class); when(source.read(any())).thenReturn(ANY_POSITIVE_INT); - + inTest.transferFrom(source, ANY_POSITIVE_LONG, ANY_POSITIVE_INT); } - + @Test public void testTransferTo() throws IOException { when(options.readable()).thenReturn(true); WritableByteChannel target = mock(WritableByteChannel.class); when(target.write(any())).thenReturn(ANY_POSITIVE_INT); - + inTest.transferTo(ANY_POSITIVE_LONG, ANY_POSITIVE_INT, target); } - + @Test public void testSize() throws IOException { inTest.size(); } - + @Test public void testTruncate() throws IOException { inTest.truncate(ANY_POSITIVE_LONG); } - + @Test public void testForce() throws IOException { inTest.force(true); } - + @Test public void testLock() throws IOException { inTest.lock(ANY_POSITIVE_LONG, ANY_POSITIVE_LONG, true); } - + @Test public void testTryLock() throws IOException { inTest.tryLock(ANY_POSITIVE_LONG, ANY_POSITIVE_LONG, true); } - + } - + } diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemTest.java index 9780c1d7..70e60cd7 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemTest.java @@ -76,7 +76,6 @@ public class CryptoFileSystemTest { private final OpenCryptoFiles openCryptoFiles = mock(OpenCryptoFiles.class); private final CryptoPathMapper cryptoPathMapper = mock(CryptoPathMapper.class); private final DirectoryIdProvider dirIdProvider = mock(DirectoryIdProvider.class); - private final LongFileNameProvider longFileNameProvider = mock(LongFileNameProvider.class); private final CryptoFileAttributeProvider fileAttributeProvider = mock(CryptoFileAttributeProvider.class); private final CryptoFileAttributeByNameProvider fileAttributeByNameProvider = mock(CryptoFileAttributeByNameProvider.class); private final CryptoFileAttributeViewProvider fileAttributeViewProvider = mock(CryptoFileAttributeViewProvider.class); @@ -84,6 +83,7 @@ public class CryptoFileSystemTest { private final CryptoPathFactory cryptoPathFactory = mock(CryptoPathFactory.class); private final CryptoFileSystemStats stats = mock(CryptoFileSystemStats.class); private final RootDirectoryInitializer rootDirectoryInitializer = mock(RootDirectoryInitializer.class); + private final DirectoryStreamFactory directoryStreamFactory = mock(DirectoryStreamFactory.class); private final CryptoPath root = mock(CryptoPath.class); private final CryptoPath empty = mock(CryptoPath.class); @@ -95,8 +95,8 @@ public void setup() { when(cryptoPathFactory.rootFor(any())).thenReturn(root); when(cryptoPathFactory.emptyFor(any())).thenReturn(empty); - inTest = new CryptoFileSystem(pathToVault, properties, cryptor, provider, cryptoFileSystems, fileStore, openCryptoFiles, cryptoPathMapper, dirIdProvider, longFileNameProvider, fileAttributeProvider, - fileAttributeViewProvider, pathMatcherFactory, cryptoPathFactory, stats, rootDirectoryInitializer, fileAttributeByNameProvider); + inTest = new CryptoFileSystem(pathToVault, properties, cryptor, provider, cryptoFileSystems, fileStore, openCryptoFiles, cryptoPathMapper, dirIdProvider, fileAttributeProvider, fileAttributeViewProvider, + pathMatcherFactory, cryptoPathFactory, stats, rootDirectoryInitializer, fileAttributeByNameProvider, directoryStreamFactory); } @Test @@ -125,29 +125,41 @@ public void testGetFileStoresReturnsFileStore() { } @Test - public void testCloseRemovesThisFromCryptoFileSystems() { + public void testCloseRemovesThisFromCryptoFileSystems() throws IOException { inTest.close(); verify(cryptoFileSystems).remove(inTest); } @Test - public void testCloseDestroysCryptor() { + public void testCloseDestroysCryptor() throws IOException { inTest.close(); verify(cryptor).destroy(); } @Test - public void testIsOpenReturnsTrueWhenContainedInCryptoFileSystems() { - when(cryptoFileSystems.contains(inTest)).thenReturn(true); + public void testCloseClosesDirectoryStreams() throws IOException { + inTest.close(); + + verify(directoryStreamFactory).close(); + } + + @Test + public void testCloseClosesOpenCryptoFiles() throws IOException { + inTest.close(); + + verify(openCryptoFiles).close(); + } + @Test + public void testIsOpenReturnsTrueWhenNotClosed() { assertTrue(inTest.isOpen()); } @Test - public void testIsOpenReturnsFalseWhenNotContainedInCryptoFileSystems() { - when(cryptoFileSystems.contains(inTest)).thenReturn(false); + public void testIsOpenReturnsFalseWhenClosed() throws IOException { + inTest.close(); assertFalse(inTest.isOpen()); } diff --git a/src/test/java/org/cryptomator/cryptofs/TestHelper.java b/src/test/java/org/cryptomator/cryptofs/TestHelper.java index 10aad8f3..3d8a1a74 100644 --- a/src/test/java/org/cryptomator/cryptofs/TestHelper.java +++ b/src/test/java/org/cryptomator/cryptofs/TestHelper.java @@ -18,8 +18,10 @@ public static void prepareMockForPathCreation(CryptoFileSystem fileSystemMock, P } }); Mockito.when(fileSystemMock.getPathToVault()).thenReturn(pathToVault); - Mockito.when(fileSystemMock.getRootPath()).thenReturn(cryptoPathFactory.rootFor(fileSystemMock)); - Mockito.when(fileSystemMock.getEmptyPath()).thenReturn(cryptoPathFactory.emptyFor(fileSystemMock)); + CryptoPath root = cryptoPathFactory.rootFor(fileSystemMock); + CryptoPath empty = cryptoPathFactory.emptyFor(fileSystemMock); + Mockito.when(fileSystemMock.getRootPath()).thenReturn(root); + Mockito.when(fileSystemMock.getEmptyPath()).thenReturn(empty); } } From e67c3ba96991a237ad52a39391e5484fb6f32966 Mon Sep 17 00:00:00 2001 From: Markus Kreusch Date: Fri, 30 Sep 2016 09:04:50 +0200 Subject: [PATCH 06/10] Travis config: use more recent JDK version --- .travis.yml | 2 +- src/main/java/org/cryptomator/cryptofs/CryptoFileSystem.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index beb1593a..34da4152 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: java -sudo: false +sudo: true jdk: - oraclejdk8 env: diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystem.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystem.java index d33cb05b..53677e03 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystem.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystem.java @@ -149,8 +149,7 @@ public Iterable getFileStores() { @Override public void close() throws IOException { - // TODO implement correct closing behavior: - // * close all directory streams and watch services + // TODO close watch services when implemented if (open) { open = false; guaranteeInvocationOf( // From 76fc14be37603ab6b3a267eb6da1d3d854a26e88 Mon Sep 17 00:00:00 2001 From: Markus Kreusch Date: Fri, 30 Sep 2016 09:07:57 +0200 Subject: [PATCH 07/10] Travis config: use more recent JDK version --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 34da4152..6fe76b94 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: java -sudo: true +sudo: required jdk: - oraclejdk8 env: From 7724ac458fa4cd4dee509794bbc363a55f8be761 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 25 Oct 2016 16:13:29 +0200 Subject: [PATCH 08/10] install haveged to provide enough entropy during integration tests --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 6fe76b94..acd3a536 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ env: - secure: aLCaSgLfFniEPpiEdvEyFLlak4cTspVE3uqeGFCncPFUyyxPZ0JW+DdyFpvO5DXBa3hr9oS240H6f1T6Eo/J4K5SaKq3gS3yIzbvs2BS5yoCE4hGIZ6yS/70sIhlxvFvujJDT9KPYo8J6KE0w5vqy8BHEMZYBMEDWWvswwrvlqdaoZqe6vaoV1TsOeWeW+2qFdYu5j1KVQgSwOgzMdQuqQVSsrdA1FH+AzmMMbk3QgdGGd1U4MnFStIeG5/YfqVHkUVpZayhYB9yFzUCObT9R5zZ1OtghdOrkX1FRTKbFPAmJouToeFrIqrZEMbaE9PWaajuUZeqL2hQD1s0A+wj65cXjgbQhz4v7Gr0J/+2AU+9N+IRAIguiq6rXtt1HeR3YbBwlxFdwmEgYJjqO7+XFZFNYbr3YPgqbqJbgXpoxeUTdPNdNwckBUEQTTN6fs89Og6wHLDgoZb5niypSKWd/SFCU81FAGQ0mxy41jJOdltxVtG1+UOfb/41PRAzS6br7CpNnaq379kKlMiyOXY4UCWuFgwW4KhlUx0dgidqi2etcYrDZhZbguHmfLRfumEQ8nCp8LJ5FwJWlkmJhE8hFTu3sGmVUl06Ly1gybMPazev8hYjyr3vaexmek7qeqDtNT6n8L96qZkResjFI2pJmzZaNIg2yK92ZS/5v073OgM= # MAVEN_OSSRH_PASSWORD - secure: JUBj12rP7sEsNp8R3o9Mjwl7HCYKQan58CZ1WDm7L2MteFRDBJr625P4fk4cQOTjR6fb3jiogCD7dqkHoGJQlvi2ReQJLXKBhy3Bqtnzksf4c1By9bLB5NYH6MQV1FFazzAjjPjGlQYJB+kUx+pQ8vxMJC5yzp6vpKWu3vTobEFNtuFAWZq7qM91VJBUc8z+k1awas9vqgDlqWxH+K05L2c+tpnnmqXk9/HN9Z4akkKB++oACenE+w+D9Snyiif9WyhVR3+ZcMtL44YAQDabl6LlrTdM4afNaPcwgOdFuxL7GCZyIMgMP26O8JquK4ZtE2b/ZMz5h0jtE+r6M0E+yVAN+kG7UviNd9kKItiRiokDsaYeNCWVPHndGBtw6GU5GFNJTDhEb/oeqzclI/PaFHXA4O/o4A9N+nrzb28xWaihAM/NymVOE1iNNQfyNqPb9fjsMntNOTDT/sLH3tTJ62nvVPpurUXVV0+jbSrE20xclOZ1xBbMztQwya4EB/0fS+raCiR5spXHTNHpmSp2ubKAuo6V4AUXQXk6LHxiiIvcYSRYVgnBeOcreuDYv7+09xeIejxVcSzh7Nz7aK/0Fkq42ALYs/cDOq0mJGwoJ4yC7vlMj6GQNwZtCP+bzdGM3+cjTcD2F997Hf75lEVeYXK2oNE3/ycSQjdYlc3wTl0= #COVERITY_SCAN_TOKEN install: +- sudo apt-get install haveged - mvn dependency:go-offline -Pdependency-check - mvn dependency:go-offline -Pcoverage - mvn source:help javadoc:help dependency:go-offline -Prelease From 79c65e3a77f7a2ddc81b0b4a511b28cba5ee5bb4 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Wed, 2 Nov 2016 13:04:49 +0100 Subject: [PATCH 09/10] Incremented dependency versions --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 2f46947e..6079557c 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,7 @@ org.cryptomator cryptolib - 1.0.2 + 1.0.4 org.apache.commons @@ -71,7 +71,7 @@ com.google.guava guava - 19.0 + 20.0 commons-codec From ef4f8560329e6f7322e25ed865f9f14d817672e1 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Wed, 2 Nov 2016 13:09:45 +0100 Subject: [PATCH 10/10] incremented pom version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6079557c..bf9d077f 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.cryptomator cryptofs - 0.2.0-SNAPSHOT + 0.1.4 Cryptomator Crypto Filesystem This library provides the Java filesystem provider used by Cryptomator. https://github.com/cryptomator/cryptofs