1use aurora_core::{AuroraResult, Pipeline, Value};
2use std::process::Command;
3use std::path::Path;
4
5fn check_tool(name: &str) -> AuroraResult<()> {
6 let status = Command::new("which")
7 .arg(name)
8 .output()
9 .ok()
10 .map(|o| o.status.success())
11 .unwrap_or(false);
12 if !status {
13 return Err(aurora_core::AuroraError::CommandNotFound(
14 format!("{} is not installed", name)
15 ));
16 }
17 Ok(())
18}
19
20fn extension(path: &str) -> &str {
21 let p = Path::new(path);
22 p.extension()
23 .and_then(|e| e.to_str())
24 .unwrap_or("")
25}
26
27pub fn archive_list(file: &str) -> AuroraResult<Pipeline> {
28 let ext = extension(file);
29 match ext {
30 "tar" | "tar.gz" | "tgz" | "tar.bz2" | "tar.xz" => list_tar(file),
31 "zip" => list_zip(file),
32 "7z" => list_7z(file),
33 "gz" => list_gz(file),
34 _ => Err(aurora_core::AuroraError::InvalidInput(
35 format!("unsupported archive format: {}", ext)
36 )),
37 }
38}
39
40fn list_tar(file: &str) -> AuroraResult<Pipeline> {
41 check_tool("tar")?;
42 let output = Command::new("tar")
43 .args(["tf", file])
44 .output()
45 .map_err(|e| aurora_core::AuroraError::ModuleError(
46 format!("tar failed: {}", e)
47 ))?;
48
49 let stdout = String::from_utf8_lossy(&output.stdout);
50 let rows: Vec<Vec<Value>> = stdout.lines()
51 .filter(|l| !l.is_empty())
52 .map(|l| vec![Value::String(l.into())])
53 .collect();
54
55 Ok(Pipeline::table(vec!["entry".into()], rows))
56}
57
58fn list_zip(file: &str) -> AuroraResult<Pipeline> {
59 check_tool("unzip")?;
60 let output = Command::new("unzip")
61 .args(["-l", file])
62 .output()
63 .map_err(|e| aurora_core::AuroraError::ModuleError(
64 format!("unzip failed: {}", e)
65 ))?;
66
67 let stdout = String::from_utf8_lossy(&output.stdout);
68 let mut rows: Vec<Vec<Value>> = Vec::new();
69
70 for line in stdout.lines() {
71 let line = line.trim();
72 if line.is_empty() || line.starts_with("Archive:") || line.starts_with("Length") || line.contains("----") || line.ends_with("file") {
73 continue;
74 }
75 rows.push(vec![Value::String(line.into())]);
76 }
77
78 Ok(Pipeline::table(vec!["entry".into()], rows))
79}
80
81fn list_7z(file: &str) -> AuroraResult<Pipeline> {
82 check_tool("7z")?;
83 let output = Command::new("7z")
84 .args(["l", file])
85 .output()
86 .map_err(|e| aurora_core::AuroraError::ModuleError(
87 format!("7z failed: {}", e)
88 ))?;
89
90 let stdout = String::from_utf8_lossy(&output.stdout);
91 let mut rows: Vec<Vec<Value>> = Vec::new();
92 let mut capture = false;
93
94 for line in stdout.lines() {
95 if line.contains("----") && !capture {
96 capture = true;
97 continue;
98 }
99 if line.contains("----") && capture {
100 break;
101 }
102 if capture {
103 let line = line.trim();
104 if !line.is_empty() {
105 rows.push(vec![Value::String(line.into())]);
106 }
107 }
108 }
109
110 Ok(Pipeline::table(vec!["entry".into()], rows))
111}
112
113fn list_gz(file: &str) -> AuroraResult<Pipeline> {
114 check_tool("gunzip")?;
115 let output = Command::new("gunzip")
116 .args(["-l", file])
117 .output()
118 .map_err(|e| aurora_core::AuroraError::ModuleError(
119 format!("gunzip failed: {}", e)
120 ))?;
121
122 let stdout = String::from_utf8_lossy(&output.stdout);
123 let mut rows: Vec<Vec<Value>> = Vec::new();
124
125 for (i, line) in stdout.lines().enumerate() {
126 if i == 0 || line.trim().is_empty() { continue; }
127 rows.push(vec![Value::String(line.into())]);
128 }
129
130 Ok(Pipeline::table(vec!["entry".into()], rows))
131}
132
133pub fn archive_extract(file: &str, output: Option<&str>) -> AuroraResult<Pipeline> {
134 let ext = extension(file);
135 match ext {
136 "tar" | "tar.gz" | "tgz" | "tar.bz2" | "tar.xz" => extract_tar(file, output),
137 "zip" => extract_zip(file, output),
138 "7z" => extract_7z(file, output),
139 "gz" => extract_gz(file, output),
140 _ => Err(aurora_core::AuroraError::InvalidInput(
141 format!("unsupported archive format: {}", ext)
142 )),
143 }
144}
145
146fn extract_tar(file: &str, output: Option<&str>) -> AuroraResult<Pipeline> {
147 check_tool("tar")?;
148 let mut cmd = Command::new("tar");
149 cmd.arg("xf");
150 cmd.arg(file);
151 if let Some(dir) = output {
152 cmd.args(["-C", dir]);
153 }
154 let result = cmd.output()
155 .map_err(|e| aurora_core::AuroraError::ModuleError(
156 format!("tar extract failed: {}", e)
157 ))?;
158 if !result.status.success() {
159 let stderr = String::from_utf8_lossy(&result.stderr);
160 return Err(aurora_core::AuroraError::ModuleError(
161 format!("tar extract failed: {}", stderr)
162 ));
163 }
164 Ok(Pipeline::table(
165 vec!["action".into(), "file".into(), "status".into()],
166 vec![vec![
167 Value::String("extract".into()),
168 Value::String(file.into()),
169 Value::String("ok".into()),
170 ]],
171 ))
172}
173
174fn extract_zip(file: &str, output: Option<&str>) -> AuroraResult<Pipeline> {
175 check_tool("unzip")?;
176 let mut cmd = Command::new("unzip");
177 cmd.arg(file);
178 if let Some(dir) = output {
179 cmd.args(["-d", dir]);
180 }
181 let result = cmd.output()
182 .map_err(|e| aurora_core::AuroraError::ModuleError(
183 format!("unzip failed: {}", e)
184 ))?;
185 if !result.status.success() {
186 let stderr = String::from_utf8_lossy(&result.stderr);
187 return Err(aurora_core::AuroraError::ModuleError(
188 format!("unzip failed: {}", stderr)
189 ));
190 }
191 Ok(Pipeline::table(
192 vec!["action".into(), "file".into(), "status".into()],
193 vec![vec![
194 Value::String("extract".into()),
195 Value::String(file.into()),
196 Value::String("ok".into()),
197 ]],
198 ))
199}
200
201fn extract_7z(file: &str, output: Option<&str>) -> AuroraResult<Pipeline> {
202 check_tool("7z")?;
203 let mut cmd = Command::new("7z");
204 cmd.args(["x", file]);
205 if let Some(dir) = output {
206 cmd.args([&format!("-o{}", dir)]);
207 }
208 let result = cmd.output()
209 .map_err(|e| aurora_core::AuroraError::ModuleError(
210 format!("7z extract failed: {}", e)
211 ))?;
212 if !result.status.success() {
213 let stderr = String::from_utf8_lossy(&result.stderr);
214 return Err(aurora_core::AuroraError::ModuleError(
215 format!("7z extract failed: {}", stderr)
216 ));
217 }
218 Ok(Pipeline::table(
219 vec!["action".into(), "file".into(), "status".into()],
220 vec![vec![
221 Value::String("extract".into()),
222 Value::String(file.into()),
223 Value::String("ok".into()),
224 ]],
225 ))
226}
227
228fn extract_gz(file: &str, output: Option<&str>) -> AuroraResult<Pipeline> {
229 check_tool("gunzip")?;
230 let result = if let Some(out) = output {
231 Command::new("gunzip")
232 .args(["-c", file])
233 .stdout(std::process::Stdio::piped())
234 .spawn()
235 .and_then(|child| {
236 use std::fs::File;
237 let _out_file = File::create(out)?;
238 let output = child.wait_with_output()?;
239 if output.status.success() {
240 use std::io::Write;
241 let mut f = File::create(out)?;
242 f.write_all(&output.stdout)?;
243 }
244 Ok(output)
245 })
246 } else {
247 Command::new("gunzip")
248 .arg(file)
249 .output()
250 };
251
252 match result {
253 Ok(r) if r.status.success() => {
254 Ok(Pipeline::table(
255 vec!["action".into(), "file".into(), "status".into()],
256 vec![vec![
257 Value::String("extract".into()),
258 Value::String(file.into()),
259 Value::String("ok".into()),
260 ]],
261 ))
262 }
263 Ok(r) => {
264 let stderr = String::from_utf8_lossy(&r.stderr);
265 Err(aurora_core::AuroraError::ModuleError(
266 format!("gunzip failed: {}", stderr)
267 ))
268 }
269 Err(e) => Err(aurora_core::AuroraError::ModuleError(
270 format!("gunzip failed: {}", e)
271 )),
272 }
273}
274
275pub fn archive_compress(input: &str, output: &str) -> AuroraResult<Pipeline> {
276 let ext = extension(output);
277 match ext {
278 "tar.gz" | "tgz" => compress_tar_gz(input, output),
279 "tar.bz2" => compress_tar_bz2(input, output),
280 "tar.xz" => compress_tar_xz(input, output),
281 "tar" => compress_tar(input, output),
282 "zip" => compress_zip(input, output),
283 "7z" => compress_7z(input, output),
284 "gz" => compress_gz(input, output),
285 _ => Err(aurora_core::AuroraError::InvalidInput(
286 format!("unsupported output format: {}", ext)
287 )),
288 }
289}
290
291fn compress_tar_gz(input: &str, output: &str) -> AuroraResult<Pipeline> {
292 check_tool("tar")?;
293 let result = Command::new("tar")
294 .args(["czf", output, input])
295 .output()
296 .map_err(|e| aurora_core::AuroraError::ModuleError(
297 format!("tar failed: {}", e)
298 ))?;
299 if !result.status.success() {
300 let stderr = String::from_utf8_lossy(&result.stderr);
301 return Err(aurora_core::AuroraError::ModuleError(
302 format!("tar failed: {}", stderr)
303 ));
304 }
305 Ok(Pipeline::table(
306 vec!["action".into(), "input".into(), "output".into(), "status".into()],
307 vec![vec![
308 Value::String("compress".into()),
309 Value::String(input.into()),
310 Value::String(output.into()),
311 Value::String("ok".into()),
312 ]],
313 ))
314}
315
316fn compress_tar_bz2(input: &str, output: &str) -> AuroraResult<Pipeline> {
317 check_tool("tar")?;
318 let result = Command::new("tar")
319 .args(["cjf", output, input])
320 .output()
321 .map_err(|e| aurora_core::AuroraError::ModuleError(
322 format!("tar failed: {}", e)
323 ))?;
324 if !result.status.success() {
325 let stderr = String::from_utf8_lossy(&result.stderr);
326 return Err(aurora_core::AuroraError::ModuleError(
327 format!("tar failed: {}", stderr)
328 ));
329 }
330 Ok(Pipeline::table(
331 vec!["action".into(), "input".into(), "output".into(), "status".into()],
332 vec![vec![
333 Value::String("compress".into()),
334 Value::String(input.into()),
335 Value::String(output.into()),
336 Value::String("ok".into()),
337 ]],
338 ))
339}
340
341fn compress_tar_xz(input: &str, output: &str) -> AuroraResult<Pipeline> {
342 check_tool("tar")?;
343 let result = Command::new("tar")
344 .args(["cJf", output, input])
345 .output()
346 .map_err(|e| aurora_core::AuroraError::ModuleError(
347 format!("tar failed: {}", e)
348 ))?;
349 if !result.status.success() {
350 let stderr = String::from_utf8_lossy(&result.stderr);
351 return Err(aurora_core::AuroraError::ModuleError(
352 format!("tar failed: {}", stderr)
353 ));
354 }
355 Ok(Pipeline::table(
356 vec!["action".into(), "input".into(), "output".into(), "status".into()],
357 vec![vec![
358 Value::String("compress".into()),
359 Value::String(input.into()),
360 Value::String(output.into()),
361 Value::String("ok".into()),
362 ]],
363 ))
364}
365
366fn compress_tar(input: &str, output: &str) -> AuroraResult<Pipeline> {
367 check_tool("tar")?;
368 let result = Command::new("tar")
369 .args(["cf", output, input])
370 .output()
371 .map_err(|e| aurora_core::AuroraError::ModuleError(
372 format!("tar failed: {}", e)
373 ))?;
374 if !result.status.success() {
375 let stderr = String::from_utf8_lossy(&result.stderr);
376 return Err(aurora_core::AuroraError::ModuleError(
377 format!("tar failed: {}", stderr)
378 ));
379 }
380 Ok(Pipeline::table(
381 vec!["action".into(), "input".into(), "output".into(), "status".into()],
382 vec![vec![
383 Value::String("compress".into()),
384 Value::String(input.into()),
385 Value::String(output.into()),
386 Value::String("ok".into()),
387 ]],
388 ))
389}
390
391fn compress_zip(input: &str, output: &str) -> AuroraResult<Pipeline> {
392 check_tool("zip")?;
393 let result = Command::new("zip")
394 .args(["-r", output, input])
395 .output()
396 .map_err(|e| aurora_core::AuroraError::ModuleError(
397 format!("zip failed: {}", e)
398 ))?;
399 if !result.status.success() {
400 let stderr = String::from_utf8_lossy(&result.stderr);
401 return Err(aurora_core::AuroraError::ModuleError(
402 format!("zip failed: {}", stderr)
403 ));
404 }
405 Ok(Pipeline::table(
406 vec!["action".into(), "input".into(), "output".into(), "status".into()],
407 vec![vec![
408 Value::String("compress".into()),
409 Value::String(input.into()),
410 Value::String(output.into()),
411 Value::String("ok".into()),
412 ]],
413 ))
414}
415
416fn compress_7z(input: &str, output: &str) -> AuroraResult<Pipeline> {
417 check_tool("7z")?;
418 let result = Command::new("7z")
419 .args(["a", output, input])
420 .output()
421 .map_err(|e| aurora_core::AuroraError::ModuleError(
422 format!("7z failed: {}", e)
423 ))?;
424 if !result.status.success() {
425 let stderr = String::from_utf8_lossy(&result.stderr);
426 return Err(aurora_core::AuroraError::ModuleError(
427 format!("7z failed: {}", stderr)
428 ));
429 }
430 Ok(Pipeline::table(
431 vec!["action".into(), "input".into(), "output".into(), "status".into()],
432 vec![vec![
433 Value::String("compress".into()),
434 Value::String(input.into()),
435 Value::String(output.into()),
436 Value::String("ok".into()),
437 ]],
438 ))
439}
440
441fn compress_gz(input: &str, output: &str) -> AuroraResult<Pipeline> {
442 check_tool("gzip")?;
443 let result = Command::new("gzip")
444 .args(["-c", input])
445 .stdout(std::process::Stdio::piped())
446 .spawn()
447 .and_then(|child| {
448 use std::fs::File;
449 use std::io::Write;
450 let mut out_file = File::create(output)?;
451 let output = child.wait_with_output()?;
452 out_file.write_all(&output.stdout)?;
453 Ok(output)
454 });
455 match result {
456 Ok(r) if r.status.success() => {
457 Ok(Pipeline::table(
458 vec!["action".into(), "input".into(), "output".into(), "status".into()],
459 vec![vec![
460 Value::String("compress".into()),
461 Value::String(input.into()),
462 Value::String(output.into()),
463 Value::String("ok".into()),
464 ]],
465 ))
466 }
467 Ok(r) => {
468 let stderr = String::from_utf8_lossy(&r.stderr);
469 Err(aurora_core::AuroraError::ModuleError(
470 format!("gzip failed: {}", stderr)
471 ))
472 }
473 Err(e) => Err(aurora_core::AuroraError::ModuleError(
474 format!("gzip failed: {}", e)
475 )),
476 }
477}