os_bridge/common/
unzip.rs1use 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 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 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 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 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 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 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 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 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}