use super::{content_hash, default_root, parse_or_default};
use serde_json::{Map, Value};
use std::fs;
use std::io;
use std::path::PathBuf;
pub(super) struct Doc {
pub(super) path: PathBuf,
pub(super) root: Map<String, Value>,
pub(super) last_hash: Option<u64>,
}
impl Doc {
pub(super) fn open(path: PathBuf) -> Self {
let (root, last_hash) = match fs::read(&path) {
Ok(bytes) => (parse_or_default(&bytes), Some(content_hash(&bytes))),
Err(_) => (default_root(), None),
};
Self {
path,
root,
last_hash,
}
}
pub(super) fn save(&mut self) {
let bytes = self.serialize();
let hash = content_hash(&bytes);
if self.last_hash == Some(hash) {
return;
}
if self.write_atomic(&bytes).is_ok() {
self.last_hash = Some(hash);
}
}
fn serialize(&self) -> Vec<u8> {
serde_json::to_vec_pretty(&self.root).unwrap_or_default()
}
fn write_atomic(&self, bytes: &[u8]) -> io::Result<()> {
let dir = self.path.parent().ok_or(io::Error::other("no parent"))?;
fs::create_dir_all(dir)?;
let name = self
.path
.file_name()
.ok_or(io::Error::other("no file name"))?;
let tmp = crate::scratch::temp_in(dir, &name.to_string_lossy());
fs::write(&tmp, bytes)?;
fs::rename(&tmp, &self.path)
}
}