use std::collections::HashMap;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use yog_sync::{Arc, Mutex};
#[derive(Default)]
pub(crate) struct Shelf {
pub(crate) path: Option<PathBuf>,
pub(crate) values: yog_data::Map,
pub(crate) dirty: bool,
}
#[derive(Clone, PartialEq, Eq, Hash)]
enum Key {
File(PathBuf),
Session(String),
}
type Shelves = HashMap<Key, Arc<Mutex<Shelf>>>;
static SHELVES: Mutex<Option<Shelves>> = Mutex::new(None);
pub(crate) fn file(path: PathBuf) -> Arc<Mutex<Shelf>> {
open(Key::File(path.clone()), || Shelf {
values: read(&path),
path: Some(path),
dirty: false,
})
}
pub(crate) fn session(mod_id: &str) -> Arc<Mutex<Shelf>> {
open(Key::Session(mod_id.to_owned()), Shelf::default)
}
fn open(key: Key, new: impl FnOnce() -> Shelf) -> Arc<Mutex<Shelf>> {
let mut shelves = SHELVES.lock();
let shelves = shelves.get_or_insert_with(HashMap::new);
shelves
.entry(key)
.or_insert_with(|| Arc::new(Mutex::new(new())))
.clone()
}
pub fn flush_all() -> io::Result<()> {
let open: Vec<_> = SHELVES
.lock()
.iter()
.flat_map(|s| s.values().cloned())
.collect();
let mut result = Ok(());
for shelf in open {
if let Err(e) = shelf.lock().flush()
&& result.is_ok()
{
result = Err(e);
}
}
result
}
pub fn close_under(root: &Path) -> io::Result<()> {
let closing: Vec<_> = {
let mut shelves = SHELVES.lock();
let Some(shelves) = shelves.as_mut() else {
return Ok(());
};
let keys: Vec<Key> = shelves
.keys()
.filter(|k| matches!(k, Key::File(p) if p.starts_with(root)))
.cloned()
.collect();
keys.iter().filter_map(|k| shelves.remove(k)).collect()
};
let mut result = Ok(());
for shelf in closing {
if let Err(e) = shelf.lock().flush()
&& result.is_ok()
{
result = Err(e);
}
}
result
}
impl Shelf {
pub(crate) fn flush(&mut self) -> io::Result<()> {
let Some(path) = &self.path else {
return Ok(());
};
if !self.dirty {
return Ok(());
}
write(path, &self.values)?;
self.dirty = false;
Ok(())
}
}
const TMP_EXT: &str = yog_ids::concatcp!(yog_ids::Format::Data.ext(), yog_ids::sep::DOT, "tmp");
fn write(path: &Path, values: &yog_data::Map) -> io::Result<()> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let tmp = path.with_extension(TMP_EXT);
{
let mut f = std::fs::File::create(&tmp)?;
f.write_all(&yog_data::binary::encode(&values.clone().into()))?;
f.flush()?;
}
std::fs::rename(&tmp, path)
}
fn read(path: &Path) -> yog_data::Map {
let Ok(bytes) = std::fs::read(path) else {
return yog_data::Map::new();
};
match yog_data::binary::decode(&bytes) {
Ok(yog_data::Value::Map(m)) => m,
_ => yog_data::Map::new(),
}
}