diff --git a/main/filesystem-api/src/main/java/org/cryptomator/io/ByteBuffers.java b/main/filesystem-api/src/main/java/org/cryptomator/io/ByteBuffers.java index ef31b5478..37597fb6c 100644 --- a/main/filesystem-api/src/main/java/org/cryptomator/io/ByteBuffers.java +++ b/main/filesystem-api/src/main/java/org/cryptomator/io/ByteBuffers.java @@ -31,7 +31,7 @@ public final class ByteBuffers { final ByteBuffer tmp = source.asReadOnlyBuffer(); tmp.limit(tmp.position() + numBytes); destination.put(tmp); - source.position(tmp.position()); + source.position(tmp.position()); // until now only tmp pos has been incremented, so we need to adjust the position return numBytes; } diff --git a/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFile.java b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFile.java index 24db8c564..22dc06ec9 100644 --- a/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFile.java +++ b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFile.java @@ -13,16 +13,17 @@ import java.io.UncheckedIOException; import java.nio.ByteBuffer; import java.time.Instant; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; import org.cryptomator.filesystem.File; import org.cryptomator.filesystem.ReadableFile; import org.cryptomator.filesystem.WritableFile; -import org.cryptomator.io.ByteBuffers; -class InMemoryFile extends InMemoryNode implements File, ReadableFile, WritableFile { +class InMemoryFile extends InMemoryNode implements File { private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - private ByteBuffer content = ByteBuffer.wrap(new byte[0]); + private ByteBuffer content = ByteBuffer.allocate(0); public InMemoryFile(InMemoryFolder parent, String name, Instant lastModified) { super(parent, name, lastModified); @@ -33,14 +34,15 @@ class InMemoryFile extends InMemoryNode implements File, ReadableFile, WritableF if (!exists()) { throw new UncheckedIOException(new FileNotFoundException(this.name() + " does not exist")); } - lock.readLock().lock(); - content.rewind(); - return this; + final ReadLock readLock = lock.readLock(); + readLock.lock(); + return new InMemoryReadableFile(this::getContent, readLock); } @Override public WritableFile openWritable() { - lock.writeLock().lock(); + final WriteLock writeLock = lock.writeLock(); + writeLock.lock(); final InMemoryFolder parent = parent().get(); parent.children.compute(this.name(), (k, v) -> { if (v != null && v != this) { @@ -48,94 +50,30 @@ class InMemoryFile extends InMemoryNode implements File, ReadableFile, WritableF } return this; }); - return this; + return new InMemoryWritableFile(this::setLastModified, this::getContent, this::setContent, this::delete, writeLock); } - @Override - public void position(long position) throws UncheckedIOException { - content.position((int) position); + private void setLastModified(Instant lastModified) { + this.lastModified = lastModified; } - @Override - public int read(ByteBuffer target) { - if (content.hasRemaining()) { - return ByteBuffers.copy(content, target); - } else { - return -1; - } + private ByteBuffer getContent() { + return content; } - @Override - public int write(ByteBuffer source) { - assert content != null; - final int initialContentPosition = content.position(); - expandContentCapacityIfRequired(initialContentPosition + source.remaining()); - content.position(initialContentPosition); - assert content.remaining() >= source.remaining(); - content.put(source); - return content.position() - initialContentPosition; + private void setContent(ByteBuffer content) { + this.content = content; } - private void expandContentCapacityIfRequired(int requiredCapacity) { - if (requiredCapacity > content.capacity()) { - final int currentPos = content.position(); - final ByteBuffer tmp = ByteBuffer.allocate(requiredCapacity); - content.rewind(); - ByteBuffers.copy(content, tmp); - content = tmp; - content.position(currentPos); - } - } - - @Override - public void setLastModified(Instant instant) { - this.lastModified = instant; - } - - @Override - public void truncate() { - content = ByteBuffer.wrap(new byte[0]); - } - - @Override - public void copyTo(WritableFile other) { - content.rewind(); - other.truncate(); - other.write(content); - } - - @Override - public void moveTo(WritableFile other) { - this.copyTo(other); - this.delete(); - } - - @Override - public void delete() { + private void delete(Void param) { final InMemoryFolder parent = parent().get(); parent.children.computeIfPresent(this.name(), (k, v) -> { - truncate(); // returning null removes the entry. return null; }); assert!this.exists(); } - @Override - public boolean isOpen() { - return lock.isWriteLockedByCurrentThread() || lock.getReadHoldCount() > 0; - } - - @Override - public void close() { - if (lock.isWriteLockedByCurrentThread()) { - this.setLastModified(Instant.now()); - lock.writeLock().unlock(); - } else if (lock.getReadHoldCount() > 0) { - lock.readLock().unlock(); - } - } - @Override public String toString() { return parent.toString() + name; 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 new file mode 100644 index 000000000..3c8ed2c75 --- /dev/null +++ b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryReadableFile.java @@ -0,0 +1,62 @@ +package org.cryptomator.filesystem.inmem; + +import java.io.UncheckedIOException; +import java.nio.ByteBuffer; +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 { + + private final Supplier contentGetter; + private final ReadLock readLock; + private boolean open = true; + private int position = 0; + + public InMemoryReadableFile(Supplier contentGetter, ReadLock readLock) { + this.contentGetter = contentGetter; + this.readLock = readLock; + } + + @Override + public boolean isOpen() { + return open; + } + + @Override + public void copyTo(WritableFile other) throws UncheckedIOException { + ByteBuffer source = contentGetter.get().asReadOnlyBuffer(); + source.position(position); + this.position += other.write(source); + } + + @Override + public int read(ByteBuffer destination) throws UncheckedIOException { + ByteBuffer source = contentGetter.get().asReadOnlyBuffer(); + if (position >= source.limit()) { + return -1; + } else { + source.position(position); + assert source.hasRemaining(); + int numRead = ByteBuffers.copy(source, destination); + this.position += numRead; + return numRead; + } + } + + @Override + public void position(long position) throws UncheckedIOException { + assert position < Integer.MAX_VALUE : "Can not use that big in-memory files."; + this.position = (int) position; + } + + @Override + public void close() throws UncheckedIOException { + open = false; + readLock.unlock(); + } + +} diff --git a/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryWritableFile.java b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryWritableFile.java new file mode 100644 index 000000000..4bba2ecc5 --- /dev/null +++ b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryWritableFile.java @@ -0,0 +1,93 @@ +package org.cryptomator.filesystem.inmem; + +import java.io.UncheckedIOException; +import java.nio.ByteBuffer; +import java.time.Instant; +import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.cryptomator.filesystem.WritableFile; +import org.cryptomator.io.ByteBuffers; + +public class InMemoryWritableFile implements WritableFile { + + private final Consumer lastModifiedSetter; + private final Supplier contentGetter; + private final Consumer contentSetter; + private final Consumer deleter; + private final WriteLock writeLock; + + private boolean open; + private int position = 0; + + public InMemoryWritableFile(Consumer lastModifiedSetter, Supplier contentGetter, Consumer contentSetter, Consumer deleter, WriteLock writeLock) { + this.lastModifiedSetter = lastModifiedSetter; + this.contentGetter = contentGetter; + this.contentSetter = contentSetter; + this.deleter = deleter; + this.writeLock = writeLock; + } + + @Override + public boolean isOpen() { + return open; + } + + @Override + public void moveTo(WritableFile other) throws UncheckedIOException { + if (other instanceof InMemoryWritableFile) { + InMemoryWritableFile destination = (InMemoryWritableFile) other; + destination.contentSetter.accept(this.contentGetter.get()); + destination.contentGetter.get().rewind(); + } + deleter.accept(null); + } + + @Override + public void setLastModified(Instant instant) throws UncheckedIOException { + lastModifiedSetter.accept(instant); + } + + @Override + public void delete() throws UncheckedIOException { + deleter.accept(null); + open = false; + } + + @Override + public void truncate() throws UncheckedIOException { + contentSetter.accept(ByteBuffer.allocate(0)); + } + + @Override + public int write(ByteBuffer source) throws UncheckedIOException { + ByteBuffer destination = contentGetter.get(); + int requiredSize = position + source.remaining(); + if (destination.capacity() < requiredSize) { + ByteBuffer old = destination; + old.rewind(); + destination = ByteBuffer.allocate(requiredSize); + ByteBuffers.copy(old, destination); + contentSetter.accept(destination); + } + destination.position(position); + int numWritten = ByteBuffers.copy(source, destination); + this.position += numWritten; + return numWritten; + } + + @Override + public void position(long position) throws UncheckedIOException { + assert position < Integer.MAX_VALUE : "Can not use that big in-memory files."; + this.position = (int) position; + } + + @Override + public void close() throws UncheckedIOException { + open = false; + writeLock.unlock(); + lastModifiedSetter.accept(Instant.now()); + } + +}