use std::fs;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use crate::storage::cas::CasStore;
use super::lease::{GenerationLease, read_current_generation, read_generation_manifest};
use super::manifest::{LayerKind, Manifest};
use super::reader::{NeuralReader, PdgReader, SymbolReader, TfidfReader};
#[derive(Debug, thiserror::Error)]
pub enum SnapshotError {
#[error("no current generation for storage root {0}")]
NoCurrentGeneration(PathBuf),
#[error("manifest error: {0}")]
Manifest(#[from] super::manifest::ManifestError),
#[error("lease error: {0}")]
Lease(#[from] super::lease::LeaseError),
#[error("cas error: {0}")]
Cas(#[from] crate::storage::cas::CasError),
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("reader error: {0}")]
Reader(#[from] super::reader::ReaderError),
#[error("generation {generation} is missing the Db layer")]
MissingDbLayer {
generation: u64,
},
}
pub struct GenerationSnapshot {
storage_root: PathBuf,
manifest: Manifest,
_lease: GenerationLease,
_cas: Arc<Mutex<CasStore>>,
_db_tempdir: tempfile::TempDir,
db_path: PathBuf,
neural: Option<NeuralReader>,
tfidf: Option<TfidfReader>,
pdg: Option<PdgReader>,
symbols: Option<SymbolReader>,
}
impl GenerationSnapshot {
pub fn open(storage_root: impl AsRef<Path>) -> Result<Self, SnapshotError> {
let storage_root = storage_root.as_ref().to_path_buf();
let generation = read_current_generation(&storage_root)
.ok_or_else(|| SnapshotError::NoCurrentGeneration(storage_root.clone()))?;
let manifest = read_generation_manifest(&storage_root, generation)?;
let cas = Arc::new(Mutex::new(CasStore::open(storage_root.join("cas"))?));
let lease = GenerationLease::acquire(cas.clone(), &manifest)?;
let db_hash = manifest
.layers
.get(&LayerKind::Db)
.copied()
.ok_or(SnapshotError::MissingDbLayer { generation })?;
let db_bytes = {
let store = cas.lock().expect("cas store mutex poisoned");
store.get(&db_hash)?
};
let db_tempdir = tempfile::tempdir()?;
let db_path = db_tempdir.path().join("leindex.db");
fs::write(&db_path, &db_bytes)?;
let blob = |cas: &Arc<Mutex<CasStore>>, hash: &[u8; 32]| {
cas.lock()
.expect("cas store mutex poisoned")
.blob_path(hash)
};
let neural = manifest
.layers
.get(&LayerKind::Neural)
.map(|h| NeuralReader::open(&blob(&cas, h)))
.transpose()?;
let tfidf = manifest
.layers
.get(&LayerKind::Tfidf)
.map(|h| TfidfReader::open(&blob(&cas, h)))
.transpose()?;
let pdg = manifest
.layers
.get(&LayerKind::Pdg)
.map(|h| PdgReader::open(&blob(&cas, h)))
.transpose()?;
let symbols = manifest
.layers
.get(&LayerKind::Symbols)
.map(|h| SymbolReader::open(&blob(&cas, h)))
.transpose()?;
Ok(GenerationSnapshot {
storage_root,
manifest,
_lease: lease,
_cas: cas,
_db_tempdir: db_tempdir,
db_path,
neural,
tfidf,
pdg,
symbols,
})
}
pub fn generation(&self) -> u64 {
self.manifest.generation
}
pub fn manifest(&self) -> &Manifest {
&self.manifest
}
pub fn storage_root(&self) -> &Path {
&self.storage_root
}
pub fn db_path(&self) -> &Path {
&self.db_path
}
pub fn artifact_dir(&self) -> &Path {
self._db_tempdir.path()
}
pub fn neural(&self) -> Option<&NeuralReader> {
self.neural.as_ref()
}
pub fn tfidf(&self) -> Option<&TfidfReader> {
self.tfidf.as_ref()
}
pub fn pdg(&self) -> Option<&PdgReader> {
self.pdg.as_ref()
}
pub fn symbols(&self) -> Option<&SymbolReader> {
self.symbols.as_ref()
}
pub fn layer_bytes(&self, kind: LayerKind) -> Result<Option<Vec<u8>>, SnapshotError> {
let Some(hash) = self.manifest.layers.get(&kind) else {
return Ok(None);
};
let store = self._cas.lock().expect("cas store mutex poisoned");
Ok(Some(store.get(hash)?))
}
}
pub fn manifest_has_neural_vectors(manifest: &Manifest) -> bool {
let empty = crate::storage::cas::blob::blob_hash(&super::migrate::encode_empty_neural());
manifest
.layers
.get(&LayerKind::Neural)
.is_some_and(|hash| *hash != empty)
}
#[cfg(test)]
#[path = "snapshot_test.rs"]
mod tests;