1use std::{
4 fs::File,
5 io::{Seek, Write},
6 path::{Path, PathBuf},
7};
8
9#[cfg(feature = "aes256")]
10use crate::encoder_options::AesEncoderOptions;
11use crate::{ArchiveEntry, ArchiveWriter, EncoderMethod, Error, Password, writer::LazyFileReader};
12
13pub fn compress<W: Write + Seek>(src: impl AsRef<Path>, dest: W) -> Result<W, Error> {
19 let mut archive_writer = ArchiveWriter::new(dest)?;
20 let parent = if src.as_ref().is_dir() {
21 src.as_ref()
22 } else {
23 src.as_ref().parent().unwrap_or(src.as_ref())
24 };
25 compress_path(src.as_ref(), parent, &mut archive_writer)?;
26 Ok(archive_writer.finish()?)
27}
28
29#[cfg(feature = "aes256")]
36pub fn compress_encrypted<W: Write + Seek>(
37 src: impl AsRef<Path>,
38 dest: W,
39 password: Password,
40) -> Result<W, Error> {
41 let mut archive_writer = ArchiveWriter::new(dest)?;
42 if !password.is_empty() {
43 archive_writer.set_content_methods(vec![
44 AesEncoderOptions::new(password).into(),
45 EncoderMethod::LZMA2.into(),
46 ]);
47 }
48 let parent = if src.as_ref().is_dir() {
49 src.as_ref()
50 } else {
51 src.as_ref().parent().unwrap_or(src.as_ref())
52 };
53 compress_path(src.as_ref(), parent, &mut archive_writer)?;
54 Ok(archive_writer.finish()?)
55}
56
57pub fn compress_to_path(src: impl AsRef<Path>, dest: impl AsRef<Path>) -> Result<(), Error> {
65 if let Some(path) = dest.as_ref().parent() {
66 if !path.exists() {
67 std::fs::create_dir_all(path)
68 .map_err(|e| Error::io_msg(e, format!("Create dir failed:{:?}", dest.as_ref())))?;
69 }
70 }
71 compress(
72 src,
73 File::create(dest.as_ref())
74 .map_err(|e| Error::file_open(e, dest.as_ref().to_string_lossy().to_string()))?,
75 )?;
76 Ok(())
77}
78
79#[cfg(feature = "aes256")]
88pub fn compress_to_path_encrypted(
89 src: impl AsRef<Path>,
90 dest: impl AsRef<Path>,
91 password: Password,
92) -> Result<(), Error> {
93 if let Some(path) = dest.as_ref().parent() {
94 if !path.exists() {
95 std::fs::create_dir_all(path)
96 .map_err(|e| Error::io_msg(e, format!("Create dir failed:{:?}", dest.as_ref())))?;
97 }
98 }
99 compress_encrypted(
100 src,
101 File::create(dest.as_ref())
102 .map_err(|e| Error::file_open(e, dest.as_ref().to_string_lossy().to_string()))?,
103 password,
104 )?;
105 Ok(())
106}
107
108fn compress_path<W: Write + Seek, P: AsRef<Path>>(
109 src: P,
110 root: &Path,
111 archive_writer: &mut ArchiveWriter<W>,
112) -> Result<(), Error> {
113 let entry_name = src
114 .as_ref()
115 .strip_prefix(root)
116 .map_err(|e| Error::other(e.to_string()))?
117 .to_string_lossy()
118 .to_string();
119 let entry = ArchiveEntry::from_path(src.as_ref(), entry_name);
120 let path = src.as_ref();
121 if path.is_dir() {
122 archive_writer.push_archive_entry::<&[u8]>(entry, None)?;
123 for dir in path
124 .read_dir()
125 .map_err(|e| Error::io_msg(e, "error read dir"))?
126 {
127 let dir = dir?;
128 let ftype = dir.file_type()?;
129 if ftype.is_dir() || ftype.is_file() {
130 compress_path(dir.path(), root, archive_writer)?;
131 }
132 }
133 } else {
134 archive_writer.push_archive_entry(
135 entry,
136 Some(
137 File::open(path)
138 .map_err(|e| Error::file_open(e, path.to_string_lossy().to_string()))?,
139 ),
140 )?;
141 }
142 Ok(())
143}
144
145impl<W: Write + Seek> ArchiveWriter<W> {
146 pub fn push_source_path(
155 &mut self,
156 path: impl AsRef<Path>,
157 filter: impl Fn(&Path) -> bool,
158 ) -> Result<&mut Self, Error> {
159 encode_path(true, &path, self, filter)?;
160 Ok(self)
161 }
162
163 pub fn push_source_path_non_solid(
172 &mut self,
173 path: impl AsRef<Path>,
174 filter: impl Fn(&Path) -> bool,
175 ) -> Result<&mut Self, Error> {
176 encode_path(false, &path, self, filter)?;
177 Ok(self)
178 }
179}
180
181fn collect_file_paths(
182 src: impl AsRef<Path>,
183 paths: &mut Vec<PathBuf>,
184 filter: &dyn Fn(&Path) -> bool,
185) -> std::io::Result<()> {
186 let path = src.as_ref();
187 if !filter(path) {
188 return Ok(());
189 }
190 if path.is_dir() {
191 for dir in path.read_dir()? {
192 let dir = dir?;
193 let ftype = dir.file_type()?;
194 if ftype.is_file() || ftype.is_dir() {
195 collect_file_paths(dir.path(), paths, filter)?;
196 }
197 }
198 } else {
199 paths.push(path.to_path_buf())
200 }
201 Ok(())
202}
203
204const MAX_BLOCK_SIZE: u64 = 4 * 1024 * 1024 * 1024; fn encode_path<W: Write + Seek>(
207 solid: bool,
208 src: impl AsRef<Path>,
209 zip: &mut ArchiveWriter<W>,
210 filter: impl Fn(&Path) -> bool,
211) -> Result<(), Error> {
212 let mut entries = Vec::new();
213 let mut paths = Vec::new();
214 collect_file_paths(&src, &mut paths, &filter).map_err(|e| {
215 Error::io_msg(
216 e,
217 format!("Failed to collect entries from path:{:?}", src.as_ref()),
218 )
219 })?;
220
221 if !solid {
222 for ele in paths.into_iter() {
223 let name = extract_file_name(&src, &ele)?;
224
225 zip.push_archive_entry(
226 ArchiveEntry::from_path(ele.as_path(), name),
227 Some(File::open(ele.as_path())?),
228 )?;
229 }
230 return Ok(());
231 }
232 let mut files = Vec::new();
233 let mut file_size = 0;
234 for ele in paths.into_iter() {
235 let size = ele.metadata()?.len();
236 let name = extract_file_name(&src, &ele)?;
237
238 if size >= MAX_BLOCK_SIZE {
239 zip.push_archive_entry(
240 ArchiveEntry::from_path(ele.as_path(), name),
241 Some(File::open(ele.as_path())?),
242 )?;
243 continue;
244 }
245 if file_size + size >= MAX_BLOCK_SIZE {
246 zip.push_archive_entries(entries, files)?;
247 entries = Vec::new();
248 files = Vec::new();
249 file_size = 0;
250 }
251 file_size += size;
252 entries.push(ArchiveEntry::from_path(ele.as_path(), name));
253 files.push(LazyFileReader::new(ele).into());
254 }
255 if !entries.is_empty() {
256 zip.push_archive_entries(entries, files)?;
257 }
258
259 Ok(())
260}
261
262fn extract_file_name(src: &impl AsRef<Path>, ele: &PathBuf) -> Result<String, Error> {
263 if ele == src.as_ref() {
264 Ok(ele
266 .file_name()
267 .ok_or_else(|| {
268 Error::io_msg(
269 std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid filename"),
270 format!("Failed to get filename from {ele:?}"),
271 )
272 })?
273 .to_string_lossy()
274 .to_string())
275 } else {
276 Ok(ele.strip_prefix(src).unwrap().to_string_lossy().to_string())
278 }
279}