From eaee52d64d05126765ae57792333c13f585caa73 Mon Sep 17 00:00:00 2001 From: lifegpc Date: Sat, 7 Jun 2025 13:12:41 +0800 Subject: [PATCH] Add support to unpack archive files --- src/args.rs | 8 ++++ src/main.rs | 101 +++++++++++++++++++++++++++++++++++++++++++++ src/utils/files.rs | 46 ++++++++++++++++++++- 3 files changed, 153 insertions(+), 2 deletions(-) diff --git a/src/args.rs b/src/args.rs index 8245f08..9152b80 100644 --- a/src/args.rs +++ b/src/args.rs @@ -132,12 +132,20 @@ pub enum Command { }, /// Import to script Import(ImportArgs), + /// Pack files to archive Pack { /// Input directory input: String, /// Output archive file output: Option, }, + /// Unpack archive to directory + Unpack { + /// Input archive file + input: String, + /// Output directory + output: Option, + }, } pub fn parse_args() -> Arg { diff --git a/src/main.rs b/src/main.rs index 1075fd3..551afbd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -975,6 +975,73 @@ pub fn pack_archive( Ok(()) } +pub fn unpack_archive( + filename: &str, + arg: &args::Arg, + config: &types::ExtraConfig, + output: &Option, + is_dir: bool, +) -> anyhow::Result { + eprintln!("Unpacking {}", filename); + let mut script = parse_script(filename, arg, config)?.0; + if !script.is_archive() { + return Ok(types::ScriptResult::Ignored); + } + let odir = match output.as_ref() { + Some(output) => { + let mut pb = std::path::PathBuf::from(output); + let filename = std::path::PathBuf::from(filename); + if is_dir { + if let Some(fname) = filename.file_name() { + pb.push(fname); + } + } + pb.to_string_lossy().into_owned() + } + None => { + let mut pb = std::path::PathBuf::from(filename); + pb.set_extension(""); + pb.to_string_lossy().into_owned() + } + }; + if !std::fs::exists(&odir)? { + std::fs::create_dir_all(&odir)?; + } + for f in script.iter_archive_mut()? { + let f = f?; + let out_path = std::path::PathBuf::from(&odir).join(f.name()); + match utils::files::make_sure_dir_exists(&out_path) { + Ok(_) => {} + Err(e) => { + eprintln!( + "Error creating parent directory for {}: {}", + out_path.display(), + e + ); + COUNTER.inc_error(); + continue; + } + } + match utils::files::write_file(&out_path) { + Ok(mut fi) => match fi.write_all(f.data()) { + Ok(_) => {} + Err(e) => { + eprintln!("Error writing to file {}: {}", out_path.display(), e); + COUNTER.inc_error(); + continue; + } + }, + Err(e) => { + eprintln!("Error writing file {}: {}", out_path.display(), e); + COUNTER.inc_error(); + continue; + } + } + COUNTER.inc(types::ScriptResult::Ok); + } + Ok(types::ScriptResult::Ok) +} + lazy_static::lazy_static! { static ref COUNTER: utils::counter::Counter = utils::counter::Counter::new(); } @@ -1089,6 +1156,40 @@ fn main() { eprintln!("Error packing archive: {}", e); } } + args::Command::Unpack { input, output } => { + let (scripts, is_dir) = utils::files::collect_arc_files(input, arg.recursive).unwrap(); + if is_dir { + match &output { + Some(output) => { + let op = std::path::Path::new(output); + if op.exists() { + if !op.is_dir() { + eprintln!("Output path is not a directory"); + return; + } + } else { + std::fs::create_dir_all(op).unwrap(); + } + } + None => {} + } + } + for script in scripts.iter() { + let re = unpack_archive(&script, &arg, &cfg, output, is_dir); + match re { + Ok(s) => { + COUNTER.inc(s); + } + Err(e) => { + COUNTER.inc_error(); + eprintln!("Error unpacking {}: {}", script, e); + if arg.backtrace { + eprintln!("Backtrace: {}", e.backtrace()); + } + } + } + } + } } eprintln!("{}", std::ops::Deref::deref(&COUNTER)); } diff --git a/src/utils/files.rs b/src/utils/files.rs index c6d7545..02b7c9b 100644 --- a/src/utils/files.rs +++ b/src/utils/files.rs @@ -1,10 +1,9 @@ +use crate::scripts::{ALL_EXTS, ARCHIVE_EXTS}; use std::fs; use std::io; use std::io::{Read, Write}; use std::path::Path; -use crate::scripts::ALL_EXTS; - pub fn find_files(path: &str, recursive: bool, no_ext_filter: bool) -> io::Result> { let mut result = Vec::new(); let dir_path = Path::new(&path); @@ -36,6 +35,35 @@ pub fn find_files(path: &str, recursive: bool, no_ext_filter: bool) -> io::Resul Ok(result) } +pub fn find_arc_files(path: &str, recursive: bool) -> io::Result> { + let mut result = Vec::new(); + let dir_path = Path::new(&path); + + if dir_path.is_dir() { + for entry in fs::read_dir(dir_path)? { + let entry = entry?; + let path = entry.path(); + + if path.is_file() + && path.extension().map_or(false, |ext| { + ARCHIVE_EXTS.contains(&ext.to_string_lossy().to_lowercase()) + }) + { + if let Some(path_str) = path.to_str() { + result.push(path_str.to_string()); + } + } else if recursive && path.is_dir() { + if let Some(path_str) = path.to_str() { + let mut sub_files = find_arc_files(&path_str.to_string(), recursive)?; + result.append(&mut sub_files); + } + } + } + } + + Ok(result) +} + pub fn collect_files( path: &str, recursive: bool, @@ -54,6 +82,20 @@ pub fn collect_files( )) } +pub fn collect_arc_files(path: &str, recursive: bool) -> io::Result<(Vec, bool)> { + let pa = Path::new(path); + if pa.is_dir() { + return Ok((find_arc_files(path, recursive)?, true)); + } + if pa.is_file() { + return Ok((vec![path.to_string()], false)); + } + Err(io::Error::new( + io::ErrorKind::NotFound, + format!("Path {} is neither a file nor a directory", pa.display()), + )) +} + pub fn read_file + ?Sized>(f: &F) -> io::Result> { let mut content = Vec::new(); if f.as_ref() == Path::new("-") {