Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.janelia.saalfeldlab.n5;

import org.janelia.saalfeldlab.n5.readdata.ReadData;

import java.io.*;

class BufferedKvaLockedChannel implements LockedChannel {

private final KeyValueAccess kva;
private final String key;
private ByteArrayOutputStream baos = null;

BufferedKvaLockedChannel(final KeyValueAccess kva, final String key) {
this.kva = kva;
this.key = key;
}

@Override
public Reader newReader() throws N5Exception.N5IOException {

return new InputStreamReader(newInputStream());
}

@Override
public InputStream newInputStream() throws N5Exception.N5IOException {
return kva.createReadData(key).inputStream();
}

@Override
public Writer newWriter() throws N5Exception.N5IOException {

return new BufferedWriter(new OutputStreamWriter(newOutputStream()));
}

@Override
public OutputStream newOutputStream() throws N5Exception.N5IOException {
if (baos == null)
baos = new ByteArrayOutputStream();
return baos;
}

@Override
public void close() throws IOException {
if (baos != null && baos.size() > 0)
kva.write(key, ReadData.from(baos.toByteArray()));
}
}
153 changes: 13 additions & 140 deletions src/main/java/org/janelia/saalfeldlab/n5/FileSystemKeyValueAccess.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@
import org.janelia.saalfeldlab.n5.N5Exception.N5IOException;
import org.janelia.saalfeldlab.n5.N5Exception.N5NoSuchKeyException;

import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.attribute.FileAttribute;
Expand All @@ -45,11 +43,8 @@
import java.util.stream.Stream;

import org.janelia.saalfeldlab.n5.readdata.ReadData;
import org.janelia.saalfeldlab.n5.readdata.LazyRead;
import org.janelia.saalfeldlab.n5.readdata.VolatileReadData;

import static org.janelia.saalfeldlab.n5.FileKeyLockManager.FILE_LOCK_MANAGER;

/**
* Filesystem {@link KeyValueAccess}.
*
Expand All @@ -67,11 +62,11 @@ private static IoPolicy getIoPolicy() {
return FsIoPolicy.atomicWithFallback;

switch (property) {
case "atomic":
case "strict":
return new FsIoPolicy.Atomic();
case "unsafe":
return new FsIoPolicy.Unsafe();
case "atomicFallbackUnsafe":
case "permissive":
default:
return FsIoPolicy.atomicWithFallback;
}
Expand Down Expand Up @@ -100,44 +95,6 @@ public void write(final String normalPath, final ReadData data) throws N5IOExcep
}
}

@Override
@Deprecated
public LockedFileChannel lockForReading(final String normalPath) throws N5IOException {

return lockForReading(Paths.get(normalPath));
}

@Override
@Deprecated
public LockedChannel lockForWriting(final String normalPath) throws N5IOException {

return lockForWriting(Paths.get(normalPath));
}

@Deprecated
protected static LockedFileChannel lockForReading(final Path path) throws N5IOException {

try {
return FILE_LOCK_MANAGER.lockForReading(path);
} catch (final NoSuchFileException e) {
throw new N5NoSuchKeyException("No such file", e);
} catch (IOException | UncheckedIOException e) {
throw new N5IOException("Failed to lock file for reading: " + path, e);
}
}

@Deprecated
static LockedFileChannel lockForWriting(final Path path) throws N5IOException {

try {
return FILE_LOCK_MANAGER.lockForWriting(path);
} catch (final NoSuchFileException e) {
throw new N5NoSuchKeyException("No such file", e);
} catch (IOException | UncheckedIOException e) {
throw new N5IOException("Failed to lock file for writing: " + path, e);
}
}

@Override
public boolean isDirectory(final String normalPath) {

Expand Down Expand Up @@ -165,17 +122,6 @@ public long size(final String normalPath) {
return size(Paths.get(normalPath));
}

protected static long size(final Path path) {

try {
return Files.size(path);
} catch (NoSuchFileException e) {
throw new N5NoSuchKeyException("No such file", e);
} catch (IOException | UncheckedIOException e) {
throw new N5IOException(e);
}
}

@Override
public String[] listDirectories(final String normalPath) throws N5IOException {

Expand Down Expand Up @@ -346,6 +292,17 @@ public void delete(final String normalPath) throws N5IOException {
}
}

protected static long size(final Path path) {

try {
return Files.size(path);
} catch (NoSuchFileException e) {
throw new N5NoSuchKeyException("No such file", e);
} catch (IOException | UncheckedIOException e) {
throw new N5IOException(e);
}
}

protected static void tryDelete(final Path path) throws IOException {

try {
Expand Down Expand Up @@ -501,88 +458,4 @@ protected static void createAndCheckIsDirectory(
throw x;
}
}

static class FileLazyRead implements LazyRead {

private static final Closeable NO_OP = () -> { };

private final Path path;
private Closeable lock;

FileLazyRead(final Path path) throws IOException {
this(path, true);
}

FileLazyRead(final Path path, final boolean requireLock ) throws IOException {
this.path = path;
if (requireLock)
lock = FILE_LOCK_MANAGER.lockForReading(path);
else
lock = NO_OP;
}

@Override
public long size() throws N5IOException {

if (lock == null) {
throw new N5IOException("FileLazyRead is already closed.");
}
return FileSystemKeyValueAccess.size(path);
}

@Override
public ReadData materialize(final long offset, final long length) {

if (lock == null) {
throw new N5IOException("FileLazyRead is already closed.");
}

try (final FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) {

channel.position(offset);

final long channelSize = channel.size();
if (!validBounds(channelSize, offset, length)) {
throw new IndexOutOfBoundsException();
}

final long size = length < 0 ? (channelSize - offset) : length;
if (size > Integer.MAX_VALUE) {
throw new IndexOutOfBoundsException("Attempt to materialize too large data");
}

final byte[] data = new byte[(int) size];
final ByteBuffer buf = ByteBuffer.wrap(data);
channel.read(buf);
return ReadData.from(data);

} catch (final NoSuchFileException e) {
throw new N5NoSuchKeyException("No such file", e);
} catch (IOException | UncheckedIOException e) {
throw new N5IOException(e);
}
}

@Override
public void close() throws IOException {

if (lock != null) {
lock.close();
lock = null;
}
}
}

private static boolean validBounds(long channelSize, long offset, long length) {

if (offset < 0)
return false;
else if (channelSize > 0 && offset >= channelSize) // offset == 0 and channelSize == 0 is okay
return false;
else if (length >= 0 && offset + length > channelSize)
return false;

return true;
}

}
91 changes: 89 additions & 2 deletions src/main/java/org/janelia/saalfeldlab/n5/FsIoPolicy.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package org.janelia.saalfeldlab.n5;

import org.janelia.saalfeldlab.n5.readdata.LazyRead;
import org.janelia.saalfeldlab.n5.readdata.ReadData;
import org.janelia.saalfeldlab.n5.readdata.VolatileReadData;

import java.io.Closeable;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.*;

Expand All @@ -13,6 +17,18 @@ public class FsIoPolicy {

static final IoPolicy atomicWithFallback = IoPolicy.withFallback(new Atomic(), new Unsafe());

private static boolean validBounds(long channelSize, long offset, long length) {

if (offset < 0)
return false;
else if (channelSize > 0 && offset >= channelSize) // offset == 0 and channelSize == 0 is okay
return false;
else if (length >= 0 && offset + length > channelSize)
return false;

return true;
}

public static class Unsafe implements IoPolicy {
@Override
public void write(String key, ReadData readData) throws IOException {
Expand All @@ -23,7 +39,7 @@ public void write(String key, ReadData readData) throws IOException {
@Override
public VolatileReadData read(final String key) throws IOException {
final Path path = Paths.get(key);
FileSystemKeyValueAccess.FileLazyRead fileLazyRead = new FileSystemKeyValueAccess.FileLazyRead(path, false);
FileLazyRead fileLazyRead = new FileLazyRead(path, false);
return VolatileReadData.from(fileLazyRead);
}

Expand All @@ -46,7 +62,7 @@ public void write(String key, ReadData readData) throws IOException {
@Override
public VolatileReadData read(String key) throws IOException {
final Path path = Paths.get(key);
FileSystemKeyValueAccess.FileLazyRead fileLazyRead = new FileSystemKeyValueAccess.FileLazyRead(path, true);
FileLazyRead fileLazyRead = new FileLazyRead(path, true);
return VolatileReadData.from(fileLazyRead);
}

Expand All @@ -58,4 +74,75 @@ public void delete(final String key) throws IOException {
}
}
}

static class FileLazyRead implements LazyRead {

private static final Closeable NO_OP = () -> { };

private final Path path;
private Closeable lock;

FileLazyRead(final Path path) throws IOException {
this(path, true);
}

FileLazyRead(final Path path, final boolean requireLock ) throws IOException {
this.path = path;
if (requireLock)
lock = FILE_LOCK_MANAGER.lockForReading(path);
else
lock = NO_OP;
}

@Override
public long size() throws N5Exception.N5IOException {

if (lock == null) {
throw new N5Exception.N5IOException("FileLazyRead is already closed.");
}
return FileSystemKeyValueAccess.size(path);
}

@Override
public ReadData materialize(final long offset, final long length) {

if (lock == null) {
throw new N5Exception.N5IOException("FileLazyRead is already closed.");
}

try (final FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) {

channel.position(offset);

final long channelSize = channel.size();
if (!validBounds(channelSize, offset, length)) {
throw new IndexOutOfBoundsException();
}

final long size = length < 0 ? (channelSize - offset) : length;
if (size > Integer.MAX_VALUE) {
throw new IndexOutOfBoundsException("Attempt to materialize too large data");
}

final byte[] data = new byte[(int) size];
final ByteBuffer buf = ByteBuffer.wrap(data);
channel.read(buf);
return ReadData.from(data);

} catch (final NoSuchFileException e) {
throw new N5Exception.N5NoSuchKeyException("No such file", e);
} catch (IOException | UncheckedIOException e) {
throw new N5Exception.N5IOException(e);
}
}

@Override
public void close() throws IOException {

if (lock != null) {
lock.close();
lock = null;
}
}
}
}
Loading