use std::collections::hash_map::RandomState;
use std::fs::{File, OpenOptions};
use std::hash::BuildHasher;
use std::io::Write;
use std::path::{Path, PathBuf};
pub(crate) struct TempChart {
file: File,
path: PathBuf,
}
impl TempChart {
pub(crate) fn path(&self) -> &Path {
&self.path
}
pub(crate) fn write_html(&mut self, html: &str) -> Result<(), String> {
self.file
.write_all(html.as_bytes())
.map_err(|error| format!("failed to write chart file: {error}"))?;
self.file
.sync_all()
.map_err(|error| format!("failed to persist chart file: {error}"))
}
}
pub(crate) fn reserve_temp_chart() -> Result<TempChart, String> {
let temp = std::env::temp_dir();
for attempt in 0..16_u64 {
let nonce = RandomState::new().hash_one((std::process::id(), attempt));
let path = temp.join(format!("saya-chart-{nonce:016x}.html"));
let mut options = OpenOptions::new();
options.write(true).create_new(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt;
options.mode(0o600);
}
match options.open(&path) {
Ok(file) => {
super::cleanup::record_temp_chart(&path);
return Ok(TempChart { file, path });
}
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => continue,
Err(error) => return Err(format!("failed to create chart file: {error}")),
}
}
Err("failed to allocate a unique chart file".into())
}
#[cfg(test)]
pub(crate) fn lock_charts_for_test() -> std::sync::MutexGuard<'static, ()> {
use std::sync::Mutex;
static LOCK: Mutex<()> = Mutex::new(());
LOCK.lock().unwrap()
}
#[cfg(test)]
#[path = "temp_chart_tests.rs"]
mod tests;