os_bridge/common/
unzip.rs

1use std::fs::{self, File};
2use std::io::{self, BufReader, Write};
3use std::path::Path;
4use walkdir::WalkDir;
5use zip::read::ZipArchive;
6use zip::result::ZipError;
7use zip::unstable::write::FileOptionsExt;
8use zip::{write::FileOptions, CompressionMethod, ZipWriter};
9
10use crate::core::Zip;
11use crate::errors::BridgeError;
12use crate::BridgeResult;
13
14pub struct Unzip {
15  pub zip_pwd: Option<String>,
16}
17
18impl Unzip {
19  pub fn new(zip_pwd: Option<String>) -> Self {
20    Self { zip_pwd: zip_pwd }
21  }
22}
23
24impl Zip for Unzip {
25  /// @description:  Decompression extractor
26  /// @parama zip_src_path: Decompression target path
27  /// @parama zip_output_path: Output Path
28  fn extract<P>(&self, zip_input_path: P, zip_output_path: P) -> BridgeResult<bool>
29  where
30    P: AsRef<Path>,
31  {
32    if !zip_input_path.as_ref().exists() {
33      return Err(BridgeError::from(format!(
34        "The file '{}' does not exist",
35        zip_input_path.as_ref().display()
36      )));
37    }
38    if let Some(parent) = zip_output_path.as_ref().parent() {
39      if !parent.exists() {
40        fs::create_dir_all(parent).map_err(|err| BridgeError::from(err.to_string()))?;
41      }
42    }
43    let file = File::open(zip_input_path)?;
44    let mut archive =
45      ZipArchive::new(BufReader::new(file)).map_err(|err| BridgeError::from(err.to_string()))?;
46    match &self.zip_pwd {
47      Some(pwd) => {
48        for i in 0..archive.len() {
49          let mut file = match archive.by_index_decrypt(i, pwd.as_bytes()) {
50            Ok(f) => f,
51            Err(ZipError::InvalidPassword) => {
52              return Err(BridgeError::from(
53                "Password error, unable to decompress the file!",
54              ));
55            }
56            Err(err) => return Err(BridgeError::from(err.to_string())),
57          };
58          let outpath = Path::new(zip_output_path.as_ref()).join(file.name());
59          if file.is_dir() {
60            std::fs::create_dir_all(&outpath).map_err(|err| BridgeError::from(err.to_string()))?;
61          } else {
62            if let Some(parent) = outpath.parent() {
63              if !parent.exists() {
64                std::fs::create_dir_all(parent)?;
65              }
66            }
67            let mut outfile =
68              File::create(&outpath).map_err(|err| BridgeError::from(err.to_string()))?;
69            io::copy(&mut file, &mut outfile).map_err(|err| BridgeError::from(err.to_string()))?;
70          }
71        }
72      }
73      None => {
74        for i in 0..archive.len() {
75          let mut file = archive
76            .by_index(i)
77            .map_err(|err| BridgeError::from(err.to_string()))?;
78          let outpath = Path::new(zip_output_path.as_ref()).join(file.name());
79          if file.name().ends_with('/') {
80            std::fs::create_dir_all(&outpath).map_err(|err| BridgeError::from(err.to_string()))?;
81          } else {
82            if let Some(parent) = outpath.parent() {
83              if !parent.exists() {
84                std::fs::create_dir_all(parent)
85                  .map_err(|err| BridgeError::from(err.to_string()))?;
86              }
87            }
88            let mut outfile =
89              File::create(&outpath).map_err(|err| BridgeError::from(err.to_string()))?;
90            io::copy(&mut file, &mut outfile).map_err(|err| BridgeError::from(err.to_string()))?;
91          }
92        }
93      }
94    }
95
96    Ok(true)
97  }
98
99  /// @description:  Single file compression
100  /// @parama zip_input_path: Compression target path
101  /// @parama zip_output_path: Output Path
102  fn file_compression<P>(&self, zip_input_path: P, zip_output_path: P) -> BridgeResult<bool>
103  where
104    P: AsRef<Path>,
105  {
106    if !zip_input_path.as_ref().is_file() {
107      return Err(BridgeError::from("Please input the file path".to_string()));
108    }
109    if let Some(parent) = zip_output_path.as_ref().parent() {
110      if !parent.exists() {
111        fs::create_dir_all(parent).map_err(|err| BridgeError::from(err.to_string()))?;
112      }
113    }
114    let file = fs::File::create(zip_output_path.as_ref())
115      .map_err(|err| BridgeError::from(err.to_string()))?;
116    let mut zip = ZipWriter::new(file);
117    let mut options: FileOptions<'_, ()> = FileOptions::default()
118      .compression_method(CompressionMethod::Deflated)
119      .compression_level(Some(9))
120      .large_file(true)
121      .unix_permissions(0o755);
122    if self.zip_pwd.is_some() {
123      options = options.with_deprecated_encryption(
124        &self
125          .zip_pwd
126          .clone()
127          .unwrap_or(Default::default())
128          .to_string()
129          .as_bytes()
130          .to_vec(),
131      );
132    }
133    match zip_input_path.as_ref().file_name() {
134      Some(file_name) => {
135        if let Some(s) = file_name.to_os_string().to_str() {
136          zip
137            .start_file(s, options)
138            .map_err(|e| BridgeError::from(format!("{}", e.to_string())))?;
139          let src_file_content = fs::read(zip_input_path.as_ref())
140            .map_err(|err| BridgeError::from(format!("File read error: {}", err.to_string())))?;
141          zip.write_all(&src_file_content)?;
142          zip
143            .finish()
144            .map_err(|err| BridgeError::from(err.to_string()))?;
145        } else {
146          return Err(BridgeError::from("The file does not exist"));
147        }
148      }
149      None => return Err(BridgeError::from("The file does not exist")),
150    }
151    Ok(true)
152  }
153
154  /// @description:  Single folder compression
155  /// @parama zip_input_path: Compression target path
156  /// @parama zip_output_path: Output Path
157  fn single_folder_compression<P: AsRef<Path>>(
158    &self,
159    zip_input_path: P,
160    zip_output_path: P,
161  ) -> BridgeResult<bool> {
162    if !zip_input_path.as_ref().exists() {
163      return Err(BridgeError::from(format!(
164        "The folder path '{}' does not exist",
165        zip_input_path.as_ref().display()
166      )));
167    }
168    if let Some(parent) = zip_output_path.as_ref().parent() {
169      if !parent.exists() {
170        fs::create_dir_all(parent).map_err(|err| BridgeError::from(err.to_string()))?;
171      }
172    }
173    let zip_file = File::create(zip_output_path)?;
174    let mut zip_writer = ZipWriter::new(zip_file);
175    let base_dir = zip_input_path.as_ref().parent().unwrap_or(Path::new(""));
176    self
177      .add_dir(&mut zip_writer, base_dir, zip_input_path.as_ref())
178      .map_err(|err| BridgeError::WithMsg(err.to_string()))?;
179    zip_writer
180      .finish()
181      .map_err(|err| BridgeError::WithMsg(err.to_string()))?;
182    Ok(true)
183  }
184
185  /// batch compression
186  /// Please provide a list path
187  /// @parama zip_input_paths: [zip_input_path1, zip_input_path2, ...]
188  /// @parama zip_output_path: Output Path
189  fn batch_compression<P>(&self, zip_input_paths: Vec<P>, zip_output_path: P) -> BridgeResult<bool>
190  where
191    P: AsRef<Path>,
192  {
193    if zip_input_paths.is_empty() {
194      return Err(BridgeError::from("The file path cannot be empty"));
195    }
196    if let Some(parent) = zip_output_path.as_ref().parent() {
197      if !parent.exists() {
198        std::fs::create_dir_all(parent).map_err(|err| BridgeError::WithMsg(err.to_string()))?;
199      }
200    }
201    for p in &zip_input_paths {
202      if !p.as_ref().exists() {
203        return Err(BridgeError::from(format!(
204          "The path '{}' does not exist",
205          p.as_ref().display()
206        )));
207      }
208    }
209    let zip_file = File::create(zip_output_path)?;
210    let mut zip_writer = ZipWriter::new(zip_file);
211    for path in zip_input_paths {
212      self
213        .add_path(&mut zip_writer, &path)
214        .map_err(|err| BridgeError::from(err.to_string()))?;
215    }
216    zip_writer
217      .finish()
218      .map_err(|err| BridgeError::from(err.to_string()))?;
219    Ok(true)
220  }
221
222  /// @description:  Add directory
223  /// @parama zip_writer: ZipWriter
224  /// @parama base_dir: Directory path
225  /// @parama current_dir: Base directory
226  fn add_dir<P: AsRef<Path>>(
227    &self,
228    zip_writer: &mut ZipWriter<File>,
229    base_dir: P,
230    current_dir: P,
231  ) -> BridgeResult<bool> {
232    if !current_dir.as_ref().exists() {
233      return Err(BridgeError::from(format!(
234        "The path '{}' does not exist",
235        current_dir.as_ref().display()
236      )));
237    }
238    for entry in fs::read_dir(current_dir).map_err(|err| BridgeError::from(err.to_string()))? {
239      let entry = entry.map_err(|err| BridgeError::from(err.to_string()))?;
240      let entry_path = entry.path();
241      let relative_path = entry_path
242        .strip_prefix(base_dir.as_ref())
243        .map_err(|err| BridgeError::from(err.to_string()))?;
244      let zip_path = format!("{}", relative_path.display()).replace("\\", "/");
245      if entry_path.is_dir() {
246        let mut options: FileOptions<'_, ()> = FileOptions::default()
247          .compression_method(CompressionMethod::Deflated)
248          .large_file(true)
249          .unix_permissions(0o755);
250        if self.zip_pwd.is_some() {
251          options = options.with_deprecated_encryption(
252            &self
253              .zip_pwd
254              .clone()
255              .unwrap_or(Default::default())
256              .to_string()
257              .as_bytes()
258              .to_vec(),
259          );
260        }
261        zip_writer
262          .add_directory(zip_path, options)
263          .map_err(|err| BridgeError::from(err.to_string()))?;
264        self
265          .add_dir(zip_writer, base_dir.as_ref(), &entry_path)
266          .map_err(|err| BridgeError::from(err.to_string()))?;
267      } else {
268        // 添加文件
269        let mut options: FileOptions<'_, ()> = FileOptions::default()
270          .compression_method(CompressionMethod::Deflated)
271          .large_file(true)
272          .unix_permissions(0o755);
273        if self.zip_pwd.is_some() {
274          options = options.with_deprecated_encryption(
275            &self
276              .zip_pwd
277              .clone()
278              .unwrap_or(Default::default())
279              .to_string()
280              .as_bytes()
281              .to_vec(),
282          );
283        }
284        let mut file = File::open(&entry_path)?;
285        zip_writer
286          .start_file(zip_path, options)
287          .map_err(|err| BridgeError::from(err.to_string()))?;
288        io::copy(&mut file, zip_writer)?;
289      }
290    }
291    Ok(true)
292  }
293
294  /// @description: Add a path to the zip file
295  /// @parama zip_writer: zip_writer
296  /// @parama path: Path
297  fn add_path<P: AsRef<Path>>(
298    &self,
299    zip_writer: &mut ZipWriter<File>,
300    path: P,
301  ) -> BridgeResult<bool> {
302    if path.as_ref().is_file() {
303      let file_name = path
304        .as_ref()
305        .file_name()
306        .and_then(|n| n.to_str())
307        .unwrap_or(Default::default());
308      let mut options: FileOptions<'_, ()> = FileOptions::default()
309        .compression_method(CompressionMethod::Deflated)
310        .large_file(true)
311        .unix_permissions(0o755);
312      if self.zip_pwd.is_some() {
313        options = options.with_deprecated_encryption(
314          &self
315            .zip_pwd
316            .clone()
317            .unwrap_or(Default::default())
318            .to_string()
319            .as_bytes()
320            .to_vec(),
321        );
322      }
323      zip_writer
324        .start_file(file_name, options)
325        .map_err(|err| BridgeError::new(&err.to_string()))?;
326      let mut file = fs::File::open(path).map_err(|err| BridgeError::new(&err.to_string()))?;
327      io::copy(&mut file, zip_writer).map_err(|err| BridgeError::new(&err.to_string()))?;
328    } else if path.as_ref().is_dir() {
329      let path_ref = path.as_ref();
330      if self
331        .check_folder_is_empty(path_ref)
332        .map_err(|err| BridgeError::new(&err.to_string()))?
333      {
334        let create_path = path_ref
335          .strip_prefix(path.as_ref().parent().unwrap_or(Path::new("")))
336          .unwrap_or(Path::new(""))
337          .to_str()
338          .unwrap_or_default()
339          .replace("\\", "/");
340        let mut options: FileOptions<'_, ()> = FileOptions::default()
341          .compression_method(CompressionMethod::Deflated)
342          .large_file(true)
343          .unix_permissions(0o755);
344        if self.zip_pwd.is_some() {
345          options = options.with_deprecated_encryption(
346            &self
347              .zip_pwd
348              .clone()
349              .unwrap_or(Default::default())
350              .to_string()
351              .as_bytes()
352              .to_vec(),
353          );
354        }
355        let out_path = create_path.replace("\\", "/");
356        zip_writer
357          .add_directory(out_path, options)
358          .map_err(|err| BridgeError::new(&err.to_string()))?;
359      } else {
360        let prefix = path_ref.parent().unwrap_or(Path::new(""));
361        self.add_dir(zip_writer, prefix, path.as_ref())?;
362      }
363    }
364    Ok(true)
365  }
366
367  /// @description Check whether the folder is empty
368  /// @parama path: Path
369  fn check_folder_is_empty<P: AsRef<Path>>(&self, path: P) -> BridgeResult<bool> {
370    let path = path.as_ref();
371    if path.is_dir() {
372      let entries = fs::read_dir(path)?;
373      for entry in entries {
374        let entry = entry?;
375        if entry.file_type()?.is_file() || entry.file_type()?.is_dir() {
376          return Ok(false);
377        }
378      }
379      Ok(true)
380    } else {
381      return Err(BridgeError::new("Path is not a directory"));
382    }
383  }
384
385  fn calculate_size<P>(&self, path: P) -> BridgeResult<f64>
386  where
387    P: AsRef<Path>,
388  {
389    let mut paths = vec![path.as_ref().to_path_buf()];
390    let mut res_size = 0u64;
391    while let Some(path) = paths.pop() {
392      let meta =
393        std::fs::symlink_metadata(&path).map_err(|err| BridgeError::from(err.to_string()))?;
394      let file_type = meta.file_type();
395      if file_type.is_dir() {
396        let entries = std::fs::read_dir(path).map_err(|err| BridgeError::from(err.to_string()))?;
397        for entry in entries {
398          let entry = entry.map_err(|err| BridgeError::from(err.to_string()))?;
399          let path = entry.path();
400          paths.push(path);
401        }
402      }
403      if file_type.is_file() {
404        res_size += meta.len();
405      }
406    }
407    Ok(res_size as f64)
408  }
409
410  fn get_size<P>(&self, path: P) -> u64
411  where
412    P: AsRef<Path>,
413  {
414    let total_size = WalkDir::new(path)
415      .min_depth(1)
416      .max_depth(3)
417      .into_iter()
418      .filter_map(|entry| entry.ok())
419      .filter_map(|entry| entry.metadata().ok())
420      .filter(|metadata| metadata.is_file())
421      .fold(0, |acc, m| acc + m.len());
422    total_size
423  }
424}