mirror of
https://github.com/logos-storage/nim-datastore.git
synced 2026-01-02 13:43:11 +00:00
164 lines
3.5 KiB
Nim
164 lines
3.5 KiB
Nim
import std/os
|
|
|
|
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
|
|
FileSystemDatastore* = ref object of Datastore
|
|
root: string
|
|
|
|
const
|
|
objExt* = ".dsobject"
|
|
|
|
proc new*(
|
|
T: type FileSystemDatastore,
|
|
root = "data"): ?!T =
|
|
|
|
try:
|
|
let
|
|
root = if root.isAbsolute: root
|
|
else: getCurrentDir() / root
|
|
|
|
createDir(root)
|
|
success T(root: root)
|
|
|
|
except IOError as e:
|
|
failure e
|
|
|
|
except OSError as e:
|
|
failure e
|
|
|
|
proc root*(self: FileSystemDatastore): string =
|
|
self.root
|
|
|
|
proc path*(
|
|
self: FileSystemDatastore,
|
|
key: Key): string =
|
|
|
|
var
|
|
segments: seq[string]
|
|
|
|
for ns in key:
|
|
without field =? ns.field:
|
|
segments.add ns.value
|
|
continue
|
|
|
|
segments.add(field / ns.value)
|
|
|
|
# is it problematic that per this logic Key(/a:b) evaluates to the same path
|
|
# as Key(/a/b)? may need to check if/how other Datastore implementations
|
|
# distinguish them
|
|
|
|
self.root / joinPath(segments) & objExt
|
|
|
|
method contains*(
|
|
self: FileSystemDatastore,
|
|
key: Key): Future[?!bool] {.async, locks: "unknown".} =
|
|
|
|
return success fileExists(self.path(key))
|
|
|
|
method delete*(
|
|
self: FileSystemDatastore,
|
|
key: Key): Future[?!void] {.async, locks: "unknown".} =
|
|
|
|
let
|
|
path = self.path(key)
|
|
|
|
try:
|
|
removeFile(path)
|
|
return success()
|
|
|
|
# removing an empty directory might lead to surprising behavior depending
|
|
# on what the user specified as the `root` of the FileSystemDatastore, so
|
|
# until further consideration, empty directories will be left in place
|
|
|
|
except OSError as e:
|
|
return failure e
|
|
|
|
method get*(
|
|
self: FileSystemDatastore,
|
|
key: Key): Future[?!(?seq[byte])] {.async, locks: "unknown".} =
|
|
|
|
# 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`
|
|
|
|
let
|
|
path = self.path(key)
|
|
containsRes = await self.contains(key)
|
|
|
|
if containsRes.isErr: return failure containsRes.error.msg
|
|
|
|
if containsRes.get:
|
|
var
|
|
file: File
|
|
|
|
if not file.open(path):
|
|
return failure "unable to open file: " & path
|
|
else:
|
|
try:
|
|
let
|
|
size = file.getFileSize
|
|
|
|
var
|
|
bytes: seq[byte]
|
|
|
|
if size > 0:
|
|
newSeq(bytes, size)
|
|
|
|
let
|
|
bytesRead = file.readBytes(bytes, 0, size)
|
|
|
|
if bytesRead < size:
|
|
return failure $bytesRead & " bytes were read from " & path &
|
|
" but " & $size & " bytes were expected"
|
|
|
|
return success bytes.some
|
|
|
|
except IOError as e:
|
|
return failure e
|
|
|
|
finally:
|
|
file.close
|
|
|
|
else:
|
|
return success seq[byte].none
|
|
|
|
method put*(
|
|
self: FileSystemDatastore,
|
|
key: Key,
|
|
data: seq[byte]): Future[?!void] {.async, locks: "unknown".} =
|
|
|
|
let
|
|
path = self.path(key)
|
|
|
|
try:
|
|
createDir(parentDir(path))
|
|
if data.len > 0: writeFile(path, data)
|
|
else: writeFile(path, "")
|
|
return success()
|
|
|
|
except IOError as e:
|
|
return failure e
|
|
|
|
except OSError as e:
|
|
return failure e
|
|
|
|
# method query*(
|
|
# self: FileSystemDatastore,
|
|
# query: ...): Future[?!(?...)] {.async, locks: "unknown".} =
|
|
#
|
|
# return success ....some
|