use std::io::Write;
use mnem_core::id::Cid;
use mnem_core::store::Blockstore;
use crate::car::{CarHeader, usize_to_u64, write_block, write_header};
use crate::error::TransportError;
#[derive(Debug, Clone, Copy, Default)]
pub struct ExportStats {
pub blocks: u64,
pub bytes: u64,
}
#[tracing::instrument(
name = "export",
level = "info",
target = "mnem::transport::export",
skip(bs, w),
fields(
root = %root,
block_count = tracing::field::Empty,
bytes = tracing::field::Empty,
)
)]
pub fn export<W, B>(bs: &B, root: &Cid, w: &mut W) -> Result<ExportStats, TransportError>
where
W: Write + ?Sized,
B: Blockstore + ?Sized,
{
let header = CarHeader::single_root(root.clone());
let mut header_counter = CountingWriter::new(Vec::<u8>::new());
write_header(&mut header_counter, &header)?;
let header_buf = header_counter.into_inner();
let header_len = usize_to_u64(header_buf.len());
w.write_all(&header_buf)?;
let mut block_counter = CountingWriter::new(w);
let mut blocks: u64 = 0;
for item in bs.iter_from_root(root) {
let (cid, data) = item?;
write_block(&mut block_counter, &cid, &data)?;
blocks += 1;
}
let block_bytes = block_counter.count;
let total_bytes = header_len + block_bytes;
let span = tracing::Span::current();
span.record("block_count", blocks);
span.record("bytes", total_bytes);
Ok(ExportStats {
blocks,
bytes: total_bytes,
})
}
struct CountingWriter<W: Write + ?Sized> {
count: u64,
inner: W,
}
impl<W: Write> CountingWriter<W> {
const fn new(inner: W) -> Self {
Self { count: 0, inner }
}
fn into_inner(self) -> W {
self.inner
}
}
impl<W: Write + ?Sized> Write for CountingWriter<W> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let n = self.inner.write(buf)?;
self.count += usize_to_u64(n);
Ok(n)
}
fn flush(&mut self) -> std::io::Result<()> {
self.inner.flush()
}
}
#[cfg(test)]
mod tests {
use super::*;
use mnem_core::codec::hash_to_cid;
use mnem_core::store::MemoryBlockstore;
use serde::Serialize;
#[derive(Serialize)]
struct Leaf {
tag: &'static str,
n: u32,
}
#[test]
fn export_single_leaf_emits_header_plus_one_block() {
let store = MemoryBlockstore::new();
let (bytes, cid) = hash_to_cid(&Leaf { tag: "x", n: 1 }).unwrap();
store.put(cid.clone(), bytes).unwrap();
let mut sink = Vec::new();
let stats = export(&store, &cid, &mut sink).unwrap();
assert_eq!(stats.blocks, 1);
assert_eq!(usize::try_from(stats.bytes).unwrap(), sink.len());
assert!(!sink.is_empty());
}
}