Ver Fonte

updated InMemoryFile to support distinct read and write access without mixing up positions and stuff

Sebastian Stenzel há 9 anos atrás
pai
commit
c86068d7bb

+ 1 - 1
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;
 	}
 

+ 17 - 79
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);
-	}
-
-	@Override
-	public int read(ByteBuffer target) {
-		if (content.hasRemaining()) {
-			return ByteBuffers.copy(content, target);
-		} else {
-			return -1;
-		}
-	}
-
-	@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 setLastModified(Instant lastModified) {
+		this.lastModified = lastModified;
 	}
 
-	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);
-		}
+	private ByteBuffer getContent() {
+		return content;
 	}
 
-	@Override
-	public void setLastModified(Instant instant) {
-		this.lastModified = instant;
+	private void setContent(ByteBuffer content) {
+		this.content = content;
 	}
 
-	@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;

+ 62 - 0
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<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();
+	}
+
+}

+ 93 - 0
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<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());
+	}
+
+}