leyline-engine 0.1.0

Storage engine: IDs, segments, CSR adjacency, manifest, LSM
Documentation
//! Local-filesystem store — the embedded mode, and the template for the
//! object-store backend (phase 4): same layout, same commit protocol, with
//! rename standing in for conditional PUT.

use std::fs;
use std::path::{Path, PathBuf};

use crate::manifest::Manifest;
use crate::{Error, Result};

const MANIFEST: &str = "manifest.json";
const SEGMENTS_DIR: &str = "segments";

pub struct LocalStore {
    root: PathBuf,
}

impl LocalStore {
    pub fn open(root: impl AsRef<Path>) -> Result<Self> {
        let root = root.as_ref().to_path_buf();
        fs::create_dir_all(root.join(SEGMENTS_DIR))?;
        Ok(Self { root })
    }

    pub fn load_manifest(&self) -> Result<Option<Manifest>> {
        let path = self.root.join(MANIFEST);
        let bytes = match fs::read(&path) {
            Ok(b) => b,
            Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
            Err(e) => return Err(e.into()),
        };
        let manifest = serde_json::from_slice(&bytes)
            .map_err(|e| Error::CorruptManifest(e.to_string()))?;
        Ok(Some(manifest))
    }

    /// The commit: write sideways, rename over. Atomic on POSIX and on
    /// Windows (MoveFileEx with replace). The object-store version of this
    /// is a conditional PUT on the manifest's ETag.
    pub fn commit_manifest(&self, manifest: &Manifest) -> Result<()> {
        let tmp = self.root.join(format!("{MANIFEST}.tmp"));
        let bytes = serde_json::to_vec_pretty(manifest)
            .map_err(|e| Error::CorruptManifest(e.to_string()))?;
        fs::write(&tmp, bytes)?;
        fs::rename(&tmp, self.root.join(MANIFEST))?;
        Ok(())
    }

    /// Segments are uploaded before the manifest that names them: a crash
    /// between the two leaves an orphan file for the sweep, never a
    /// dangling reference.
    pub fn write_segment(&self, name: &str, bytes: &[u8]) -> Result<()> {
        fs::write(self.root.join(SEGMENTS_DIR).join(name), bytes)?;
        Ok(())
    }

    pub fn read_segment(&self, name: &str) -> Result<Vec<u8>> {
        Ok(fs::read(self.root.join(SEGMENTS_DIR).join(name))?)
    }
}