use crate::sync::{AtomicU32, Ordering};
use alloc::string::{String, ToString};
#[cfg(std_io)]
use alloc::vec::Vec;
use crate::persistence::{StoreKey, StoreValue};
use crate::sync::{Arc, Lazy, Mutex};
pub use crate::persistence::{CacheOption, Namespace, Store, StoreOptions};
pub const DEFAULT: &str = "default";
#[cfg(std_io)]
pub const EXTENSION: &str = "db";
#[derive(Debug, Clone)]
struct Active {
name: Arc<str>,
#[cfg(std_io)]
root: Option<std::path::PathBuf>,
#[cfg(std_io)]
file: Option<std::path::PathBuf>,
}
static ACTIVE: Lazy<Mutex<Active>> = Lazy::new(|| {
Mutex::new(Active {
name: Arc::from(DEFAULT),
#[cfg(std_io)]
root: None,
#[cfg(std_io)]
file: None,
})
});
static GENERATION: AtomicU32 = AtomicU32::new(0);
pub fn generation() -> u32 {
GENERATION.load(Ordering::Relaxed)
}
fn switched() {
GENERATION.fetch_add(1, Ordering::Relaxed);
}
#[cfg(std_io)]
fn active_state() -> Active {
ACTIVE.lock().clone()
}
pub fn activate<N: AsRef<str>>(name: N) {
let name = sanitize(name.as_ref());
log::debug!("Activating environment '{name}'");
let mut active = ACTIVE.lock();
active.name = name.into();
#[cfg(std_io)]
{
active.file = None;
}
switched();
}
#[cfg(std_io)]
pub(crate) fn scope() -> String {
path().display().to_string()
}
#[cfg(not(std_io))]
pub(crate) fn scope() -> String {
active().to_string()
}
pub fn active() -> Arc<str> {
ACTIVE.lock().name.clone()
}
#[cfg(std_io)]
pub fn set_root<P: Into<std::path::PathBuf>>(root: P) {
let root = root.into();
log::debug!("Environments rooted at {root:?}");
let mut active = ACTIVE.lock();
active.root = Some(root);
active.file = None;
switched();
}
#[cfg(std_io)]
pub fn load<P: Into<std::path::PathBuf>>(file: P) {
let file = file.into();
log::debug!("Loading environment from {file:?}");
let mut active = ACTIVE.lock();
active.file = Some(file);
switched();
}
#[cfg(std_io)]
pub fn root() -> std::path::PathBuf {
active_state().root_or_default()
}
#[cfg(std_io)]
pub fn path() -> std::path::PathBuf {
let active = active_state();
match active.file.clone() {
Some(file) => file,
None => {
let name = active.name.clone();
active.root_or_default().join(file_name(&name))
}
}
}
#[cfg(std_io)]
impl Active {
fn root_or_default(self) -> std::path::PathBuf {
self.root
.unwrap_or_else(|| crate::persistence::CacheConfig::default().root())
}
}
#[cfg(std_io)]
pub fn file_name(name: &str) -> String {
alloc::format!("{}.{EXTENSION}", sanitize(name))
}
fn sanitize(name: &str) -> String {
let cleaned: String = name
.chars()
.map(|c| {
if c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.' {
c
} else {
'_'
}
})
.collect();
if cleaned.is_empty() || cleaned.chars().all(|c| c == '.') {
return DEFAULT.to_string();
}
cleaned
}
#[cfg(std_io)]
pub fn list() -> Vec<String> {
let Ok(entries) = std::fs::read_dir(root()) else {
return Vec::new();
};
let mut names: Vec<String> = entries
.filter_map(|entry| {
let path = entry.ok()?.path();
if path.extension()? != EXTENSION {
return None;
}
Some(path.file_stem()?.to_string_lossy().to_string())
})
.collect();
names.sort();
names
}
pub fn store<K: StoreKey, V: StoreValue>(options: StoreOptions) -> Store<K, V> {
Store::new(options)
}
#[cfg(native_cache)]
#[derive(Debug, Clone)]
pub struct Bundle {
source: std::path::PathBuf,
name: String,
}
#[cfg(native_cache)]
pub fn bundle() -> Bundle {
Bundle {
source: path(),
name: active().to_string(),
}
}
#[cfg(native_cache)]
impl Bundle {
pub fn save<P: AsRef<std::path::Path>>(
&self,
out: P,
format: crate::bundle::BundleFormat,
) -> Result<crate::bundle::BundleManifest, crate::bundle::BundleError> {
let options = crate::bundle::ExportOptions {
name: self.name.clone(),
format,
..Default::default()
};
crate::bundle::export(&[&self.source], out, &options)
}
}
#[cfg(std_io)]
pub fn namespaces() -> Vec<crate::persistence::NamespaceSummary> {
#[cfg(native_cache)]
match crate::persistence::Database::open_active() {
Some(database) => database.summary(),
None => crate::persistence::MemoryStorage::namespaces(),
}
#[cfg(not(native_cache))]
crate::persistence::MemoryStorage::namespaces()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_name_can_never_escape_the_cache_root() {
assert_eq!(sanitize("../../etc/passwd"), ".._.._etc_passwd");
assert_eq!(sanitize("a/b"), "a_b");
assert_eq!(sanitize(""), DEFAULT);
assert_eq!(sanitize(".."), DEFAULT);
assert_eq!(sanitize("h100-linux"), "h100-linux");
}
}