1use std::{
4 fs::File,
5 io::{Seek, Write},
6 path::{Path, PathBuf},
7};
8
9use crate::*;
10
11pub fn compress<W: Write + Seek>(src: impl AsRef<Path>, dest: W) -> Result<W, Error> {
13 let mut z = SevenZWriter::new(dest)?;
14 let parent = if src.as_ref().is_dir() {
15 src.as_ref()
16 } else {
17 src.as_ref().parent().unwrap_or(src.as_ref())
18 };
19 compress_path(src.as_ref(), parent, &mut z)?;
20 z.finish().map_err(Error::io)
21}
22
23#[cfg(feature = "aes256")]
24pub fn compress_encrypted<W: Write + Seek>(
25 src: impl AsRef<Path>,
26 dest: W,
27 password: Password,
28) -> Result<W, Error> {
29 let mut z = SevenZWriter::new(dest)?;
30 if !password.is_empty() {
31 z.set_content_methods(vec![
32 AesEncoderOptions::new(password).into(),
33 SevenZMethod::LZMA2.into(),
34 ]);
35 }
36 let parent = if src.as_ref().is_dir() {
37 src.as_ref()
38 } else {
39 src.as_ref().parent().unwrap_or(src.as_ref())
40 };
41 compress_path(src.as_ref(), parent, &mut z)?;
42 z.finish().map_err(Error::io)
43}
44
45pub fn compress_to_path(src: impl AsRef<Path>, dest: impl AsRef<Path>) -> Result<(), Error> {
47 if let Some(p) = dest.as_ref().parent() {
48 if !p.exists() {
49 std::fs::create_dir_all(p)
50 .map_err(|e| Error::io_msg(e, format!("Create dir failed:{:?}", dest.as_ref())))?;
51 }
52 }
53 compress(
54 src,
55 File::create(dest.as_ref())
56 .map_err(|e| Error::file_open(e, dest.as_ref().to_string_lossy().to_string()))?,
57 )?;
58 Ok(())
59}
60
61#[cfg(feature = "aes256")]
62pub fn compress_to_path_encrypted(
63 src: impl AsRef<Path>,
64 dest: impl AsRef<Path>,
65 password: Password,
66) -> Result<(), Error> {
67 if let Some(p) = dest.as_ref().parent() {
68 if !p.exists() {
69 std::fs::create_dir_all(p)
70 .map_err(|e| Error::io_msg(e, format!("Create dir failed:{:?}", dest.as_ref())))?;
71 }
72 }
73 compress_encrypted(
74 src,
75 File::create(dest.as_ref())
76 .map_err(|e| Error::file_open(e, dest.as_ref().to_string_lossy().to_string()))?,
77 password,
78 )?;
79 Ok(())
80}
81
82fn compress_path<W: Write + Seek, P: AsRef<Path>>(
83 src: P,
84 root: &Path,
85 z: &mut SevenZWriter<W>,
86) -> Result<(), Error> {
87 let entry_name = src
88 .as_ref()
89 .strip_prefix(root)
90 .map_err(|e| Error::other(e.to_string()))?
91 .to_string_lossy()
92 .to_string();
93 let entry = SevenZArchiveEntry::from_path(src.as_ref(), entry_name);
94 let path = src.as_ref();
95 if path.is_dir() {
96 z.push_archive_entry::<&[u8]>(entry, None)?;
97 for dir in path
98 .read_dir()
99 .map_err(|e| Error::io_msg(e, "error read dir"))?
100 {
101 let dir = dir.map_err(Error::io)?;
102 let ftype = dir.file_type().map_err(Error::io)?;
103 if ftype.is_dir() || ftype.is_file() {
104 compress_path(dir.path(), root, z)?;
105 }
106 }
107 } else {
108 z.push_archive_entry(
109 entry,
110 Some(
111 File::open(path)
112 .map_err(|e| Error::file_open(e, path.to_string_lossy().to_string()))?,
113 ),
114 )?;
115 }
116 Ok(())
117}
118
119impl<W: Write + Seek> SevenZWriter<W> {
120 #[inline]
124 pub fn push_source_path(
125 &mut self,
126 path: impl AsRef<Path>,
127 filter: impl Fn(&Path) -> bool,
128 ) -> Result<&mut Self, Error> {
129 encode_path(true, &path, self, filter)?;
130 Ok(self)
131 }
132
133 pub fn push_source_path_non_solid(
137 &mut self,
138 path: impl AsRef<Path>,
139 filter: impl Fn(&Path) -> bool,
140 ) -> Result<&mut Self, Error> {
141 encode_path(false, &path, self, filter)?;
142 Ok(self)
143 }
144}
145
146fn collect_file_paths(
147 src: impl AsRef<Path>,
148 paths: &mut Vec<PathBuf>,
149 filter: &dyn Fn(&Path) -> bool,
150) -> std::io::Result<()> {
151 let path = src.as_ref();
152 if !filter(path) {
153 return Ok(());
154 }
155 if path.is_dir() {
156 for dir in path.read_dir()? {
157 let dir = dir?;
158 let ftype = dir.file_type()?;
159 if ftype.is_file() || ftype.is_dir() {
160 collect_file_paths(dir.path(), paths, filter)?;
161 }
162 }
163 } else {
164 paths.push(path.to_path_buf())
165 }
166 Ok(())
167}
168
169const MAX_BLOCK_SIZE: u64 = 4 * 1024 * 1024 * 1024; fn encode_path<W: Write + Seek>(
172 solid: bool,
173 src: impl AsRef<Path>,
174 zip: &mut SevenZWriter<W>,
175 filter: impl Fn(&Path) -> bool,
176) -> Result<(), Error> {
177 let mut entries = Vec::new();
178 let mut paths = Vec::new();
179 collect_file_paths(&src, &mut paths, &filter).map_err(|e| {
180 Error::io_msg(
181 e,
182 format!("Failed to collect entries from path:{:?}", src.as_ref()),
183 )
184 })?;
185 if !solid {
186 for ele in paths.into_iter() {
187 let name = ele
188 .strip_prefix(&src)
189 .unwrap()
190 .to_string_lossy()
191 .to_string();
192 zip.push_archive_entry(
193 SevenZArchiveEntry::from_path(ele.as_path(), name),
194 Some(File::open(ele.as_path()).map_err(Error::io)?),
195 )?;
196 }
197 return Ok(());
198 }
199 let mut files = Vec::new();
200 let mut file_size = 0;
201 for ele in paths.into_iter() {
202 let size = ele.metadata()?.len();
203 let name = ele
204 .strip_prefix(&src)
205 .unwrap()
206 .to_string_lossy()
207 .to_string();
208 if size >= MAX_BLOCK_SIZE {
209 zip.push_archive_entry(
210 SevenZArchiveEntry::from_path(ele.as_path(), name),
211 Some(File::open(ele.as_path()).map_err(Error::io)?),
212 )?;
213 continue;
214 }
215 if file_size + size >= MAX_BLOCK_SIZE {
216 zip.push_archive_entries(entries, SeqReader::new(files))?;
217 entries = Vec::new();
218 files = Vec::new();
219 file_size = 0;
220 }
221 file_size += size;
222 entries.push(SevenZArchiveEntry::from_path(ele.as_path(), name));
223 files.push(LazyFileReader::new(ele).into());
224 }
225 if !entries.is_empty() {
226 zip.push_archive_entries(entries, SeqReader::new(files))?;
227 }
228
229 Ok(())
230}