package it.cavallium.strangedb.database.blocks; import it.cavallium.strangedb.database.IBlocksMetadata; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousFileChannel; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; public class DatabaseBlocksMetadata implements IBlocksMetadata { public static final BlockInfo ERROR_BLOCK_INFO = new BlockInfo(-2, 0); private final AsynchronousFileChannel metaFileChannel; private final int BLOCK_META_BYTES_COUNT = Long.BYTES + Integer.BYTES; private final DatabaseBlocksMetadataCache cache; private long firstFreeBlock; public DatabaseBlocksMetadata(Path metaFile) throws IOException { metaFileChannel = AsynchronousFileChannel.open(metaFile, StandardOpenOption.READ, StandardOpenOption.WRITE); firstFreeBlock = metaFileChannel.size() / BLOCK_META_BYTES_COUNT; this.cache = new DatabaseBlocksMetadataCache(this::writeBlockToDisk); } @Override public BlockInfo getBlockInfo(long blockId) throws IOException { if (blockId == EMPTY_BLOCK_ID) { return EMPTY_BLOCK_INFO; } BlockInfo blockInfo; if ((blockInfo = cache.get(blockId)) != ERROR_BLOCK_INFO) { return blockInfo; } ByteBuffer buffer = ByteBuffer.allocate(BLOCK_META_BYTES_COUNT); try { metaFileChannel.read(buffer, blockId * BLOCK_META_BYTES_COUNT).get(); } catch (InterruptedException e) { throw new IOException(e); } catch (ExecutionException e) { throw new IOException(e.getCause()); } buffer.flip(); long index = buffer.getLong(); int size = buffer.getInt(); blockInfo = new BlockInfo(index, size); cache.put(blockId, blockInfo); return blockInfo; } @Override public long newBlock(long index, int size) throws IOException { if (size == 0) { return EMPTY_BLOCK_ID; } long newBlockId = firstFreeBlock++; BlockInfo blockInfo = new BlockInfo(index, size); cache.put(newBlockId, blockInfo); return newBlockId; } @Override public void close() throws IOException { cache.close(); metaFileChannel.close(); } private Future writeBlockToDisk(long blockId, long index, int size) { ByteBuffer data = ByteBuffer.allocate(BLOCK_META_BYTES_COUNT); data.putLong(index); data.putInt(size); data.flip(); return metaFileChannel.write(data, blockId * BLOCK_META_BYTES_COUNT); } @Override public long getTotalBlocksCount() { return firstFreeBlock; } }