Skip to main content

edgehdf5_memory/
storage.rs

1//! Disk I/O operations for HDF5 memory files.
2//!
3//! Uses memory-mapped I/O via `rustyhdf5_io::MmapReader` for efficient
4//! file reading with OS-managed paging.
5
6use std::path::Path;
7
8use crate::cache::MemoryCache;
9use crate::knowledge::KnowledgeCache;
10use crate::schema;
11use crate::session::SessionCache;
12use crate::MemoryConfig;
13use crate::MemoryError;
14
15/// Write all in-memory state to an HDF5 file on disk.
16pub fn write_to_disk(
17    path: &Path,
18    config: &MemoryConfig,
19    cache: &MemoryCache,
20    sessions: &SessionCache,
21    knowledge: &KnowledgeCache,
22) -> Result<(), MemoryError> {
23    let bytes = schema::build_hdf5_file(config, cache, sessions, knowledge)?;
24
25    // Write to a temp file first, then rename for atomicity
26    let tmp_path = path.with_extension("h5.tmp");
27    std::fs::write(&tmp_path, &bytes).map_err(MemoryError::Io)?;
28    std::fs::rename(&tmp_path, path).map_err(MemoryError::Io)?;
29
30    Ok(())
31}
32
33/// Read an HDF5 file and return all state.
34///
35/// Uses memory-mapped I/O via `rustyhdf5_io::MmapReader` for efficient
36/// file access. The OS pages in data on demand rather than reading the
37/// entire file into a contiguous buffer upfront.
38pub fn read_from_disk(
39    path: &Path,
40) -> Result<(MemoryConfig, MemoryCache, SessionCache, KnowledgeCache), MemoryError> {
41    let mmap = rustyhdf5_io::MmapReader::open(path).map_err(MemoryError::Io)?;
42
43    // Advise the OS we'll need the whole file for parsing
44    mmap.advise_willneed(0, mmap.len());
45
46    // Parse the HDF5 file from the mmap'd bytes
47    let file = rustyhdf5::File::from_bytes(mmap.as_bytes().to_vec())
48        .map_err(|e| MemoryError::Hdf5(format!("cannot open {}: {e}", path.display())))?;
49
50    let (mut config, cache, sessions, knowledge) = schema::validate_and_load(&file)?;
51    config.path = path.to_path_buf();
52
53    Ok((config, cache, sessions, knowledge))
54}
55
56/// Copy an HDF5 file atomically to a destination.
57pub fn snapshot_file(src: &Path, dest: &Path) -> Result<std::path::PathBuf, MemoryError> {
58    let dest_file = if dest.is_dir() {
59        let filename = src.file_name().ok_or_else(|| {
60            MemoryError::Io(std::io::Error::new(
61                std::io::ErrorKind::InvalidInput,
62                "source has no filename",
63            ))
64        })?;
65        let ts = std::time::SystemTime::now()
66            .duration_since(std::time::UNIX_EPOCH)
67            .unwrap_or_default()
68            .as_secs();
69        dest.join(format!("snapshot_{ts}_{}", filename.to_string_lossy()))
70    } else {
71        dest.to_path_buf()
72    };
73
74    // Atomic copy: write to temp, then rename
75    let tmp_path = dest_file.with_extension("h5.tmp");
76    std::fs::copy(src, &tmp_path).map_err(MemoryError::Io)?;
77    std::fs::rename(&tmp_path, &dest_file).map_err(MemoryError::Io)?;
78
79    Ok(dest_file)
80}