use super::FormatHandler;
use crate::{progress::ProgressContext, Error, Result};
use std::path::{Path, PathBuf};
pub struct ZipHandler;
impl ZipHandler {
pub fn new() -> Self {
Self
}
}
#[async_trait::async_trait]
impl FormatHandler for ZipHandler {
fn name(&self) -> &str {
"zip"
}
fn can_handle(&self, file_path: &Path) -> bool {
if let Some(filename) = file_path.file_name().and_then(|n| n.to_str()) {
filename.ends_with(".zip")
} else {
false
}
}
async fn extract(
&self,
source_path: &Path,
target_dir: &Path,
progress: &ProgressContext,
) -> Result<Vec<PathBuf>> {
std::fs::create_dir_all(target_dir)?;
let file = std::fs::File::open(source_path)?;
let mut archive = zip::ZipArchive::new(file).map_err(|e| {
Error::extraction_failed(source_path, format!("Failed to open ZIP archive: {}", e))
})?;
let total_files = archive.len();
progress
.start(
&format!("Extracting {} files", total_files),
Some(total_files as u64),
)
.await?;
let mut extracted_files = Vec::new();
for i in 0..total_files {
let mut file = archive.by_index(i).map_err(|e| {
Error::extraction_failed(
source_path,
format!("Failed to access file at index {}: {}", i, e),
)
})?;
let file_path = match file.enclosed_name() {
Some(path) => target_dir.join(path),
None => {
progress.increment(1).await?;
continue;
}
};
if let Some(parent) = file_path.parent() {
std::fs::create_dir_all(parent)?;
}
if file.is_dir() {
std::fs::create_dir_all(&file_path)?;
} else {
let mut output_file = std::fs::File::create(&file_path)?;
std::io::copy(&mut file, &mut output_file)?;
#[cfg(unix)]
{
if file.unix_mode().unwrap_or(0) & 0o111 != 0 {
self.make_executable(&file_path)?;
}
}
extracted_files.push(file_path);
}
progress.increment(1).await?;
}
progress.finish("Extraction completed").await?;
Ok(extracted_files)
}
}
impl Default for ZipHandler {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_zip_handler_can_handle() {
let handler = ZipHandler::new();
assert!(handler.can_handle(Path::new("test.zip")));
assert!(!handler.can_handle(Path::new("test.tar.gz")));
assert!(!handler.can_handle(Path::new("test.exe")));
}
#[tokio::test]
async fn test_zip_handler_name() {
let handler = ZipHandler::new();
assert_eq!(handler.name(), "zip");
}
}