200 lines
4.4 KiB
Nim
Raw Normal View History

2022-09-16 21:13:25 -06:00
import std/os
import std/options
import std/strutils
2022-09-16 21:13:25 -06:00
import pkg/chronos
import pkg/questionable
import pkg/questionable/results
from pkg/stew/results as stewResults import get, isErr
import pkg/upraises
import ./datastore
export datastore
push: {.upraises: [].}
type
FSDatastore* = ref object of Datastore
root*: string
ignoreProtected: bool
2022-09-19 15:52:00 -06:00
depth: int
2022-09-16 21:13:25 -06:00
2022-09-19 15:52:00 -06:00
template path*(self: FSDatastore, key: Key): string =
2022-09-16 21:13:25 -06:00
var
segments: seq[string]
for ns in key:
if ns.field == "":
segments.add ns.value
continue
2022-09-19 15:52:00 -06:00
# `:` are replaced with `/`
2022-09-16 21:13:25 -06:00
segments.add(ns.field / ns.value)
(self.root / segments.joinPath()).absolutePath()
2022-09-19 15:52:00 -06:00
template validDepth*(self: FSDatastore, key: Key): bool =
key.len <= self.depth
2022-09-16 21:13:25 -06:00
method contains*(self: FSDatastore, key: Key): Future[?!bool] {.async.} =
2022-09-19 15:52:00 -06:00
if not self.validDepth(key):
return failure "Path has invalid depth!"
let
path = self.path(key).addFileExt(FileExt)
2022-09-19 15:52:00 -06:00
return success fileExists(path)
2022-09-16 21:13:25 -06:00
method delete*(self: FSDatastore, key: Key): Future[?!void] {.async.} =
2022-09-19 15:52:00 -06:00
if not self.validDepth(key):
return failure "Path has invalid depth!"
2022-09-16 21:13:25 -06:00
let
path = self.path(key).addFileExt(FileExt)
if not path.fileExists():
return failure newException(DatastoreKeyNotFound, "Key not found!")
2022-09-16 21:13:25 -06:00
try:
removeFile(path)
return success()
except OSError as e:
return failure e
proc readFile*(self: FSDatastore, path: string): ?!seq[byte] =
2022-09-16 21:13:25 -06:00
var
file: File
defer:
file.close
if not file.open(path):
return failure newException(DatastoreKeyNotFound, "Key not found!")
2022-09-16 21:13:25 -06:00
try:
let
size = file.getFileSize
var
bytes = newSeq[byte](size)
read = 0
while read < size:
read += file.readBytes(bytes, read, size)
if read < size:
return failure $read & " bytes were read from " & path &
" but " & $size & " bytes were expected"
return success bytes
2022-09-19 15:52:00 -06:00
except CatchableError as e:
2022-09-16 21:13:25 -06:00
return failure e
method get*(self: FSDatastore, key: Key): Future[?!seq[byte]] {.async.} =
# to support finer control of memory allocation, maybe could/should change
# the signature of `get` so that it has a 3rd parameter
# `bytes: var openArray[byte]` and return type `?!bool`; this variant with
# return type `?!(?seq[byte])` would be a special case (convenience method)
# calling the former after allocating a seq with size automatically
# determined via `getFileSize`
if not self.validDepth(key):
return failure "Path has invalid depth!"
let
path = self.path(key).addFileExt(FileExt)
if not path.fileExists():
return failure(newException(DatastoreKeyNotFound, "Key doesn't exist"))
return self.readFile(path)
2022-09-16 21:13:25 -06:00
method put*(
self: FSDatastore,
key: Key,
data: seq[byte]): Future[?!void] {.async, locks: "unknown".} =
2022-09-19 15:52:00 -06:00
if not self.validDepth(key):
return failure "Path has invalid depth!"
2022-09-16 21:13:25 -06:00
let
path = self.path(key)
try:
createDir(parentDir(path))
writeFile(path.addFileExt(FileExt), data)
2022-09-19 15:52:00 -06:00
except CatchableError as e:
2022-09-16 21:13:25 -06:00
return failure e
return success()
proc dirWalker(path: string): iterator: string {.gcsafe.} =
return iterator(): string =
try:
for p in path.walkDirRec(yieldFilter = {pcFile}, relative = true):
yield p
except CatchableError as exc:
raise newException(Defect, exc.msg)
method query*(
self: FSDatastore,
query: Query): Future[?!QueryIter] {.async.} =
var
iter = QueryIter.new()
let
basePath = self.path(query.key).parentDir
walker = dirWalker(basePath)
proc next(): Future[?!QueryResponse] {.async.} =
let
path = walker()
if finished(walker):
iter.finished = true
return success (Key.none, EmptyBytes)
without data =? self.readFile((basePath / path).absolutePath), err:
return failure err
var
keyPath = basePath
keyPath.removePrefix(self.root)
keyPath = keyPath / path.changeFileExt("")
keyPath = keyPath.replace("\\", "/")
let
key = Key.init(keyPath).expect("should not fail")
return success (key.some, data)
iter.next = next
return success iter
2022-09-16 21:13:25 -06:00
proc new*(
T: type FSDatastore,
root: string,
2022-09-19 15:52:00 -06:00
depth = 2,
2022-09-16 21:13:25 -06:00
caseSensitive = true,
ignoreProtected = false): ?!T =
let root = ? (
block:
if root.isAbsolute: root
else: getCurrentDir() / root).catch
if not dirExists(root):
return failure "directory does not exist: " & root
success T(
root: root,
2022-09-19 15:52:00 -06:00
ignoreProtected: ignoreProtected,
depth: depth)