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