diff --git a/main/commons-test/src/main/java/org/cryptomator/common/test/mockito/Answers.java b/main/commons-test/src/main/java/org/cryptomator/common/test/mockito/Answers.java new file mode 100644 index 000000000..57a60a40d --- /dev/null +++ b/main/commons-test/src/main/java/org/cryptomator/common/test/mockito/Answers.java @@ -0,0 +1,61 @@ +package org.cryptomator.common.test.mockito; + +import static java.util.Arrays.asList; + +import java.util.function.Consumer; + +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +public class Answers { + + public static Answer collectParameters(Answer answer, Consumer... parameterConsumers) { + return new Answer() { + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public T answer(InvocationOnMock invocation) throws Throwable { + for (int i = 0; i < invocation.getArguments().length; i++) { + if (parameterConsumers.length > i) { + ((Consumer) parameterConsumers[i]).accept(invocation.getArguments()[i]); + } + } + return answer.answer(invocation); + } + }; + + } + + @SafeVarargs + public static Answer consecutiveAnswers(Answer... answers) { + if (answers == null || answers.length == 0) { + throw new IllegalArgumentException("Required at least one answer"); + } + if (asList(answers).contains(null)) { + throw new IllegalArgumentException("No answers must be null"); + } + return new Answer() { + private int nextIndex = 0; + + @Override + public T answer(InvocationOnMock invocation) throws Throwable { + try { + return answers[nextIndex].answer(invocation); + } finally { + nextIndex = (nextIndex + 1) % answers.length; + } + } + + }; + } + + public static Answer value(T value) { + return new Answer() { + @Override + public T answer(InvocationOnMock invocation) throws Throwable { + return value; + } + + }; + } + +} diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Copier.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Copier.java index ff790d73d..5d7ede1c4 100644 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Copier.java +++ b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/Copier.java @@ -8,8 +8,14 @@ *******************************************************************************/ package org.cryptomator.filesystem; +import static org.cryptomator.filesystem.File.EOF; + +import java.nio.ByteBuffer; + class Copier { + private static final int COPY_BUFFER_SIZE = 128 * 1024; + public static void copy(Folder source, Folder destination) { assertFoldersAreNotNested(source, destination); @@ -29,7 +35,15 @@ class Copier { public static void copy(File source, File destination) { try (OpenFiles openFiles = DeadlockSafeFileOpener.withReadable(source).andWritable(destination).open()) { - openFiles.readable(source).copyTo(openFiles.writable(destination)); + ReadableFile readable = openFiles.readable(source); + WritableFile writable = openFiles.writable(destination); + ByteBuffer buffer = ByteBuffer.allocate(COPY_BUFFER_SIZE); + writable.truncate(); + while (readable.read(buffer) != EOF) { + buffer.flip(); + writable.write(buffer); + buffer.clear(); + } } } diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/File.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/File.java index 4cb6665aa..72f4db22c 100644 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/File.java +++ b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/File.java @@ -15,6 +15,8 @@ import java.io.UncheckedIOException; */ public interface File extends Node, Comparable { + static final int EOF = -1; + /** *

* Opens this file for reading. diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/ReadableFile.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/ReadableFile.java index 634ede5d6..398d87e72 100644 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/ReadableFile.java +++ b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/ReadableFile.java @@ -12,8 +12,6 @@ import java.nio.channels.ReadableByteChannel; public interface ReadableFile extends ReadableByteChannel { - void copyTo(WritableFile other) throws UncheckedIOException; - /** *

* Tries to fill the remaining space in the given byte buffer with data from diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingReadableFile.java b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingReadableFile.java index 68a41234e..435601782 100644 --- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingReadableFile.java +++ b/main/filesystem-api/src/main/java/org/cryptomator/filesystem/delegating/DelegatingReadableFile.java @@ -12,7 +12,6 @@ import java.io.UncheckedIOException; import java.nio.ByteBuffer; import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.WritableFile; public class DelegatingReadableFile implements ReadableFile { @@ -27,16 +26,6 @@ public class DelegatingReadableFile implements ReadableFile { return delegate.isOpen(); } - @Override - public void copyTo(WritableFile destination) throws UncheckedIOException { - if (destination instanceof DelegatingWritableFile) { - final WritableFile delegateDest = ((DelegatingWritableFile) destination).delegate; - delegate.copyTo(delegateDest); - } else { - delegate.copyTo(destination); - } - } - @Override public int read(ByteBuffer target) throws UncheckedIOException { return delegate.read(target); diff --git a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/ByteBufferMatcher.java b/main/filesystem-api/src/test/java/org/cryptomator/filesystem/ByteBufferMatcher.java new file mode 100644 index 000000000..23e4c50a6 --- /dev/null +++ b/main/filesystem-api/src/test/java/org/cryptomator/filesystem/ByteBufferMatcher.java @@ -0,0 +1,36 @@ +package org.cryptomator.filesystem; + +import java.nio.ByteBuffer; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +public class ByteBufferMatcher { + + public static Matcher byteBufferFilledWith(int value) { + if (((byte) value) != value) { + throw new IllegalArgumentException("Invalid byte value"); + } + return new TypeSafeDiagnosingMatcher(ByteBuffer.class) { + + @Override + public void describeTo(Description description) { + description.appendText("a byte buffer filled with " + value); + } + + @Override + protected boolean matchesSafely(ByteBuffer item, Description mismatchDescription) { + while (item.hasRemaining()) { + byte currentValue = item.get(); + if (currentValue != value) { + mismatchDescription.appendText("a byte buffer containing also " + currentValue); + return false; + } + } + return true; + } + }; + } + +} diff --git a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/CopierTest.java b/main/filesystem-api/src/test/java/org/cryptomator/filesystem/CopierTest.java index d32d1d231..a00252e85 100644 --- a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/CopierTest.java +++ b/main/filesystem-api/src/test/java/org/cryptomator/filesystem/CopierTest.java @@ -1,21 +1,35 @@ package org.cryptomator.filesystem; import static java.util.Arrays.asList; +import static org.cryptomator.common.test.matcher.ContainsMatcher.contains; +import static org.cryptomator.common.test.mockito.Answers.collectParameters; +import static org.cryptomator.common.test.mockito.Answers.consecutiveAnswers; +import static org.cryptomator.common.test.mockito.Answers.value; +import static org.cryptomator.filesystem.File.EOF; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.any; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collection; import java.util.stream.Stream; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.InOrder; import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import org.mockito.stubbing.Answer; import de.bechte.junit.runners.context.HierarchicalContextRunner; @@ -36,11 +50,22 @@ public class CopierTest { @Mock private File destination; + @Mock + private ReadableFile readable; + + @Mock + private WritableFile writable; + + @Before + public void setUp() { + when(source.openReadable()).thenReturn(readable); + when(destination.openWritable()).thenReturn(writable); + } + @Test public void testCopyFileOpensFilesInSortedOrderIfSourceIsSmallerDestination() { mockCompareToWithOrder(source, destination); - when(source.openReadable()).thenReturn(mock(ReadableFile.class)); - when(destination.openWritable()).thenReturn(mock(WritableFile.class)); + when(readable.read(any())).thenReturn(EOF); Copier.copy(source, destination); @@ -52,8 +77,7 @@ public class CopierTest { @Test public void testCopyFileOpensFilesInSortedOrderIfDestinationIsSmallerSource() { mockCompareToWithOrder(destination, source); - when(source.openReadable()).thenReturn(mock(ReadableFile.class)); - when(destination.openWritable()).thenReturn(mock(WritableFile.class)); + when(readable.read(any())).thenReturn(EOF); Copier.copy(source, destination); @@ -63,16 +87,46 @@ public class CopierTest { } @Test - public void testCopyFileInvokesCopyToOnReadableSourceWithWritableDestintation() { - ReadableFile readableSource = mock(ReadableFile.class); - WritableFile writableDestination = mock(WritableFile.class); + public void testCopyFileReadsAndWritesReadableSourceAndWritableDestintationUntilEof() { + int irrelevantValue = 0; + Collection written = new ArrayList<>(); mockCompareToWithOrder(source, destination); - when(source.openReadable()).thenReturn(readableSource); - when(destination.openWritable()).thenReturn(writableDestination); + byte[] read1 = {1, 48, 32, 33, 22}; + byte[] read2 = {4, 3, 1, -2, -8}; + when(readable.read(any())).then(consecutiveAnswers(fillBufferWith(read1), fillBufferWith(read2), value(EOF))); + when(writable.write(any())).then(collectParameters(value(irrelevantValue), (ByteBuffer buffer) -> { + byte[] data = new byte[buffer.remaining()]; + buffer.get(data); + written.add(data); + })); Copier.copy(source, destination); - verify(readableSource).copyTo(writableDestination); + InOrder inOrder = inOrder(readable, writable); + inOrder.verify(writable).truncate(); + inOrder.verify(readable).read(any()); + inOrder.verify(writable).write(any()); + inOrder.verify(readable).read(any()); + inOrder.verify(writable).write(any()); + inOrder.verify(readable).read(any()); + inOrder.verify(readable).close(); + inOrder.verify(writable).close(); + + assertThat(written, contains(is(read1), is(read2))); + } + + private Answer fillBufferWith(byte[] data) { + return new Answer() { + @Override + public Integer answer(InvocationOnMock invocation) throws Throwable { + ByteBuffer buffer = invocation.getArgumentAt(0, ByteBuffer.class); + for (byte value : data) { + buffer.put(value); + } + return data.length; + } + + }; } private void mockCompareToWithOrder(File first, File last) { @@ -105,7 +159,7 @@ public class CopierTest { } @Test - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({"unchecked", "rawtypes"}) public void testCopyFolderInvokesCopyToOnAllFilesInSourceWithFileWithSameNameFromDestination() { String filename1 = "nameOfFile1"; String filename2 = "nameOfFile2"; @@ -127,7 +181,7 @@ public class CopierTest { } @Test - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({"unchecked", "rawtypes"}) public void testCopyFolderInvokesCopyToOnAllFoldersInSourceWithFolderWithSameNameFromDestination() { String folderName1 = "nameOfFolder1"; String folderName2 = "nameOfFolder2"; diff --git a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingReadableFileTest.java b/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingReadableFileTest.java index 994536fd9..cd3fb106a 100644 --- a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingReadableFileTest.java +++ b/main/filesystem-api/src/test/java/org/cryptomator/filesystem/delegating/DelegatingReadableFileTest.java @@ -11,7 +11,6 @@ package org.cryptomator.filesystem.delegating; import java.nio.ByteBuffer; import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.WritableFile; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; @@ -31,29 +30,6 @@ public class DelegatingReadableFileTest { Assert.assertFalse(delegatingReadableFile.isOpen()); } - @Test - public void testCopyTo() { - ReadableFile mockReadableFile = Mockito.mock(ReadableFile.class); - WritableFile mockWritableFile = Mockito.mock(WritableFile.class); - @SuppressWarnings("resource") - DelegatingReadableFile delegatingReadableFile = new DelegatingReadableFile(mockReadableFile); - DelegatingWritableFile delegatingWritableFile = new DelegatingWritableFile(mockWritableFile); - - delegatingReadableFile.copyTo(delegatingWritableFile); - Mockito.verify(mockReadableFile).copyTo(mockWritableFile); - } - - @Test - public void testCopyToDestinationFromDifferentLayer() { - ReadableFile mockReadableFile = Mockito.mock(ReadableFile.class); - WritableFile mockWritableFile = Mockito.mock(WritableFile.class); - @SuppressWarnings("resource") - DelegatingReadableFile delegatingReadableFile = new DelegatingReadableFile(mockReadableFile); - - delegatingReadableFile.copyTo(mockWritableFile); - Mockito.verify(mockReadableFile).copyTo(mockWritableFile); - } - @Test public void testRead() { ReadableFile mockReadableFile = Mockito.mock(ReadableFile.class); diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentEncryptor.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentEncryptor.java index e295a5a72..e93511326 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentEncryptor.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentEncryptor.java @@ -27,6 +27,11 @@ public interface FileContentEncryptor extends Destroyable, Closeable { */ ByteBuffer getHeader(); + /** + * @return the size of headers created by this {@code FileContentCryptor}. The length of headers returned by {@link #getHeader()} equals this value. + */ + int getHeaderSize(); + /** * Appends further cleartext to this encryptor. This method might block until space becomes available. * diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImpl.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImpl.java index e48692974..a8df1fb45 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImpl.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImpl.java @@ -60,6 +60,11 @@ class FileContentEncryptorImpl implements FileContentEncryptor { return header.toByteBuffer(headerKey, hmacSha256); } + @Override + public int getHeaderSize() { + return FileHeader.HEADER_SIZE; + } + @Override public void append(ByteBuffer cleartext) throws InterruptedException { cleartextBytesEncrypted.add(cleartext.remaining()); diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoReadableFile.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoReadableFile.java index e751f97b2..0c7ebda76 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoReadableFile.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoReadableFile.java @@ -18,7 +18,6 @@ import java.util.concurrent.Future; import org.cryptomator.crypto.engine.FileContentCryptor; import org.cryptomator.crypto.engine.FileContentDecryptor; import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.WritableFile; import org.cryptomator.io.ByteBuffers; class CryptoReadableFile implements ReadableFile { @@ -90,16 +89,6 @@ class CryptoReadableFile implements ReadableFile { return ByteBuffers.copy(bufferedCleartext, target); } - @Override - public void copyTo(WritableFile other) { - if (other instanceof CryptoWritableFile) { - CryptoWritableFile dst = (CryptoWritableFile) other; - file.copyTo(dst.file); - } else { - throw new IllegalArgumentException("Can not move CryptoFile to conventional File."); - } - } - @Override public boolean isOpen() { return file.isOpen(); diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoWritableFile.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoWritableFile.java index eb4fb4626..127f4eee0 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoWritableFile.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoWritableFile.java @@ -28,14 +28,21 @@ class CryptoWritableFile implements WritableFile { final WritableFile file; private final ExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); - private final FileContentEncryptor encryptor; - private final Future writeTask; + private final FileContentCryptor cryptor; + + private FileContentEncryptor encryptor; + private Future writeTask; public CryptoWritableFile(FileContentCryptor cryptor, WritableFile file) { this.file = file; - this.encryptor = cryptor.createFileContentEncryptor(Optional.empty(), 0); + this.cryptor = cryptor; + initialize(0); + } + + private void initialize(long firstCleartextByte) { + encryptor = cryptor.createFileContentEncryptor(Optional.empty(), firstCleartextByte); writeHeader(); - this.writeTask = executorService.submit(new CiphertextWriter(file, encryptor)); + writeTask = executorService.submit(new CiphertextWriter(file, encryptor)); } private void writeHeader() { @@ -87,11 +94,9 @@ class CryptoWritableFile implements WritableFile { @Override public void truncate() { - /* - * TODO kill writer thread (EOF) and reinitialize CryptoWritableFile - * after truncating the file - */ - throw new UnsupportedOperationException("Truncate not supported yet"); + terminateAndWaitForWriteTask(); + file.truncate(); + initialize(0); } @Override @@ -108,11 +113,20 @@ class CryptoWritableFile implements WritableFile { public void close() { try { if (file.isOpen()) { - encryptor.append(FileContentCryptor.EOF); - writeTask.get(); + terminateAndWaitForWriteTask(); writeHeader(); // TODO append padding } + } finally { + executorService.shutdownNow(); + file.close(); + } + } + + private void terminateAndWaitForWriteTask() { + try { + encryptor.append(FileContentCryptor.EOF); + writeTask.get(); } catch (ExecutionException e) { if (e.getCause() instanceof UncheckedIOException || e.getCause() instanceof IOException) { throw new UncheckedIOException(new IOException(e)); @@ -121,9 +135,6 @@ class CryptoWritableFile implements WritableFile { } } catch (InterruptedException e) { throw new UncheckedIOException(new InterruptedIOException("Task interrupted while flushing encrypted content")); - } finally { - executorService.shutdownNow(); - file.close(); } } diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoFileContentCryptor.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoFileContentCryptor.java index 7348da451..3fc43503e 100644 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoFileContentCryptor.java +++ b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoFileContentCryptor.java @@ -100,6 +100,11 @@ class NoFileContentCryptor implements FileContentCryptor { return buf; } + @Override + public int getHeaderSize() { + return Long.BYTES; + } + @Override public void append(ByteBuffer cleartext) { try { diff --git a/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryReadableFile.java b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryReadableFile.java index c36371764..d0921a5f9 100644 --- a/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryReadableFile.java +++ b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryReadableFile.java @@ -14,7 +14,6 @@ import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; import java.util.function.Supplier; import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.WritableFile; import org.cryptomator.io.ByteBuffers; class InMemoryReadableFile implements ReadableFile { @@ -34,14 +33,6 @@ class InMemoryReadableFile implements ReadableFile { return open; } - @Override - public void copyTo(WritableFile other) throws UncheckedIOException { - ByteBuffer source = contentGetter.get().asReadOnlyBuffer(); - source.position(0); - other.truncate(); - this.position += other.write(source); - } - @Override public int read(ByteBuffer destination) throws UncheckedIOException { ByteBuffer source = contentGetter.get().asReadOnlyBuffer(); diff --git a/main/filesystem-inmemory/src/test/java/org/cryptomator/filesystem/inmem/InMemoryFileSystemTest.java b/main/filesystem-inmemory/src/test/java/org/cryptomator/filesystem/inmem/InMemoryFileSystemTest.java index 9c20c9709..af5f6914c 100644 --- a/main/filesystem-inmemory/src/test/java/org/cryptomator/filesystem/inmem/InMemoryFileSystemTest.java +++ b/main/filesystem-inmemory/src/test/java/org/cryptomator/filesystem/inmem/InMemoryFileSystemTest.java @@ -110,11 +110,7 @@ public class InMemoryFileSystemTest { // copy foo to bar File barFile = fs.file("bar.txt"); - try (WritableFile writable = barFile.openWritable()) { - try (ReadableFile readable = fooFile.openReadable()) { - readable.copyTo(writable); - } - } + fooFile.copyTo(barFile); Assert.assertTrue(fooFile.exists()); Assert.assertTrue(barFile.exists()); diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/DefaultInstanceFactory.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/DefaultInstanceFactory.java index 4d1a6d965..a53aace0e 100644 --- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/DefaultInstanceFactory.java +++ b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/DefaultInstanceFactory.java @@ -28,8 +28,8 @@ class DefaultInstanceFactory implements InstanceFactory { } @Override - public ReadableNioFile readableNioFile(FileSystem fileSystem, Path path, SharedFileChannel channel, Runnable afterCloseCallback) { - return new ReadableNioFile(fileSystem, path, channel, afterCloseCallback); + public ReadableNioFile readableNioFile(Path path, SharedFileChannel channel, Runnable afterCloseCallback) { + return new ReadableNioFile(path, channel, afterCloseCallback); } } diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/InstanceFactory.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/InstanceFactory.java index 461e507e1..d0b9deb69 100644 --- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/InstanceFactory.java +++ b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/InstanceFactory.java @@ -18,6 +18,6 @@ interface InstanceFactory { WritableNioFile writableNioFile(FileSystem fileSystem, Path path, SharedFileChannel channel, Runnable afterCloseCallback, NioAccess nioAccess); - ReadableNioFile readableNioFile(FileSystem fileSystem, Path path, SharedFileChannel channel, Runnable afterCloseCallback); + ReadableNioFile readableNioFile(Path path, SharedFileChannel channel, Runnable afterCloseCallback); } diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFile.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFile.java index fa418c186..d3affa509 100644 --- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFile.java +++ b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/NioFile.java @@ -32,7 +32,7 @@ class NioFile extends NioNode implements File { throw new IllegalStateException("Current thread is already reading this file"); } lock.readLock().lock(); - return instanceFactory.readableNioFile(fileSystem(), path, sharedChannel, this::unlockReadLock); + return instanceFactory.readableNioFile(path, sharedChannel, this::unlockReadLock); } private void unlockReadLock() { diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/ReadableNioFile.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/ReadableNioFile.java index c98b2222e..ef6621eb1 100644 --- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/ReadableNioFile.java +++ b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/ReadableNioFile.java @@ -8,13 +8,10 @@ import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.file.Path; -import org.cryptomator.filesystem.FileSystem; import org.cryptomator.filesystem.ReadableFile; -import org.cryptomator.filesystem.WritableFile; class ReadableNioFile implements ReadableFile { - private final FileSystem fileSystem; private final Path path; private final SharedFileChannel channel; private final Runnable afterCloseCallback; @@ -22,8 +19,7 @@ class ReadableNioFile implements ReadableFile { private boolean open = true; private long position = 0; - public ReadableNioFile(FileSystem fileSystem, Path path, SharedFileChannel channel, Runnable afterCloseCallback) { - this.fileSystem = fileSystem; + public ReadableNioFile(Path path, SharedFileChannel channel, Runnable afterCloseCallback) { this.path = path; this.channel = channel; this.afterCloseCallback = afterCloseCallback; @@ -59,32 +55,6 @@ class ReadableNioFile implements ReadableFile { this.position = position; } - @Override - public void copyTo(WritableFile other) throws UncheckedIOException { - assertOpen(); - if (belongsToSameFilesystem(other)) { - internalCopyTo((WritableNioFile) other); - } else { - throw new IllegalArgumentException("Can only copy to a WritableFile from the same FileSystem"); - } - } - - private boolean belongsToSameFilesystem(WritableFile other) { - return other instanceof WritableNioFile && ((WritableNioFile) other).fileSystem() == fileSystem; - } - - private void internalCopyTo(WritableNioFile target) { - target.assertOpen(); - target.ensureChannelIsOpened(); - SharedFileChannel targetChannel = target.channel(); - targetChannel.truncate(0); - long size = size(); - long transferred = 0; - while (transferred < size) { - transferred += channel.transferTo(transferred, size - transferred, targetChannel, transferred); - } - } - @Override public void close() { if (!open) { diff --git a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/SharedFileChannel.java b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/SharedFileChannel.java index 9d3b08da9..692e02833 100644 --- a/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/SharedFileChannel.java +++ b/main/filesystem-nio/src/main/java/org/cryptomator/filesystem/nio/SharedFileChannel.java @@ -134,37 +134,6 @@ class SharedFileChannel { } } - public long transferTo(long position, long count, SharedFileChannel targetChannel, long targetPosition) { - assertOpen(); - targetChannel.assertOpen(); - if (count < 0) { - throw new IllegalArgumentException("Count must not be negative"); - } - try { - ByteBuffer buffer = ByteBuffer.allocate(32 * 1024); - long maxPosition = Math.min(delegate.size(), position + count); - long transferCount = Math.max(0, maxPosition - position); - long transferred = 0; - while (transferred < transferCount) { - int read = delegate.read(buffer, position + transferred).get(); - if (read == -1) { - throw new IllegalStateException("Reached end of file during transfer to"); - } - buffer.flip(); - while (buffer.hasRemaining()) { - transferred += targetChannel.delegate.write(buffer, targetPosition + transferred).get(); - } - } - return transferCount; - } catch (InterruptedException e) { - throw new UncheckedIOException(new InterruptedIOException("read has been interrupted")); - } catch (ExecutionException e) { - throw new UncheckedIOException(new IOException(e)); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - private void doLocked(Runnable task) { lock.lock(); try { diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/DefaultInstanceFactoryTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/DefaultInstanceFactoryTest.java index 4181273ba..99343de30 100644 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/DefaultInstanceFactoryTest.java +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/DefaultInstanceFactoryTest.java @@ -81,7 +81,7 @@ public class DefaultInstanceFactoryTest { @Test public void testReadableNioFileCreatesWritableNioFile() throws IOException { Runnable afterCloseCallback = mock(Runnable.class); - ReadableNioFile result = inTest.readableNioFile(fileSystem, path, channel, afterCloseCallback); + ReadableNioFile result = inTest.readableNioFile(path, channel, afterCloseCallback); assertThat(result.toString(), is(format("ReadableNioFile(%s)", path))); diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileTest.java index 75f2282ff..401020e2b 100644 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileTest.java +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/NioFileTest.java @@ -85,7 +85,7 @@ public class NioFileTest { @Test public void testOpenReadableCreatesReadableNioFileFromNioFile() { ReadableNioFile readableNioFile = mock(ReadableNioFile.class); - when(instanceFactory.readableNioFile(same(fileSystem), same(path), same(channel), any())).thenReturn(readableNioFile); + when(instanceFactory.readableNioFile(same(path), same(channel), any())).thenReturn(readableNioFile); ReadableFile readableFile = inTest.openReadable(); @@ -106,7 +106,7 @@ public class NioFileTest { public void testOpenReadableInvokedAfterAfterCloseOperationCreatesNewReadableFile() { ReadableNioFile readableNioFile = mock(ReadableNioFile.class); ArgumentCaptor captor = ArgumentCaptor.forClass(Runnable.class); - when(instanceFactory.readableNioFile(same(fileSystem), same(path), same(channel), captor.capture())).thenReturn(null, readableNioFile); + when(instanceFactory.readableNioFile(same(path), same(channel), captor.capture())).thenReturn(null, readableNioFile); inTest.openReadable(); captor.getValue().run(); @@ -181,7 +181,7 @@ public class NioFileTest { @Test public void testOpenWritableInvokedAfterInvokingAfterCloseOperationWorks() { ArgumentCaptor captor = ArgumentCaptor.forClass(Runnable.class); - when(instanceFactory.readableNioFile(same(fileSystem), same(path), same(channel), captor.capture())).thenReturn(null); + when(instanceFactory.readableNioFile(same(path), same(channel), captor.capture())).thenReturn(null); inTest.openReadable(); captor.getValue().run(); diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ReadableNioFileTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ReadableNioFileTest.java index 3ca8d26a0..4ff589f43 100644 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ReadableNioFileTest.java +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/ReadableNioFileTest.java @@ -5,7 +5,6 @@ import static org.cryptomator.filesystem.nio.OpenMode.READ; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -16,7 +15,6 @@ import java.io.UncheckedIOException; import java.nio.ByteBuffer; import java.nio.file.Path; -import org.cryptomator.filesystem.FileSystem; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -33,8 +31,6 @@ public class ReadableNioFileTest { @Rule public ExpectedException thrown = ExpectedException.none(); - private FileSystem fileSystem; - private Path path; private SharedFileChannel channel; @@ -45,12 +41,11 @@ public class ReadableNioFileTest { @Before public void setup() { - fileSystem = mock(FileSystem.class); path = mock(Path.class); channel = mock(SharedFileChannel.class); afterCloseCallback = mock(Runnable.class); - inTest = new ReadableNioFile(fileSystem, path, channel, afterCloseCallback); + inTest = new ReadableNioFile(path, channel, afterCloseCallback); } @Test @@ -162,79 +157,6 @@ public class ReadableNioFileTest { verifyZeroInteractions(buffer); } - public class CopyTo { - - private WritableNioFile target; - - private SharedFileChannel channelOfTarget; - - @Before - public void setup() { - target = mock(WritableNioFile.class); - channelOfTarget = mock(SharedFileChannel.class); - when(target.fileSystem()).thenReturn(fileSystem); - when(target.channel()).thenReturn(channelOfTarget); - } - - @Test - public void testCopyToFailsIfTargetBelongsToOtherFileSystem() { - WritableNioFile targetFromOtherFileSystem = mock(WritableNioFile.class); - when(targetFromOtherFileSystem.fileSystem()).thenReturn(mock(FileSystem.class)); - - thrown.expect(IllegalArgumentException.class); - - inTest.copyTo(targetFromOtherFileSystem); - } - - @Test - public void testCopyToFailsIfSourceIsClosed() { - when(target.fileSystem()).thenReturn(fileSystem); - inTest.close(); - - thrown.expect(UncheckedIOException.class); - thrown.expectMessage("already closed"); - - inTest.copyTo(target); - } - - @Test - public void testCopyToAssertsThatTargetIsOpenEnsuresTargetChannelIsOpenTuncatesItAndTransfersDataFromSourceChannel() { - long sizeOfSourceChannel = 3283; - when(channel.size()).thenReturn(sizeOfSourceChannel); - when(channel.transferTo(0, sizeOfSourceChannel, channelOfTarget, 0)).thenReturn(sizeOfSourceChannel); - - inTest.copyTo(target); - - InOrder inOrder = inOrder(target, channel, channelOfTarget); - inOrder.verify(target).assertOpen(); - inOrder.verify(target).ensureChannelIsOpened(); - inOrder.verify(channelOfTarget).truncate(0); - inOrder.verify(channel).transferTo(0, sizeOfSourceChannel, channelOfTarget, 0); - } - - @Test - public void testCopyToInvokesTransferToUntilAllBytesHaveBeenTransferred() { - long firstTransferAmount = 100; - long secondTransferAmount = 300; - long thirdTransferAmount = 500; - long sizeRemainingAfterSecondTransfer = thirdTransferAmount; - long sizeRemainingAfterFirstTransfer = sizeRemainingAfterSecondTransfer + secondTransferAmount; - long size = sizeRemainingAfterFirstTransfer + firstTransferAmount; - when(channel.size()).thenReturn(size); - when(channel.transferTo(0, size, channelOfTarget, 0)).thenReturn(firstTransferAmount); - when(channel.transferTo(firstTransferAmount, sizeRemainingAfterFirstTransfer, channelOfTarget, firstTransferAmount)).thenReturn(secondTransferAmount); - when(channel.transferTo(firstTransferAmount + secondTransferAmount, sizeRemainingAfterSecondTransfer, channelOfTarget, firstTransferAmount + secondTransferAmount)).thenReturn(thirdTransferAmount); - - inTest.copyTo(target); - - InOrder inOrder = inOrder(channel); - inOrder.verify(channel).transferTo(0, size, channelOfTarget, 0); - inOrder.verify(channel).transferTo(firstTransferAmount, sizeRemainingAfterFirstTransfer, channelOfTarget, firstTransferAmount); - inOrder.verify(channel).transferTo(firstTransferAmount + secondTransferAmount, sizeRemainingAfterSecondTransfer, channelOfTarget, firstTransferAmount + secondTransferAmount); - } - - } - @Test public void testIsOpenReturnsTrueForNewReadableNioFile() { assertThat(inTest.isOpen(), is(true)); diff --git a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/SharedFileChannelTest.java b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/SharedFileChannelTest.java index 27226ee62..af27fc61e 100644 --- a/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/SharedFileChannelTest.java +++ b/main/filesystem-nio/src/test/java/org/cryptomator/filesystem/nio/SharedFileChannelTest.java @@ -653,28 +653,6 @@ public class SharedFileChannelTest { inTest.size(); } - @Test - public void testTransferToFailsIfSourceNotOpen() { - SharedFileChannel irrelevant = null; - - thrown.expect(IllegalStateException.class); - thrown.expectMessage("SharedFileChannel is not open"); - - inTest.transferTo(0, 0, irrelevant, 0); - } - - @Test - public void testTransferToFailsIfTargetNotOpen() { - Path targetPath = mock(Path.class); - SharedFileChannel targetInTest = new SharedFileChannel(targetPath, nioAccess); - inTest.open(OpenMode.WRITE); - - thrown.expect(IllegalStateException.class); - thrown.expectMessage("SharedFileChannel is not open"); - - inTest.transferTo(0, 0, targetInTest, 0); - } - @Test public void testWriteFullyFailsIfNotOpen() { ByteBuffer irrelevant = null;