All Downloads are FREE. Search and download functionalities are using the official Maven repository.

bt.data.file.FileSystemStorageUnit Maven / Gradle / Ivy

package bt.data.file;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import bt.BtException;
import bt.data.StorageUnit;

class FileSystemStorageUnit implements StorageUnit {

    private static final Logger LOGGER = LoggerFactory.getLogger(FileSystemStorageUnit.class);

    private Path parent, file;
    private SeekableByteChannel sbc;
    private long capacity;

    private volatile boolean closed;

    FileSystemStorageUnit(Path root, String path, long capacity) {
        this.file = root.resolve(path);
        this.parent = file.getParent();
        this.capacity = capacity;
        this.closed = true;
    }

    // TODO: this is temporary fix for verification upon app start
    // should be re-done (probably need additional API to know if storage unit is "empty")
    private boolean init(boolean create) {

        if (closed) {
            if (!Files.exists(parent)) {
                try {
                    Files.createDirectories(parent);
                } catch(IOException e) {
                    if(create) {
                        throw new BtException("Failed to create file storage -- can't create (some of the) directories", e);
                    }
                        throw new BtException("Failed to create file storage -- unexpected I/O error", e);
                    }
            }

            if (!Files.exists(file)) {
                if (create) {
                    try {
                        Files.createFile(file);
                    } catch (IOException e) {
                        throw new BtException("Failed to create file storage -- " +
                                "can't create new file: " + file.toAbsolutePath(), e);
                    }
                } else {
                    return false;
                }
            }

            try {
                sbc = Files.newByteChannel(file, StandardOpenOption.READ, StandardOpenOption.WRITE);
            } catch (IOException e) {
                throw new BtException("Unexpected I/O error", e);
            }

            closed = false;
        }
        return true;
    }

    @Override
    public synchronized void readBlock(ByteBuffer buffer, long offset) {

        if (closed) {
            if (!init(false)) {
                return;
            }
        }

        if (offset < 0) {
            throw new BtException("Illegal arguments: offset (" + offset + ")");
        } else if (offset > capacity - buffer.remaining()) {
            throw new BtException("Received a request to read past the end of file (offset: " + offset +
                    ", requested block length: " + buffer.remaining() + ", file size: " + capacity);
        }

        try {
            sbc.position(offset);
            int read = 1;
            while (buffer.hasRemaining() && read > 0) {
              read = sbc.read(buffer);
            }

        } catch (IOException e) {
            throw new BtException("Failed to read bytes (offset: " + offset +
                    ", requested block length: " + buffer.remaining() + ", file size: " + capacity + ")", e);
        }
    }

    @Override
    public synchronized byte[] readBlock(long offset, int length) {

        if (closed) {
            if (!init(false)) {
                // TODO: should we return null here? or init this "stub" in constructor?
                return new byte[length];
            }
        }

        if (offset < 0 || length < 0) {
            throw new BtException("Illegal arguments: offset (" + offset + "), length (" + length + ")");
        } else if (offset > capacity - length) {
            throw new BtException("Received a request to read past the end of file (offset: " + offset +
                    ", requested block length: " + length + ", file size: " + capacity);
        }

        try {
            sbc.position(offset);
            ByteBuffer buf = ByteBuffer.allocate(length);
            int read = 1;
            while(buf.hasRemaining() && read > 0) {
              read = sbc.read(buf);
            }
            return buf.array();

        } catch (IOException e) {
            throw new BtException("Failed to read bytes (offset: " + offset +
                    ", requested block length: " + length + ", file size: " + capacity + ")", e);
        }
    }

    @Override
    public synchronized void writeBlock(ByteBuffer buffer, long offset) {

        if (closed) {
            init(true);
        }

        if (offset < 0) {
            throw new BtException("Negative offset: " + offset);
        } else if (offset > capacity - buffer.remaining()) {
            throw new BtException("Received a request to write past the end of file (offset: " + offset +
                    ", block length: " + buffer.remaining() + ", file size: " + capacity);
        }

        try {
            sbc.position(offset);
            int written = 1;
            while (buffer.hasRemaining() && written > 0) {
              written = sbc.write(buffer);
            }

        } catch (IOException e) {
            throw new BtException("Failed to write bytes (offset: " + offset +
                    ", block length: " + buffer.remaining() + ", file size: " + capacity + ")", e);
        }
    }

    @Override
    public synchronized void writeBlock(byte[] block, long offset) {

        if (closed) {
            init(true);
        }

        if (offset < 0) {
            throw new BtException("Negative offset: " + offset);
        } else if (offset > capacity - block.length) {
            throw new BtException("Received a request to write past the end of file (offset: " + offset +
                    ", block length: " + block.length + ", file size: " + capacity);
        }

        try {
            sbc.position(offset);
            ByteBuffer buf = ByteBuffer.wrap(block);
            int written = 1;
            while (buf.hasRemaining() && written > 0) {
              written = sbc.write(buf);
            }

        } catch (IOException e) {
            throw new BtException("Failed to write bytes (offset: " + offset +
                    ", block length: " + block.length + ", file size: " + capacity + ")", e);
        }
    }

    @Override
    public long capacity() {
        return capacity;
    }

    @Override
    public long size() {

        try {
            return Files.exists(file) ? Files.size(file) : 0;
        } catch (IOException e) {
            throw new BtException("Unexpected I/O error", e);
        }
    }

    @Override
    public String toString() {
        return "(" + capacity + " B) " + file;
    }

    @Override
    public void close() throws IOException {
        if (!closed) {
            try {
                sbc.close();
            } catch (IOException e) {
                LOGGER.warn("Failed to close file: " + file, e);
            } finally {
                closed = true;
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy