use std::fmt;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StorageMode {
Memory,
Disk,
}
impl StorageMode {
pub fn as_str(self) -> &'static str {
match self {
StorageMode::Memory => "memory",
StorageMode::Disk => "disk",
}
}
}
impl fmt::Display for StorageMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl std::str::FromStr for StorageMode {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"memory" => Ok(StorageMode::Memory),
"disk" => Ok(StorageMode::Disk),
other => Err(format!(
"unknown storage mode '{other}'; expected memory|disk"
)),
}
}
}
#[derive(Debug, Clone)]
pub struct Workdir {
root: PathBuf,
}
impl Workdir {
pub fn new(root: impl Into<PathBuf>) -> Self {
Self { root: root.into() }
}
pub fn root(&self) -> &Path {
&self.root
}
pub fn csv_dir(&self) -> PathBuf {
self.root.join("csv")
}
pub fn csv_path(&self, stem: &str) -> PathBuf {
self.csv_dir().join(format!("{stem}.csv"))
}
pub fn index_file(&self) -> PathBuf {
self.root.join("sodir_index.json")
}
pub fn complement_file(&self) -> PathBuf {
self.root.join("blueprint_complement.json")
}
pub fn compiled_blueprint(&self) -> PathBuf {
self.root.join("_compiled_blueprint.json")
}
pub fn graph_dir(&self) -> PathBuf {
self.root.join("graph")
}
pub fn disk_graph_meta(&self) -> PathBuf {
self.graph_dir().join("disk_graph_meta.json")
}
pub fn source_meta(&self) -> PathBuf {
self.graph_dir().join("sodir_source.json")
}
pub fn graph_exists(&self) -> bool {
self.disk_graph_meta().is_file()
}
pub fn disk_graph_age_days(&self) -> Option<f64> {
crate::datasets::sodir::index::file_mtime_age_days(&self.disk_graph_meta())
}
pub fn ensure_dirs(&self) -> std::io::Result<()> {
std::fs::create_dir_all(self.csv_dir())?;
std::fs::create_dir_all(self.graph_dir())?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn paths_are_well_formed() {
let w = Workdir::new("/tmp/sodir");
assert_eq!(w.csv_dir(), Path::new("/tmp/sodir/csv"));
assert_eq!(w.csv_path("field"), Path::new("/tmp/sodir/csv/field.csv"));
assert_eq!(w.index_file(), Path::new("/tmp/sodir/sodir_index.json"));
assert_eq!(w.graph_dir(), Path::new("/tmp/sodir/graph"));
assert_eq!(
w.disk_graph_meta(),
Path::new("/tmp/sodir/graph/disk_graph_meta.json")
);
}
#[test]
fn storage_mode_roundtrip() {
for m in [StorageMode::Memory, StorageMode::Disk] {
assert_eq!(m.as_str().parse::<StorageMode>().unwrap(), m);
}
assert!("mapped".parse::<StorageMode>().is_err());
}
#[test]
fn ensure_dirs_idempotent_and_graph_probe() {
let tmp = tempfile::tempdir().unwrap();
let w = Workdir::new(tmp.path());
assert!(!w.graph_exists());
w.ensure_dirs().unwrap();
w.ensure_dirs().unwrap(); assert!(w.csv_dir().is_dir());
assert!(w.graph_dir().is_dir());
assert!(!w.graph_exists()); }
}