mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-17 10:11:27 +00:00
Removed SharedFileChannel transferTo and corresponding methods
* Removed from SharedFileChannel and Test * Refactored Copier#copy(File,File) to sequence of truncated, followed by looping read and write till EOF * Changed tests accordingly * Implemented CryptoWritableFile#truncate to make things work
This commit is contained in:
@@ -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 <T> Answer<T> collectParameters(Answer<T> answer, Consumer<?>... parameterConsumers) {
|
||||
return new Answer<T>() {
|
||||
@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 <T> Answer<T> consecutiveAnswers(Answer<T>... 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<T>() {
|
||||
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 <T> Answer<T> value(T value) {
|
||||
return new Answer<T>() {
|
||||
@Override
|
||||
public T answer(InvocationOnMock invocation) throws Throwable {
|
||||
return value;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ import java.io.UncheckedIOException;
|
||||
*/
|
||||
public interface File extends Node, Comparable<File> {
|
||||
|
||||
static final int EOF = -1;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Opens this file for reading.
|
||||
|
||||
@@ -12,8 +12,6 @@ import java.nio.channels.ReadableByteChannel;
|
||||
|
||||
public interface ReadableFile extends ReadableByteChannel {
|
||||
|
||||
void copyTo(WritableFile other) throws UncheckedIOException;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Tries to fill the remaining space in the given byte buffer with data from
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<ByteBuffer> byteBufferFilledWith(int value) {
|
||||
if (((byte) value) != value) {
|
||||
throw new IllegalArgumentException("Invalid byte value");
|
||||
}
|
||||
return new TypeSafeDiagnosingMatcher<ByteBuffer>(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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<byte[]> 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<Integer> fillBufferWith(byte[] data) {
|
||||
return new Answer<Integer>() {
|
||||
@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";
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -28,14 +28,21 @@ class CryptoWritableFile implements WritableFile {
|
||||
|
||||
final WritableFile file;
|
||||
private final ExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
|
||||
private final FileContentEncryptor encryptor;
|
||||
private final Future<Void> writeTask;
|
||||
private final FileContentCryptor cryptor;
|
||||
|
||||
private FileContentEncryptor encryptor;
|
||||
private Future<Void> 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,6 +100,11 @@ class NoFileContentCryptor implements FileContentCryptor {
|
||||
return buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeaderSize() {
|
||||
return Long.BYTES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void append(ByteBuffer cleartext) {
|
||||
try {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)));
|
||||
|
||||
|
||||
@@ -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<Runnable> 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<Runnable> 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();
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user