Skip to main content

leyline_engine/
store.rs

1//! Local-filesystem store — the embedded mode, and the template for the
2//! object-store backend (phase 4): same layout, same commit protocol, with
3//! rename standing in for conditional PUT.
4
5use std::fs;
6use std::path::{Path, PathBuf};
7
8use crate::manifest::Manifest;
9use crate::{Error, Result};
10
11const MANIFEST: &str = "manifest.json";
12const SEGMENTS_DIR: &str = "segments";
13
14pub struct LocalStore {
15    root: PathBuf,
16}
17
18impl LocalStore {
19    pub fn open(root: impl AsRef<Path>) -> Result<Self> {
20        let root = root.as_ref().to_path_buf();
21        fs::create_dir_all(root.join(SEGMENTS_DIR))?;
22        Ok(Self { root })
23    }
24
25    pub fn load_manifest(&self) -> Result<Option<Manifest>> {
26        let path = self.root.join(MANIFEST);
27        let bytes = match fs::read(&path) {
28            Ok(b) => b,
29            Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
30            Err(e) => return Err(e.into()),
31        };
32        let manifest = serde_json::from_slice(&bytes)
33            .map_err(|e| Error::CorruptManifest(e.to_string()))?;
34        Ok(Some(manifest))
35    }
36
37    /// The commit: write sideways, rename over. Atomic on POSIX and on
38    /// Windows (MoveFileEx with replace). The object-store version of this
39    /// is a conditional PUT on the manifest's ETag.
40    pub fn commit_manifest(&self, manifest: &Manifest) -> Result<()> {
41        let tmp = self.root.join(format!("{MANIFEST}.tmp"));
42        let bytes = serde_json::to_vec_pretty(manifest)
43            .map_err(|e| Error::CorruptManifest(e.to_string()))?;
44        fs::write(&tmp, bytes)?;
45        fs::rename(&tmp, self.root.join(MANIFEST))?;
46        Ok(())
47    }
48
49    /// Segments are uploaded before the manifest that names them: a crash
50    /// between the two leaves an orphan file for the sweep, never a
51    /// dangling reference.
52    pub fn write_segment(&self, name: &str, bytes: &[u8]) -> Result<()> {
53        fs::write(self.root.join(SEGMENTS_DIR).join(name), bytes)?;
54        Ok(())
55    }
56
57    pub fn read_segment(&self, name: &str) -> Result<Vec<u8>> {
58        Ok(fs::read(self.root.join(SEGMENTS_DIR).join(name))?)
59    }
60}