vx_installer/formats/
zip.rs1use super::FormatHandler;
4use crate::{progress::ProgressContext, Error, Result};
5use std::path::{Path, PathBuf};
6
7pub struct ZipHandler;
9
10impl ZipHandler {
11 pub fn new() -> Self {
13 Self
14 }
15}
16
17#[async_trait::async_trait]
18impl FormatHandler for ZipHandler {
19 fn name(&self) -> &str {
20 "zip"
21 }
22
23 fn can_handle(&self, file_path: &Path) -> bool {
24 if let Some(filename) = file_path.file_name().and_then(|n| n.to_str()) {
25 filename.ends_with(".zip")
26 } else {
27 false
28 }
29 }
30
31 async fn extract(
32 &self,
33 source_path: &Path,
34 target_dir: &Path,
35 progress: &ProgressContext,
36 ) -> Result<Vec<PathBuf>> {
37 std::fs::create_dir_all(target_dir)?;
39
40 let file = std::fs::File::open(source_path)?;
42 let mut archive = zip::ZipArchive::new(file).map_err(|e| {
43 Error::extraction_failed(source_path, format!("Failed to open ZIP archive: {}", e))
44 })?;
45
46 let total_files = archive.len();
47 progress
48 .start(
49 &format!("Extracting {} files", total_files),
50 Some(total_files as u64),
51 )
52 .await?;
53
54 let mut extracted_files = Vec::new();
55
56 for i in 0..total_files {
58 let mut file = archive.by_index(i).map_err(|e| {
59 Error::extraction_failed(
60 source_path,
61 format!("Failed to access file at index {}: {}", i, e),
62 )
63 })?;
64
65 let file_path = match file.enclosed_name() {
66 Some(path) => target_dir.join(path),
67 None => {
68 progress.increment(1).await?;
70 continue;
71 }
72 };
73
74 if let Some(parent) = file_path.parent() {
76 std::fs::create_dir_all(parent)?;
77 }
78
79 if file.is_dir() {
81 std::fs::create_dir_all(&file_path)?;
83 } else {
84 let mut output_file = std::fs::File::create(&file_path)?;
86 std::io::copy(&mut file, &mut output_file)?;
87
88 #[cfg(unix)]
90 {
91 if file.unix_mode().unwrap_or(0) & 0o111 != 0 {
92 self.make_executable(&file_path)?;
93 }
94 }
95
96 extracted_files.push(file_path);
97 }
98
99 progress.increment(1).await?;
100 }
101
102 progress.finish("Extraction completed").await?;
103
104 Ok(extracted_files)
105 }
106}
107
108impl Default for ZipHandler {
109 fn default() -> Self {
110 Self::new()
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[tokio::test]
119 async fn test_zip_handler_can_handle() {
120 let handler = ZipHandler::new();
121
122 assert!(handler.can_handle(Path::new("test.zip")));
123 assert!(!handler.can_handle(Path::new("test.tar.gz")));
124 assert!(!handler.can_handle(Path::new("test.exe")));
125 }
126
127 #[tokio::test]
128 async fn test_zip_handler_name() {
129 let handler = ZipHandler::new();
130 assert_eq!(handler.name(), "zip");
131 }
132
133 }