1use std::{
2 env, fmt,
3 fs::{self, File},
4 io::{self, BufReader, Cursor, Error, ErrorKind, Read, Write},
5 os::unix::fs::PermissionsExt,
6 path::{Path, PathBuf},
7};
8
9use flate2::{
10 bufread::{GzDecoder, GzEncoder},
11 Compression,
12};
13use fs_extra;
14use path_clean::PathClean;
15use tar::{Archive, Builder};
16use walkdir::{DirEntry, WalkDir};
17use zip::{write::FileOptions, ZipArchive, ZipWriter};
18use zstd;
19
20#[derive(Eq, PartialEq, Clone)]
22pub enum Encoder {
23 Gzip,
25 Zstd(i32),
27 ZstdBase58(i32),
29}
30
31impl fmt::Display for Encoder {
35 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
37 match self {
38 Encoder::Gzip => write!(f, "gzip"),
39 Encoder::Zstd(level) => write!(f, "zstd{}", level),
40 Encoder::ZstdBase58(level) => {
41 write!(f, "zstd-base58{}", level)
42 }
43 }
44 }
45}
46
47impl Encoder {
48 pub fn id(&self) -> &str {
49 match self {
50 Encoder::Gzip => "gzip",
51 Encoder::Zstd(1) => "zstd1",
52 Encoder::Zstd(2) => "zstd2",
53 Encoder::Zstd(3) => "zstd3",
54 Encoder::ZstdBase58(1) => "zstd1-base58",
55 Encoder::ZstdBase58(2) => "zstd2-base58",
56 Encoder::ZstdBase58(3) => "zstd3-base58",
57 _ => "unknown",
58 }
59 }
60
61 pub fn new(id: &str) -> io::Result<Self> {
62 match id {
63 "gzip" => Ok(Encoder::Gzip),
64 "zstd1" => Ok(Encoder::Zstd(1)),
65 "zstd2" => Ok(Encoder::Zstd(2)),
66 "zstd3" => Ok(Encoder::Zstd(3)),
67 "zstd1-base58" => Ok(Encoder::ZstdBase58(1)),
68 "zstd2-base58" => Ok(Encoder::ZstdBase58(2)),
69 "zstd3-base58" => Ok(Encoder::ZstdBase58(3)),
70 _ => Err(Error::new(
71 ErrorKind::InvalidInput,
72 format!("unknown id {}", id),
73 )),
74 }
75 }
76
77 pub fn suffix(&self) -> &str {
78 match self {
79 Encoder::Gzip => "gz",
80 Encoder::Zstd(_) => "zstd",
81 Encoder::ZstdBase58(_) => "zstd.base58",
82 }
83 }
84
85 pub fn ext(&self) -> &str {
86 match self {
87 Encoder::Gzip => ".gz",
88 Encoder::Zstd(_) => ".zstd",
89 Encoder::ZstdBase58(_) => ".zstd.base58",
90 }
91 }
92}
93
94#[derive(Clone)]
96pub enum Decoder {
97 Gzip,
98 Zstd,
99 ZstdBase58,
100}
101
102impl fmt::Display for Decoder {
106 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
107 match self {
108 Decoder::Gzip => write!(f, "gzip"),
109 Decoder::Zstd => write!(f, "zstd"),
110 Decoder::ZstdBase58 => write!(f, "zstd-base58"),
111 }
112 }
113}
114
115impl Decoder {
116 pub fn id(&self) -> &str {
117 match self {
118 Decoder::Gzip => "gzip",
119 Decoder::Zstd => "zstd",
120 Decoder::ZstdBase58 => "zstd-base58",
121 }
122 }
123
124 pub fn new(id: &str) -> io::Result<Self> {
125 match id {
126 "gzip" => Ok(Decoder::Gzip),
127 "zstd" => Ok(Decoder::Zstd),
128 "zstd-base58" => Ok(Decoder::ZstdBase58),
129 _ => Err(Error::new(
130 ErrorKind::InvalidInput,
131 format!("unknown id {}", id),
132 )),
133 }
134 }
135}
136
137pub fn pack(d: &[u8], enc: Encoder) -> io::Result<Vec<u8>> {
138 let size_before = d.len() as f64;
139 log::info!(
140 "packing (algorithm {}, current size {})",
141 enc.to_string(),
142 human_readable::bytes(size_before),
143 );
144
145 let packed = match enc {
146 Encoder::Gzip => {
147 let mut gz = GzEncoder::new(Cursor::new(d), Compression::default());
148 let mut encoded = Vec::new();
149 gz.read_to_end(&mut encoded)?;
150 encoded
151 }
152 Encoder::Zstd(lvl) => zstd::stream::encode_all(Cursor::new(d), lvl)?,
153 Encoder::ZstdBase58(lvl) => {
154 let encoded = zstd::stream::encode_all(Cursor::new(d), lvl)?;
155 bs58::encode(encoded).into_vec()
156 }
157 };
158
159 let size_after = packed.len() as f64;
160 log::info!(
161 "packed to {} (before {}, new size {})",
162 enc.to_string(),
163 human_readable::bytes(size_before),
164 human_readable::bytes(size_after),
165 );
166 Ok(packed)
167}
168
169pub fn unpack(d: &[u8], dec: Decoder) -> io::Result<Vec<u8>> {
170 let size_before = d.len() as f64;
171 log::info!(
172 "unpacking (algorithm {}, current size {})",
173 dec.to_string(),
174 human_readable::bytes(size_before),
175 );
176
177 let unpacked = match dec {
178 Decoder::Gzip => {
179 let mut gz = GzDecoder::new(Cursor::new(d));
180 let mut decoded = Vec::new();
181 gz.read_to_end(&mut decoded)?;
182 decoded
183 }
184 Decoder::Zstd => zstd::stream::decode_all(Cursor::new(d))?,
185 Decoder::ZstdBase58 => {
186 let d_decoded = match bs58::decode(d).into_vec() {
187 Ok(v) => v,
188 Err(e) => {
189 return Err(Error::new(
190 ErrorKind::Other,
191 format!("failed bs58::decode {}", e),
192 ));
193 }
194 };
195 zstd::stream::decode_all(Cursor::new(d_decoded))?
196 }
197 };
198
199 let size_after = unpacked.len() as f64;
200 log::info!(
201 "unpacked to {} (before {}, new size {})",
202 dec.to_string(),
203 human_readable::bytes(size_before),
204 human_readable::bytes(size_after),
205 );
206 Ok(unpacked)
207}
208
209pub fn pack_file(src_path: &str, dst_path: &str, enc: Encoder) -> io::Result<()> {
227 let meta = fs::metadata(src_path)?;
228 let size_before = meta.len() as f64;
229 log::info!(
230 "packing file '{}' to '{}' (algorithm {}, current size {})",
231 src_path,
232 dst_path,
233 enc.to_string(),
234 human_readable::bytes(size_before),
235 );
236
237 match enc {
238 Encoder::Gzip => {
239 let f1 = File::open(src_path)?;
240 let mut f2 = File::create(dst_path)?;
241
242 let mut enc = GzEncoder::new(BufReader::new(f1), Compression::default());
243
244 io::copy(&mut enc, &mut f2)?;
246 }
247 Encoder::Zstd(lvl) => {
248 let mut f1 = File::open(src_path)?;
249 let f2 = File::create(dst_path)?;
250
251 let mut enc = zstd::Encoder::new(f2, lvl)?;
252
253 io::copy(&mut f1, &mut enc)?;
255 enc.finish()?;
256 }
257 Encoder::ZstdBase58(lvl) => {
258 let d = fs::read(src_path)?;
260 let encoded = pack(&d, Encoder::ZstdBase58(lvl))?;
261 let mut f = File::create(dst_path)?;
262 f.write_all(&encoded[..])?;
263 }
264 };
265
266 let meta = fs::metadata(dst_path)?;
267 let size_after = meta.len() as f64;
268 log::info!(
269 "packed file '{}' to '{}' (algorithm {}, before {}, new size {})",
270 src_path,
271 dst_path,
272 enc.to_string(),
273 human_readable::bytes(size_before),
274 human_readable::bytes(size_after),
275 );
276 Ok(())
277}
278
279pub fn unpack_file(src_path: &str, dst_path: &str, dec: Decoder) -> io::Result<()> {
297 let meta = fs::metadata(src_path)?;
298 let size_before = meta.len() as f64;
299 log::info!(
300 "unpacking file '{}' to '{}' (algorithm {}, current size {})",
301 src_path,
302 dst_path,
303 dec.to_string(),
304 human_readable::bytes(size_before),
305 );
306
307 match dec {
308 Decoder::Gzip => {
309 let f1 = File::open(src_path)?;
310 let mut f2 = File::create(dst_path)?;
311
312 let mut dec = GzDecoder::new(BufReader::new(f1));
313
314 io::copy(&mut dec, &mut f2)?;
316 }
317 Decoder::Zstd => {
318 let f1 = File::open(src_path)?;
319 let mut f2 = File::create(dst_path)?;
320
321 let mut dec = zstd::Decoder::new(BufReader::new(f1))?;
322
323 io::copy(&mut dec, &mut f2)?;
325 }
326 Decoder::ZstdBase58 => {
327 let d = fs::read(src_path)?;
329 let decoded = unpack(&d, Decoder::ZstdBase58)?;
330 let mut f = File::create(dst_path)?;
331 f.write_all(&decoded[..])?;
332 }
333 };
334
335 let meta = fs::metadata(dst_path)?;
336 let size_after = meta.len() as f64;
337 log::info!(
338 "unpacked file '{}' to '{}' (algorithm {}, before {}, new size {})",
339 src_path,
340 dst_path,
341 dec.to_string(),
342 human_readable::bytes(size_before),
343 human_readable::bytes(size_after),
344 );
345 Ok(())
346}
347
348#[derive(Clone)]
350pub enum DirEncoder {
351 Zip,
353 TarGzip,
356 ZipGzip,
359 TarZstd(i32),
362 ZipZstd(i32),
365}
366
367impl fmt::Display for DirEncoder {
371 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
373 match self {
374 DirEncoder::Zip => write!(f, "zip"),
375 DirEncoder::TarGzip => write!(f, "tar-gzip"),
376 DirEncoder::ZipGzip => write!(f, "zip-gzip"),
377 DirEncoder::TarZstd(level) => write!(f, "tar-zstd{}", level),
378 DirEncoder::ZipZstd(level) => write!(f, "zip-zstd{}", level),
379 }
380 }
381}
382
383impl DirEncoder {
384 pub fn id(&self) -> &str {
385 match self {
386 DirEncoder::Zip => "zip",
387 DirEncoder::TarGzip => "tar-gzip",
388 DirEncoder::ZipGzip => "zip-gzip",
389 DirEncoder::TarZstd(1) => "tar-zstd1",
390 DirEncoder::TarZstd(2) => "tar-zstd2",
391 DirEncoder::TarZstd(3) => "tar-zstd3",
392 DirEncoder::ZipZstd(1) => "zip-zstd1",
393 DirEncoder::ZipZstd(2) => "zip-zstd2",
394 DirEncoder::ZipZstd(3) => "zip-zstd3",
395 _ => "unknown",
396 }
397 }
398
399 pub fn new(id: &str) -> io::Result<Self> {
400 match id {
401 "zip" => Ok(DirEncoder::Zip),
402 "tar-gzip" => Ok(DirEncoder::TarGzip),
403 "zip-gzip" => Ok(DirEncoder::ZipGzip),
404 "tar-zstd1" => Ok(DirEncoder::TarZstd(1)),
405 "tar-zstd2" => Ok(DirEncoder::TarZstd(2)),
406 "tar-zstd3" => Ok(DirEncoder::TarZstd(3)),
407 "zip-zstd1" => Ok(DirEncoder::ZipZstd(1)),
408 "zip-zstd2" => Ok(DirEncoder::ZipZstd(2)),
409 "zip-zstd3" => Ok(DirEncoder::ZipZstd(3)),
410 _ => Err(Error::new(
411 ErrorKind::InvalidInput,
412 format!("unknown id {}", id),
413 )),
414 }
415 }
416
417 pub fn suffix(&self) -> &str {
418 match self {
419 DirEncoder::Zip => ".zip",
420 DirEncoder::TarGzip => "tar.gz",
421 DirEncoder::ZipGzip => "zip.gz",
422 DirEncoder::TarZstd(_) => "tar.zstd",
423 DirEncoder::ZipZstd(_) => "zip.zstd",
424 }
425 }
426
427 pub fn ext(&self) -> &str {
428 match self {
429 DirEncoder::Zip => ".zip",
430 DirEncoder::TarGzip => ".tar.gz",
431 DirEncoder::ZipGzip => ".zip.gz",
432 DirEncoder::TarZstd(_) => ".tar.zstd",
433 DirEncoder::ZipZstd(_) => ".zip.zstd",
434 }
435 }
436}
437
438#[derive(Clone)]
440pub enum DirDecoder {
441 Zip,
442 TarGzip,
443 ZipGzip,
444 TarZstd,
445 ZipZstd,
446}
447
448impl fmt::Display for DirDecoder {
452 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
453 match self {
454 DirDecoder::Zip => write!(f, "zip"),
455 DirDecoder::TarGzip => write!(f, "tar-gzip"),
456 DirDecoder::ZipGzip => write!(f, "zip-gzip"),
457 DirDecoder::TarZstd => write!(f, "tar-zstd"),
458 DirDecoder::ZipZstd => write!(f, "zip-zstd"),
459 }
460 }
461}
462
463impl DirDecoder {
464 pub fn id(&self) -> &str {
465 match self {
466 DirDecoder::Zip => "zip",
467 DirDecoder::TarGzip => "tar-gzip",
468 DirDecoder::ZipGzip => "zip-gzip",
469 DirDecoder::TarZstd => "tar-zstd",
470 DirDecoder::ZipZstd => "zip-zstd",
471 }
472 }
473
474 pub fn new(id: &str) -> io::Result<Self> {
475 match id {
476 "zip" => Ok(DirDecoder::Zip),
477 "tar-gzip" => Ok(DirDecoder::TarGzip),
478 "zip-gzip" => Ok(DirDecoder::ZipGzip),
479 "tar-zstd" => Ok(DirDecoder::TarZstd),
480 "zip-zstd" => Ok(DirDecoder::ZipZstd),
481 _ => Err(Error::new(
482 ErrorKind::InvalidInput,
483 format!("unknown id {}", id),
484 )),
485 }
486 }
487
488 pub fn new_from_file_name(name: &str) -> io::Result<Self> {
489 if name.ends_with(DirDecoder::Zip.suffix()) {
490 Ok(DirDecoder::Zip)
491 } else if name.ends_with(DirDecoder::TarGzip.suffix()) {
492 Ok(DirDecoder::TarGzip)
493 } else if name.ends_with(DirDecoder::ZipGzip.suffix()) {
494 Ok(DirDecoder::ZipGzip)
495 } else if name.ends_with(DirDecoder::TarZstd.suffix()) {
496 Ok(DirDecoder::TarZstd)
497 } else if name.ends_with(DirDecoder::ZipZstd.suffix()) {
498 Ok(DirDecoder::ZipZstd)
499 } else {
500 Err(Error::new(
501 ErrorKind::InvalidInput,
502 format!("unknown suffix {}", name),
503 ))
504 }
505 }
506
507 pub fn suffix(&self) -> &str {
508 match self {
509 DirDecoder::Zip => ".zip",
510 DirDecoder::TarGzip => "tar.gz",
511 DirDecoder::ZipGzip => "zip.gz",
512 DirDecoder::TarZstd => "tar.zstd",
513 DirDecoder::ZipZstd => "zip.zstd",
514 }
515 }
516
517 pub fn ext(&self) -> &str {
518 match self {
519 DirDecoder::Zip => ".zip",
520 DirDecoder::TarGzip => ".tar.gz",
521 DirDecoder::ZipGzip => ".zip.gz",
522 DirDecoder::TarZstd => ".tar.zstd",
523 DirDecoder::ZipZstd => ".zip.zstd",
524 }
525 }
526
527 pub fn compression_ext(&self) -> &str {
528 match self {
529 DirDecoder::Zip => ".zip",
530 DirDecoder::TarGzip => ".gz",
531 DirDecoder::ZipGzip => ".gz",
532 DirDecoder::TarZstd => ".zstd",
533 DirDecoder::ZipZstd => ".zstd",
534 }
535 }
536}
537
538pub fn pack_directory(src_dir_path: &str, dst_path: &str, enc: DirEncoder) -> io::Result<()> {
541 if Path::new(src_dir_path).parent().is_none() {
542 return Err(Error::new(
543 ErrorKind::Other,
544 format!("cannot archive root directory {}", src_dir_path),
545 ));
546 };
547 let size = fs_extra::dir::get_size(src_dir_path).map_err(|e| {
548 Error::new(
549 ErrorKind::Other,
550 format!("failed get_size {} for directory {}", e, src_dir_path),
551 )
552 })?;
553 let size_before = size as f64;
554 log::info!(
555 "packing directory from '{}' to '{}' (algorithm {}, current size {})",
556 src_dir_path,
557 dst_path,
558 enc.to_string(),
559 human_readable::bytes(size_before),
560 );
561
562 let parent_dir = Path::new(src_dir_path)
563 .parent()
564 .expect("unexpected no parent dir");
565 let archive_path =
566 parent_dir.join(random_manager::tmp_path(10, None).expect("expected some tmp_path"));
567 let archive_path = archive_path
568 .as_path()
569 .to_str()
570 .expect("unexpected None path");
571 let archive_file = File::create(&archive_path)?;
572 match enc {
573 DirEncoder::Zip => {
574 let mut zip = ZipWriter::new(archive_file);
575
576 let mut buffer = Vec::new();
577 let src_dir = Path::new(src_dir_path);
578 let src_dir_full_path = absolute_path(src_dir)?;
579
580 let options = FileOptions::default()
581 .compression_method(zip::CompressionMethod::Stored)
582 .unix_permissions(0o755);
583 for entry in WalkDir::new(src_dir_path).into_iter() {
584 let entry = match entry {
585 Ok(v) => v,
586 Err(e) => {
587 return Err(Error::new(
588 ErrorKind::Other,
589 format!("failed walk dir {} ({})", src_dir_path, e),
590 ));
591 }
592 };
593
594 let full_path = absolute_path(entry.path())?;
595 let rel_path = match full_path.strip_prefix(&src_dir_full_path) {
598 Ok(v) => v,
599 Err(e) => {
600 return Err(Error::new(
601 ErrorKind::Other,
602 format!("failed strip_prefix on {:?} ({})", full_path, e),
603 ));
604 }
605 };
606
607 if is_dir(&entry) {
608 if !rel_path.as_os_str().is_empty() {
611 let dir_name = rel_path
612 .as_os_str()
613 .to_str()
614 .expect("unexpected None os_str");
615 log::info!("adding directory {}", dir_name);
616 zip.add_directory(dir_name, options)?;
617 }
618 continue;
619 }
620
621 let file_name = rel_path
622 .as_os_str()
623 .to_str()
624 .expect("unexpected None os_str");
625 log::info!("adding file {}", file_name);
626 zip.start_file(file_name, options)?;
627 let mut f = File::open(full_path)?;
628 f.read_to_end(&mut buffer)?;
629 zip.write_all(&*buffer)?;
630 buffer.clear();
631 }
632 zip.finish()?;
633
634 log::info!("renaming archived file {} to {}", archive_path, dst_path);
635 fs::rename(archive_path, dst_path)?;
636 }
637
638 DirEncoder::TarGzip => {
639 let mut tar = Builder::new(archive_file);
646 let src_dir = Path::new(src_dir_path);
647 let src_dir_full_path = absolute_path(src_dir)?;
648 for entry in WalkDir::new(src_dir_path).into_iter() {
649 let entry = match entry {
650 Ok(v) => v,
651 Err(e) => {
652 return Err(Error::new(
653 ErrorKind::Other,
654 format!("failed walk dir {} ({})", src_dir_path, e),
655 ));
656 }
657 };
658
659 let full_path = absolute_path(entry.path())?;
660 let rel_path = match full_path.strip_prefix(&src_dir_full_path) {
663 Ok(v) => v,
664 Err(e) => {
665 return Err(Error::new(
666 ErrorKind::Other,
667 format!("failed strip_prefix on {:?} ({})", full_path, e),
668 ));
669 }
670 };
671
672 if is_dir(&entry) {
673 continue;
674 }
675
676 let file_name = rel_path
677 .as_os_str()
678 .to_str()
679 .expect("unexpected None os_str");
680 log::info!("adding file {}", file_name);
681 let mut f = File::open(&full_path)?;
682 tar.append_file(&file_name, &mut f)?;
683 }
684 pack_file(archive_path, dst_path, Encoder::Gzip)?;
685 }
686
687 DirEncoder::ZipGzip => {
688 let mut zip = ZipWriter::new(archive_file);
689
690 let mut buffer = Vec::new();
691 let src_dir = Path::new(src_dir_path);
692 let src_dir_full_path = absolute_path(src_dir)?;
693
694 let options = FileOptions::default()
695 .compression_method(zip::CompressionMethod::Stored)
696 .unix_permissions(0o755);
697 for entry in WalkDir::new(src_dir_path).into_iter() {
698 let entry = match entry {
699 Ok(v) => v,
700 Err(e) => {
701 return Err(Error::new(
702 ErrorKind::Other,
703 format!("failed walk dir {} ({})", src_dir_path, e),
704 ));
705 }
706 };
707
708 let full_path = absolute_path(entry.path())?;
709 let rel_path = match full_path.strip_prefix(&src_dir_full_path) {
712 Ok(v) => v,
713 Err(e) => {
714 return Err(Error::new(
715 ErrorKind::Other,
716 format!("failed strip_prefix on {:?} ({})", full_path, e),
717 ));
718 }
719 };
720
721 if is_dir(&entry) {
722 if !rel_path.as_os_str().is_empty() {
725 let dir_name = rel_path
726 .as_os_str()
727 .to_str()
728 .expect("unexpected None os_str");
729 log::info!("adding directory {}", dir_name);
730 zip.add_directory(dir_name, options)?;
731 }
732 continue;
733 }
734
735 let file_name = rel_path
736 .as_os_str()
737 .to_str()
738 .expect("unexpected None os_str");
739 log::info!("adding file {}", file_name);
740 zip.start_file(file_name, options)?;
741 let mut f = File::open(full_path)?;
742 f.read_to_end(&mut buffer)?;
743 zip.write_all(&*buffer)?;
744 buffer.clear();
745 }
746 zip.finish()?;
747 pack_file(archive_path, dst_path, Encoder::Gzip)?;
748 }
749
750 DirEncoder::TarZstd(lvl) => {
751 let mut tar = Builder::new(archive_file);
752 let src_dir = Path::new(src_dir_path);
753 let src_dir_full_path = absolute_path(src_dir)?;
754 for entry in WalkDir::new(src_dir_path).into_iter() {
755 let entry = match entry {
756 Ok(v) => v,
757 Err(e) => {
758 return Err(Error::new(
759 ErrorKind::Other,
760 format!("failed walk dir {} ({})", src_dir_path, e),
761 ));
762 }
763 };
764
765 let full_path = absolute_path(entry.path())?;
766 let rel_path = match full_path.strip_prefix(&src_dir_full_path) {
769 Ok(v) => v,
770 Err(e) => {
771 return Err(Error::new(
772 ErrorKind::Other,
773 format!("failed strip_prefix on {:?} ({})", full_path, e),
774 ));
775 }
776 };
777
778 if is_dir(&entry) {
779 continue;
780 }
781
782 let file_name = rel_path
783 .as_os_str()
784 .to_str()
785 .expect("unexpected None os_str");
786 log::info!("adding file {}", file_name);
787 let mut f = File::open(&full_path)?;
788 tar.append_file(&file_name, &mut f)?;
789 }
790 pack_file(archive_path, dst_path, Encoder::Zstd(lvl))?;
791 }
792
793 DirEncoder::ZipZstd(lvl) => {
794 let mut zip = ZipWriter::new(archive_file);
795
796 let mut buffer = Vec::new();
797 let src_dir = Path::new(src_dir_path);
798 let src_dir_full_path = absolute_path(src_dir)?;
799
800 let options = FileOptions::default()
801 .compression_method(zip::CompressionMethod::Stored)
802 .unix_permissions(0o755);
803 for entry in WalkDir::new(src_dir_path).into_iter() {
804 let entry = match entry {
805 Ok(v) => v,
806 Err(e) => {
807 return Err(Error::new(
808 ErrorKind::Other,
809 format!("failed walk dir {} ({})", src_dir_path, e),
810 ));
811 }
812 };
813
814 let full_path = absolute_path(entry.path())?;
815 let rel_path = match full_path.strip_prefix(&src_dir_full_path) {
818 Ok(v) => v,
819 Err(e) => {
820 return Err(Error::new(
821 ErrorKind::Other,
822 format!("failed strip_prefix on {:?} ({})", full_path, e),
823 ));
824 }
825 };
826
827 if is_dir(&entry) {
828 if !rel_path.as_os_str().is_empty() {
831 let dir_name = rel_path
832 .as_os_str()
833 .to_str()
834 .expect("unexpected None os_str");
835 log::info!("adding directory {}", dir_name);
836 zip.add_directory(dir_name, options)?;
837 }
838 continue;
839 }
840
841 let file_name = rel_path
842 .as_os_str()
843 .to_str()
844 .expect("unexpected None os_str");
845 log::info!("adding file {}", file_name);
846 zip.start_file(file_name, options)?;
847 let mut f = File::open(full_path)?;
848 f.read_to_end(&mut buffer)?;
849 zip.write_all(&*buffer)?;
850 buffer.clear();
851 }
852 zip.finish()?;
853 pack_file(archive_path, dst_path, Encoder::Zstd(lvl))?;
854 }
855 }
856
857 let meta = fs::metadata(dst_path)?;
858 let size_after = meta.len() as f64;
859 log::info!(
860 "packed directory from '{}' to '{}' (algorithm {}, before {}, new size {})",
861 src_dir_path,
862 dst_path,
863 enc.to_string(),
864 human_readable::bytes(size_before),
865 human_readable::bytes(size_after),
866 );
867 Ok(())
868}
869
870pub fn unpack_directory(
873 src_archive_path: &str,
874 dst_dir_path: &str,
875 dec: DirDecoder,
876) -> io::Result<()> {
877 let meta = fs::metadata(src_archive_path)?;
878 let size_before = meta.len() as f64;
879 log::info!(
880 "unpacking directory from '{}' to '{}' (algorithm {}, current size {})",
881 src_archive_path,
882 dst_dir_path,
883 dec.to_string(),
884 human_readable::bytes(size_before),
885 );
886 fs::create_dir_all(dst_dir_path)?;
887 let target_dir = Path::new(dst_dir_path);
888
889 fs::set_permissions(target_dir, PermissionsExt::from_mode(0o775))?;
891
892 let decompressed_path = {
893 if src_archive_path.ends_with(dec.compression_ext()) {
894 let p = src_archive_path.replace(dec.compression_ext(), "");
895 if Path::new(&p).exists() {
896 log::info!("decompressed path already exists, removing {}", p);
897 fs::remove_file(&p)?;
898 }
899 p
900 } else {
901 format!("{}.decompressed", src_archive_path)
902 }
903 };
904
905 match dec {
906 DirDecoder::Zip => {
907 log::info!("unarchiving the zip file {}", src_archive_path);
908 let zip_file = File::open(&src_archive_path)?;
909 let mut zip = match ZipArchive::new(zip_file) {
910 Ok(v) => v,
911 Err(e) => {
912 return Err(Error::new(
913 ErrorKind::Other,
914 format!("failed ZipArchive::new on {} ({})", src_archive_path, e),
915 ));
916 }
917 };
918
919 for i in 0..zip.len() {
920 let mut f = match zip.by_index(i) {
921 Ok(v) => v,
922 Err(e) => {
923 return Err(Error::new(
924 ErrorKind::Other,
925 format!("failed zip.by_index ({})", e),
926 ));
927 }
928 };
929 let output_path = match f.enclosed_name() {
930 Some(p) => p.to_owned(),
931 None => continue,
932 };
933 let output_path = target_dir.join(output_path);
934
935 let is_dir = (*f.name()).ends_with('/');
936 if is_dir {
937 log::info!("extracting directory {}", output_path.display());
938 fs::create_dir_all(&output_path)?;
939 } else {
940 log::info!("extracting file {}", output_path.display());
941 if let Some(p) = output_path.parent() {
942 if !p.exists() {
943 fs::create_dir_all(&p)?;
944 }
945 }
946 let mut f2 = File::create(&output_path)?;
947 io::copy(&mut f, &mut f2)?;
948 }
949
950 fs::set_permissions(&output_path, PermissionsExt::from_mode(0o775))?;
952 }
953 }
954
955 DirDecoder::TarGzip => {
956 unpack_file(src_archive_path, &decompressed_path, Decoder::Gzip)?;
957
958 log::info!("unarchiving decompressed file {}", decompressed_path);
959 let tar_file = File::open(&decompressed_path)?;
960 let mut tar = Archive::new(tar_file);
961 let entries = tar.entries()?;
962 for file in entries {
963 let mut f = file?;
964 let output_path = f.path()?;
965 let output_path = target_dir.join(output_path);
966 if let Some(p) = output_path.parent() {
967 if !p.exists() {
968 fs::create_dir_all(&p)?;
969 }
970 }
971 if output_path.is_dir()
972 || output_path
973 .to_str()
974 .expect("unexpected None str")
975 .ends_with('/')
976 {
977 log::info!("extracting directory {}", output_path.display());
978 fs::create_dir_all(output_path)?;
979 } else {
980 log::info!("extracting file {}", output_path.display());
981 let mut f2 = File::create(&output_path)?;
982 io::copy(&mut f, &mut f2)?;
983 fs::set_permissions(&output_path, PermissionsExt::from_mode(0o775))?;
984 }
985 }
986 }
987
988 DirDecoder::ZipGzip => {
989 unpack_file(src_archive_path, &decompressed_path, Decoder::Gzip)?;
990
991 log::info!("unarchiving decompressed file {}", decompressed_path);
992 let zip_file = File::open(&decompressed_path)?;
993 let mut zip = match ZipArchive::new(zip_file) {
994 Ok(v) => v,
995 Err(e) => {
996 return Err(Error::new(
997 ErrorKind::Other,
998 format!("failed ZipArchive::new on {} ({})", decompressed_path, e),
999 ));
1000 }
1001 };
1002 for i in 0..zip.len() {
1003 let mut f = match zip.by_index(i) {
1004 Ok(v) => v,
1005 Err(e) => {
1006 return Err(Error::new(
1007 ErrorKind::Other,
1008 format!("failed zip.by_index ({})", e),
1009 ));
1010 }
1011 };
1012 let output_path = match f.enclosed_name() {
1013 Some(p) => p.to_owned(),
1014 None => continue,
1015 };
1016 let output_path = target_dir.join(output_path);
1017
1018 let is_dir = (*f.name()).ends_with('/');
1019 if is_dir {
1020 log::info!("extracting directory {}", output_path.display());
1021 fs::create_dir_all(&output_path)?;
1022 } else {
1023 log::info!("extracting file {}", output_path.display());
1024 if let Some(p) = output_path.parent() {
1025 if !p.exists() {
1026 fs::create_dir_all(&p)?;
1027 }
1028 }
1029 let mut f2 = File::create(&output_path)?;
1030 io::copy(&mut f, &mut f2)?;
1031 }
1032
1033 fs::set_permissions(&output_path, PermissionsExt::from_mode(0o775))?;
1034 }
1035 }
1036
1037 DirDecoder::TarZstd => {
1038 unpack_file(src_archive_path, &decompressed_path, Decoder::Zstd)?;
1039
1040 log::info!("unarchiving decompressed file {}", decompressed_path);
1041 let tar_file = File::open(&decompressed_path)?;
1042 let mut tar = Archive::new(tar_file);
1043 let entries = tar.entries()?;
1044 for file in entries {
1045 let mut f = file?;
1046 let output_path = f.path()?;
1047 let output_path = target_dir.join(output_path);
1048 if let Some(p) = output_path.parent() {
1049 if !p.exists() {
1050 fs::create_dir_all(&p)?;
1051 }
1052 }
1053 if output_path.is_dir()
1054 || output_path
1055 .to_str()
1056 .expect("unexpected None str")
1057 .ends_with('/')
1058 {
1059 log::info!("extracting directory {}", output_path.display());
1060 fs::create_dir_all(output_path)?;
1061 } else {
1062 log::info!("extracting file {}", output_path.display());
1063 let mut f2 = File::create(&output_path)?;
1064 io::copy(&mut f, &mut f2)?;
1065 fs::set_permissions(&output_path, PermissionsExt::from_mode(0o775))?;
1066 }
1067 }
1068 }
1069
1070 DirDecoder::ZipZstd => {
1071 unpack_file(src_archive_path, &decompressed_path, Decoder::Zstd)?;
1072
1073 log::info!("unarchiving decompressed file {}", decompressed_path);
1074 let zip_file = File::open(&decompressed_path)?;
1075 let mut zip = match ZipArchive::new(zip_file) {
1076 Ok(v) => v,
1077 Err(e) => {
1078 return Err(Error::new(
1079 ErrorKind::Other,
1080 format!("failed ZipArchive::new on {} ({})", decompressed_path, e),
1081 ));
1082 }
1083 };
1084 for i in 0..zip.len() {
1085 let mut f = match zip.by_index(i) {
1086 Ok(v) => v,
1087 Err(e) => {
1088 return Err(Error::new(
1089 ErrorKind::Other,
1090 format!("failed zip.by_index ({})", e),
1091 ));
1092 }
1093 };
1094 let output_path = match f.enclosed_name() {
1095 Some(p) => p.to_owned(),
1096 None => continue,
1097 };
1098 let output_path = target_dir.join(output_path);
1099
1100 let is_dir = (*f.name()).ends_with('/');
1101 if is_dir {
1102 log::info!("extracting directory {}", output_path.display());
1103 fs::create_dir_all(&output_path)?;
1104 } else {
1105 log::info!("extracting file {}", output_path.display());
1106 if let Some(p) = output_path.parent() {
1107 if !p.exists() {
1108 fs::create_dir_all(&p)?;
1109 }
1110 }
1111 let mut f2 = File::create(&output_path)?;
1112 io::copy(&mut f, &mut f2)?;
1113 }
1114
1115 fs::set_permissions(&output_path, PermissionsExt::from_mode(0o775))?;
1116 }
1117 }
1118 }
1119
1120 if Path::new(&decompressed_path).exists() {
1121 log::info!(
1122 "removing decompressed file {} after unarchive",
1123 decompressed_path
1124 );
1125 fs::remove_file(decompressed_path)?;
1126 }
1127
1128 let size = fs_extra::dir::get_size(target_dir).map_err(|e| {
1129 Error::new(
1130 ErrorKind::Other,
1131 format!(
1132 "failed get_size {} for directory {}",
1133 e,
1134 target_dir.display()
1135 ),
1136 )
1137 })?;
1138 let size_after = size as f64;
1139 log::info!(
1140 "decompressed directory from '{}' to '{}' (algorithm {}, before {}, new size {})",
1141 src_archive_path,
1142 dst_dir_path,
1143 dec.to_string(),
1144 human_readable::bytes(size_before),
1145 human_readable::bytes(size_after),
1146 );
1147 Ok(())
1148}
1149
1150fn is_dir(entry: &DirEntry) -> bool {
1151 entry.file_type().is_dir()
1152}
1153
1154fn absolute_path(path: impl AsRef<Path>) -> io::Result<PathBuf> {
1155 let p = path.as_ref();
1156
1157 let ap = if p.is_absolute() {
1158 p.to_path_buf()
1159 } else {
1160 env::current_dir()?.join(p)
1161 }
1162 .clean();
1163
1164 Ok(ap)
1165}
1166
1167#[test]
1169fn test_pack_unpack() {
1170 let _ = env_logger::builder()
1171 .filter_level(log::LevelFilter::Info)
1172 .is_test(true)
1173 .try_init();
1174
1175 let contents = vec![7; 1 * 1024 * 1024];
1176
1177 let encs = vec![
1178 "gzip",
1179 "zstd1",
1180 "zstd2",
1181 "zstd3",
1182 "zstd1-base58",
1183 "zstd2-base58",
1184 "zstd3-base58",
1185 ];
1186 let decs = vec![
1187 "gzip",
1188 "zstd",
1189 "zstd",
1190 "zstd",
1191 "zstd-base58",
1192 "zstd-base58",
1193 "zstd-base58",
1194 ];
1195 for (i, _) in encs.iter().enumerate() {
1196 let encoder = Encoder::new(encs[i]).unwrap();
1197 let decoder = Decoder::new(decs[i]).unwrap();
1198
1199 let packed = pack(&contents, encoder).unwrap();
1200
1201 if !encs[i].contains("base58") {
1203 assert!(contents.len() > packed.len());
1204 }
1205
1206 let unpacked = unpack(&packed, decoder).unwrap();
1207
1208 assert_eq!(contents, unpacked);
1210 }
1211
1212 let mut orig_file = tempfile::NamedTempFile::new().unwrap();
1213 orig_file.write_all(&contents).unwrap();
1214 let orig_path = orig_file.path().to_str().unwrap();
1215 let orig_meta = fs::metadata(orig_path).unwrap();
1216 for (i, _) in encs.iter().enumerate() {
1217 let encoder = Encoder::new(encs[i]).unwrap();
1218 let decoder = Decoder::new(decs[i]).unwrap();
1219
1220 let packed = tempfile::NamedTempFile::new().unwrap();
1221 let packed_path = packed.path().to_str().unwrap();
1222 pack_file(&orig_path, packed_path, encoder).unwrap();
1223
1224 if !encs[i].contains("base58") {
1226 let meta_packed = fs::metadata(packed_path).unwrap();
1227 assert!(orig_meta.len() > meta_packed.len());
1228 }
1229
1230 let unpacked = tempfile::NamedTempFile::new().unwrap();
1231 let unpacked_path = unpacked.path().to_str().unwrap();
1232 unpack_file(packed_path, unpacked_path, decoder).unwrap();
1233
1234 let contents_unpacked = fs::read(unpacked_path).unwrap();
1236 assert_eq!(contents, contents_unpacked);
1237 }
1238
1239 let src_dir_path_buf = env::temp_dir().join(random_manager::secure_string(10));
1240 fs::create_dir_all(&src_dir_path_buf).unwrap();
1241 let src_dir_path = src_dir_path_buf.to_str().unwrap();
1242 for _i in 0..20 {
1243 let p = src_dir_path_buf.join(random_manager::secure_string(10));
1244 let mut f = File::create(&p).unwrap();
1245 f.write_all(&contents).unwrap();
1246 }
1247 log::info!("created {}", src_dir_path_buf.display());
1248 let src_dir_size = fs_extra::dir::get_size(src_dir_path_buf.clone()).unwrap();
1249
1250 let encs = vec![
1251 "zip",
1252 "tar-gzip",
1253 "zip-gzip",
1254 "tar-zstd1",
1255 "tar-zstd2",
1256 "tar-zstd3",
1257 "zip-zstd1",
1258 "zip-zstd2",
1259 "zip-zstd3",
1260 ];
1261 let decs = vec![
1262 "zip", "tar-gzip", "zip-gzip", "tar-zstd", "tar-zstd", "tar-zstd", "zip-zstd", "zip-zstd", "zip-zstd", ];
1272 for (i, _) in encs.iter().enumerate() {
1273 let encoder = DirEncoder::new(encs[i]).unwrap();
1274 let decoder = DirDecoder::new(decs[i]).unwrap();
1275
1276 let compressed_path = random_manager::tmp_path(10, Some(encoder.suffix())).unwrap();
1277 pack_directory(src_dir_path, &compressed_path, encoder).unwrap();
1278
1279 if encs[i].ne("zip") {
1280 let meta_packed = fs::metadata(&compressed_path).unwrap();
1282 assert!(src_dir_size > meta_packed.len());
1283 }
1284
1285 let dst_dir_path = random_manager::tmp_path(10, None).unwrap();
1286 unpack_directory(&compressed_path, &dst_dir_path, decoder).unwrap();
1287
1288 fs::remove_file(compressed_path).unwrap();
1289 fs::remove_dir_all(dst_dir_path).unwrap();
1290 }
1291 fs::remove_dir_all(src_dir_path).unwrap();
1292}