async_sevenz/util/
decompress.rs

1use std::path::{Path, PathBuf};
2
3use async_fs as afs;
4use futures_lite::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, SeekFrom};
5use std::future::Future;
6use std::io::{Read, Seek};
7use std::pin::Pin;
8
9use crate::{Error, Password, *};
10
11pub(crate) struct AsyncReadSeekAsStd<R: AsyncRead + Unpin> {
12    inner: R,
13}
14
15impl<R: AsyncRead + Unpin> AsyncReadSeekAsStd<R> {
16    pub(crate) fn new(inner: R) -> Self {
17        Self { inner }
18    }
19}
20
21impl<R: AsyncRead + Unpin> Read for AsyncReadSeekAsStd<R> {
22    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
23        async_io::block_on(AsyncReadExt::read(&mut self.inner, buf))
24    }
25}
26
27impl<R: AsyncRead + AsyncSeek + Unpin> Seek for AsyncReadSeekAsStd<R> {
28    fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
29        async_io::block_on(AsyncSeekExt::seek(
30            &mut self.inner,
31            match pos {
32                SeekFrom::Start(n) => SeekFrom::Start(n),
33                SeekFrom::End(i) => SeekFrom::End(i),
34                SeekFrom::Current(i) => SeekFrom::Current(i),
35            },
36        ))
37    }
38}
39
40/// Decompresses an archive file to a destination directory.
41///
42/// This is a convenience function for decompressing archive files directly from the filesystem.
43///
44/// # Arguments
45/// * `src_path` - Path to the source archive file
46/// * `dest` - Path to the destination directory where files will be extracted
47pub async fn decompress_file(
48    src_path: impl AsRef<Path>,
49    dest: impl AsRef<Path>,
50) -> Result<(), Error> {
51    let dest_path = dest.as_ref().to_path_buf();
52    decompress_path_impl(
53        src_path.as_ref(),
54        dest_path,
55        Password::empty(),
56        |entry, reader, dest| Box::pin(default_entry_extract_fn(entry, reader, dest)),
57    )
58    .await
59}
60
61/// Decompresses an archive file to a destination directory with a custom extraction function.
62///
63/// The extraction function is called for each entry in the archive, allowing custom handling
64/// of individual files and directories during extraction.
65///
66/// # Arguments
67/// * `src_path` - Path to the source archive file
68/// * `dest` - Path to the destination directory where files will be extracted
69/// * `extract_fn` - Custom function to handle each archive entry during extraction
70pub async fn decompress_file_with_extract_fn(
71    src_path: impl AsRef<Path>,
72    dest: impl AsRef<Path>,
73    mut extract_fn: impl for<'a> FnMut(
74        &'a ArchiveEntry,
75        &'a mut (dyn AsyncRead + Unpin + Send + 'a),
76        &'a Path,
77    )
78        -> Pin<Box<dyn Future<Output = Result<bool, Error>> + Send + 'a>>
79    + 'static
80    + Send,
81) -> Result<(), Error> {
82    decompress_path_impl(
83        src_path.as_ref(),
84        dest.as_ref().to_path_buf(),
85        Password::empty(),
86        move |entry, reader, path| extract_fn(entry, reader, path),
87    )
88    .await
89}
90
91/// Decompresses an archive from a reader to a destination directory.
92///
93/// # Arguments
94/// * `src_reader` - Reader containing the archive data
95/// * `dest` - Path to the destination directory where files will be extracted
96pub async fn decompress<R: AsyncRead + AsyncSeek + Unpin + Send>(
97    mut src_reader: R,
98    dest: impl AsRef<Path>,
99) -> Result<(), Error> {
100    let pos = src_reader.seek(SeekFrom::Current(0)).await?;
101    AsyncSeekExt::seek(&mut src_reader, SeekFrom::Start(pos)).await?;
102    decompress_impl(
103        src_reader,
104        dest,
105        Password::empty(),
106        |entry, reader, dest| Box::pin(default_entry_extract_fn(entry, reader, dest)),
107    )
108    .await
109}
110
111/// Decompresses an archive from a reader to a destination directory with a custom extraction function.
112///
113/// This provides the most flexibility, allowing both custom input sources and custom extraction logic.
114///
115/// # Arguments
116/// * `src_reader` - Reader containing the archive data
117/// * `dest` - Path to the destination directory where files will be extracted
118/// * `extract_fn` - Custom function to handle each archive entry during extraction
119#[cfg(not(target_arch = "wasm32"))]
120pub async fn decompress_with_extract_fn<R: AsyncRead + AsyncSeek + Unpin + Send>(
121    mut src_reader: R,
122    dest: impl AsRef<Path>,
123    extract_fn: impl for<'a> FnMut(
124        &'a ArchiveEntry,
125        &'a mut (dyn AsyncRead + Unpin + Send + 'a),
126        &'a Path,
127    )
128        -> Pin<Box<dyn Future<Output = Result<bool, Error>> + Send + 'a>>
129    + 'static
130    + Send,
131) -> Result<(), Error> {
132    let pos = src_reader.seek(SeekFrom::Current(0)).await?;
133    AsyncSeekExt::seek(&mut src_reader, SeekFrom::Start(pos)).await?;
134    decompress_impl(src_reader, dest, Password::empty(), extract_fn).await
135}
136
137/// Decompresses an encrypted archive file with the given password.
138///
139/// # Arguments
140/// * `src_path` - Path to the encrypted source archive file
141/// * `dest` - Path to the destination directory where files will be extracted
142/// * `password` - Password to decrypt the archive
143#[cfg(all(feature = "aes256", not(target_arch = "wasm32")))]
144pub async fn decompress_file_with_password(
145    src_path: impl AsRef<Path>,
146    dest: impl AsRef<Path>,
147    password: Password,
148) -> Result<(), Error> {
149    let dest_path = dest.as_ref().to_path_buf();
150    decompress_path_impl(
151        src_path.as_ref(),
152        dest_path,
153        password,
154        |entry, reader, dest| Box::pin(default_entry_extract_fn(entry, reader, dest)),
155    )
156    .await
157}
158
159/// Decompresses an encrypted archive from a reader with the given password.
160///
161/// # Arguments
162/// * `src_reader` - Reader containing the encrypted archive data
163/// * `dest` - Path to the destination directory where files will be extracted
164/// * `password` - Password to decrypt the archive
165#[cfg(all(feature = "aes256", not(target_arch = "wasm32")))]
166pub async fn decompress_with_password<R: AsyncRead + AsyncSeek + Unpin + Send>(
167    mut src_reader: R,
168    dest: impl AsRef<Path>,
169    password: Password,
170) -> Result<(), Error> {
171    let pos = src_reader.seek(SeekFrom::Current(0)).await?;
172    AsyncSeekExt::seek(&mut src_reader, SeekFrom::Start(pos)).await?;
173    decompress_impl(src_reader, dest, password, |entry, reader, dest| {
174        Box::pin(default_entry_extract_fn(entry, reader, dest))
175    })
176    .await
177}
178
179/// Decompresses an encrypted archive from a reader with a custom extraction function and password.
180///
181/// This provides maximum flexibility for encrypted archives, allowing custom input sources,
182/// custom extraction logic, and password decryption.
183///
184/// # Arguments
185/// * `src_reader` - Reader containing the encrypted archive data
186/// * `dest` - Path to the destination directory where files will be extracted
187/// * `password` - Password to decrypt the archive
188/// * `extract_fn` - Custom function to handle each archive entry during extraction
189#[cfg(all(feature = "aes256", not(target_arch = "wasm32")))]
190pub async fn decompress_with_extract_fn_and_password<R: AsyncRead + AsyncSeek + Unpin + Send>(
191    mut src_reader: R,
192    dest: impl AsRef<Path>,
193    password: Password,
194    extract_fn: impl for<'a> FnMut(
195        &'a ArchiveEntry,
196        &'a mut (dyn AsyncRead + Unpin + Send + 'a),
197        &'a Path,
198    )
199        -> Pin<Box<dyn Future<Output = Result<bool, Error>> + Send + 'a>>
200    + 'static
201    + Send,
202) -> Result<(), Error> {
203    let pos = src_reader.seek(SeekFrom::Current(0)).await?;
204    AsyncSeekExt::seek(&mut src_reader, SeekFrom::Start(pos)).await?;
205    decompress_impl(src_reader, dest, password, extract_fn).await
206}
207
208#[cfg(not(target_arch = "wasm32"))]
209async fn decompress_impl<R: AsyncRead + AsyncSeek + Unpin + Send>(
210    mut src_reader: R,
211    dest: impl AsRef<Path>,
212    password: Password,
213    extract_fn: impl for<'a> FnMut(
214        &'a ArchiveEntry,
215        &'a mut (dyn AsyncRead + Unpin + Send + 'a),
216        &'a Path,
217    )
218        -> Pin<Box<dyn Future<Output = Result<bool, Error>> + Send + 'a>>
219    + 'static
220    + Send,
221) -> Result<(), Error> {
222    let pos = src_reader.seek(SeekFrom::Current(0)).await?;
223    AsyncSeekExt::seek(&mut src_reader, SeekFrom::Start(pos)).await?;
224    let mut seven = ArchiveReader::new(src_reader, password).await?;
225    let dest = PathBuf::from(dest.as_ref());
226    if !dest.exists() {
227        afs::create_dir_all(&dest).await?;
228    }
229    let extract_fn_cell = std::sync::Arc::new(std::sync::Mutex::new(extract_fn));
230    seven
231        .for_each_entries(|entry, reader| {
232            let dest_path = dest.join(entry.name());
233            let extract_fn_cell = std::sync::Arc::clone(&extract_fn_cell);
234            Box::pin(async move {
235                let fut = {
236                    let mut f = extract_fn_cell.lock().unwrap();
237                    f(entry, reader, dest_path.as_path())
238                };
239                fut.await
240            })
241        })
242        .await?;
243
244    Ok(())
245}
246
247#[cfg(not(target_arch = "wasm32"))]
248async fn decompress_path_impl(
249    src_path: &Path,
250    dest: PathBuf,
251    password: Password,
252    extract_fn: impl for<'a> FnMut(
253        &'a ArchiveEntry,
254        &'a mut (dyn AsyncRead + Unpin + Send + 'a),
255        &'a Path,
256    )
257        -> Pin<Box<dyn Future<Output = Result<bool, Error>> + Send + 'a>>
258    + 'static
259    + Send,
260) -> Result<(), Error> {
261    let mut seven = ArchiveReader::open(src_path, password).await?;
262    if !dest.exists() {
263        afs::create_dir_all(&dest).await?;
264    }
265    let extract_fn_cell = std::sync::Arc::new(std::sync::Mutex::new(extract_fn));
266    seven
267        .for_each_entries(|entry, reader| {
268            let dest_path = dest.join(entry.name());
269            let extract_fn_cell = std::sync::Arc::clone(&extract_fn_cell);
270            Box::pin(async move {
271                let fut = {
272                    let mut f = extract_fn_cell.lock().unwrap();
273                    f(entry, reader, dest_path.as_path())
274                };
275                fut.await
276            })
277        })
278        .await?;
279    Ok(())
280}
281
282/// Default extraction function that handles standard file and directory extraction.
283///
284/// # Arguments
285/// * `entry` - Archive entry being processed
286/// * `reader` - Reader for the entry's data
287/// * `dest` - Destination path for the entry
288#[cfg(not(target_arch = "wasm32"))]
289pub async fn default_entry_extract_fn(
290    entry: &ArchiveEntry,
291    reader: &mut (dyn AsyncRead + Unpin + Send),
292    dest: &Path,
293) -> Result<bool, Error> {
294    if entry.is_directory() {
295        let dir = dest.to_path_buf();
296        afs::create_dir_all(&dir).await?;
297    } else {
298        let path = dest.to_path_buf();
299        if let Some(p) = path.parent() {
300            if !p.exists() {
301                afs::create_dir_all(p).await?;
302            }
303        }
304        if entry.size() > 0 {
305            let mut data = Vec::new();
306            AsyncReadExt::read_to_end(reader, &mut data)
307                .await
308                .map_err(|e| Error::io_msg(e, "read entry data"))?;
309            afs::write(&path, &data).await?;
310        } else {
311            afs::File::create(&path).await?;
312        }
313    }
314
315    Ok(true)
316}