1use std::fs::File;
2use std::io::{Read, Write};
3use std::path::Path;
4use std::time::SystemTime;
5
6use serde::{Deserialize, Serialize};
7
8use crate::display::{format_count, format_size};
9use crate::error::{HeftError, Result};
10use crate::tree::TreeNode;
11
12const MAGIC: &[u8; 5] = b"HEFT\x02";
14
15#[derive(Debug, Serialize, Deserialize)]
17pub struct PackMeta {
18 pub root: String,
19 pub timestamp: u64,
20 pub heft_version: String,
21 pub file_count: u64,
22 pub dir_count: u64,
23 pub size_actual: u64,
24}
25
26pub fn pack_tree(
28 tree: &TreeNode,
29 root_path: &str,
30 output: &Path,
31) -> Result<()> {
32 let (file_count, dir_count) = count_nodes(tree);
33 let timestamp = SystemTime::now()
34 .duration_since(SystemTime::UNIX_EPOCH)
35 .map(|d| d.as_secs())
36 .unwrap_or(0);
37
38 let meta = PackMeta {
39 root: root_path.to_string(),
40 timestamp,
41 heft_version: env!("CARGO_PKG_VERSION").to_string(),
42 file_count,
43 dir_count,
44 size_actual: tree.size_actual,
45 };
46
47 let mut file = File::create(output)?;
48
49 file.write_all(MAGIC)?;
51
52 let meta_json = serde_json::to_vec(&meta)?;
54 file.write_all(&(meta_json.len() as u32).to_le_bytes())?;
55 file.write_all(&meta_json)?;
56
57 let mut encoder = zstd::Encoder::new(&mut file, 3)?;
59 serde_json::to_writer(&mut encoder, tree)?;
60 encoder.finish()?;
61
62 let file_size = file.metadata().map(|m| m.len()).unwrap_or(0);
63 println!(
64 "Packed {} ({} files, {} dirs, {}) → {} ({})",
65 root_path,
66 format_count(file_count),
67 format_count(dir_count),
68 format_size(tree.size_actual),
69 output.display(),
70 format_size(file_size),
71 );
72
73 Ok(())
74}
75
76pub fn load_tree(input: &Path) -> Result<(PackMeta, TreeNode)> {
78 let mut file = File::open(input)?;
79
80 let mut magic = [0u8; 5];
82 file.read_exact(&mut magic)?;
83 if &magic != MAGIC {
84 return Err(HeftError::Other(format!(
85 "not a .heft file (bad magic): {}",
86 input.display()
87 )));
88 }
89
90 let mut len_buf = [0u8; 4];
92 file.read_exact(&mut len_buf)?;
93 let meta_len = u32::from_le_bytes(len_buf) as usize;
94 let mut meta_buf = vec![0u8; meta_len];
95 file.read_exact(&mut meta_buf)?;
96 let meta: PackMeta = serde_json::from_slice(&meta_buf)?;
97
98 let decoder = zstd::Decoder::new(&mut file)?;
100 let tree: TreeNode = serde_json::from_reader(decoder)?;
101
102 Ok((meta, tree))
103}
104
105pub fn read_info(input: &Path) -> Result<PackMeta> {
107 let mut file = File::open(input)?;
108
109 let mut magic = [0u8; 5];
110 file.read_exact(&mut magic)?;
111 if &magic != MAGIC {
112 return Err(HeftError::Other(format!(
113 "not a .heft file (bad magic): {}",
114 input.display()
115 )));
116 }
117
118 let mut len_buf = [0u8; 4];
119 file.read_exact(&mut len_buf)?;
120 let meta_len = u32::from_le_bytes(len_buf) as usize;
121 let mut meta_buf = vec![0u8; meta_len];
122 file.read_exact(&mut meta_buf)?;
123
124 Ok(serde_json::from_slice(&meta_buf)?)
125}
126
127fn count_nodes(node: &TreeNode) -> (u64, u64) {
131 let mut files = 0u64;
132 let mut dirs = 1u64; if let Some(children) = &node.children {
134 for child in children {
135 if child.children.is_some() {
136 let (f, d) = count_nodes(child);
137 files += f;
138 dirs += d;
139 } else {
140 files += 1;
141 }
142 }
143 }
144 (files, dirs)
145}