Skip to content

Commit

Permalink
add features to extract lha/lzh
Browse files Browse the repository at this point in the history
  • Loading branch information
tamada committed May 26, 2024
1 parent 526f7a6 commit 288a601
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 23 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ edition = "2021"
[dependencies]
bzip2 = "0.4.4"
clap = { version = "4.5.4", features = ["derive"] }
delharc = "0.6.1"
flate2 = "1.0.29"
sevenz-rust = "0.6.0"
tar = "0.4.40"
Expand Down
3 changes: 3 additions & 0 deletions src/archiver.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::fs::{create_dir_all, File};
use std::path::PathBuf;

use crate::archiver::lha::LhaArchiver;
use crate::archiver::rar::RarArchiver;
use crate::archiver::sevenz::SevenZArchiver;
use crate::archiver::tar::{TarArchiver, TarBz2Archiver, TarGzArchiver, TarXzArchiver};
Expand All @@ -10,6 +11,7 @@ use crate::format::{find_format, Format};
use crate::verboser::{create_verboser, Verboser};
use crate::CliOpts;

mod lha;
mod os;
mod rar;
mod sevenz;
Expand All @@ -31,6 +33,7 @@ pub fn create_archiver(dest: &PathBuf) -> Result<Box<dyn Archiver>> {
Format::TarGz => Ok(Box::new(TarGzArchiver {})),
Format::TarBz2 => Ok(Box::new(TarBz2Archiver {})),
Format::TarXz => Ok(Box::new(TarXzArchiver {})),
Format::LHA => Ok(Box::new(LhaArchiver {})),
Format::Rar => Ok(Box::new(RarArchiver {})),
Format::SevenZ => Ok(Box::new(SevenZArchiver {})),
_ => Err(ToteError::UnknownFormat(format.to_string())),
Expand Down
42 changes: 42 additions & 0 deletions src/archiver/lha.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use crate::archiver::{Archiver, Format, ArchiverOpts};
use crate::cli::{ToteError, Result};

pub(super) struct LhaArchiver {
}

impl Archiver for LhaArchiver {
fn perform(&self, _: &ArchiverOpts) -> Result<()> {
Err(ToteError::UnsupportedFormat("only extraction support for lha".to_string()))
}
fn format(&self) -> Format {
Format::LHA
}
}

#[cfg(test)]
mod tests {
use super::*;

use std::path::PathBuf;
use crate::verboser::create_verboser;

#[test]
fn test_format() {
let archiver = LhaArchiver{};
assert_eq!(archiver.format(), Format::LHA);
}

#[test]
fn test_archive() {
let archiver = LhaArchiver{};
let opts = ArchiverOpts {
dest: PathBuf::from("results/test.rar"),
targets: vec![],
overwrite: false,
recursive: false,
v: create_verboser(false),
};
let r = archiver.perform(&opts);
assert!(r.is_err());
}
}
49 changes: 29 additions & 20 deletions src/extractor.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
use std::path::PathBuf;

use crate::format::{find_format, Format};
use crate::cli::{Result, ToteError};
use crate::CliOpts;
use crate::format::{find_format, Format};
use crate::verboser::{create_verboser, Verboser};
use crate::CliOpts;

mod zip;
mod lha;
mod rar;
mod tar;
mod sevenz;
mod tar;
mod zip;

pub struct ExtractorOpts {
pub dest: PathBuf,
Expand All @@ -21,23 +22,23 @@ impl ExtractorOpts {
pub fn new(opts: &CliOpts) -> ExtractorOpts {
let d = opts.output.clone();
ExtractorOpts {
dest: d.unwrap_or_else(|| {
PathBuf::from(".")
}),
dest: d.unwrap_or_else(|| PathBuf::from(".")),
use_archive_name_dir: opts.to_archive_name_dir,
overwrite: opts.overwrite,
v: create_verboser(opts.verbose),
}
}

/// Returns the base of the destination directory for the archive file.
/// The target is the archive file name of source.
pub fn destination(&self, target: &PathBuf) -> PathBuf {
if self.use_archive_name_dir {
let file_name = target.file_name().unwrap().to_str().unwrap();
let ext = target.extension().unwrap().to_str().unwrap();
let dir_name = file_name.trim_end_matches(ext)
.trim_end_matches(".").to_string();
let dir_name = file_name
.trim_end_matches(ext)
.trim_end_matches(".")
.to_string();
self.dest.join(dir_name)
} else {
self.dest.clone()
Expand All @@ -56,21 +57,29 @@ 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::Rar => Ok(Box::new(rar::RarExtractor{})),
Format::Tar => Ok(Box::new(tar::TarExtractor{})),
Format::TarGz => Ok(Box::new(tar::TarGzExtractor{})),
Format::TarBz2 => Ok(Box::new(tar::TarBz2Extractor{})),
Format::TarXz => Ok(Box::new(tar::TarXzExtractor{})),
Format::SevenZ => Ok(Box::new(sevenz::SevenZExtractor{})),
Format::Unknown(s) => Err(ToteError::UnknownFormat(format!("{}: unsupported format", s))),
Format::Zip => Ok(Box::new(zip::ZipExtractor {})),
Format::Rar => Ok(Box::new(rar::RarExtractor {})),
Format::Tar => Ok(Box::new(tar::TarExtractor {})),
Format::TarGz => Ok(Box::new(tar::TarGzExtractor {})),
Format::TarBz2 => Ok(Box::new(tar::TarBz2Extractor {})),
Format::TarXz => Ok(Box::new(tar::TarXzExtractor {})),
Format::LHA => Ok(Box::new(lha::LhaExtractor {})),
Format::SevenZ => Ok(Box::new(sevenz::SevenZExtractor {})),
Format::Unknown(s) => Err(ToteError::UnknownFormat(format!(
"{}: unsupported format",
s
))),
}
}
Err(msg) => Err(msg),
}
}

pub fn extractor_info(extractor: &Box<dyn Extractor>, target: &PathBuf, opts: &ExtractorOpts) -> String {
pub fn extractor_info(
extractor: &Box<dyn Extractor>,
target: &PathBuf,
opts: &ExtractorOpts,
) -> String {
format!(
"Format: {:?}\nFile: {:?}\nDestination: {:?}",
extractor.format(),
Expand Down Expand Up @@ -142,4 +151,4 @@ mod tests {
assert!(false);
}
}
}
}
135 changes: 135 additions & 0 deletions src/extractor/lha.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use std::fs::{create_dir_all, File};
use std::io::copy;
use std::path::PathBuf;

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

pub(super) struct LhaExtractor {}

impl Extractor for LhaExtractor {
fn list_archives(&self, archive_file: PathBuf) -> Result<Vec<String>> {
let mut result = Vec::<String>::new();
let mut reader = match delharc::parse_file(&archive_file) {
Err(e) => return Err(ToteError::IOError(e)),
Ok(h) => h,
};
loop {
let header = reader.header();
let name = header.parse_pathname();
if !header.is_directory() {
result.push(name.to_str().unwrap().to_string());
}
match reader.next_file() {
Ok(r) => {
if !r {
break;
}
}
Err(e) => return Err(ToteError::SomeError(Box::new(e))),
}
}
Ok(result)
}

fn perform(&self, archive_file: PathBuf, opts: &ExtractorOpts) -> Result<()> {
let mut reader = match delharc::parse_file(&archive_file) {
Err(e) => return Err(ToteError::IOError(e)),
Ok(h) => h,
};
loop {
let header = reader.header();
let name = header.parse_pathname();
let dest = opts.destination(&archive_file).join(&name);
if reader.is_decoder_supported() {
opts.v.verbose(format!(
"extracting {} ({} bytes)",
&name.to_str().unwrap().to_string(),
header.original_size
));
create_dir_all(dest.parent().unwrap()).unwrap();
let mut dest = match File::create(dest) {
Ok(f) => f,
Err(e) => return Err(ToteError::IOError(e)),
};
match copy(&mut reader, &mut dest) {
Ok(_) => {}
Err(e) => return Err(ToteError::IOError(e)),
}
if let Err(e) = reader.crc_check() {
return Err(ToteError::SomeError(Box::new(e)));
};
} else if !header.is_directory() {
opts.v.verbose(format!(
"{:?}: unsupported compression method ({:?})",
&name, header.compression
));
}
match reader.next_file() {
Ok(r) => {
if !r {
break;
}
}
Err(e) => return Err(ToteError::SomeError(Box::new(e))),
}
}
Ok(())
}

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

#[cfg(test)]
mod tests {
use super::*;
use crate::format::Format;
use crate::verboser::create_verboser;

#[test]
fn test_list_archives() {
let extractor = LhaExtractor {};
let file = PathBuf::from("testdata/test.lzh");
match extractor.list_archives(file) {
Ok(r) => {
assert_eq!(r.len(), 23);
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("README.md".to_string()).as_ref());
assert_eq!(r.get(3), Some("build.rs".to_string()).as_ref());
}
Err(_) => assert!(false),
}
}

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

#[test]
fn test_format() {
let extractor = LhaExtractor {};
assert_eq!(extractor.format(), Format::LHA);
}
}
10 changes: 7 additions & 3 deletions src/format.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::ffi::OsStr;
use std::fmt::Display;
use crate::cli::{ToteError, Result};

use crate::cli::{Result, ToteError};

pub fn find_format(file_name: Option<&OsStr>) -> Result<Format> {
match file_name {
Expand All @@ -16,6 +17,8 @@ pub fn find_format(file_name: Option<&OsStr>) -> Result<Format> {
return Ok(Format::SevenZ);
} else if name.ends_with(".tar") {
return Ok(Format::Tar);
} else if name.ends_with(".lha") || name.ends_with(".lzh") {
return Ok(Format::LHA);
} else if name.ends_with(".rar") {
return Ok(Format::Rar);
} else if name.ends_with(".zip") || name.ends_with(".jar") || name.ends_with(".war") || name.ends_with(".ear") {
Expand All @@ -28,7 +31,6 @@ pub fn find_format(file_name: Option<&OsStr>) -> Result<Format> {
}
}


#[derive(Debug, PartialEq)]
pub enum Format {
Zip,
Expand All @@ -37,6 +39,7 @@ pub enum Format {
TarBz2,
TarXz,
SevenZ,
LHA,
Rar,
Unknown(String),
}
Expand All @@ -50,6 +53,7 @@ impl Display for Format {
Format::TarBz2 => write!(f, "TarBz2"),
Format::TarXz => write!(f, "TarXz"),
Format::SevenZ => write!(f, "SevenZ"),
Format::LHA => write!(f, "LHA"),
Format::Rar => write!(f, "Rar"),
Format::Unknown(s) => write!(f, "{}: unknown format", s),
}
Expand Down Expand Up @@ -102,4 +106,4 @@ mod tests {
}
}
}
}
}
1 change: 1 addition & 0 deletions templates/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Supported archive formats:
- Tar+Xz
- Zip
- 7z
- Lha, Lzh (extraction only)
- Rar (extraction only)

## Install
Expand Down
Binary file added testdata/test.lzh
Binary file not shown.

0 comments on commit 288a601

Please sign in to comment.