async_sevenz/util/
decompress.rs

1use std::path::{Path, PathBuf};
2
3use async_fs as afs;
4use futures::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt};
5use std::future::Future;
6use std::io::{Read, Seek, SeekFrom};
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) => futures::io::SeekFrom::Start(n),
33                SeekFrom::End(i) => futures::io::SeekFrom::End(i),
34                SeekFrom::Current(i) => futures::io::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 futures::io::AsyncRead + Unpin + 'a),
76        &'a Path,
77    ) -> Pin<Box<dyn Future<Output = Result<bool, Error>> + 'a>>
78    + 'static,
79) -> Result<(), Error> {
80    decompress_path_impl(
81        src_path.as_ref(),
82        dest.as_ref().to_path_buf(),
83        Password::empty(),
84        move |entry, reader, path| extract_fn(entry, reader, path),
85    )
86    .await
87}
88
89/// Decompresses an archive from a reader to a destination directory.
90///
91/// # Arguments
92/// * `src_reader` - Reader containing the archive data
93/// * `dest` - Path to the destination directory where files will be extracted
94pub async fn decompress<R: AsyncRead + AsyncSeek + Unpin>(
95    mut src_reader: R,
96    dest: impl AsRef<Path>,
97) -> Result<(), Error> {
98    let pos = AsyncSeekExt::stream_position(&mut src_reader).await?;
99    AsyncSeekExt::seek(&mut src_reader, futures::io::SeekFrom::Start(pos)).await?;
100    decompress_impl(
101        src_reader,
102        dest,
103        Password::empty(),
104        |entry, reader, dest| Box::pin(default_entry_extract_fn(entry, reader, dest)),
105    )
106    .await
107}
108
109/// Decompresses an archive from a reader to a destination directory with a custom extraction function.
110///
111/// This provides the most flexibility, allowing both custom input sources and custom extraction logic.
112///
113/// # Arguments
114/// * `src_reader` - Reader containing the archive data
115/// * `dest` - Path to the destination directory where files will be extracted
116/// * `extract_fn` - Custom function to handle each archive entry during extraction
117#[cfg(not(target_arch = "wasm32"))]
118pub async fn decompress_with_extract_fn<R: AsyncRead + AsyncSeek + Unpin>(
119    mut src_reader: R,
120    dest: impl AsRef<Path>,
121    extract_fn: impl for<'a> FnMut(
122        &'a ArchiveEntry,
123        &'a mut (dyn futures::io::AsyncRead + Unpin + 'a),
124        &'a Path,
125    ) -> Pin<Box<dyn Future<Output = Result<bool, Error>> + 'a>>
126    + 'static,
127) -> Result<(), Error> {
128    let pos = AsyncSeekExt::stream_position(&mut src_reader).await?;
129    AsyncSeekExt::seek(&mut src_reader, futures::io::SeekFrom::Start(pos)).await?;
130    decompress_impl(src_reader, dest, Password::empty(), extract_fn).await
131}
132
133/// Decompresses an encrypted archive file with the given password.
134///
135/// # Arguments
136/// * `src_path` - Path to the encrypted source archive file
137/// * `dest` - Path to the destination directory where files will be extracted
138/// * `password` - Password to decrypt the archive
139#[cfg(all(feature = "aes256", not(target_arch = "wasm32")))]
140pub async fn decompress_file_with_password(
141    src_path: impl AsRef<Path>,
142    dest: impl AsRef<Path>,
143    password: Password,
144) -> Result<(), Error> {
145    let dest_path = dest.as_ref().to_path_buf();
146    decompress_path_impl(
147        src_path.as_ref(),
148        dest_path,
149        password,
150        |entry, reader, dest| Box::pin(default_entry_extract_fn(entry, reader, dest)),
151    )
152    .await
153}
154
155/// Decompresses an encrypted archive from a reader with the given password.
156///
157/// # Arguments
158/// * `src_reader` - Reader containing the encrypted archive data
159/// * `dest` - Path to the destination directory where files will be extracted
160/// * `password` - Password to decrypt the archive
161#[cfg(all(feature = "aes256", not(target_arch = "wasm32")))]
162pub async fn decompress_with_password<R: AsyncRead + AsyncSeek + Unpin>(
163    mut src_reader: R,
164    dest: impl AsRef<Path>,
165    password: Password,
166) -> Result<(), Error> {
167    let pos = AsyncSeekExt::stream_position(&mut src_reader).await?;
168    AsyncSeekExt::seek(&mut src_reader, futures::io::SeekFrom::Start(pos)).await?;
169    decompress_impl(src_reader, dest, password, |entry, reader, dest| {
170        Box::pin(default_entry_extract_fn(entry, reader, dest))
171    })
172    .await
173}
174
175/// Decompresses an encrypted archive from a reader with a custom extraction function and password.
176///
177/// This provides maximum flexibility for encrypted archives, allowing custom input sources,
178/// custom extraction logic, and password decryption.
179///
180/// # Arguments
181/// * `src_reader` - Reader containing the encrypted archive data
182/// * `dest` - Path to the destination directory where files will be extracted
183/// * `password` - Password to decrypt the archive
184/// * `extract_fn` - Custom function to handle each archive entry during extraction
185#[cfg(all(feature = "aes256", not(target_arch = "wasm32")))]
186pub async fn decompress_with_extract_fn_and_password<R: AsyncRead + AsyncSeek + Unpin>(
187    mut src_reader: R,
188    dest: impl AsRef<Path>,
189    password: Password,
190    extract_fn: impl for<'a> FnMut(
191        &'a ArchiveEntry,
192        &'a mut (dyn futures::io::AsyncRead + Unpin + 'a),
193        &'a Path,
194    ) -> Pin<Box<dyn Future<Output = Result<bool, Error>> + 'a>>
195    + 'static,
196) -> Result<(), Error> {
197    let pos = AsyncSeekExt::stream_position(&mut src_reader).await?;
198    AsyncSeekExt::seek(&mut src_reader, futures::io::SeekFrom::Start(pos)).await?;
199    decompress_impl(src_reader, dest, password, extract_fn).await
200}
201
202#[cfg(not(target_arch = "wasm32"))]
203async fn decompress_impl<R: AsyncRead + AsyncSeek + Unpin>(
204    mut src_reader: R,
205    dest: impl AsRef<Path>,
206    password: Password,
207    extract_fn: impl for<'a> FnMut(
208        &'a ArchiveEntry,
209        &'a mut (dyn futures::io::AsyncRead + Unpin + 'a),
210        &'a Path,
211    ) -> Pin<Box<dyn Future<Output = Result<bool, Error>> + 'a>>
212    + 'static,
213) -> Result<(), Error> {
214    let pos = AsyncSeekExt::stream_position(&mut src_reader).await?;
215    AsyncSeekExt::seek(&mut src_reader, futures::io::SeekFrom::Start(pos)).await?;
216    let mut seven = ArchiveReader::new(src_reader, password).await?;
217    let dest = PathBuf::from(dest.as_ref());
218    if !dest.exists() {
219        afs::create_dir_all(&dest).await?;
220    }
221    let extract_fn_cell = std::rc::Rc::new(std::cell::RefCell::new(extract_fn));
222    seven
223        .for_each_entries(|entry, reader| {
224            let dest_path = dest.join(entry.name());
225            let extract_fn_cell = std::rc::Rc::clone(&extract_fn_cell);
226            Box::pin(async move {
227                let fut = {
228                    let mut f = extract_fn_cell.borrow_mut();
229                    f(entry, reader, dest_path.as_path())
230                };
231                fut.await
232            })
233        })
234        .await?;
235
236    Ok(())
237}
238
239#[cfg(not(target_arch = "wasm32"))]
240async fn decompress_path_impl(
241    src_path: &Path,
242    dest: PathBuf,
243    password: Password,
244    extract_fn: impl for<'a> FnMut(
245        &'a ArchiveEntry,
246        &'a mut (dyn futures::io::AsyncRead + Unpin + 'a),
247        &'a Path,
248    ) -> Pin<Box<dyn Future<Output = Result<bool, Error>> + 'a>>
249    + 'static,
250) -> Result<(), Error> {
251    let mut seven = ArchiveReader::open(src_path, password).await?;
252    if !dest.exists() {
253        afs::create_dir_all(&dest).await?;
254    }
255    let extract_fn_cell = std::rc::Rc::new(std::cell::RefCell::new(extract_fn));
256    seven
257        .for_each_entries(|entry, reader| {
258            let dest_path = dest.join(entry.name());
259            let extract_fn_cell = std::rc::Rc::clone(&extract_fn_cell);
260            Box::pin(async move {
261                let fut = {
262                    let mut f = extract_fn_cell.borrow_mut();
263                    f(entry, reader, dest_path.as_path())
264                };
265                fut.await
266            })
267        })
268        .await?;
269    Ok(())
270}
271
272/// Default extraction function that handles standard file and directory extraction.
273///
274/// # Arguments
275/// * `entry` - Archive entry being processed
276/// * `reader` - Reader for the entry's data
277/// * `dest` - Destination path for the entry
278#[cfg(not(target_arch = "wasm32"))]
279pub async fn default_entry_extract_fn(
280    entry: &ArchiveEntry,
281    reader: &mut (dyn futures::io::AsyncRead + Unpin),
282    dest: &Path,
283) -> Result<bool, Error> {
284    if entry.is_directory() {
285        let dir = dest.to_path_buf();
286        afs::create_dir_all(&dir).await?;
287    } else {
288        let path = dest.to_path_buf();
289        if let Some(p) = path.parent() {
290            if !p.exists() {
291                afs::create_dir_all(p).await?;
292            }
293        }
294        if entry.size() > 0 {
295            let mut data = Vec::new();
296            AsyncReadExt::read_to_end(reader, &mut data)
297                .await
298                .map_err(|e| Error::io_msg(e, "read entry data"))?;
299            afs::write(&path, &data).await?;
300        } else {
301            afs::File::create(&path).await?;
302        }
303    }
304
305    Ok(true)
306}