From 2390839406196448977752509de5a67c883c0535 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Fri, 16 Sep 2022 21:13:25 -0600 Subject: [PATCH] rename and cleanup fs store --- datastore/filesystem_datastore.nim | 159 --------------------------- datastore/fsstore.nim | 168 +++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 159 deletions(-) delete mode 100644 datastore/filesystem_datastore.nim create mode 100644 datastore/fsstore.nim diff --git a/datastore/filesystem_datastore.nim b/datastore/filesystem_datastore.nim deleted file mode 100644 index 045df35..0000000 --- a/datastore/filesystem_datastore.nim +++ /dev/null @@ -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) diff --git a/datastore/fsstore.nim b/datastore/fsstore.nim new file mode 100644 index 0000000..7254549 --- /dev/null +++ b/datastore/fsstore.nim @@ -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)