use std::path::Path;
use tf_tree_arena::{Arena, FrozenArena, FrozenError, FrozenHeader};
use crate::cbor::Writer;
use crate::tree::Tree;
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum FrozenFileError {
#[error("could not open the .tft path (errno {raw_os_error})")]
Path {
raw_os_error: i32,
},
#[error("{0:?}")]
Frozen(FrozenError),
}
impl From<FrozenError> for FrozenFileError {
fn from(e: FrozenError) -> FrozenFileError {
FrozenFileError::Frozen(e)
}
}
fn path_err(e: &std::io::Error) -> FrozenFileError {
FrozenFileError::Path {
raw_os_error: e.raw_os_error().unwrap_or(0),
}
}
fn temp_sibling(path: &Path) -> std::path::PathBuf {
use std::sync::atomic::{AtomicU32, Ordering};
static N: AtomicU32 = AtomicU32::new(0);
let dir = path.parent().unwrap_or_else(|| Path::new("."));
let stem = path
.file_name()
.unwrap_or_else(|| std::ffi::OsStr::new("out.tft"));
let mut name = std::ffi::OsString::from(".");
name.push(stem);
name.push(format!(
".tmp.{}.{}",
std::process::id(),
N.fetch_add(1, Ordering::Relaxed)
));
dir.join(name)
}
fn tool_version() -> [u8; 32] {
let mut out = [0u8; 32];
let src = env!("CARGO_PKG_VERSION").as_bytes();
let n = src.len().min(32);
out[..n].copy_from_slice(&src[..n]);
out
}
impl Tree {
pub fn open_frozen(path: &Path) -> Result<Tree, FrozenFileError> {
let file = std::fs::File::open(path).map_err(|e| path_err(&e))?;
let arena = FrozenArena::open(file.into())?;
Ok(Tree::from_frozen(arena))
}
pub fn freeze_to(
&self,
path: &Path,
source: Option<&str>,
source_digest: [u8; 32],
created_unix_ns: i64,
) -> Result<FrozenHeader, FrozenFileError> {
let manifest = self.manifest(source, created_unix_ns);
let tmp = temp_sibling(path);
let file = std::fs::File::create(&tmp).map_err(|e| path_err(&e))?;
let arena: &dyn Arena = self.backing();
let written = (|| -> Result<FrozenHeader, FrozenFileError> {
let header = tf_tree_arena::write_frozen(
std::os::fd::AsFd::as_fd(&file),
arena,
&manifest,
source_digest,
created_unix_ns,
tool_version(),
)?;
std::fs::rename(&tmp, path).map_err(|e| path_err(&e))?;
Ok(header)
})();
if written.is_err() {
let _ = std::fs::remove_file(&tmp);
}
written
}
fn manifest(&self, source: Option<&str>, created_unix_ns: i64) -> Vec<u8> {
let view = self.view();
let header = view.header();
let frames = header
.frame_count
.load(std::sync::atomic::Ordering::Acquire);
let edges = header.edge_count.load(std::sync::atomic::Ordering::Acquire);
let mut w = Writer::new();
w.map(7);
w.text("tf_tree");
w.text(env!("CARGO_PKG_VERSION"));
w.text("format_version");
w.u64(u64::from(crate::arena_format_version()));
w.text("layout_hash");
w.u64(u64::from(crate::arena_layout_hash()));
w.text("created_unix_ns");
w.i64(created_unix_ns);
w.text("source");
match source {
Some(s) => w.text(s),
None => w.null(),
}
w.text("frames");
w.array(frames as usize);
for i in 1..=frames {
let name = tf_tree_core::FrameId::new(i)
.and_then(|id| view.frame_record(id))
.map(|r| {
let n = (r.name_len as usize).min(r.name.len());
String::from_utf8_lossy(&r.name[..n]).into_owned()
})
.unwrap_or_default();
w.text(&name);
}
w.text("edges");
w.array(edges.saturating_sub(1) as usize);
for i in 1..edges {
let id = tf_tree_core::EdgeId(i);
let e = view.edge(id);
w.map(8);
w.text("parent");
w.u64(u64::from(e.map_or(0, |e| e.parent)));
w.text("child");
w.u64(u64::from(e.map_or(0, |e| e.child)));
w.text("kind");
w.u64(u64::from(e.map_or(0, |e| e.kind)));
w.text("capacity");
w.u64(u64::from(e.map_or(0, |e| e.capacity)));
let ring = view.ring(id);
w.text("samples");
w.u64(ring.as_ref().map_or(0, |r| r.stored()));
w.text("pushes_total");
w.u64(e.map_or(0, |e| e.head.load(std::sync::atomic::Ordering::Acquire)));
let span = ring
.as_ref()
.and_then(|r| Some((r.oldest_stamp()?, r.newest_stamp()?)));
w.text("oldest_ns");
match span {
Some((oldest, _)) => w.i64(oldest),
None => w.null(),
}
w.text("newest_ns");
match span {
Some((_, newest)) => w.i64(newest),
None => w.null(),
}
}
w.finish()
}
}