use chrono::NaiveDateTime;
use serde::Serialize;
use std::fmt::Display;
use std::path::{Path, PathBuf};
use typed_builder::TypedBuilder;
use crate::format::Format;
use crate::{Result, Error};
mod ar;
mod cab;
mod cpio;
mod lha;
mod rar;
mod sevenz;
mod tar;
mod zip;
#[derive(Debug, TypedBuilder, Serialize)]
pub struct Entry {
#[builder(setter(into))]
pub name: String,
#[builder(setter(into, strip_option), default = None)]
#[serde(skip_serializing_if = "Option::is_none")]
pub compressed_size: Option<u64>,
#[builder(setter(into, strip_option), default = None)]
#[serde(skip_serializing_if = "Option::is_none")]
pub original_size: Option<u64>,
#[builder(setter(into, strip_option), default = Some(0o644))]
#[serde(
serialize_with = "crate::outputs::serialize_option_u32_octal",
skip_serializing_if = "Option::is_none"
)]
pub unix_mode: Option<u32>,
#[builder(setter(into), default = None)]
#[serde(skip_serializing_if = "Option::is_none")]
pub date: Option<NaiveDateTime>,
}
impl Display for Entry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name)
}
}
impl Entry {
pub fn new(
name: String,
compressed_size: Option<u64>,
original_size: Option<u64>,
unix_mode: Option<u32>,
date: Option<NaiveDateTime>,
) -> Self {
Self {
name,
compressed_size,
original_size,
unix_mode,
date,
}
}
}
#[derive(Debug, Serialize)]
#[serde(rename = "archive-file")]
pub struct Entries {
pub path: PathBuf,
pub entries: Vec<Entry>,
}
impl Entries {
pub fn new(path: PathBuf, entries: Vec<Entry>) -> Self {
Self { path, entries }
}
pub fn iter(&self) -> impl Iterator<Item = &Entry> {
self.entries.iter()
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
pub trait ToteExtractor {
fn list(&self, archive_file: PathBuf) -> Result<Entries>;
fn perform(&self, archive_file: PathBuf, opts: PathBuf) -> Result<()>;
}
#[allow(dead_code)]
pub(super) fn create<P: AsRef<Path>>(file: P) -> Result<Box<dyn ToteExtractor>> {
let file = file.as_ref();
let binding = crate::format::default_format_detector();
let format = binding.detect(file);
create_with(file, format)
}
pub(super) fn create_with<P: AsRef<Path>>(file: P, format: Option<&Format>) -> Result<Box<dyn ToteExtractor>> {
let file = file.as_ref();
match format {
Some(format) => match format.name.as_str() {
"Ar" => Ok(Box::new(ar::Extractor {})),
"Cab" => Ok(Box::new(cab::Extractor {})),
"Cpio" => Ok(Box::new(cpio::Extractor {})),
"Lha" => Ok(Box::new(lha::Extractor {})),
"Rar" => Ok(Box::new(rar::Extractor {})),
"SevenZ" => Ok(Box::new(sevenz::Extractor {})),
"Tar" => Ok(Box::new(tar::Extractor {})),
"TarBz2" => Ok(Box::new(tar::Bz2Extractor {})),
"TarGz" => Ok(Box::new(tar::GzExtractor {})),
"TarXz" => Ok(Box::new(tar::XzExtractor {})),
"TarZstd" => Ok(Box::new(tar::ZstdExtractor {})),
"Zip" => Ok(Box::new(zip::Extractor {})),
s => Err(Error::UnknownFormat(format!("{s}: unknown format"))),
},
None => Err(Error::Extractor(format!(
"{file:?} no suitable extractor"
))),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_with(){
let r = create_with(PathBuf::from("../testdata/test.zip"), None);
assert!(r.is_err());
}
#[test]
fn test_create_with_unknown_format() {
let format = Format::new("Hoge", vec![".hoge"]);
let r = create_with(PathBuf::from("../testdata/test.zip"), Some(&format));
assert!(r.is_err());
}
#[test]
fn test_destination1() {
let archive_file = PathBuf::from("/tmp/archive.zip");
let opts1 = crate::ExtractConfig::builder()
.use_archive_name_dir(true)
.build();
let dest = opts1.dest(&archive_file).unwrap();
assert_eq!(dest, PathBuf::from("./archive"));
}
#[test]
fn test_destination2() {
let archive_file = PathBuf::from("/tmp/archive.zip");
let opts2 = crate::ExtractConfig::builder().build();
let dest = opts2.dest(&archive_file).unwrap();
assert_eq!(dest, PathBuf::from("."));
}
#[test]
fn test_list_entries() {
let archive_file = PathBuf::from("../testdata/test.zip");
let extractor = create(&archive_file).unwrap();
let entries = extractor.list(archive_file).unwrap();
assert_eq!(entries.len(), 19);
}
#[test]
fn test_list_entries_for_camouflaged_archive() {
let archive_file = PathBuf::from("../testdata/camouflage_of_zip.rar");
let format = crate::format::find_format_by_ext(".zip");
let extractor = create_with(&archive_file, format).unwrap();
let entries = extractor.list(archive_file).unwrap();
assert_eq!(entries.len(), 19);
}
}