1use std::path::{Path, PathBuf};
4
5use async_fs as afs;
6use futures_lite::StreamExt;
7use futures_lite::io::{AsyncSeek, AsyncWrite, Cursor};
8
9#[cfg(feature = "aes256")]
10use crate::encoder_options::AesEncoderOptions;
11use crate::{ArchiveEntry, ArchiveWriter, EncoderMethod, Error, Password, writer::LazyFileReader};
12
13pub async fn compress<W: AsyncWrite + AsyncSeek + Unpin>(
19 src: impl AsRef<Path>,
20 dest: W,
21) -> Result<W, Error> {
22 let mut archive_writer = ArchiveWriter::new(dest).await?;
23 let parent = if src.as_ref().is_dir() {
24 src.as_ref()
25 } else {
26 src.as_ref().parent().unwrap_or(src.as_ref())
27 };
28 compress_path(src.as_ref(), parent, &mut archive_writer).await?;
29 let out = archive_writer.finish().await?;
30 Ok(out)
31}
32
33#[cfg(feature = "aes256")]
40pub async fn compress_encrypted<W: AsyncWrite + AsyncSeek + Unpin>(
41 src: impl AsRef<Path>,
42 dest: W,
43 password: Password,
44) -> Result<W, Error> {
45 let mut archive_writer = ArchiveWriter::new(dest).await?;
46 if !password.is_empty() {
47 archive_writer.set_content_methods(vec![
48 AesEncoderOptions::new(password).into(),
49 EncoderMethod::LZMA2.into(),
50 ]);
51 }
52 let parent = if src.as_ref().is_dir() {
53 src.as_ref()
54 } else {
55 src.as_ref().parent().unwrap_or(src.as_ref())
56 };
57 compress_path(src.as_ref(), parent, &mut archive_writer).await?;
58 let out = archive_writer.finish().await?;
59 Ok(out)
60}
61
62pub async fn compress_to_path(src: impl AsRef<Path>, dest: impl AsRef<Path>) -> Result<(), Error> {
70 if let Some(path) = dest.as_ref().parent() {
71 if !path.exists() {
72 afs::create_dir_all(path)
73 .await
74 .map_err(|e| Error::io_msg(e, format!("Create dir failed:{:?}", dest.as_ref())))?;
75 }
76 }
77 let cursor = Cursor::new(Vec::<u8>::new());
78 let cursor = compress(src, cursor).await?;
79 let data = cursor.into_inner();
80 afs::write(dest.as_ref(), data).await?;
81 Ok(())
82}
83
84#[cfg(feature = "aes256")]
93pub async fn compress_to_path_encrypted(
94 src: impl AsRef<Path>,
95 dest: impl AsRef<Path>,
96 password: Password,
97) -> Result<(), Error> {
98 if let Some(path) = dest.as_ref().parent() {
99 if !path.exists() {
100 afs::create_dir_all(path)
101 .await
102 .map_err(|e| Error::io_msg(e, format!("Create dir failed:{:?}", dest.as_ref())))?;
103 }
104 }
105 let cursor = Cursor::new(Vec::<u8>::new());
106 let cursor = compress_encrypted(src, cursor, password).await?;
107 let data = cursor.into_inner();
108 afs::write(dest.as_ref(), data).await?;
109 Ok(())
110}
111
112async fn compress_path<W: AsyncWrite + AsyncSeek + Unpin, P: AsRef<Path>>(
113 src: P,
114 root: &Path,
115 archive_writer: &mut ArchiveWriter<W>,
116) -> Result<(), Error> {
117 let mut stack: Vec<PathBuf> = vec![src.as_ref().to_path_buf()];
118 while let Some(path) = stack.pop() {
119 let entry_name = path
120 .strip_prefix(root)
121 .map_err(|e| Error::other(e.to_string()))?
122 .to_string_lossy()
123 .to_string();
124 let entry = ArchiveEntry::from_path(path.as_path(), entry_name).await;
125 let meta = afs::metadata(&path)
126 .await
127 .map_err(|e| Error::io_msg(e, "error metadata"))?;
128 if meta.is_dir() {
129 archive_writer
130 .push_archive_entry::<&[u8]>(entry, None)
131 .await?;
132 let mut rd = afs::read_dir(&path)
133 .await
134 .map_err(|e| Error::io_msg(e, "error read dir"))?;
135 while let Some(res) = rd.next().await {
136 let dir = res.map_err(|e| Error::io_msg(e, "error read dir entry"))?;
137 let ftype = dir
138 .file_type()
139 .await
140 .map_err(|e| Error::io_msg(e, "error file type"))?;
141 if ftype.is_dir() || ftype.is_file() {
142 stack.push(dir.path());
143 }
144 }
145 } else {
146 archive_writer
147 .push_archive_entry::<crate::writer::SourceReader<crate::writer::LazyFileReader>>(
148 entry,
149 Some(LazyFileReader::new(path.clone()).into()),
150 )
151 .await?;
152 }
153 }
154 Ok(())
155}
156
157impl<W: AsyncWrite + AsyncSeek + Unpin> ArchiveWriter<W> {
158 pub async fn push_source_path<Fut>(
167 &mut self,
168 path: impl AsRef<Path>,
169 mut filter: impl FnMut(&Path) -> Fut,
170 ) -> Result<(), Error>
171 where
172 Fut: std::future::Future<Output = bool>,
173 {
174 encode_path(true, &path, self, &mut filter).await?;
175 Ok(())
176 }
177
178 pub async fn push_source_path_non_solid<Fut>(
187 &mut self,
188 path: impl AsRef<Path>,
189 mut filter: impl FnMut(&Path) -> Fut,
190 ) -> Result<(), Error>
191 where
192 Fut: std::future::Future<Output = bool>,
193 {
194 encode_path(false, &path, self, &mut filter).await?;
195 Ok(())
196 }
197}
198
199async fn collect_file_paths<Fut>(
200 src: impl AsRef<Path>,
201 paths: &mut Vec<PathBuf>,
202 filter: &mut impl FnMut(&Path) -> Fut,
203) -> std::io::Result<()>
204where
205 Fut: std::future::Future<Output = bool>,
206{
207 let mut stack: Vec<PathBuf> = vec![src.as_ref().to_path_buf()];
208 while let Some(path) = stack.pop() {
209 if !filter(&path).await {
210 continue;
211 }
212 let meta = afs::metadata(&path).await?;
213 if meta.is_dir() {
214 let mut rd = afs::read_dir(&path).await?;
215 while let Some(res) = rd.next().await {
216 let dir = res?;
217 let ftype = dir.file_type().await?;
218 if ftype.is_file() || ftype.is_dir() {
219 stack.push(dir.path());
220 }
221 }
222 } else {
223 paths.push(path);
224 }
225 }
226 Ok(())
227}
228
229const MAX_BLOCK_SIZE: u64 = 4 * 1024 * 1024 * 1024; async fn encode_path<W: AsyncWrite + AsyncSeek + Unpin, Fut>(
232 solid: bool,
233 src: impl AsRef<Path>,
234 zip: &mut ArchiveWriter<W>,
235 filter: &mut impl FnMut(&Path) -> Fut,
236) -> Result<(), Error>
237where
238 Fut: std::future::Future<Output = bool>,
239{
240 let mut entries = Vec::new();
241 let mut paths = Vec::new();
242 collect_file_paths(&src, &mut paths, filter)
243 .await
244 .map_err(|e| {
245 Error::io_msg(
246 e,
247 format!("Failed to collect entries from path:{:?}", src.as_ref()),
248 )
249 })?;
250
251 if !solid {
252 for ele in paths.into_iter() {
253 let name = extract_file_name(&src, &ele)?;
254
255 zip.push_archive_entry::<crate::writer::SourceReader<crate::writer::LazyFileReader>>(
256 ArchiveEntry::from_path(ele.as_path(), name).await,
257 Some(LazyFileReader::new(ele.clone()).into()),
258 )
259 .await?;
260 }
261 return Ok(());
262 }
263 let mut files = Vec::new();
264 let mut file_size = 0;
265 for ele in paths.into_iter() {
266 let size = afs::metadata(&ele).await?.len();
267 let name = extract_file_name(&src, &ele)?;
268
269 if size >= MAX_BLOCK_SIZE {
270 zip.push_archive_entry::<crate::writer::SourceReader<crate::writer::LazyFileReader>>(
271 ArchiveEntry::from_path(ele.as_path(), name).await,
272 Some(LazyFileReader::new(ele.clone()).into()),
273 )
274 .await?;
275 continue;
276 }
277 if file_size + size >= MAX_BLOCK_SIZE {
278 zip.push_archive_entries(entries, files).await?;
279 entries = Vec::new();
280 files = Vec::new();
281 file_size = 0;
282 }
283 file_size += size;
284 entries.push(ArchiveEntry::from_path(ele.as_path(), name).await);
285 files.push(LazyFileReader::new(ele).into());
286 }
287 if !entries.is_empty() {
288 zip.push_archive_entries(entries, files).await?;
289 }
290
291 Ok(())
292}
293
294fn extract_file_name(src: &impl AsRef<Path>, ele: &PathBuf) -> Result<String, Error> {
295 if ele == src.as_ref() {
296 Ok(ele
298 .file_name()
299 .ok_or_else(|| {
300 Error::io_msg(
301 std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid filename"),
302 format!("Failed to get filename from {ele:?}"),
303 )
304 })?
305 .to_string_lossy()
306 .to_string())
307 } else {
308 Ok(ele.strip_prefix(src).unwrap().to_string_lossy().to_string())
310 }
311}