///|
/// Crash-safe file persistence for an index (native only).
/// Protocol: write the snapshot to `index.msk.tmp`, fsync it, then
/// atomically rename over `index.msk`. A crash at any point leaves either
/// the old complete snapshot or no snapshot — never a torn one. The
/// snapshot's own CRC catches any corruption that slips through.
///|
/// Errors from the store layer.
pub(all) suberror StoreError {
IoError(String)
CorruptIndex(String)
} derive(Eq, Debug, Show)
///|
/// Save the index to `path` atomically (write temp + fsync + rename).
pub async fn Index::save(self : Index, path : String) -> Unit raise StoreError {
let image = self.to_bytes()
let tmp = path + ".tmp"
try {
@fs.write_file(tmp, image, sync=Data)
@fs.rename(tmp, path)
} catch {
e => raise StoreError::IoError(e.to_string())
}
}
///|
/// Load an index from `path`. Raises if the file is missing or corrupt.
pub async fn Index::load(path : String) -> Index raise StoreError {
let data = @fs.read_file(path) catch {
e => raise StoreError::IoError(e.to_string())
}
Index::from_bytes(data.binary()) catch {
e => raise StoreError::CorruptIndex(@debug.to_string(e))
}
}
///|
/// Load an index from `path`, or create a fresh one if the file does not
/// exist. A corrupt file is still an error (never silently discarded).
pub async fn Index::load_or_create(
path : String,
schema : Array[FieldSchema],
) -> Index raise StoreError {
let exists = @fs.can_read(path) catch { _ => false }
if exists {
Index::load(path)
} else {
Index::new(schema)
}
}