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
13#[cfg_attr(docsrs, doc(cfg(all(feature = "compress", feature = "util"))))]
19pub fn compress<W: Write + Seek>(src: impl AsRef<Path>, dest: W) -> Result<W, Error> {
20 let mut archive_writer = ArchiveWriter::new(dest)?;
21 let parent = if src.as_ref().is_dir() {
22 src.as_ref()
23 } else {
24 src.as_ref().parent().unwrap_or(src.as_ref())
25 };
26 compress_path(src.as_ref(), parent, &mut archive_writer)?;
27 archive_writer.finish().map_err(Error::io)
28}
29
30#[cfg(feature = "aes256")]
37#[cfg_attr(
38 docsrs,
39 doc(cfg(all(feature = "aes256", feature = "compress", feature = "util")))
40)]
41pub fn compress_encrypted<W: Write + Seek>(
42 src: impl AsRef<Path>,
43 dest: W,
44 password: Password,
45) -> Result<W, Error> {
46 let mut archive_writer = ArchiveWriter::new(dest)?;
47 if !password.is_empty() {
48 archive_writer.set_content_methods(vec![
49 AesEncoderOptions::new(password).into(),
50 EncoderMethod::LZMA2.into(),
51 ]);
52 }
53 let parent = if src.as_ref().is_dir() {
54 src.as_ref()
55 } else {
56 src.as_ref().parent().unwrap_or(src.as_ref())
57 };
58 compress_path(src.as_ref(), parent, &mut archive_writer)?;
59 archive_writer.finish().map_err(Error::io)
60}
61
62#[cfg_attr(docsrs, doc(cfg(all(feature = "compress", feature = "util"))))]
70pub fn compress_to_path(src: impl AsRef<Path>, dest: impl AsRef<Path>) -> Result<(), Error> {
71 if let Some(path) = dest.as_ref().parent() {
72 if !path.exists() {
73 std::fs::create_dir_all(path)
74 .map_err(|e| Error::io_msg(e, format!("Create dir failed:{:?}", dest.as_ref())))?;
75 }
76 }
77 compress(
78 src,
79 File::create(dest.as_ref())
80 .map_err(|e| Error::file_open(e, dest.as_ref().to_string_lossy().to_string()))?,
81 )?;
82 Ok(())
83}
84
85#[cfg(feature = "aes256")]
94#[cfg_attr(
95 docsrs,
96 doc(cfg(all(feature = "aes256", feature = "compress", feature = "util")))
97)]
98pub fn compress_to_path_encrypted(
99 src: impl AsRef<Path>,
100 dest: impl AsRef<Path>,
101 password: Password,
102) -> Result<(), Error> {
103 if let Some(path) = dest.as_ref().parent() {
104 if !path.exists() {
105 std::fs::create_dir_all(path)
106 .map_err(|e| Error::io_msg(e, format!("Create dir failed:{:?}", dest.as_ref())))?;
107 }
108 }
109 compress_encrypted(
110 src,
111 File::create(dest.as_ref())
112 .map_err(|e| Error::file_open(e, dest.as_ref().to_string_lossy().to_string()))?,
113 password,
114 )?;
115 Ok(())
116}
117
118fn compress_path<W: Write + Seek, P: AsRef<Path>>(
119 src: P,
120 root: &Path,
121 archive_writer: &mut ArchiveWriter<W>,
122) -> Result<(), Error> {
123 let entry_name = src
124 .as_ref()
125 .strip_prefix(root)
126 .map_err(|e| Error::other(e.to_string()))?
127 .to_string_lossy()
128 .to_string();
129 let entry = ArchiveEntry::from_path(src.as_ref(), entry_name);
130 let path = src.as_ref();
131 if path.is_dir() {
132 archive_writer.push_archive_entry::<&[u8]>(entry, None)?;
133 for dir in path
134 .read_dir()
135 .map_err(|e| Error::io_msg(e, "error read dir"))?
136 {
137 let dir = dir.map_err(Error::io)?;
138 let ftype = dir.file_type().map_err(Error::io)?;
139 if ftype.is_dir() || ftype.is_file() {
140 compress_path(dir.path(), root, archive_writer)?;
141 }
142 }
143 } else {
144 archive_writer.push_archive_entry(
145 entry,
146 Some(
147 File::open(path)
148 .map_err(|e| Error::file_open(e, path.to_string_lossy().to_string()))?,
149 ),
150 )?;
151 }
152 Ok(())
153}
154
155impl<W: Write + Seek> ArchiveWriter<W> {
156 #[cfg_attr(docsrs, doc(cfg(all(feature = "compress", feature = "util"))))]
165 pub fn push_source_path(
166 &mut self,
167 path: impl AsRef<Path>,
168 filter: impl Fn(&Path) -> bool,
169 ) -> Result<&mut Self, Error> {
170 encode_path(true, &path, self, filter)?;
171 Ok(self)
172 }
173
174 #[cfg_attr(docsrs, doc(cfg(all(feature = "compress", feature = "util"))))]
183 pub fn push_source_path_non_solid(
184 &mut self,
185 path: impl AsRef<Path>,
186 filter: impl Fn(&Path) -> bool,
187 ) -> Result<&mut Self, Error> {
188 encode_path(false, &path, self, filter)?;
189 Ok(self)
190 }
191}
192
193fn collect_file_paths(
194 src: impl AsRef<Path>,
195 paths: &mut Vec<PathBuf>,
196 filter: &dyn Fn(&Path) -> bool,
197) -> std::io::Result<()> {
198 let path = src.as_ref();
199 if !filter(path) {
200 return Ok(());
201 }
202 if path.is_dir() {
203 for dir in path.read_dir()? {
204 let dir = dir?;
205 let ftype = dir.file_type()?;
206 if ftype.is_file() || ftype.is_dir() {
207 collect_file_paths(dir.path(), paths, filter)?;
208 }
209 }
210 } else {
211 paths.push(path.to_path_buf())
212 }
213 Ok(())
214}
215
216const MAX_BLOCK_SIZE: u64 = 4 * 1024 * 1024 * 1024; fn encode_path<W: Write + Seek>(
219 solid: bool,
220 src: impl AsRef<Path>,
221 zip: &mut ArchiveWriter<W>,
222 filter: impl Fn(&Path) -> bool,
223) -> Result<(), Error> {
224 let mut entries = Vec::new();
225 let mut paths = Vec::new();
226 collect_file_paths(&src, &mut paths, &filter).map_err(|e| {
227 Error::io_msg(
228 e,
229 format!("Failed to collect entries from path:{:?}", src.as_ref()),
230 )
231 })?;
232 if !solid {
233 for ele in paths.into_iter() {
234 let name = ele
235 .strip_prefix(&src)
236 .unwrap()
237 .to_string_lossy()
238 .to_string();
239 zip.push_archive_entry(
240 ArchiveEntry::from_path(ele.as_path(), name),
241 Some(File::open(ele.as_path()).map_err(Error::io)?),
242 )?;
243 }
244 return Ok(());
245 }
246 let mut files = Vec::new();
247 let mut file_size = 0;
248 for ele in paths.into_iter() {
249 let size = ele.metadata()?.len();
250 let name = ele
251 .strip_prefix(&src)
252 .unwrap()
253 .to_string_lossy()
254 .to_string();
255 if size >= MAX_BLOCK_SIZE {
256 zip.push_archive_entry(
257 ArchiveEntry::from_path(ele.as_path(), name),
258 Some(File::open(ele.as_path()).map_err(Error::io)?),
259 )?;
260 continue;
261 }
262 if file_size + size >= MAX_BLOCK_SIZE {
263 zip.push_archive_entries(entries, files)?;
264 entries = Vec::new();
265 files = Vec::new();
266 file_size = 0;
267 }
268 file_size += size;
269 entries.push(ArchiveEntry::from_path(ele.as_path(), name));
270 files.push(LazyFileReader::new(ele).into());
271 }
272 if !entries.is_empty() {
273 zip.push_archive_entries(entries, files)?;
274 }
275
276 Ok(())
277}