## Nim-Codex ## Copyright (c) 2021 Status Research & Development GmbH ## Licensed under either of ## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) ## * MIT license ([LICENSE-MIT](LICENSE-MIT)) ## at your option. ## This file may not be copied, modified, or distributed except according to ## those terms. import pkg/upraises push: {.upraises: [].} import std/options import std/tables import std/lists import pkg/chronicles import pkg/chronos import pkg/libp2p import pkg/questionable import pkg/questionable/results import ./blockstore import ../consts import ../chunker import ../manifest export blockstore logScope: topics = "codex memorystore" type MemoryStoreNode = ref object key: Cid val: Block MemoryStore* = ref object of BlockStore bytesUsed*: int capacity*: int table: Table[Cid, DoublyLinkedNode[MemoryStoreNode]] list: DoublyLinkedList[MemoryStoreNode] const DefaultMemoryStoreCapacityMiB* = 5 DefaultMemoryStoreCapacity* = DefaultMemoryStoreCapacityMiB * MiB method getBlock*(self: MemoryStore, cid: Cid): Future[?!Block] {.async.} = trace "Getting block from cache", cid if cid.isEmpty: trace "Empty block, ignoring" return success cid.emptyBlock if cid notin self.table: return failure (ref BlockNotFoundError)(msg: "Block not in memory store") return success self.table[cid].value.val method hasBlock*(self: MemoryStore, cid: Cid): Future[?!bool] {.async.} = trace "Checking MemoryStore for block presence", cid if cid.isEmpty: trace "Empty block, ignoring" return true.success return (cid in self.table).success func cids(self: MemoryStore): (iterator: Cid {.gcsafe.}) = var it = self.list.head return iterator(): Cid = while not isNil(it): yield it.value.key it = it.next proc isOfBlockType(cid: Cid, blockType: BlockType): ?!bool = without isManifest =? cid.isManifest, err: trace "Error checking if cid is a manifest", err = err.msg return failure("Unable to determine if CID is a manifest") case blockType: of BlockType.Manifest: return success(isManifest) of BlockType.Block: return success(not isManifest) of BlockType.Both: return success(true) return failure("Unknown block type: " & $blockType) method listBlocks*(self: MemoryStore, blockType = BlockType.Manifest): Future[?!BlocksIter] {.async.} = var iter = BlocksIter() let cids = self.cids() proc next(): Future[?Cid] {.async.} = await idleAsync() var cid: Cid while true: if iter.finished: return Cid.none cid = cids() if finished(cids): iter.finished = true return Cid.none without isCorrectBlockType =? isOfBlockType(cid, blockType), err: warn "Error checking if cid of blocktype", err = err.msg return Cid.none if not isCorrectBlockType: trace "Cid does not match blocktype, skipping", cid, blockType continue return cid.some iter.next = next return success iter proc getFreeCapacity(self: MemoryStore): int = self.capacity - self.bytesUsed func putBlockSync(self: MemoryStore, blk: Block): ?!void = let freeCapacity = self.getFreeCapacity() blkSize = blk.data.len if blkSize > freeCapacity: trace "Block size is larger than free capacity", blk = blkSize, freeCapacity return failure("Unable to store block: Insufficient free capacity.") let node = newDoublyLinkedNode[MemoryStoreNode](MemoryStoreNode(key: blk.cid, val: blk)) self.list.prepend(node) self.table[blk.cid] = node self.bytesUsed += blkSize return success() method putBlock*(self: MemoryStore, blk: Block, ttl = Duration.none): Future[?!void] {.async.} = trace "Storing block in store", cid = blk.cid if blk.isEmpty: trace "Empty block, ignoring" return success() return self.putBlockSync(blk) method delBlock*(self: MemoryStore, cid: Cid): Future[?!void] {.async.} = trace "Deleting block from memory store", cid if cid.isEmpty: trace "Empty block, ignoring" return success() if cid notin self.table: return success() let nodeToRemove = self.table[cid] self.table.del(cid) self.list.remove(nodeToRemove) self.bytesUsed -= nodeToRemove.value.val.data.len return success() method close*(self: MemoryStore): Future[void] {.async.} = discard func new*( _: type MemoryStore, blocks: openArray[Block] = [], capacity: Positive = DefaultMemoryStoreCapacity, ): MemoryStore {.raises: [Defect, ValueError].} = let store = MemoryStore( table: initTable[Cid, DoublyLinkedNode[MemoryStoreNode]](), list: initDoublyLinkedList[MemoryStoreNode](), bytesUsed: 0, capacity: capacity) for blk in blocks: discard store.putBlockSync(blk) return store