os_bridge/common/
unzip.rs

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