Skip to content

Commit

Permalink
ref #33 support .cab file
Browse files Browse the repository at this point in the history
  • Loading branch information
tamada committed Aug 4, 2024
1 parent c17543f commit b000157
Show file tree
Hide file tree
Showing 9 changed files with 290 additions and 75 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ edition = "2021"

[dependencies]
bzip2 = "0.4.4"
cab = "0.6.0"
clap = { version = "4.5.4", features = ["derive"] }
delharc = "0.6.1"
flate2 = "1.0.29"
Expand Down
26 changes: 16 additions & 10 deletions src/archiver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ use crate::archiver::rar::RarArchiver;
use crate::archiver::sevenz::SevenZArchiver;
use crate::archiver::tar::{TarArchiver, TarBz2Archiver, TarGzArchiver, TarXzArchiver, TarZstdArchiver};
use crate::archiver::zip::ZipArchiver;
use crate::cli::{Result, ToteError, is_archive_file};
use crate::format::{find_format, Format};
use crate::cli::{Result, ToteError};
use crate::format::{find_format, is_archive_file, Format};
use crate::verboser::{create_verboser, Verboser};
use crate::CliOpts;

mod cab;
mod lha;
mod os;
mod rar;
Expand All @@ -28,15 +29,16 @@ pub fn create_archiver(dest: &PathBuf) -> Result<Box<dyn Archiver>> {
match format {
Ok(format) => {
return match format {
Format::Zip => Ok(Box::new(ZipArchiver {})),
Format::Cab => Ok(Box::new(cab::CabArchiver {})),
Format::LHA => Ok(Box::new(LhaArchiver {})),
Format::Rar => Ok(Box::new(RarArchiver {})),
Format::SevenZ => Ok(Box::new(SevenZArchiver {})),
Format::Tar => Ok(Box::new(TarArchiver {})),
Format::TarGz => Ok(Box::new(TarGzArchiver {})),
Format::TarBz2 => Ok(Box::new(TarBz2Archiver {})),
Format::TarGz => Ok(Box::new(TarGzArchiver {})),
Format::TarXz => Ok(Box::new(TarXzArchiver {})),
Format::TarZstd => Ok(Box::new(TarZstdArchiver {})),
Format::LHA => Ok(Box::new(LhaArchiver {})),
Format::Rar => Ok(Box::new(RarArchiver {})),
Format::SevenZ => Ok(Box::new(SevenZArchiver {})),
Format::Zip => Ok(Box::new(ZipArchiver {})),
_ => Err(ToteError::UnknownFormat(format.to_string())),
}
}
Expand Down Expand Up @@ -190,9 +192,13 @@ mod tests {
assert!(a9.is_ok());
assert_eq!(a9.unwrap().format(), Format::LHA);

let a10 = create_archiver(&PathBuf::from("results/test.unknown"));
assert!(a10.is_err());
if let Err(e) = a10 {
let a10 = create_archiver(&PathBuf::from("results/test.cab"));
assert!(a10.is_ok());
assert_eq!(a10.unwrap().format(), Format::Cab);

let ae = create_archiver(&PathBuf::from("results/test.unknown"));
assert!(ae.is_err());
if let Err(e) = ae {
if let ToteError::UnknownFormat(msg) = e {
assert_eq!(msg, "test.unknown: unknown format".to_string());
} else {
Expand Down
85 changes: 85 additions & 0 deletions src/archiver/cab.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use std::fs::File;
use std::path::PathBuf;

use cab::CabinetBuilder;

use crate::archiver::{Archiver, ArchiverOpts};
use crate::cli::{ToteError, Result};
use crate::format::Format;

pub(super) struct CabArchiver {
}

impl Archiver for CabArchiver {
fn perform(&self, opts: &ArchiverOpts) -> Result<()> {
match opts.destination() {
Err(e) => Err(e),
Ok(file) => {
write_impl(file, opts.targets(), opts.recursive, opts.base_dir.clone())
}
}
}

fn format(&self) -> Format {
Format::Cab
}
}

fn write_impl(file: File, targets: Vec<PathBuf>, recursive: bool, base_dir: PathBuf) -> Result<()> {
let mut builder = CabinetBuilder::new();
let folder = builder.add_folder(cab::CompressionType::MsZip);
let list = correct_targets(targets, recursive, base_dir);
for (_from, dest_file) in list.clone() {
folder.add_file(dest_file);
}
let mut writer = match builder.build(file) {
Ok(w) => w,
Err(e) => return Err(ToteError::Archiver(e.to_string())),
};
let mut iter = list.iter();
while let Some(mut w) = writer.next_file().unwrap() {
let (from, _) = iter.next().unwrap();
if let Ok(mut reader) = File::open(from) {
std::io::copy(&mut reader, &mut w).unwrap();
}
}
match writer.finish() {
Ok(_) => Ok(()),
Err(e) => Err(ToteError::Archiver(e.to_string())),
}
}

fn correct_targets(targets: Vec<PathBuf>, recursive: bool, base_dir: PathBuf) -> Vec<(PathBuf, String)> {
let mut result = vec![];
for target in targets {
let path = target.as_path();
if path.is_dir() && recursive {
process_dir(&mut result, path.to_path_buf(), &base_dir);
} else {
process_file(&mut result, path.to_path_buf(), &base_dir);
}
}
result
}

fn process_dir(result: &mut Vec<(PathBuf, String)>, path: PathBuf, base_dir: &PathBuf) {
for entry in path.read_dir().unwrap() {
if let Ok(e) = entry {
let p = e.path();
if p.is_dir() {
process_dir(result, e.path(), &base_dir)
} else if p.is_file() {
process_file(result, e.path(), &base_dir)
}
}
}
}

fn process_file(result: &mut Vec<(PathBuf, String)>, target: PathBuf, base_dir: &PathBuf) {
let target_path = match target.strip_prefix(base_dir) {
Ok(p) => p.to_path_buf(),
Err(_) => target.clone(),
};
let name = target_path.to_str().unwrap();
result.push((target, name.to_string()));
}
40 changes: 0 additions & 40 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,41 +35,6 @@ If the frist argument was not the archive name, the default archive name `toteba
pub args: Vec<PathBuf>,
}

impl CliOpts {
pub fn run_mode(&mut self) -> Result<RunMode> {
if self.args.len() == 0 {
return Err(ToteError::NoArgumentsGiven)
}
if self.mode == RunMode::Auto {
if is_all_args_archives(&self.args) {
self.mode = RunMode::Extract;
Ok(RunMode::Extract)
} else {
self.mode = RunMode::Archive;
Ok(RunMode::Archive)
}
} else {
Ok(self.mode)
}
}

}

fn is_all_args_archives(args: &[PathBuf]) -> bool {
args.iter().all(is_archive_file)
}

pub fn is_archive_file(arg: &PathBuf) -> bool {
let name = arg.to_str().unwrap().to_lowercase();
let exts = vec![".zip", ".tar", ".tar.gz", ".tgz", ".tar.bz2", ".tbz2", ".rar", ".jar", ".war", ".ear", "7z", ];
for ext in exts.iter() {
if name.ends_with(ext) {
return true
}
}
return false
}

#[derive(Debug, Clone, ValueEnum, PartialEq, Copy)]
pub enum RunMode {
Auto,
Expand Down Expand Up @@ -120,9 +85,4 @@ mod tests {
assert!(r4.is_ok());
assert_eq!(cli4.run_mode().unwrap(), RunMode::List);
}

#[test]
fn test_is_all_args_archives() {
assert!(is_all_args_archives(&[PathBuf::from("test.zip"), PathBuf::from("test.tar"), PathBuf::from("test.tar.gz"), PathBuf::from("test.tgz"), PathBuf::from("test.tar.bz2"), PathBuf::from("test.tbz2"), PathBuf::from("test.rar")]));
}
}
11 changes: 6 additions & 5 deletions src/extractor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::format::{find_format, Format};
use crate::verboser::{create_verboser, Verboser};
use crate::CliOpts;

mod cab;
mod lha;
mod rar;
mod sevenz;
Expand Down Expand Up @@ -33,7 +34,6 @@ impl ExtractorOpts {
/// The target is the archive file name of source.
pub fn destination(&self, target: &PathBuf) -> Result<PathBuf> {
let dest = self.destination_file(target);
println!("destination: {:?}", dest);
if dest.exists() && !self.overwrite {
Err(ToteError::FileExists(dest.clone()))
} else {
Expand Down Expand Up @@ -67,15 +67,16 @@ pub fn create_extractor(file: &PathBuf) -> Result<Box<dyn Extractor>> {
match format {
Ok(format) => {
return match format {
Format::Zip => Ok(Box::new(zip::ZipExtractor {})),
Format::Cab => Ok(Box::new(cab::CabExtractor {})),
Format::LHA => Ok(Box::new(lha::LhaExtractor {})),
Format::Rar => Ok(Box::new(rar::RarExtractor {})),
Format::SevenZ => Ok(Box::new(sevenz::SevenZExtractor {})),
Format::Tar => Ok(Box::new(tar::TarExtractor {})),
Format::TarGz => Ok(Box::new(tar::TarGzExtractor {})),
Format::TarBz2 => Ok(Box::new(tar::TarBz2Extractor {})),
Format::TarGz => Ok(Box::new(tar::TarGzExtractor {})),
Format::TarXz => Ok(Box::new(tar::TarXzExtractor {})),
Format::TarZstd => Ok(Box::new(tar::TarZstdExtractor {})),
Format::LHA => Ok(Box::new(lha::LhaExtractor {})),
Format::SevenZ => Ok(Box::new(sevenz::SevenZExtractor {})),
Format::Zip => Ok(Box::new(zip::ZipExtractor {})),
Format::Unknown(s) => Err(ToteError::UnknownFormat(format!(
"{}: unsupported format",
s
Expand Down
114 changes: 114 additions & 0 deletions src/extractor/cab.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use std::path::PathBuf;
use std::fs::{create_dir_all, File};

use cab::Cabinet;

use crate::cli::{Result, ToteError};
use crate::extractor::{Extractor, ExtractorOpts};

pub(super) struct CabExtractor {}

fn list_impl<F, T>(archive_file: &PathBuf, mapper: F) -> Result<Vec<T>>
where F: Fn(&cab::FileEntry) -> T {
let cabinet = open_cabinet(&archive_file)?;
let mut result = vec![];
for folder in cabinet.folder_entries() {
for file in folder.file_entries() {
result.push(mapper(file));
}
}
Ok(result)
}

impl Extractor for CabExtractor {
fn list_archives(&self, archive_file: PathBuf) -> Result<Vec<String>> {
list_impl(&archive_file, |file| file.name().to_string())
}

fn perform(&self, archive_file: PathBuf, opts: &ExtractorOpts) -> Result<()> {
let list = match list_impl(&archive_file,
|file| (file.name().to_string(), file.uncompressed_size())) {
Ok(l) => l,
Err(e) => return Err(e),
};
let mut cabinet = open_cabinet(&archive_file)?;
for file in list {
let file_name = file.0.clone();
let dest_file = opts.destination(&archive_file)?.join(&file_name);
opts.v.verbose(format!("extracting {} ({} bytes)", &file_name, file.1));
create_dir_all(dest_file.parent().unwrap()).unwrap();
let mut dest = match File::create(dest_file) {
Ok(f) => f,
Err(e) => return Err(ToteError::IO(e)),
};
let mut file_from = cabinet.read_file(&file_name).unwrap();
match std::io::copy(&mut file_from, &mut dest) {
Ok(_) => {}
Err(e) => return Err(ToteError::IO(e)),
}
}
Ok(())
}

fn format(&self) -> crate::format::Format {
crate::format::Format::Cab
}
}

fn open_cabinet(archive_file: &PathBuf) -> Result<Cabinet<File>> {
let cab_file = match File::open(archive_file) {
Ok(f) => f,
Err(e) => return Err(ToteError::IO(e)),
};
match Cabinet::new(cab_file) {
Ok(c) => Ok(c),
Err(e) => Err(ToteError::IO(e)),
}
}

#[cfg(test)]
mod tests {
use crate::{format::Format, verboser::create_verboser};
use super::*;
#[test]
fn test_list_archives() {
let extractor = CabExtractor{};
let file = PathBuf::from("testdata/test.cab");
match extractor.list_archives(file) {
Ok(r) => {
assert_eq!(r.len(), 16);
assert_eq!(r.get(0), Some("Cargo.toml".to_string()).as_ref());
assert_eq!(r.get(1), Some("LICENSE".to_string()).as_ref());
assert_eq!(r.get(2), Some("build.rs".to_string()).as_ref());
assert_eq!(r.get(3), Some("README.md".to_string()).as_ref());
},
Err(_) => assert!(false),
}
}

#[test]
fn test_extract_archive() {
let e = CabExtractor{};
let file = PathBuf::from("testdata/test.cab");
let opts = ExtractorOpts {
dest: PathBuf::from("results/cab"),
use_archive_name_dir: true,
overwrite: true,
v: create_verboser(false),
};
match e.perform(file, &opts) {
Ok(_) => {
assert!(true);
assert!(PathBuf::from("results/cab/test/Cargo.toml").exists());
std::fs::remove_dir_all(PathBuf::from("results/cab")).unwrap();
},
Err(_) => assert!(false),
};
}

#[test]
fn test_format() {
let extractor = CabExtractor {};
assert_eq!(extractor.format(), Format::Cab);
}
}
Loading

0 comments on commit b000157

Please sign in to comment.