use std::fs::{File, create_dir_all};
use std::io::copy;
use std::path::{Path, PathBuf};
use chrono::DateTime;
use delharc::{LhaDecodeReader, LhaHeader};
use crate::extractor::{Entries, Entry, ToteExtractor};
use crate::{Result, Error};
pub(super) struct Extractor {}
impl ToteExtractor for Extractor {
fn list(&self, archive_file: PathBuf) -> Result<Entries> {
let mut result = vec![];
let mut reader = delharc::parse_file(&archive_file).map_err(Error::IO)?;
loop {
let header = reader.header();
if !header.is_directory() {
result.push(convert(header));
}
match reader.next_file() {
Ok(r) => {
if !r {
break;
}
}
Err(e) => return Err(Error::Fatal(Box::new(e))),
}
}
Ok(Entries::new(archive_file, result))
}
fn perform(&self, archive_file: PathBuf, base: PathBuf) -> Result<()> {
let mut reader = delharc::parse_file(archive_file).map_err(Error::IO)?;
let mut errs = vec![];
loop {
if let Err(e) = write_data_impl(&mut reader, &base) {
errs.push(e);
}
match reader.next_file() {
Ok(r) => {
if !r {
break;
}
}
Err(e) => return Err(Error::Fatal(Box::new(e))),
}
}
Error::error_or((), errs)
}
}
fn write_data_impl(reader: &mut LhaDecodeReader<File>, base: &Path) -> Result<()> {
let header = reader.header();
let name = header.parse_pathname();
let dest = base.join(&name);
if reader.is_decoder_supported() {
log::info!("extracting {:?} ({} bytes)", &name, header.original_size);
create_dir_all(dest.parent().unwrap()).unwrap();
let mut dest = File::create(dest).map_err(Error::IO)?;
copy(reader, &mut dest).map_err(Error::IO)?;
if let Err(e) = reader.crc_check() {
return Err(Error::Fatal(Box::new(e)));
};
} else if !header.is_directory() {
log::info!(
"{name:?}: unsupported compression method ({:?})",
header.compression
);
}
Ok(())
}
fn convert(h: &LhaHeader) -> Entry {
let name = h.parse_pathname().to_str().unwrap().to_string();
let compressed_size = h.compressed_size;
let original_size = h.original_size;
let mtime = h.last_modified as i64;
let dt = DateTime::from_timestamp(mtime, 0)
.map(|dt| dt.naive_local());
Entry::builder()
.name(name)
.compressed_size(compressed_size)
.original_size(original_size)
.date(dt)
.build()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_list_archives() {
let file = PathBuf::from("../testdata/test.lzh");
let extractor = Extractor {};
match extractor.list(file) {
Ok(r) => {
let r = r.iter().map(|e| e.name.clone()).collect::<Vec<_>>();
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 archive_file = PathBuf::from("../testdata/test.lzh");
let opts = crate::ExtractConfig::builder()
.dest("results/lha")
.use_archive_name_dir(true)
.overwrite(true)
.build();
match crate::extract(archive_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);
}
};
}
}