use crate::config::ConfigValues;
use anyhow::anyhow;
use std::path::{Path, PathBuf};
use std::{
fs::{self, File},
io::Write,
sync::{Arc, RwLock},
time::Duration,
};
#[must_use]
pub struct AtomicUpdater<V: ConfigValues> {
config_path: PathBuf,
inner: Arc<RwLock<V>>,
}
fn make_tmp_path(p: &Path) -> PathBuf {
let mut p2 = p.to_path_buf();
let new_name = format!(
"{}.tmp",
p2.file_name()
.expect("config path ended in '..' -- this is not allowed")
.to_str()
.expect("config name was not valid UTF-8")
);
p2.set_file_name(new_name);
p2
}
impl<V: ConfigValues> AtomicUpdater<V> {
pub fn new(config_path: PathBuf, inner: Arc<RwLock<V>>) -> Self {
Self { config_path, inner }
}
pub fn run(self) -> anyhow::Result<()> {
let inner = match self.inner.read() {
Ok(i) => i,
Err(_) => {
return Err(anyhow!("Failed to get config file lock"));
}
};
let tmp_path = make_tmp_path(&self.config_path);
let mut new_f = loop {
match File::create(&tmp_path) {
Ok(f) => break f,
Err(_) => {
std::thread::sleep(Duration::from_millis(10));
}
}
};
let json: String = serde_json::to_string_pretty(&*inner)?;
new_f.write_all(json.as_bytes())?;
fs::rename(&tmp_path, &self.config_path)?;
Ok(())
}
}