hexz_cli/cmd/data/
convert.rs1use crate::ui::progress::create_progress_bar;
7use anyhow::{Context, Result, bail};
8use hexz_core::algo::compression::create_compressor_from_str;
9use hexz_ops::archive_writer::ArchiveWriter;
10use std::io::Read;
11use std::path::Path;
12use std::sync::{Arc, Mutex};
13use colored::Colorize;
14
15#[allow(clippy::too_many_arguments)]
17pub fn run(
18 format: &str,
19 input: &Path,
20 output: &Path,
21 compression: &str,
22 block_size: u32,
23 silent: bool,
24) -> Result<()> {
25 if !silent {
26 println!("{} Converting {}", "╭".dimmed(), input.display().to_string().cyan());
27 println!("{} Format {}", "│".dimmed(), format.bright_black());
28 println!("{} Output {}", "╰".dimmed(), output.display().to_string().bright_black());
29 println!();
30 }
31 match format.to_lowercase().as_str() {
32 "tar" => convert_tar(input, output, compression, block_size, silent),
33 other => bail!("Unknown format: {other:?}. Supported formats: tar"),
34 }
35}
36
37fn convert_tar(
42 input: &Path,
43 output: &Path,
44 compression: &str,
45 block_size: u32,
46 silent: bool,
47) -> Result<()> {
48 let total_size = std::fs::metadata(input)
54 .with_context(|| format!("Cannot read input file: {}", input.display()))?
55 .len();
56
57 let pb = if silent {
59 None
60 } else {
61 let pb = create_progress_bar(total_size);
62 Some(Arc::new(Mutex::new(pb)))
63 };
64
65 let (compressor, compression_type) =
67 create_compressor_from_str(compression, None, None).map_err(|e| anyhow::anyhow!("{e}"))?;
68
69 let mut writer = ArchiveWriter::builder(output, compressor, compression_type)
70 .block_size(block_size)
71 .build()
72 .map_err(|e| anyhow::anyhow!("{e}"))?;
73
74 let file = std::fs::File::open(input)
76 .with_context(|| format!("Cannot open tar file: {}", input.display()))?;
77
78 let mut archive = tar::Archive::new(file);
79
80 let mut source_files: Vec<serde_json::Value> = Vec::new();
82 let mut total_bytes: u64 = 0;
83 let mut bytes_from_archive: u64 = 0;
84
85 writer.begin_stream(true, total_size);
89
90 for entry_result in archive.entries()? {
91 let mut entry = entry_result?;
92 let header = entry.header();
93
94 if !header.entry_type().is_file() {
96 continue;
97 }
98
99 let name = entry.path()?.to_string_lossy().to_string();
100 let size = header.size()?;
101
102 let mut remaining = size;
104 let mut buf = vec![0u8; block_size as usize];
105
106 while remaining > 0 {
107 let to_read = std::cmp::min(remaining as usize, buf.len());
108 entry.read_exact(&mut buf[..to_read])?;
109
110 writer
111 .write_data_block(&buf[..to_read])
112 .map_err(|e| anyhow::anyhow!("{e}"))?;
113
114 remaining -= to_read as u64;
115 bytes_from_archive += to_read as u64;
116
117 if let Some(ref pb) = pb {
118 if let Ok(pb) = pb.lock() {
119 pb.set_position(std::cmp::min(bytes_from_archive, total_size));
121 }
122 }
123 }
124
125 source_files.push(serde_json::json!({
126 "name": name,
127 "size": size,
128 "offset": total_bytes,
129 }));
130 total_bytes += size;
131 }
132
133 writer.end_stream().map_err(|e| anyhow::anyhow!("{e}"))?;
134
135 let metadata = serde_json::json!({
137 "source": {
138 "format": "tar",
139 "original_path": input.file_name().unwrap_or_default().to_string_lossy(),
140 "total_files": source_files.len(),
141 "total_bytes": total_bytes,
142 "source_files": source_files,
143 }
144 });
145 let meta_bytes = serde_json::to_vec(&metadata)?;
146
147 writer
148 .finalize(Vec::new(), Some(&meta_bytes))
149 .map_err(|e| anyhow::anyhow!("{e}"))?;
150
151 if let Some(ref pb) = pb {
152 if let Ok(pb) = pb.lock() {
153 pb.finish_with_message("Done");
154 }
155 }
156
157 if !silent {
158 println!(
159 "\n {} Converted {} files ({}) from tar archive",
160 "✓".green(),
161 source_files.len(),
162 indicatif::HumanBytes(total_bytes).to_string().bright_black(),
163 );
164 }
165
166 Ok(())
167}
168