1use std::collections::HashMap;
6use std::io::{self, Write};
7use std::path::{Path, PathBuf};
8
9use yog_sync::{Arc, Mutex};
10
11#[derive(Default)]
13pub(crate) struct Shelf {
14 pub(crate) path: Option<PathBuf>,
16 pub(crate) values: yog_data::Map,
17 pub(crate) dirty: bool,
18}
19
20#[derive(Clone, PartialEq, Eq, Hash)]
22enum Key {
23 File(PathBuf),
24 Session(String),
25}
26
27type Shelves = HashMap<Key, Arc<Mutex<Shelf>>>;
28
29static SHELVES: Mutex<Option<Shelves>> = Mutex::new(None);
30
31pub(crate) fn file(path: PathBuf) -> Arc<Mutex<Shelf>> {
33 open(Key::File(path.clone()), || Shelf {
34 values: read(&path),
35 path: Some(path),
36 dirty: false,
37 })
38}
39
40pub(crate) fn session(mod_id: &str) -> Arc<Mutex<Shelf>> {
42 open(Key::Session(mod_id.to_owned()), Shelf::default)
43}
44
45fn open(key: Key, new: impl FnOnce() -> Shelf) -> Arc<Mutex<Shelf>> {
46 let mut shelves = SHELVES.lock();
47 let shelves = shelves.get_or_insert_with(HashMap::new);
48 shelves
49 .entry(key)
50 .or_insert_with(|| Arc::new(Mutex::new(new())))
51 .clone()
52}
53
54pub fn flush_all() -> io::Result<()> {
56 let open: Vec<_> = SHELVES
57 .lock()
58 .iter()
59 .flat_map(|s| s.values().cloned())
60 .collect();
61 let mut result = Ok(());
62 for shelf in open {
63 if let Err(e) = shelf.lock().flush()
64 && result.is_ok()
65 {
66 result = Err(e);
67 }
68 }
69 result
70}
71
72pub fn close_under(root: &Path) -> io::Result<()> {
75 let closing: Vec<_> = {
76 let mut shelves = SHELVES.lock();
77 let Some(shelves) = shelves.as_mut() else {
78 return Ok(());
79 };
80 let keys: Vec<Key> = shelves
81 .keys()
82 .filter(|k| matches!(k, Key::File(p) if p.starts_with(root)))
83 .cloned()
84 .collect();
85 keys.iter().filter_map(|k| shelves.remove(k)).collect()
86 };
87 let mut result = Ok(());
88 for shelf in closing {
89 if let Err(e) = shelf.lock().flush()
90 && result.is_ok()
91 {
92 result = Err(e);
93 }
94 }
95 result
96}
97
98impl Shelf {
99 pub(crate) fn flush(&mut self) -> io::Result<()> {
101 let Some(path) = &self.path else {
102 return Ok(());
103 };
104 if !self.dirty {
105 return Ok(());
106 }
107 write(path, &self.values)?;
108 self.dirty = false;
109 Ok(())
110 }
111}
112
113const TMP_EXT: &str = yog_ids::concatcp!(yog_ids::Format::Data.ext(), yog_ids::sep::DOT, "tmp");
115
116fn write(path: &Path, values: &yog_data::Map) -> io::Result<()> {
117 if let Some(parent) = path.parent() {
118 std::fs::create_dir_all(parent)?;
119 }
120 let tmp = path.with_extension(TMP_EXT);
121 {
122 let mut f = std::fs::File::create(&tmp)?;
123 f.write_all(&yog_data::binary::encode(&values.clone().into()))?;
124 f.flush()?;
125 }
126 std::fs::rename(&tmp, path)
127}
128
129fn read(path: &Path) -> yog_data::Map {
132 let Ok(bytes) = std::fs::read(path) else {
133 return yog_data::Map::new();
134 };
135 match yog_data::binary::decode(&bytes) {
136 Ok(yog_data::Value::Map(m)) => m,
137 _ => yog_data::Map::new(),
138 }
139}