///|
/// Minimal byte-oriented storage boundary for immutable index files.
pub(open) trait Directory {
fn write(Self, String, Bytes) -> Unit raise PersistenceError
fn read(Self, String) -> Bytes raise PersistenceError
fn exists(Self, String) -> Bool
}
///|
fn validate_directory_name(name : String) -> Unit raise PersistenceError {
guard name != "" &&
name != "." &&
name != ".." &&
!name.contains("/") &&
!name.contains("\\") else {
raise PersistenceError::InvalidName(name)
}
}
///|
/// In-memory Directory implementation. Reads and writes copy bytes so a
/// caller cannot mutate an already committed snapshot through an alias.
pub struct MemoryDirectory {
names : Array[String]
contents : Array[Bytes]
}
///|
pub fn MemoryDirectory::new() -> MemoryDirectory {
{ names: [], contents: [] }
}
///|
pub impl Directory for MemoryDirectory with fn write(self, name, bytes) {
validate_directory_name(name)
let snapshot = bytes[:].to_owned()
match self.names.search_by(existing => existing == name) {
Some(index) => self.contents[index] = snapshot
None => {
self.names.push(name)
self.contents.push(snapshot)
}
}
}
///|
pub impl Directory for MemoryDirectory with fn read(self, name) {
validate_directory_name(name)
match self.names.search_by(existing => existing == name) {
Some(index) => self.contents[index][:].to_owned()
None => raise PersistenceError::NotFound(name)
}
}
///|
pub impl Directory for MemoryDirectory with fn exists(self, name) {
try validate_directory_name(name) catch {
_ => false
} noraise {
_ => self.names.search_by(existing => existing == name) is Some(_)
}
}
///|
/// Filesystem-backed Directory rooted at one directory.
pub struct FsDirectory {
root : String
}
///|
fn[T] wrap_fs_error(
operation : () -> T raise @fs.IOError,
) -> T raise PersistenceError {
operation() catch {
@fs.IOError::IOError(message) => raise PersistenceError::Io(message)
}
}
///|
pub fn FsDirectory::new(root : String) -> FsDirectory raise PersistenceError {
guard root != "" else { raise PersistenceError::InvalidName(root) }
if @fs.path_exists(root) {
guard wrap_fs_error(() => @fs.is_dir(root)) else {
raise PersistenceError::Io("directory root is not a directory: \{root}")
}
} else {
wrap_fs_error(() => @fs.create_dir(root))
}
{ root, }
}
///|
fn FsDirectory::path(
self : FsDirectory,
name : String,
) -> String raise PersistenceError {
validate_directory_name(name)
"\{self.root}/\{name}"
}
///|
pub impl Directory for FsDirectory with fn write(self, name, bytes) {
let path = self.path(name)
wrap_fs_error(() => @fs.write_bytes_to_file(path, bytes))
}
///|
pub impl Directory for FsDirectory with fn read(self, name) {
let path = self.path(name)
if !@fs.path_exists(path) {
raise PersistenceError::NotFound(name)
}
wrap_fs_error(() => @fs.read_file_to_bytes(path))
}
///|
pub impl Directory for FsDirectory with fn exists(self, name) {
try self.path(name) catch {
_ => false
} noraise {
path => @fs.path_exists(path)
}
}