//! Read-only generated files. Each open captures an independent finite snapshot. use crate::*; use std::{collections::BTreeMap, io::ErrorKind as E}; type Generator = Arc Result> + Send + Sync>; /// A static tree of generated files. Parent folders are implicit. /// Generators must be cheap and must not block. Stat can invoke them as well as open. #[derive(Default)] pub struct Generated { files: BTreeMap, } impl Generated { pub fn insert( &mut self, path: &str, generate: impl Fn() -> Result> + Send + Sync + 'static, ) { self.files.insert(resolve("/", path), Arc::new(generate)); } fn directory(&self, path: &str) -> bool { path == "/" || self .files .keys() .any(|p| p.starts_with(&format!("{path}/"))) } } struct Snapshot(Vec); fn readonly() -> Result { Err(error(E::ReadOnlyFilesystem)) } #[async_trait] impl Layer for Generated { async fn stat(&self, path: &str, _follow: bool) -> Result { if let Some(f) = self.files.get(path) { return Snapshot(f()?).stat().await; } if !self.directory(path) { return Err(error(E::NotFound)); } Ok(Stat { directory: true, symlink: false, size: 0, accessed: None, modified: None, }) } async fn list(&self, path: &str) -> Result> { if !self.directory(path) { return Err(error(if self.files.contains_key(path) { E::NotADirectory } else { E::NotFound })); } let prefix = if path == "/" { "/".into() } else { format!("{path}/") }; let mut entries = BTreeMap::new(); for p in self.files.keys() { if let Some(rest) = p.strip_prefix(&prefix) { let (name, directory) = rest .split_once('/') .map_or((rest, false), |(name, _)| (name, true)); entries.insert(name.to_string(), directory); } } Ok(entries .into_iter() .map(|(name, directory)| Entry { name, directory, symlink: false, }) .collect()) } async fn open(&self, path: &str, o: Open) -> Result> { if o.write || o.create || o.truncate { return readonly(); } if let Some(f) = self.files.get(path) { if o.directory { return Err(error(E::NotADirectory)); } return Ok(Arc::new(Snapshot(f()?))); } if self.directory(path) { return Memory::default().open("/", o).await; } Err(error(E::NotFound)) } async fn mkdir(&self, _: &str) -> Result<()> { readonly() } async fn remove_file(&self, _: &str) -> Result<()> { readonly() } async fn remove_dir(&self, _: &str) -> Result<()> { readonly() } async fn rename(&self, _: &str, _: &str) -> Result<()> { readonly() } } #[async_trait] impl File for Snapshot { async fn stat(&self) -> Result { Ok(Stat { directory: false, symlink: false, size: self.0.len() as u64, accessed: None, modified: None, }) } async fn read(&self, offset: u64, length: usize) -> Result> { let start = usize::try_from(offset) .unwrap_or(usize::MAX) .min(self.0.len()); Ok(self.0[start..start.saturating_add(length).min(self.0.len())].to_vec()) } async fn write(&self, _: u64, _: Vec) -> Result { readonly() } async fn append(&self, _: Vec) -> Result { readonly() } async fn set_size(&self, _: u64) -> Result<()> { readonly() } async fn sync(&self) -> Result<()> { Ok(()) } async fn set_times(&self, _: Option, _: Option) -> Result<()> { readonly() } }