mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-19 19:21:27 +00:00
updated InMemoryFile to support distinct read and write access without mixing up positions and stuff
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<ByteBuffer> contentGetter;
|
||||
private final ReadLock readLock;
|
||||
private boolean open = true;
|
||||
private int position = 0;
|
||||
|
||||
public InMemoryReadableFile(Supplier<ByteBuffer> 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Instant> lastModifiedSetter;
|
||||
private final Supplier<ByteBuffer> contentGetter;
|
||||
private final Consumer<ByteBuffer> contentSetter;
|
||||
private final Consumer<Void> deleter;
|
||||
private final WriteLock writeLock;
|
||||
|
||||
private boolean open;
|
||||
private int position = 0;
|
||||
|
||||
public InMemoryWritableFile(Consumer<Instant> lastModifiedSetter, 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;
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user