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