///|
/// Filesystem-backed storage for native runtime.
///|
pub struct NativeFileStorage {
root_dir : String
}
///|
pub fn NativeFileStorage::new(root_dir : String) -> NativeFileStorage {
{ root_dir, }
}
///|
fn kind_dir(kind : StorageKind) -> String {
match kind {
Config => "config"
Index => "index"
Data => "data"
}
}
///|
fn join_path(base : String, child : String) -> String {
@path.Path::join(base, child).to_string()
}
///|
fn dirname(path : String) -> String {
@path.Path::dirname(path).to_string()
}
///|
fn raise_io_error(e : @fs.IOError) -> Unit raise IOError {
// Pattern-match the payload instead of calling to_string: the Show-based
// helper was removed from @fs.IOError in newer moonbitlang/x releases.
match e {
@fs.IOError(msg) => raise IoFailed(msg)
}
}
///|
fn ensure_dir(path : String) -> Unit raise IOError {
if path.length() == 0 || path == "." || path == "/" {
return
}
let chars = path.to_array()
let current : Array[Char] = []
let mut start = 0
if chars.length() > 0 && chars[0] == '/' {
current.push('/')
start = 1
}
for i in start.. 0 && part != "/" && !@fs.path_exists(part) {
@fs.create_dir(part) catch {
e => raise_io_error(e)
}
}
}
}
let final_path = String::from_array(current)
if final_path.length() > 0 &&
final_path != "/" &&
!@fs.path_exists(final_path) {
@fs.create_dir(final_path) catch {
e => raise_io_error(e)
}
}
}
///|
fn NativeFileStorage::full_path(
self : NativeFileStorage,
file_path : String,
kind : StorageKind,
) -> String {
@path.Path::join(@path.Path::join(self.root_dir, kind_dir(kind)), file_path)
.normalize()
.to_string()
}
///|
fn walk_dir(
abs_dir : String,
rel_dir : String,
files : Array[String],
) -> Unit raise IOError {
if !@fs.path_exists(abs_dir) {
return
}
let is_root_dir = @fs.is_dir(abs_dir) catch {
e => {
raise_io_error(e)
false
}
}
if !is_root_dir {
raise IoFailed(abs_dir + " is not a directory")
}
let entries = @fs.read_dir(abs_dir) catch {
e => {
raise_io_error(e)
[]
}
}
for entry in entries {
let abs_path = join_path(abs_dir, entry)
let rel_path = if rel_dir.length() == 0 {
entry
} else {
rel_dir + "/" + entry
}
let is_dir = @fs.is_dir(abs_path) catch {
e => {
raise_io_error(e)
false
}
}
if is_dir {
walk_dir(abs_path, rel_path, files)
continue
}
let is_file = @fs.is_file(abs_path) catch {
e => {
raise_io_error(e)
false
}
}
if is_file {
files.push(rel_path)
}
}
}
///|
pub fn NativeFileStorage::read(
self : NativeFileStorage,
path : String,
kind : StorageKind,
) -> Bytes raise IOError {
let full = self.full_path(path, kind)
@fs.read_file_to_bytes(full) catch {
e => {
raise_io_error(e)
Bytes::new(0)
}
}
}
///|
pub fn NativeFileStorage::write(
self : NativeFileStorage,
path : String,
data : Bytes,
kind : StorageKind,
) -> Unit raise IOError {
let full = self.full_path(path, kind)
ensure_dir(dirname(full))
@fs.write_bytes_to_file(full, data) catch {
e => raise_io_error(e)
}
}
///|
pub fn NativeFileStorage::append(
self : NativeFileStorage,
path : String,
data : Bytes,
kind : StorageKind,
) -> Unit raise IOError {
let full = self.full_path(path, kind)
let existing = if @fs.path_exists(full) {
@fs.read_file_to_bytes(full) catch {
e => {
raise_io_error(e)
Bytes::new(0)
}
}
} else {
Bytes::new(0)
}
self.write(path, concat_bytes(existing, data), kind)
}
///|
pub fn NativeFileStorage::atomic_write(
self : NativeFileStorage,
path : String,
data : Bytes,
kind : StorageKind,
) -> Unit raise IOError {
self.write(path, data, kind)
}
///|
pub fn NativeFileStorage::del(
self : NativeFileStorage,
path : String,
kind : StorageKind,
) -> Unit raise IOError {
let full = self.full_path(path, kind)
@fs.remove_file(full) catch {
e => raise_io_error(e)
}
}
///|
pub fn NativeFileStorage::exists(
self : NativeFileStorage,
path : String,
kind : StorageKind,
) -> Bool {
@fs.path_exists(self.full_path(path, kind))
}
///|
pub fn NativeFileStorage::list(
self : NativeFileStorage,
kind : StorageKind,
) -> Array[String] raise IOError {
let files : Array[String] = []
let root = join_path(self.root_dir, kind_dir(kind))
walk_dir(root, "", files)
files
}
///|
pub impl Storage for NativeFileStorage with fn read(self, path, kind) {
self.read(path, kind)
}
///|
pub impl Storage for NativeFileStorage with fn write(self, path, data, kind) {
self.write(path, data, kind)
}
///|
pub impl Storage for NativeFileStorage with fn append(self, path, data, kind) {
self.append(path, data, kind)
}
///|
pub impl Storage for NativeFileStorage with fn atomic_write(
self,
path,
data,
kind,
) {
self.atomic_write(path, data, kind)
}
///|
pub impl Storage for NativeFileStorage with fn del(self, path, kind) {
self.del(path, kind)
}
///|
pub impl Storage for NativeFileStorage with fn exists(self, path, kind) {
self.exists(path, kind)
}
///|
pub impl Storage for NativeFileStorage with fn list(self, kind) {
self.list(kind)
}
///|
fn io_error_message(e : IOError) -> String {
match e {
NotFound(s) => "Not found: " + s
PermissionDenied(s) => "Permission denied: " + s
IoFailed(s) => "IO failed: " + s
}
}
///|
/// AsyncStorage implementation for NativeFileStorage.
/// Wraps synchronous filesystem I/O — resolve is called immediately.
/// This enables NativeFileStorage to be used with PersistentVectorDB
/// and all async persistence infrastructure on native targets.
pub impl AsyncStorage for NativeFileStorage with fn async_read(
self,
path,
kind,
resolve,
reject,
) {
let data = self.read(path, kind) catch {
e => {
reject(io_error_message(e))
return
}
}
resolve(data)
}
///|
pub impl AsyncStorage for NativeFileStorage with fn async_write(
self,
path,
data,
kind,
resolve,
reject,
) {
self.write(path, data, kind) catch {
e => {
reject(io_error_message(e))
return
}
}
resolve()
}
///|
pub impl AsyncStorage for NativeFileStorage with fn async_atomic_write(
self,
path,
data,
kind,
resolve,
reject,
) {
self.atomic_write(path, data, kind) catch {
e => {
reject(io_error_message(e))
return
}
}
resolve()
}
///|
pub impl AsyncStorage for NativeFileStorage with fn async_del(
self,
path,
kind,
resolve,
reject,
) {
self.del(path, kind) catch {
e => {
reject(io_error_message(e))
return
}
}
resolve()
}
///|
pub impl AsyncStorage for NativeFileStorage with fn async_exists(
self,
path,
kind,
resolve,
_reject,
) {
resolve(self.exists(path, kind))
}
///|
pub impl AsyncStorage for NativeFileStorage with fn async_list(
self,
kind,
resolve,
reject,
) {
let files = self.list(kind) catch {
e => {
reject(io_error_message(e))
return
}
}
resolve(files)
}