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 pub fn push_source_path(
122 &mut self,
123 path: impl AsRef<Path>,
124 filter: impl Fn(&Path) -> bool,
125 ) -> Result<&mut Self, Error> {
126 encode_path(true, &path, self, filter)?;
127 Ok(self)
128 }
129
130 pub fn push_source_path_non_solid(
132 &mut self,
133 path: impl AsRef<Path>,
134 filter: impl Fn(&Path) -> bool,
135 ) -> Result<&mut Self, Error> {
136 encode_path(false, &path, self, filter)?;
137 Ok(self)
138 }
139}
140
141fn collect_file_paths(
142 src: impl AsRef<Path>,
143 paths: &mut Vec<PathBuf>,
144 filter: &dyn Fn(&Path) -> bool,
145) -> std::io::Result<()> {
146 let path = src.as_ref();
147 if !filter(path) {
148 return Ok(());
149 }
150 if path.is_dir() {
151 for dir in path.read_dir()? {
152 let dir = dir?;
153 let ftype = dir.file_type()?;
154 if ftype.is_file() || ftype.is_dir() {
155 collect_file_paths(dir.path(), paths, filter)?;
156 }
157 }
158 } else {
159 paths.push(path.to_path_buf())
160 }
161 Ok(())
162}
163
164const MAX_BLOCK_SIZE: u64 = 4 * 1024 * 1024 * 1024; fn encode_path<W: Write + Seek>(
167 solid: bool,
168 src: impl AsRef<Path>,
169 zip: &mut SevenZWriter<W>,
170 filter: impl Fn(&Path) -> bool,
171) -> Result<(), Error> {
172 let mut entries = Vec::new();
173 let mut paths = Vec::new();
174 collect_file_paths(&src, &mut paths, &filter).map_err(|e| {
175 Error::io_msg(
176 e,
177 format!("Failed to collect entries from path:{:?}", src.as_ref()),
178 )
179 })?;
180 if !solid {
181 for ele in paths.into_iter() {
182 let name = ele
183 .strip_prefix(&src)
184 .unwrap()
185 .to_string_lossy()
186 .to_string();
187 zip.push_archive_entry(
188 SevenZArchiveEntry::from_path(ele.as_path(), name),
189 Some(File::open(ele.as_path()).map_err(Error::io)?),
190 )?;
191 }
192 return Ok(());
193 }
194 let mut files = Vec::new();
195 let mut file_size = 0;
196 for ele in paths.into_iter() {
197 let size = ele.metadata()?.len();
198 let name = ele
199 .strip_prefix(&src)
200 .unwrap()
201 .to_string_lossy()
202 .to_string();
203 if size >= MAX_BLOCK_SIZE {
204 zip.push_archive_entry(
205 SevenZArchiveEntry::from_path(ele.as_path(), name),
206 Some(File::open(ele.as_path()).map_err(Error::io)?),
207 )?;
208 continue;
209 }
210 if file_size + size >= MAX_BLOCK_SIZE {
211 zip.push_archive_entries(entries, SeqReader::new(files))?;
212 entries = Vec::new();
213 files = Vec::new();
214 file_size = 0;
215 }
216 file_size += size;
217 entries.push(SevenZArchiveEntry::from_path(ele.as_path(), name));
218 files.push(LazyFileReader::new(ele).into());
219 }
220 if !entries.is_empty() {
221 zip.push_archive_entries(entries, SeqReader::new(files))?;
222 }
223
224 Ok(())
225}