sevenz_rust2/util/
decompress.rs

1#[cfg(target_os = "macos")]
2use std::os::macos::fs::FileTimesExt;
3#[cfg(windows)]
4use std::os::windows::fs::FileTimesExt;
5use std::{
6    fs::FileTimes,
7    io::{Read, Seek},
8    path::{Path, PathBuf},
9};
10
11use crate::{Error, Password, *};
12
13/// Decompresses an archive file to a destination directory.
14///
15/// This is a convenience function for decompressing archive files directly from the filesystem.
16///
17/// # Arguments
18/// * `src_path` - Path to the source archive file
19/// * `dest` - Path to the destination directory where files will be extracted
20#[cfg_attr(docsrs, doc(cfg(feature = "util")))]
21pub fn decompress_file(src_path: impl AsRef<Path>, dest: impl AsRef<Path>) -> Result<(), Error> {
22    let file = std::fs::File::open(src_path.as_ref())
23        .map_err(|e| Error::file_open(e, src_path.as_ref().to_string_lossy().to_string()))?;
24    decompress(file, dest)
25}
26
27/// Decompresses an archive file to a destination directory with a custom extraction function.
28///
29/// The extraction function is called for each entry in the archive, allowing custom handling
30/// of individual files and directories during extraction.
31///
32/// # Arguments
33/// * `src_path` - Path to the source archive file
34/// * `dest` - Path to the destination directory where files will be extracted
35/// * `extract_fn` - Custom function to handle each archive entry during extraction
36#[cfg_attr(docsrs, doc(cfg(feature = "util")))]
37pub fn decompress_file_with_extract_fn(
38    src_path: impl AsRef<Path>,
39    dest: impl AsRef<Path>,
40    extract_fn: impl FnMut(&ArchiveEntry, &mut dyn Read, &PathBuf) -> Result<bool, Error>,
41) -> Result<(), Error> {
42    let file = std::fs::File::open(src_path.as_ref())
43        .map_err(|e| Error::file_open(e, src_path.as_ref().to_string_lossy().to_string()))?;
44    decompress_with_extract_fn(file, dest, extract_fn)
45}
46
47/// Decompresses an archive from a reader to a destination directory.
48///
49/// # Arguments
50/// * `src_reader` - Reader containing the archive data
51/// * `dest` - Path to the destination directory where files will be extracted
52#[cfg_attr(docsrs, doc(cfg(feature = "util")))]
53pub fn decompress<R: Read + Seek>(src_reader: R, dest: impl AsRef<Path>) -> Result<(), Error> {
54    decompress_with_extract_fn(src_reader, dest, default_entry_extract_fn)
55}
56
57/// Decompresses an archive from a reader to a destination directory with a custom extraction function.
58///
59/// This provides the most flexibility, allowing both custom input sources and custom extraction logic.
60///
61/// # Arguments
62/// * `src_reader` - Reader containing the archive data
63/// * `dest` - Path to the destination directory where files will be extracted
64/// * `extract_fn` - Custom function to handle each archive entry during extraction
65#[cfg(not(target_arch = "wasm32"))]
66#[cfg_attr(docsrs, doc(cfg(feature = "util")))]
67pub fn decompress_with_extract_fn<R: Read + Seek>(
68    src_reader: R,
69    dest: impl AsRef<Path>,
70    extract_fn: impl FnMut(&ArchiveEntry, &mut dyn Read, &PathBuf) -> Result<bool, Error>,
71) -> Result<(), Error> {
72    decompress_impl(src_reader, dest, Password::empty(), extract_fn)
73}
74
75/// Decompresses an encrypted archive file with the given password.
76///
77/// # Arguments
78/// * `src_path` - Path to the encrypted source archive file
79/// * `dest` - Path to the destination directory where files will be extracted
80/// * `password` - Password to decrypt the archive
81#[cfg(all(feature = "aes256", not(target_arch = "wasm32")))]
82#[cfg_attr(docsrs, doc(cfg(all(feature = "aes256", feature = "util"))))]
83pub fn decompress_file_with_password(
84    src_path: impl AsRef<Path>,
85    dest: impl AsRef<Path>,
86    password: Password,
87) -> Result<(), Error> {
88    let file = std::fs::File::open(src_path.as_ref())
89        .map_err(|e| Error::file_open(e, src_path.as_ref().to_string_lossy().to_string()))?;
90    decompress_with_password(file, dest, password)
91}
92
93/// Decompresses an encrypted archive from a reader with the given password.
94///
95/// # Arguments
96/// * `src_reader` - Reader containing the encrypted archive data
97/// * `dest` - Path to the destination directory where files will be extracted
98/// * `password` - Password to decrypt the archive
99#[cfg(all(feature = "aes256", not(target_arch = "wasm32")))]
100#[cfg_attr(docsrs, doc(cfg(all(feature = "aes256", feature = "util"))))]
101pub fn decompress_with_password<R: Read + Seek>(
102    src_reader: R,
103    dest: impl AsRef<Path>,
104    password: Password,
105) -> Result<(), Error> {
106    decompress_impl(src_reader, dest, password, default_entry_extract_fn)
107}
108
109/// Decompresses an encrypted archive from a reader with a custom extraction function and password.
110///
111/// This provides maximum flexibility for encrypted archives, allowing custom input sources,
112/// custom extraction logic, and password decryption.
113///
114/// # Arguments
115/// * `src_reader` - Reader containing the encrypted archive data
116/// * `dest` - Path to the destination directory where files will be extracted
117/// * `password` - Password to decrypt the archive
118/// * `extract_fn` - Custom function to handle each archive entry during extraction
119#[cfg(all(feature = "aes256", not(target_arch = "wasm32")))]
120#[cfg_attr(docsrs, doc(cfg(all(feature = "aes256", feature = "util"))))]
121pub fn decompress_with_extract_fn_and_password<R: Read + Seek>(
122    src_reader: R,
123    dest: impl AsRef<Path>,
124    password: Password,
125    extract_fn: impl FnMut(&ArchiveEntry, &mut dyn Read, &PathBuf) -> Result<bool, Error>,
126) -> Result<(), Error> {
127    decompress_impl(src_reader, dest, password, extract_fn)
128}
129
130#[cfg(not(target_arch = "wasm32"))]
131fn decompress_impl<R: Read + Seek>(
132    mut src_reader: R,
133    dest: impl AsRef<Path>,
134    password: Password,
135    mut extract_fn: impl FnMut(&ArchiveEntry, &mut dyn Read, &PathBuf) -> Result<bool, Error>,
136) -> Result<(), Error> {
137    use std::io::SeekFrom;
138
139    let pos = src_reader.stream_position().map_err(Error::io)?;
140    src_reader.seek(SeekFrom::Start(pos)).map_err(Error::io)?;
141    let mut seven = ArchiveReader::new(src_reader, password)?;
142    let dest = PathBuf::from(dest.as_ref());
143    if !dest.exists() {
144        std::fs::create_dir_all(&dest).map_err(Error::io)?;
145    }
146    seven.for_each_entries(|entry, reader| {
147        let dest_path = dest.join(entry.name());
148        extract_fn(entry, reader, &dest_path)
149    })?;
150
151    Ok(())
152}
153
154/// Default extraction function that handles standard file and directory extraction.
155///
156/// # Arguments
157/// * `entry` - Archive entry being processed
158/// * `reader` - Reader for the entry's data
159/// * `dest` - Destination path for the entry
160#[cfg(not(target_arch = "wasm32"))]
161#[cfg_attr(docsrs, doc(cfg(feature = "util")))]
162pub fn default_entry_extract_fn(
163    entry: &ArchiveEntry,
164    reader: &mut dyn Read,
165    dest: &PathBuf,
166) -> Result<bool, Error> {
167    use std::{fs::File, io::BufWriter};
168
169    if entry.is_directory() {
170        let dir = dest;
171        if !dir.exists() {
172            std::fs::create_dir_all(dir).map_err(Error::io)?;
173        }
174    } else {
175        let path = dest;
176        path.parent().and_then(|p| {
177            if !p.exists() {
178                std::fs::create_dir_all(p).ok()
179            } else {
180                None
181            }
182        });
183        let file = File::create(path)
184            .map_err(|e| Error::file_open(e, path.to_string_lossy().to_string()))?;
185        if entry.size() > 0 {
186            let mut writer = BufWriter::new(file);
187            std::io::copy(reader, &mut writer).map_err(Error::io)?;
188
189            let file = writer.get_mut();
190            let file_times = FileTimes::new()
191                .set_accessed(entry.access_date().into())
192                .set_modified(entry.last_modified_date().into());
193
194            #[cfg(any(windows, target_os = "macos"))]
195            let file_times = file_times.set_created(entry.creation_date().into());
196
197            let _ = file.set_times(file_times);
198        }
199    }
200
201    Ok(true)
202}