mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-30 08:26:19 +00:00
Added creation time
* Getter and setter for files and folders * A way to determine if a file system supports creation dates * WebDav compliant implementation in jackrabbit-adapter * Tests
This commit is contained in:
@@ -31,6 +31,11 @@
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>commons</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -12,11 +12,24 @@
|
||||
<artifactId>commons</artifactId>
|
||||
<name>Cryptomator common</name>
|
||||
<description>Shared utilities</description>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>commons-test</artifactId>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.bechte.junit</groupId>
|
||||
<artifactId>junit-hierarchicalcontextrunner</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-all</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.cryptomator.common;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class CachingSupplier<T> implements Supplier<T> {
|
||||
|
||||
public static <T> Supplier<T> from(Supplier<T> delegate) {
|
||||
return new CachingSupplier<>(delegate);
|
||||
}
|
||||
|
||||
private Supplier<T> delegate;
|
||||
|
||||
private CachingSupplier(Supplier<T> delegate) {
|
||||
this.delegate = () -> {
|
||||
T result = delegate.get();
|
||||
CachingSupplier.this.delegate = () -> result;
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() {
|
||||
return delegate.get();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.cryptomator.common;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class CachingSupplierTest {
|
||||
|
||||
@Test
|
||||
public void testInvokingGetInvokesDelegate() {
|
||||
@SuppressWarnings("unchecked")
|
||||
Supplier<Object> delegate = mock(Supplier.class);
|
||||
Object expectedResult = new Object();
|
||||
when(delegate.get()).thenReturn(expectedResult);
|
||||
Supplier<Object> inTest = CachingSupplier.from(delegate);
|
||||
|
||||
Object result = inTest.get();
|
||||
|
||||
assertThat(result, is(expectedResult));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvokingGetTwiceDoesNotInvokeDelegateTwice() {
|
||||
@SuppressWarnings("unchecked")
|
||||
Supplier<Object> delegate = mock(Supplier.class);
|
||||
Object expectedResult = new Object();
|
||||
when(delegate.get()).thenReturn(expectedResult);
|
||||
Supplier<Object> inTest = CachingSupplier.from(delegate);
|
||||
|
||||
inTest.get();
|
||||
Object result = inTest.get();
|
||||
|
||||
assertThat(result, is(expectedResult));
|
||||
verify(delegate).get();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,4 +23,8 @@ public interface FileSystem extends Folder {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
default boolean supports(FileSystemFeature feature) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.cryptomator.filesystem;
|
||||
|
||||
public enum FileSystemFeature {
|
||||
CREATION_TIME_FEATURE
|
||||
}
|
||||
@@ -7,6 +7,7 @@ package org.cryptomator.filesystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.time.Instant;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
@@ -148,4 +149,18 @@ public interface Folder extends Node {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets the creation time of the folder.
|
||||
* <p>
|
||||
* Setting the creation time may not be supported by all {@link FileSystem FileSystems}. If the {@code FileSystem} this {@code Folder} belongs to does not support the
|
||||
* {@link FileSystemFeature#CREATION_TIME_FEATURE} the behavior of this method is unspecified.
|
||||
*
|
||||
* @param instant the time to set as creation time
|
||||
* @see FileSystem#supports(Class)
|
||||
*/
|
||||
default void setCreationTime(Instant instant) throws UncheckedIOException {
|
||||
throw new UncheckedIOException(new IOException("CreationTime not supported"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
******************************************************************************/
|
||||
package org.cryptomator.filesystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
@@ -33,6 +34,20 @@ public interface Node {
|
||||
|
||||
Instant lastModified() throws UncheckedIOException;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Determines the creation time of this node.
|
||||
* <p>
|
||||
* Setting the creation time may not be supported by all {@link FileSystem FileSystems}. If the {@code FileSystem} this {@code Node} belongs to does not support the
|
||||
* {@link FileSystemFeature#CREATION_TIME_FEATURE} the behavior of this method is unspecified.
|
||||
*
|
||||
* @returns the creation time of the file.
|
||||
* @see FileSystem#supports(Class)
|
||||
*/
|
||||
default Instant creationTime() throws UncheckedIOException {
|
||||
throw new UncheckedIOException(new IOException("CreationTime not supported"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link FileSystem} this Node belongs to
|
||||
*/
|
||||
|
||||
@@ -24,6 +24,20 @@ public interface WritableFile extends WritableByteChannel {
|
||||
|
||||
void setLastModified(Instant instant) throws UncheckedIOException;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets the creation time of the file.
|
||||
* <p>
|
||||
* Setting the creation time may not be supported by all {@link FileSystem FileSystems}. If the {@code FileSystem} this {@code WritableFile} belongs to does not support the
|
||||
* {@link FileSystemFeature#CREATION_TIME_FEATURE} the behavior of this method is unspecified.
|
||||
*
|
||||
* @param instant the time to set as creation time
|
||||
* @see FileSystem#supports(Class)
|
||||
*/
|
||||
default void setCreationTime(Instant instant) throws UncheckedIOException {
|
||||
throw new UncheckedIOException(new IOException("CreationTime not supported"));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Deletes this file from the file system.
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
package org.cryptomator.filesystem.delegating;
|
||||
|
||||
import java.io.UncheckedIOException;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -90,4 +91,9 @@ public abstract class DelegatingFolder<R extends DelegatingReadableFile, W exten
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCreationTime(Instant instant) throws UncheckedIOException {
|
||||
delegate.setCreationTime(instant);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -39,6 +39,11 @@ abstract class DelegatingNode<T extends Node> implements Node {
|
||||
return delegate.lastModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant creationTime() throws UncheckedIOException {
|
||||
return delegate.creationTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return delegate.hashCode();
|
||||
|
||||
@@ -67,4 +67,9 @@ public class DelegatingWritableFile implements WritableFile {
|
||||
delegate.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCreationTime(Instant instant) throws UncheckedIOException {
|
||||
delegate.setCreationTime(instant);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
package org.cryptomator.filesystem.blockaligned;
|
||||
|
||||
import org.cryptomator.filesystem.FileSystem;
|
||||
import org.cryptomator.filesystem.FileSystemFeature;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
|
||||
class BlockAlignedFileSystem extends BlockAlignedFolder implements FileSystem {
|
||||
@@ -17,4 +18,9 @@ class BlockAlignedFileSystem extends BlockAlignedFolder implements FileSystem {
|
||||
super(null, delegate, blockSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(FileSystemFeature feature) {
|
||||
return delegate.fileSystem().supports(feature);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -54,4 +54,9 @@ public class CryptoFile extends CryptoNode implements File {
|
||||
return toString().compareTo(o.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant creationTime() throws UncheckedIOException {
|
||||
return physicalFile().creationTime();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.crypto.engine.InvalidPassphraseException;
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.FileSystem;
|
||||
import org.cryptomator.filesystem.FileSystemFeature;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
import org.cryptomator.filesystem.ReadableFile;
|
||||
import org.cryptomator.filesystem.WritableFile;
|
||||
@@ -104,6 +105,11 @@ public class CryptoFileSystem extends CryptoFolder implements FileSystem {
|
||||
physicalFolder().create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(FileSystemFeature feature) {
|
||||
return physicalRoot.fileSystem().supports(feature);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return physicalRoot + ":::/";
|
||||
|
||||
@@ -146,6 +146,11 @@ class CryptoFolder extends CryptoNode implements Folder {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant creationTime() throws UncheckedIOException {
|
||||
return physicalFile().creationTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return parent.toString() + name + "/";
|
||||
|
||||
@@ -93,6 +93,11 @@ class CryptoWritableFile implements WritableFile {
|
||||
throw new UnsupportedOperationException("Truncate not supported yet");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCreationTime(Instant instant) throws UncheckedIOException {
|
||||
file.setCreationTime(instant);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return file.isOpen();
|
||||
|
||||
@@ -26,8 +26,8 @@ class InMemoryFile extends InMemoryNode implements File {
|
||||
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
private ByteBuffer content = ByteBuffer.allocate(0);
|
||||
|
||||
public InMemoryFile(InMemoryFolder parent, String name, Instant lastModified) {
|
||||
super(parent, name, lastModified);
|
||||
public InMemoryFile(InMemoryFolder parent, String name, Instant lastModified, Instant creationTime) {
|
||||
super(parent, name, lastModified, creationTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -48,19 +48,24 @@ class InMemoryFile extends InMemoryNode implements File {
|
||||
parent.children.compute(this.name(), (k, v) -> {
|
||||
if (v == null || v == this) {
|
||||
this.lastModified = Instant.now();
|
||||
this.creationTime = Instant.now();
|
||||
return this;
|
||||
} else {
|
||||
throw new UncheckedIOException(new FileExistsException(k));
|
||||
}
|
||||
});
|
||||
parent.volatileFiles.remove(name);
|
||||
return new InMemoryWritableFile(this::setLastModified, this::getContent, this::setContent, this::delete, writeLock);
|
||||
return new InMemoryWritableFile(this::setLastModified, this::setCreationTime, this::getContent, this::setContent, this::delete, writeLock);
|
||||
}
|
||||
|
||||
private void setLastModified(Instant lastModified) {
|
||||
this.lastModified = lastModified;
|
||||
}
|
||||
|
||||
private void setCreationTime(Instant creationTime) {
|
||||
this.creationTime = creationTime;
|
||||
}
|
||||
|
||||
private ByteBuffer getContent() {
|
||||
return content;
|
||||
}
|
||||
@@ -75,7 +80,7 @@ class InMemoryFile extends InMemoryNode implements File {
|
||||
// returning null removes the entry.
|
||||
return null;
|
||||
});
|
||||
assert!this.exists();
|
||||
assert !this.exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -12,11 +12,12 @@ import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.cryptomator.filesystem.FileSystem;
|
||||
import org.cryptomator.filesystem.FileSystemFeature;
|
||||
|
||||
public class InMemoryFileSystem extends InMemoryFolder implements FileSystem {
|
||||
|
||||
public InMemoryFileSystem() {
|
||||
super(null, "", Instant.now());
|
||||
super(null, "", Instant.now(), Instant.now());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -39,4 +40,9 @@ public class InMemoryFileSystem extends InMemoryFolder implements FileSystem {
|
||||
return "/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(FileSystemFeature feature) {
|
||||
return feature == FileSystemFeature.CREATION_TIME_FEATURE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ class InMemoryFolder extends InMemoryNode implements Folder {
|
||||
final Map<String, InMemoryFile> volatileFiles = new HashMap<>();
|
||||
final Map<String, InMemoryFolder> volatileFolders = new HashMap<>();
|
||||
|
||||
public InMemoryFolder(InMemoryFolder parent, String name, Instant lastModified) {
|
||||
super(parent, name, lastModified);
|
||||
public InMemoryFolder(InMemoryFolder parent, String name, Instant lastModified, Instant creationTime) {
|
||||
super(parent, name, lastModified, creationTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -41,7 +41,7 @@ class InMemoryFolder extends InMemoryNode implements Folder {
|
||||
return (InMemoryFile) node;
|
||||
} else {
|
||||
return volatileFiles.computeIfAbsent(name, (n) -> {
|
||||
return new InMemoryFile(this, n, Instant.MIN);
|
||||
return new InMemoryFile(this, n, Instant.MIN, Instant.MIN);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ class InMemoryFolder extends InMemoryNode implements Folder {
|
||||
return (InMemoryFolder) node;
|
||||
} else {
|
||||
return volatileFolders.computeIfAbsent(name, (n) -> {
|
||||
return new InMemoryFolder(this, n, Instant.MIN);
|
||||
return new InMemoryFolder(this, n, Instant.MIN, Instant.MIN);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,7 @@ class InMemoryFolder extends InMemoryNode implements Folder {
|
||||
});
|
||||
parent.volatileFolders.remove(name);
|
||||
assert this.exists();
|
||||
creationTime = Instant.now();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -81,11 +82,11 @@ class InMemoryFolder extends InMemoryNode implements Folder {
|
||||
if (target.exists()) {
|
||||
target.delete();
|
||||
}
|
||||
assert!target.exists();
|
||||
assert !target.exists();
|
||||
target.create();
|
||||
this.copyTo(target);
|
||||
this.delete();
|
||||
assert!this.exists();
|
||||
assert !this.exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -107,7 +108,7 @@ class InMemoryFolder extends InMemoryNode implements Folder {
|
||||
subFolder.delete();
|
||||
}
|
||||
}
|
||||
assert!this.exists();
|
||||
assert !this.exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.filesystem.inmem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -18,11 +20,13 @@ class InMemoryNode implements Node {
|
||||
protected final InMemoryFolder parent;
|
||||
protected final String name;
|
||||
protected Instant lastModified;
|
||||
protected Instant creationTime;
|
||||
|
||||
public InMemoryNode(InMemoryFolder parent, String name, Instant lastModified) {
|
||||
public InMemoryNode(InMemoryFolder parent, String name, Instant lastModified, Instant creationTime) {
|
||||
this.parent = parent;
|
||||
this.name = name;
|
||||
this.lastModified = lastModified;
|
||||
this.creationTime = creationTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,4 +70,13 @@ class InMemoryNode implements Node {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant creationTime() throws UncheckedIOException {
|
||||
if (exists()) {
|
||||
return creationTime;
|
||||
} else {
|
||||
throw new UncheckedIOException(new IOException("Node does not exist"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.cryptomator.io.ByteBuffers;
|
||||
public class InMemoryWritableFile implements WritableFile {
|
||||
|
||||
private final Consumer<Instant> lastModifiedSetter;
|
||||
private final Consumer<Instant> creationTimeSetter;
|
||||
private final Supplier<ByteBuffer> contentGetter;
|
||||
private final Consumer<ByteBuffer> contentSetter;
|
||||
private final Consumer<Void> deleter;
|
||||
@@ -29,12 +30,14 @@ public class InMemoryWritableFile implements WritableFile {
|
||||
private boolean open;
|
||||
private int position = 0;
|
||||
|
||||
public InMemoryWritableFile(Consumer<Instant> lastModifiedSetter, Supplier<ByteBuffer> contentGetter, Consumer<ByteBuffer> contentSetter, Consumer<Void> deleter, WriteLock writeLock) {
|
||||
public InMemoryWritableFile(Consumer<Instant> lastModifiedSetter, Consumer<Instant> creationTimeSetter, Supplier<ByteBuffer> contentGetter, Consumer<ByteBuffer> contentSetter, Consumer<Void> deleter,
|
||||
WriteLock writeLock) {
|
||||
this.lastModifiedSetter = lastModifiedSetter;
|
||||
this.contentGetter = contentGetter;
|
||||
this.contentSetter = contentSetter;
|
||||
this.deleter = deleter;
|
||||
this.writeLock = writeLock;
|
||||
this.creationTimeSetter = creationTimeSetter;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -98,4 +101,9 @@ public class InMemoryWritableFile implements WritableFile {
|
||||
lastModifiedSetter.accept(Instant.now());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCreationTime(Instant instant) throws UncheckedIOException {
|
||||
creationTimeSetter.accept(instant);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.cryptomator.filesystem.inmem;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.io.UncheckedIOException;
|
||||
import java.time.Instant;
|
||||
|
||||
import org.cryptomator.filesystem.WritableFile;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
public class InMemoryFileTest {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void testCreationTimeOfNonExistingFileThrowsUncheckedIOException() {
|
||||
InMemoryFileSystem fileSystem = new InMemoryFileSystem();
|
||||
InMemoryFile inTest = fileSystem.file("foo");
|
||||
|
||||
thrown.expect(UncheckedIOException.class);
|
||||
|
||||
inTest.creationTime();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreationTimeOfCreatedFileIsSetToInstantDuringCreation() {
|
||||
InMemoryFileSystem fileSystem = new InMemoryFileSystem();
|
||||
InMemoryFile inTest = fileSystem.file("foo");
|
||||
|
||||
Instant minCreationTime = Instant.now();
|
||||
Instant maxCreationTime;
|
||||
try (WritableFile writable = inTest.openWritable()) {
|
||||
maxCreationTime = Instant.now();
|
||||
}
|
||||
|
||||
assertThat(inTest.creationTime().isBefore(minCreationTime), is(false));
|
||||
assertThat(inTest.creationTime().isAfter(maxCreationTime), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreationTimeSetInWritableFileIsSaved() {
|
||||
Instant creationTime = Instant.parse("2015-03-23T21:11:32Z");
|
||||
InMemoryFileSystem fileSystem = new InMemoryFileSystem();
|
||||
InMemoryFile inTest = fileSystem.file("foo");
|
||||
try (WritableFile writable = inTest.openWritable()) {
|
||||
writable.setCreationTime(creationTime);
|
||||
}
|
||||
|
||||
assertThat(inTest.creationTime(), is(creationTime));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package org.cryptomator.filesystem.blacklisting;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.cryptomator.filesystem.FileSystem;
|
||||
import org.cryptomator.filesystem.FileSystemFeature;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
import org.cryptomator.filesystem.Node;
|
||||
|
||||
@@ -12,4 +13,9 @@ class BlacklistingFileSystem extends BlacklistingFolder implements FileSystem {
|
||||
super(null, root, hiddenNodes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(FileSystemFeature feature) {
|
||||
return delegate.fileSystem().supports(feature);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.cryptomator.filesystem.shortening;
|
||||
|
||||
import org.cryptomator.filesystem.FileSystem;
|
||||
import org.cryptomator.filesystem.FileSystemFeature;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
|
||||
class ShorteningFileSystem extends ShorteningFolder implements FileSystem {
|
||||
@@ -9,4 +10,9 @@ class ShorteningFileSystem extends ShorteningFolder implements FileSystem {
|
||||
super(null, root, "", new FilenameShortener(metadataRoot, threshold));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(FileSystemFeature feature) {
|
||||
return delegate.fileSystem().supports(feature);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,12 +8,19 @@ import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.OpenOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.BasicFileAttributeView;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.attribute.FileAttribute;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
class DefaultNioAccess implements NioAccess {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DefaultNioAccess.class);
|
||||
|
||||
@Override
|
||||
public FileChannel open(Path path, OpenOption... options) throws IOException {
|
||||
return FileChannel.open(path, options);
|
||||
@@ -74,4 +81,30 @@ class DefaultNioAccess implements NioAccess {
|
||||
return FileSystems.getDefault().getSeparator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileTime getCreationTime(Path path, LinkOption... options) throws IOException {
|
||||
return Files.readAttributes(path, BasicFileAttributes.class, options).creationTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCreationTime(Path path, FileTime creationTime, LinkOption... options) throws IOException {
|
||||
Files.getFileAttributeView(path, BasicFileAttributeView.class, options).setTimes(null, null, creationTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCreationTime(Path path) {
|
||||
try {
|
||||
Path file = Files.createTempFile(path, "creationTimeCheck", "tmp");
|
||||
long expected = 1184725140000L;
|
||||
long millisecondsInADay = 86400000L;
|
||||
FileTime fileTime = FileTime.fromMillis(expected);
|
||||
Files.getFileAttributeView(file, BasicFileAttributeView.class).setTimes(null, null, fileTime);
|
||||
long actual = Files.readAttributes(file, BasicFileAttributes.class).creationTime().toMillis();
|
||||
return Math.abs(expected - actual) <= millisecondsInADay;
|
||||
} catch (IOException e) {
|
||||
LOG.info("supportsCreationTime failed", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,4 +40,10 @@ interface NioAccess {
|
||||
|
||||
String separator();
|
||||
|
||||
FileTime getCreationTime(Path path, LinkOption... options) throws IOException;
|
||||
|
||||
void setCreationTime(Path path, FileTime creationTime, LinkOption... options) throws IOException;
|
||||
|
||||
boolean supportsCreationTime(Path path);
|
||||
|
||||
}
|
||||
|
||||
@@ -82,4 +82,12 @@ class NioFile extends NioNode implements File {
|
||||
return format("NioFile(%s)", path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant creationTime() throws UncheckedIOException {
|
||||
if (nioAccess.exists(path) && !exists()) {
|
||||
throw new UncheckedIOException(new IOException(format("%s is a folder", path)));
|
||||
}
|
||||
return super.creationTime();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,11 +2,16 @@ package org.cryptomator.filesystem.nio;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.cryptomator.common.CachingSupplier;
|
||||
import org.cryptomator.filesystem.FileSystem;
|
||||
import org.cryptomator.filesystem.FileSystemFeature;
|
||||
|
||||
public class NioFileSystem extends NioFolder implements FileSystem {
|
||||
|
||||
private final Supplier<Boolean> supportsCreationTime = CachingSupplier.from(this::supportsCreationTime);
|
||||
|
||||
public static NioFileSystem rootedAt(Path root) {
|
||||
return new NioFileSystem(root);
|
||||
}
|
||||
@@ -16,4 +21,17 @@ public class NioFileSystem extends NioFolder implements FileSystem {
|
||||
create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(FileSystemFeature feature) {
|
||||
if (feature == FileSystemFeature.CREATION_TIME_FEATURE) {
|
||||
return supportsCreationTime.get();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean supportsCreationTime() {
|
||||
return nioAccess.supportsCreationTime(path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -111,6 +111,14 @@ class NioFolder extends NioNode implements Folder {
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant creationTime() throws UncheckedIOException {
|
||||
if (nioAccess.exists(path) && !nioAccess.isDirectory(path)) {
|
||||
throw new UncheckedIOException(new IOException(format("%s is a file", path)));
|
||||
}
|
||||
return super.creationTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return format("NioFolder(%s)", path);
|
||||
|
||||
@@ -43,4 +43,13 @@ abstract class NioNode implements Node {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant creationTime() throws UncheckedIOException {
|
||||
try {
|
||||
return nioAccess.getCreationTime(path).toInstant();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -128,6 +128,17 @@ class WritableNioFile implements WritableFile {
|
||||
channel.truncate(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCreationTime(Instant instant) throws UncheckedIOException {
|
||||
assertOpen();
|
||||
ensureChannelIsOpened();
|
||||
try {
|
||||
nioAccess.setCreationTime(path, FileTime.from(instant));
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws UncheckedIOException {
|
||||
if (!open) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import static org.mockito.Mockito.when;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.CopyOption;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.FileSystem;
|
||||
@@ -34,7 +35,12 @@ import java.util.HashSet;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Matchers;
|
||||
|
||||
import de.bechte.junit.runners.context.HierarchicalContextRunner;
|
||||
|
||||
@RunWith(HierarchicalContextRunner.class)
|
||||
public class DefaultNioAccessTest {
|
||||
|
||||
private DefaultNioAccess inTest = new DefaultNioAccess();
|
||||
@@ -186,6 +192,159 @@ public class DefaultNioAccessTest {
|
||||
verify(implCloseChannel).run();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCreationTimeReadsAttributesUsingProviderAndReturnsValueFromThem() throws IOException {
|
||||
FileTime expectedValue = FileTime.from(Instant.parse("2016-01-08T22:32:00Z"));
|
||||
BasicFileAttributes attributes = mock(BasicFileAttributes.class);
|
||||
when(attributes.creationTime()).thenReturn(expectedValue);
|
||||
LinkOption[] options = {LinkOption.NOFOLLOW_LINKS};
|
||||
when(provider.readAttributes(path, BasicFileAttributes.class, options)).thenReturn(attributes);
|
||||
|
||||
FileTime result = inTest.getCreationTime(path, options);
|
||||
|
||||
assertThat(result, is(expectedValue));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetCreationTimeGetsAttributeViewUsingProviderAndSetsCreationTimeUsingIt() throws IOException {
|
||||
FileTime fileTime = FileTime.from(Instant.now());
|
||||
BasicFileAttributeView attributes = mock(BasicFileAttributeView.class);
|
||||
LinkOption[] options = {LinkOption.NOFOLLOW_LINKS};
|
||||
when(provider.getFileAttributeView(path, BasicFileAttributeView.class, options)).thenReturn(attributes);
|
||||
|
||||
inTest.setCreationTime(path, fileTime, options);
|
||||
|
||||
verify(attributes).setTimes(null, null, fileTime);
|
||||
}
|
||||
|
||||
public class SupportsCreationTimeTests {
|
||||
|
||||
@Test
|
||||
public void testSupportsCreationTimeReturnsTrueIfGetCreationTimeIsADayOfFromSetCreationTime() throws IOException {
|
||||
long expectedMillisSet = 1184725140000L;
|
||||
long millisecondsInADay = 86400000L;
|
||||
Path tempFileName = mock(Path.class);
|
||||
Path tempFile = mock(Path.class);
|
||||
when(tempFile.getFileSystem()).thenReturn(fileSystem);
|
||||
when(fileSystem.getPath(Matchers.any())).thenReturn(tempFileName);
|
||||
when(path.resolve(tempFileName)).thenReturn(tempFile);
|
||||
when(provider.newByteChannel(any(), any())).thenReturn(mock(SeekableByteChannel.class));
|
||||
BasicFileAttributeView attributesView = mock(BasicFileAttributeView.class);
|
||||
BasicFileAttributes attributes = mock(BasicFileAttributes.class);
|
||||
when(provider.getFileAttributeView(tempFile, BasicFileAttributeView.class)).thenReturn(attributesView);
|
||||
when(provider.readAttributes(tempFile, BasicFileAttributes.class)).thenReturn(attributes);
|
||||
when(attributes.creationTime()).thenReturn(FileTime.fromMillis(expectedMillisSet + millisecondsInADay));
|
||||
|
||||
boolean result = inTest.supportsCreationTime(path);
|
||||
|
||||
assertThat(result, is(true));
|
||||
verify(attributesView).setTimes(null, null, FileTime.fromMillis(expectedMillisSet));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSupportsCreationTimeReturnsTrueIfGetCreationTimeIsADayOfBeforeSetCreationTime() throws IOException {
|
||||
long expectedMillisSet = 1184725140000L;
|
||||
long millisecondsInADay = 86400000L;
|
||||
Path tempFileName = mock(Path.class);
|
||||
Path tempFile = mock(Path.class);
|
||||
when(tempFile.getFileSystem()).thenReturn(fileSystem);
|
||||
when(fileSystem.getPath(Matchers.any())).thenReturn(tempFileName);
|
||||
when(path.resolve(tempFileName)).thenReturn(tempFile);
|
||||
when(provider.newByteChannel(any(), any())).thenReturn(mock(SeekableByteChannel.class));
|
||||
BasicFileAttributeView attributesView = mock(BasicFileAttributeView.class);
|
||||
BasicFileAttributes attributes = mock(BasicFileAttributes.class);
|
||||
when(provider.getFileAttributeView(tempFile, BasicFileAttributeView.class)).thenReturn(attributesView);
|
||||
when(provider.readAttributes(tempFile, BasicFileAttributes.class)).thenReturn(attributes);
|
||||
when(attributes.creationTime()).thenReturn(FileTime.fromMillis(expectedMillisSet - millisecondsInADay));
|
||||
|
||||
boolean result = inTest.supportsCreationTime(path);
|
||||
|
||||
assertThat(result, is(true));
|
||||
verify(attributesView).setTimes(null, null, FileTime.fromMillis(expectedMillisSet));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSupportsCreationTimeSucceedsIfGetCreationTimeIsADayOfBeforeSetCreationTime() throws IOException {
|
||||
long expectedMillisSet = 1184725140000L;
|
||||
long millisecondsInADay = 86400000L;
|
||||
Path tempFileName = mock(Path.class);
|
||||
Path tempFile = mock(Path.class);
|
||||
when(tempFile.getFileSystem()).thenReturn(fileSystem);
|
||||
when(fileSystem.getPath(Matchers.any())).thenReturn(tempFileName);
|
||||
when(path.resolve(tempFileName)).thenReturn(tempFile);
|
||||
when(provider.newByteChannel(any(), any())).thenReturn(mock(SeekableByteChannel.class));
|
||||
BasicFileAttributeView attributesView = mock(BasicFileAttributeView.class);
|
||||
BasicFileAttributes attributes = mock(BasicFileAttributes.class);
|
||||
when(provider.getFileAttributeView(tempFile, BasicFileAttributeView.class)).thenReturn(attributesView);
|
||||
when(provider.readAttributes(tempFile, BasicFileAttributes.class)).thenReturn(attributes);
|
||||
when(attributes.creationTime()).thenReturn(FileTime.fromMillis(expectedMillisSet - millisecondsInADay));
|
||||
|
||||
boolean result = inTest.supportsCreationTime(path);
|
||||
|
||||
assertThat(result, is(true));
|
||||
verify(attributesView).setTimes(null, null, FileTime.fromMillis(expectedMillisSet));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSupportsCreationTimeReturnsFalseIfGetCreationTimeIsMoreAsADayOfFromSetCreationTime() throws IOException {
|
||||
long expectedMillisSet = 1184725140000L;
|
||||
long millisecondsInADay = 86400000L;
|
||||
Path tempFileName = mock(Path.class);
|
||||
Path tempFile = mock(Path.class);
|
||||
when(tempFile.getFileSystem()).thenReturn(fileSystem);
|
||||
when(fileSystem.getPath(Matchers.any())).thenReturn(tempFileName);
|
||||
when(path.resolve(tempFileName)).thenReturn(tempFile);
|
||||
when(provider.newByteChannel(any(), any())).thenReturn(mock(SeekableByteChannel.class));
|
||||
BasicFileAttributeView attributesView = mock(BasicFileAttributeView.class);
|
||||
BasicFileAttributes attributes = mock(BasicFileAttributes.class);
|
||||
when(provider.getFileAttributeView(tempFile, BasicFileAttributeView.class)).thenReturn(attributesView);
|
||||
when(provider.readAttributes(tempFile, BasicFileAttributes.class)).thenReturn(attributes);
|
||||
when(attributes.creationTime()).thenReturn(FileTime.fromMillis(expectedMillisSet + millisecondsInADay + 1));
|
||||
|
||||
boolean result = inTest.supportsCreationTime(path);
|
||||
|
||||
assertThat(result, is(false));
|
||||
verify(attributesView).setTimes(null, null, FileTime.fromMillis(expectedMillisSet));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSupportsCreationTimeReturnsFalseIfGetCreationTimeIsMoreAsADayBeforeSetCreationTime() throws IOException {
|
||||
long expectedMillisSet = 1184725140000L;
|
||||
long millisecondsInADay = 86400000L;
|
||||
Path tempFileName = mock(Path.class);
|
||||
Path tempFile = mock(Path.class);
|
||||
when(tempFile.getFileSystem()).thenReturn(fileSystem);
|
||||
when(fileSystem.getPath(Matchers.any())).thenReturn(tempFileName);
|
||||
when(path.resolve(tempFileName)).thenReturn(tempFile);
|
||||
when(provider.newByteChannel(any(), any())).thenReturn(mock(SeekableByteChannel.class));
|
||||
BasicFileAttributeView attributesView = mock(BasicFileAttributeView.class);
|
||||
BasicFileAttributes attributes = mock(BasicFileAttributes.class);
|
||||
when(provider.getFileAttributeView(tempFile, BasicFileAttributeView.class)).thenReturn(attributesView);
|
||||
when(provider.readAttributes(tempFile, BasicFileAttributes.class)).thenReturn(attributes);
|
||||
when(attributes.creationTime()).thenReturn(FileTime.fromMillis(expectedMillisSet - millisecondsInADay - 1));
|
||||
|
||||
boolean result = inTest.supportsCreationTime(path);
|
||||
|
||||
assertThat(result, is(false));
|
||||
verify(attributesView).setTimes(null, null, FileTime.fromMillis(expectedMillisSet));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSupportsCreationTimeReturnsFalseIfIOExceptionOccurs() throws IOException {
|
||||
Path tempFileName = mock(Path.class);
|
||||
Path tempFile = mock(Path.class);
|
||||
when(tempFile.getFileSystem()).thenReturn(fileSystem);
|
||||
when(fileSystem.getPath(Matchers.any())).thenReturn(tempFileName);
|
||||
when(path.resolve(tempFileName)).thenReturn(tempFile);
|
||||
when(provider.newByteChannel(any(), any())).thenThrow(new IOException());
|
||||
|
||||
boolean result = inTest.supportsCreationTime(path);
|
||||
|
||||
assertThat(result, is(false));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeparatorReturnsSeparatorOfDefaultFileSystem() {
|
||||
assertThat(inTest.separator(), is(FileSystems.getDefault().getSeparator()));
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.cryptomator.filesystem.FileSystemFeature;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -51,6 +52,31 @@ public class NioFileSystemTest {
|
||||
verify(nioAccess).createDirectories(path);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSupportsCreationTimeDelegatesToNioAccessWithTrue() {
|
||||
when(nioAccess.supportsCreationTime(path)).thenReturn(true);
|
||||
|
||||
boolean result = inTest.supports(FileSystemFeature.CREATION_TIME_FEATURE);
|
||||
|
||||
assertThat(result, is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSupportsWithOtherFeatureReturnsFalse() {
|
||||
boolean result = inTest.supports(null);
|
||||
|
||||
assertThat(result, is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSupportsCreationTimeDelegatesToNioAccessWithFalse() {
|
||||
when(nioAccess.supportsCreationTime(path)).thenReturn(false);
|
||||
|
||||
boolean result = inTest.supports(FileSystemFeature.CREATION_TIME_FEATURE);
|
||||
|
||||
assertThat(result, is(false));
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
InstanceFactory.DEFAULT.reset();
|
||||
|
||||
@@ -295,6 +295,46 @@ public class NioFileTest {
|
||||
|
||||
}
|
||||
|
||||
public class CreationTime {
|
||||
|
||||
@Test
|
||||
public void testCreationTimeDelegatesToNioAccessCreationTime() throws IOException {
|
||||
Instant exectedResult = Instant.parse("2016-01-08T19:49:00Z");
|
||||
when(nioAccess.getCreationTime(path)).thenReturn(FileTime.from(exectedResult));
|
||||
when(nioAccess.exists(path)).thenReturn(true);
|
||||
when(nioAccess.isRegularFile(path)).thenReturn(true);
|
||||
|
||||
Instant result = inTest.creationTime();
|
||||
|
||||
assertThat(result, is(exectedResult));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreationTimeWrapsIOExceptionFromNioAccessCreationTimeInUncheckedIOException() throws IOException {
|
||||
IOException exceptionFromCreationTime = new IOException();
|
||||
when(nioAccess.getCreationTime(path)).thenThrow(exceptionFromCreationTime);
|
||||
when(nioAccess.exists(path)).thenReturn(true);
|
||||
when(nioAccess.isRegularFile(path)).thenReturn(true);
|
||||
|
||||
thrown.expect(UncheckedIOException.class);
|
||||
thrown.expectCause(is(exceptionFromCreationTime));
|
||||
|
||||
inTest.creationTime();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreationTimeThrowsExceptionIfFileIsNoRegularFile() {
|
||||
when(nioAccess.exists(path)).thenReturn(true);
|
||||
when(nioAccess.isRegularFile(path)).thenReturn(false);
|
||||
|
||||
thrown.expect(UncheckedIOException.class);
|
||||
thrown.expectMessage(format("%s is a folder", path));
|
||||
|
||||
inTest.creationTime();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameReturnsFileNameOfPath() {
|
||||
Path fileName = mock(Path.class);
|
||||
|
||||
@@ -410,6 +410,57 @@ public class NioFolderTest {
|
||||
|
||||
}
|
||||
|
||||
public class CreationTimeTests {
|
||||
|
||||
@Test
|
||||
public void testCreationTimeDelegatesToNioAccessCreationTimeForExistingFolder() throws IOException {
|
||||
Instant exectedResult = Instant.parse("2016-01-08T19:49:00Z");
|
||||
when(nioAccess.getCreationTime(path)).thenReturn(FileTime.from(exectedResult));
|
||||
when(nioAccess.exists(path)).thenReturn(true);
|
||||
when(nioAccess.isDirectory(path)).thenReturn(true);
|
||||
|
||||
Instant result = inTest.creationTime();
|
||||
|
||||
assertThat(result, is(exectedResult));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreationTimeDelegatesToNioAccessCreationTimeForNonExistingFolder() throws IOException {
|
||||
Instant exectedResult = Instant.parse("2016-01-08T19:49:00Z");
|
||||
when(nioAccess.getCreationTime(path)).thenReturn(FileTime.from(exectedResult));
|
||||
when(nioAccess.exists(path)).thenReturn(false);
|
||||
|
||||
Instant result = inTest.creationTime();
|
||||
|
||||
assertThat(result, is(exectedResult));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreationTimeWrapsIOExceptionFromNioAccessCreationTimeInUncheckedIOException() throws IOException {
|
||||
IOException exceptionFromCreationTime = new IOException();
|
||||
when(nioAccess.getCreationTime(path)).thenThrow(exceptionFromCreationTime);
|
||||
when(nioAccess.exists(path)).thenReturn(true);
|
||||
when(nioAccess.isDirectory(path)).thenReturn(true);
|
||||
|
||||
thrown.expect(UncheckedIOException.class);
|
||||
thrown.expectCause(is(exceptionFromCreationTime));
|
||||
|
||||
inTest.creationTime();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreationTimeThrowsExceptionIfFileIsNoDirectory() {
|
||||
when(nioAccess.exists(path)).thenReturn(true);
|
||||
when(nioAccess.isDirectory(path)).thenReturn(false);
|
||||
|
||||
thrown.expect(UncheckedIOException.class);
|
||||
thrown.expectMessage(format("%s is a file", path));
|
||||
|
||||
inTest.creationTime();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class DeleteTests {
|
||||
|
||||
@Test
|
||||
|
||||
@@ -5,7 +5,9 @@ import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
|
||||
import static org.cryptomator.filesystem.nio.OpenMode.WRITE;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.same;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -286,6 +288,44 @@ public class WritableNioFileTest {
|
||||
|
||||
}
|
||||
|
||||
public class SetCreationTimeTests {
|
||||
|
||||
@Test
|
||||
public void testSetCreationTimeFailsIfNotOpen() {
|
||||
Instant irrelevant = null;
|
||||
inTest.close();
|
||||
|
||||
thrown.expect(UncheckedIOException.class);
|
||||
thrown.expectMessage("already closed");
|
||||
|
||||
inTest.setCreationTime(irrelevant);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetCreationTimeOpensChannelIfClosedAndInvokesNioAccessSetCreationTimeAfterwards() throws IOException {
|
||||
Instant instant = Instant.parse("2016-01-08T22:32:00Z");
|
||||
|
||||
inTest.setCreationTime(instant);
|
||||
|
||||
InOrder inOrder = inOrder(nioAccess, channel);
|
||||
inOrder.verify(channel).openIfClosed(OpenMode.WRITE);
|
||||
inOrder.verify(nioAccess).setCreationTime(path, FileTime.from(instant));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetCreationTimeWrapsIOExceptionFromSetCreationTimeInUncheckedIOException() throws IOException {
|
||||
IOException exceptionFromSetCreationTime = new IOException();
|
||||
Instant irrelevant = Instant.now();
|
||||
doThrow(exceptionFromSetCreationTime).when(nioAccess).setCreationTime(same(path), any());
|
||||
|
||||
thrown.expect(UncheckedIOException.class);
|
||||
thrown.expectCause(is(exceptionFromSetCreationTime));
|
||||
|
||||
inTest.setCreationTime(irrelevant);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class DeleteTests {
|
||||
|
||||
@Test
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.apache.jackrabbit.webdav.DavSession;
|
||||
import org.apache.jackrabbit.webdav.io.InputContext;
|
||||
import org.apache.jackrabbit.webdav.io.OutputContext;
|
||||
import org.apache.jackrabbit.webdav.lock.LockManager;
|
||||
import org.cryptomator.filesystem.FileSystemFeature;
|
||||
import org.cryptomator.filesystem.ReadableFile;
|
||||
import org.cryptomator.filesystem.WritableFile;
|
||||
import org.cryptomator.filesystem.jackrabbit.FileLocator;
|
||||
@@ -98,8 +99,11 @@ class DavFile extends DavNode<FileLocator> {
|
||||
|
||||
@Override
|
||||
protected void setCreationTime(Instant instant) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
if (node.fileSystem().supports(FileSystemFeature.CREATION_TIME_FEATURE)) {
|
||||
try (WritableFile writable = node.openWritable()) {
|
||||
writable.setCreationTime(instant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.apache.jackrabbit.webdav.property.DavPropertyName;
|
||||
import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
|
||||
import org.apache.jackrabbit.webdav.property.ResourceType;
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.FileSystemFeature;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
import org.cryptomator.filesystem.Node;
|
||||
import org.cryptomator.filesystem.WritableFile;
|
||||
@@ -154,8 +155,9 @@ class DavFolder extends DavNode<FolderLocator> {
|
||||
|
||||
@Override
|
||||
protected void setCreationTime(Instant instant) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
if (node.fileSystem().supports(FileSystemFeature.CREATION_TIME_FEATURE)) {
|
||||
node.setCreationTime(instant);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,7 +28,9 @@ import org.apache.jackrabbit.webdav.property.DavProperty;
|
||||
import org.apache.jackrabbit.webdav.property.DavPropertyName;
|
||||
import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
|
||||
import org.apache.jackrabbit.webdav.property.DavPropertySet;
|
||||
import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
|
||||
import org.apache.jackrabbit.webdav.property.PropEntry;
|
||||
import org.cryptomator.filesystem.FileSystemFeature;
|
||||
import org.cryptomator.filesystem.jackrabbit.FileSystemResourceLocator;
|
||||
|
||||
abstract class DavNode<T extends FileSystemResourceLocator> implements DavResource {
|
||||
@@ -106,7 +108,16 @@ abstract class DavNode<T extends FileSystemResourceLocator> implements DavResour
|
||||
|
||||
@Override
|
||||
public DavProperty<?> getProperty(DavPropertyName name) {
|
||||
return getProperties().get(name);
|
||||
final String namespacelessPropertyName = name.getName();
|
||||
if (Arrays.asList(DAV_CREATIONDATE_PROPNAMES).contains(namespacelessPropertyName)) {
|
||||
if (node.fileSystem().supports(FileSystemFeature.CREATION_TIME_FEATURE)) {
|
||||
return new DefaultDavProperty<>(name, DateTimeFormatter.RFC_1123_DATE_TIME.format(node.creationTime()));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return getProperties().get(name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -116,8 +127,6 @@ abstract class DavNode<T extends FileSystemResourceLocator> implements DavResour
|
||||
|
||||
@Override
|
||||
public void setProperty(DavProperty<?> property) throws DavException {
|
||||
getProperties().add(property);
|
||||
|
||||
final String namespacelessPropertyName = property.getName().getName();
|
||||
if (Arrays.asList(DAV_CREATIONDATE_PROPNAMES).contains(namespacelessPropertyName) && property.getValue() instanceof String) {
|
||||
final String createDateStr = (String) property.getValue();
|
||||
@@ -127,6 +136,9 @@ abstract class DavNode<T extends FileSystemResourceLocator> implements DavResour
|
||||
final String lastModifiedTimeStr = (String) property.getValue();
|
||||
final Instant modificationTime = Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(lastModifiedTimeStr));
|
||||
this.setModificationTime(modificationTime);
|
||||
getProperties().add(property);
|
||||
} else {
|
||||
getProperties().add(property);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user