mirror of
https://github.com/logos-storage/nim-datastore.git
synced 2026-01-02 13:43:11 +00:00
rename and cleanup fs store
This commit is contained in:
parent
f0f979539f
commit
2390839406
@ -1,159 +0,0 @@
|
||||
import std/os
|
||||
import std/sequtils
|
||||
import std/options
|
||||
|
||||
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* = ".obj"
|
||||
|
||||
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
|
||||
|
||||
proc new*(
|
||||
T: type FileSystemDatastore,
|
||||
root: string,
|
||||
caseSensitive = true): ?!T =
|
||||
|
||||
let root = ? (
|
||||
block:
|
||||
if root.isAbsolute: root
|
||||
else: getCurrentDir() / root).catch
|
||||
|
||||
if not dirExists(root):
|
||||
failure "directory does not exist: " & root
|
||||
else:
|
||||
success T(root: root)
|
||||
168
datastore/fsstore.nim
Normal file
168
datastore/fsstore.nim
Normal file
@ -0,0 +1,168 @@
|
||||
import std/os
|
||||
import std/sequtils
|
||||
import std/options
|
||||
|
||||
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: [].}
|
||||
|
||||
const
|
||||
# TODO: Add more dirs from relevant OSs
|
||||
|
||||
# Paths should be matched exactly, i.e.
|
||||
# we're forbidding this dirs from being
|
||||
# touched directly, but subdirectories
|
||||
# can still be touched
|
||||
ProtectedPaths = [
|
||||
"/",
|
||||
"/usr",
|
||||
"/etc",
|
||||
"/home",
|
||||
"/Users"]
|
||||
|
||||
type
|
||||
FSDatastore* = ref object of Datastore
|
||||
root*: string
|
||||
ignoreProtected: bool
|
||||
|
||||
func checkProtected(dir: string): bool =
|
||||
dir in ProtectedPaths
|
||||
|
||||
proc path*(self: FSDatastore, key: Key): string =
|
||||
var
|
||||
segments: seq[string]
|
||||
|
||||
for ns in key:
|
||||
if ns.field == "":
|
||||
segments.add ns.value
|
||||
continue
|
||||
|
||||
segments.add(ns.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)
|
||||
|
||||
method contains*(self: FSDatastore, key: Key): Future[?!bool] {.async.} =
|
||||
return success fileExists(self.path(key))
|
||||
|
||||
method delete*(self: FSDatastore, key: Key): Future[?!void] {.async.} =
|
||||
|
||||
let
|
||||
path = self.path(key)
|
||||
|
||||
if checkProtected(path):
|
||||
return failure "Path is protected!"
|
||||
|
||||
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 FSDatastore, so
|
||||
# until further consideration, empty directories will be left in place
|
||||
|
||||
except OSError as e:
|
||||
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`
|
||||
|
||||
let
|
||||
path = self.path(key)
|
||||
|
||||
if checkProtected(path):
|
||||
return failure "Path is protected!"
|
||||
|
||||
if not fileExists(path):
|
||||
return success(newSeq[byte]())
|
||||
|
||||
var
|
||||
file: File
|
||||
|
||||
defer:
|
||||
file.close
|
||||
|
||||
if not file.open(path):
|
||||
return failure "unable to open file: " & path
|
||||
|
||||
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
|
||||
|
||||
except IOError as e:
|
||||
return failure e
|
||||
|
||||
method put*(
|
||||
self: FSDatastore,
|
||||
key: Key,
|
||||
data: seq[byte]): Future[?!void] {.async, locks: "unknown".} =
|
||||
|
||||
let
|
||||
path = self.path(key)
|
||||
|
||||
if checkProtected(path):
|
||||
return failure "Path is protected!"
|
||||
|
||||
try:
|
||||
createDir(parentDir(path))
|
||||
writeFile(path, data)
|
||||
except IOError as e:
|
||||
return failure e
|
||||
except OSError as e:
|
||||
return failure e
|
||||
|
||||
return success()
|
||||
|
||||
# method query*(
|
||||
# self: FSDatastore,
|
||||
# query: ...): Future[?!(?...)] {.async, locks: "unknown".} =
|
||||
#
|
||||
# return success ....some
|
||||
|
||||
proc new*(
|
||||
T: type FSDatastore,
|
||||
root: string,
|
||||
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,
|
||||
ignoreProtected: ignoreProtected)
|
||||
Loading…
x
Reference in New Issue
Block a user