easy_archive/archive/
zip.rs1use crate::{Decode, Encode, File, tool::clean};
2use std::io::{Cursor, Read, Seek, SeekFrom, Write};
3use time::OffsetDateTime;
4use zip::DateTime;
5
6pub struct Zip;
7
8#[cfg(feature = "zip")]
9fn decode_zip(buffer: &[u8]) -> Option<Vec<File>> {
10 let mut c = Cursor::new(Vec::new());
11 c.write_all(buffer).ok()?;
12 c.seek(SeekFrom::Start(0)).ok()?;
13 let mut files = Vec::new();
14 let mut archive = zip::ZipArchive::new(c).ok()?;
15 for i in 0..archive.len() {
16 let mut file = archive.by_index(i).ok()?;
17 if file.is_file() {
18 let mut buffer = vec![];
19 file.read_to_end(&mut buffer).ok()?;
20 let path = file.name();
21 let is_dir = file.is_dir() || path.ends_with("/");
22 let path = clean(path);
23 let last_modified = file
24 .last_modified()
25 .and_then(|i| OffsetDateTime::try_from(i).ok())
26 .map(|i| i.unix_timestamp() as u64);
27
28 files.push(File::new(path, buffer.clone(), None, is_dir, last_modified));
29 }
30 }
31 Some(files)
32}
33
34#[cfg(feature = "rc-zip")]
35fn decode_rc_zip(buffer: &[u8]) -> Option<Vec<File>> {
36 use rc_zip_sync::ReadZip;
37 let reader = buffer.read_zip().ok()?;
38 let mut files = Vec::new();
39 for entry in reader.entries() {
40 let path = entry.name.clone();
41 let buffer = entry.bytes().ok()?;
42 let mode = entry.mode.0;
43 let is_dir = matches!(entry.kind(), rc_zip::parse::EntryKind::Directory);
44 let path = clean(&path);
45 files.push(File {
46 buffer,
47 path,
48 mode: Some(mode),
49 is_dir,
50 });
51 }
52 Some(files)
53}
54
55impl Decode for Zip {
56 fn decode(buffer: Vec<u8>) -> Option<Vec<File>> {
57 #[cfg(feature = "zip")]
58 return decode_zip(&buffer);
59 #[cfg(feature = "rc-zip")]
60 return decode_rc_zip(&buffer);
61 }
62}
63
64impl Encode for Zip {
65 fn encode(files: Vec<File>) -> Option<Vec<u8>> {
66 use std::collections::HashSet;
67 use std::io::prelude::*;
68 use zip::write::FullFileOptions;
69
70 let mut v = vec![];
71 let mut c = std::io::Cursor::new(&mut v);
72 let mut zip = zip::ZipWriter::new(&mut c);
73 let mut dir_set = HashSet::new();
74
75 for i in files.iter().filter(|i| i.is_dir) {
76 if dir_set.contains(&i.path) {
77 continue;
78 }
79 dir_set.insert(i.path.clone());
80 let mut options = FullFileOptions::default();
81 if let Some(last) = i.last_modified {
82 let mod_time = OffsetDateTime::from_unix_timestamp(last as i64)
83 .ok()
84 .and_then(|i| DateTime::try_from(i).ok());
85 if let Some(offset) = mod_time {
86 options = options.last_modified_time(offset);
87 }
88 }
89 zip.add_directory(i.path.as_str(), options).ok()?;
90 }
91
92 for i in files.iter().filter(|i| !i.is_dir) {
93 if !i.path.contains("/") {
94 continue;
95 }
96 if let Some(p) = std::path::Path::new(&i.path).parent() {
97 let path = p.to_string_lossy().to_string();
98 if dir_set.contains(&path) || path.is_empty() {
99 continue;
100 }
101
102 let mut options = FullFileOptions::default();
103 if let Some(last) = i.last_modified {
104 let mod_time = OffsetDateTime::from_unix_timestamp(last as i64)
105 .ok()
106 .and_then(|i| DateTime::try_from(i).ok());
107 if let Some(offset) = mod_time {
108 options = options.last_modified_time(offset);
109 }
110 }
111 zip.add_directory(path.clone(), options).ok()?;
112 dir_set.insert(path);
113 }
114 }
115
116 for i in &files {
117 if i.is_dir {
118 continue;
119 }
120 let mode = i.mode.unwrap_or(0o755);
121 let mut options = FullFileOptions::default()
122 .compression_method(zip::CompressionMethod::Zstd)
124 .unix_permissions(mode);
125
126 if let Some(last) = i.last_modified {
127 let mod_time = OffsetDateTime::from_unix_timestamp(last as i64)
128 .ok()
129 .and_then(|i| DateTime::try_from(i).ok());
130 if let Some(offset) = mod_time {
131 options = options.last_modified_time(offset);
132 }
133 }
134
135 zip.start_file(i.path.as_str(), options).ok()?;
136 zip.write_all(&i.buffer).ok()?;
137 }
138 zip.finish().ok()?;
139 Some(v)
140 }
141}