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