sevenz_rust2/
en_funcs.rs

1//! 7z Compressor helper functions
2
3use std::{
4    fs::File,
5    io::{Seek, Write},
6    path::{Path, PathBuf},
7};
8
9use crate::*;
10
11/// Helper function to compress `src` path to `dest` writer.
12pub fn compress<W: Write + Seek>(src: impl AsRef<Path>, dest: W) -> Result<W, Error> {
13    let mut z = SevenZWriter::new(dest)?;
14    let parent = if src.as_ref().is_dir() {
15        src.as_ref()
16    } else {
17        src.as_ref().parent().unwrap_or(src.as_ref())
18    };
19    compress_path(src.as_ref(), parent, &mut z)?;
20    z.finish().map_err(Error::io)
21}
22
23#[cfg(feature = "aes256")]
24pub fn compress_encrypted<W: Write + Seek>(
25    src: impl AsRef<Path>,
26    dest: W,
27    password: Password,
28) -> Result<W, Error> {
29    let mut z = SevenZWriter::new(dest)?;
30    if !password.is_empty() {
31        z.set_content_methods(vec![
32            AesEncoderOptions::new(password).into(),
33            SevenZMethod::LZMA2.into(),
34        ]);
35    }
36    let parent = if src.as_ref().is_dir() {
37        src.as_ref()
38    } else {
39        src.as_ref().parent().unwrap_or(src.as_ref())
40    };
41    compress_path(src.as_ref(), parent, &mut z)?;
42    z.finish().map_err(Error::io)
43}
44
45/// Helper function to compress `src` path to `dest` path.
46pub fn compress_to_path(src: impl AsRef<Path>, dest: impl AsRef<Path>) -> Result<(), Error> {
47    if let Some(p) = dest.as_ref().parent() {
48        if !p.exists() {
49            std::fs::create_dir_all(p)
50                .map_err(|e| Error::io_msg(e, format!("Create dir failed:{:?}", dest.as_ref())))?;
51        }
52    }
53    compress(
54        src,
55        File::create(dest.as_ref())
56            .map_err(|e| Error::file_open(e, dest.as_ref().to_string_lossy().to_string()))?,
57    )?;
58    Ok(())
59}
60
61#[cfg(feature = "aes256")]
62pub fn compress_to_path_encrypted(
63    src: impl AsRef<Path>,
64    dest: impl AsRef<Path>,
65    password: Password,
66) -> Result<(), Error> {
67    if let Some(p) = dest.as_ref().parent() {
68        if !p.exists() {
69            std::fs::create_dir_all(p)
70                .map_err(|e| Error::io_msg(e, format!("Create dir failed:{:?}", dest.as_ref())))?;
71        }
72    }
73    compress_encrypted(
74        src,
75        File::create(dest.as_ref())
76            .map_err(|e| Error::file_open(e, dest.as_ref().to_string_lossy().to_string()))?,
77        password,
78    )?;
79    Ok(())
80}
81
82fn compress_path<W: Write + Seek, P: AsRef<Path>>(
83    src: P,
84    root: &Path,
85    z: &mut SevenZWriter<W>,
86) -> Result<(), Error> {
87    let entry_name = src
88        .as_ref()
89        .strip_prefix(root)
90        .map_err(|e| Error::other(e.to_string()))?
91        .to_string_lossy()
92        .to_string();
93    let entry = SevenZArchiveEntry::from_path(src.as_ref(), entry_name);
94    let path = src.as_ref();
95    if path.is_dir() {
96        z.push_archive_entry::<&[u8]>(entry, None)?;
97        for dir in path
98            .read_dir()
99            .map_err(|e| Error::io_msg(e, "error read dir"))?
100        {
101            let dir = dir.map_err(Error::io)?;
102            let ftype = dir.file_type().map_err(Error::io)?;
103            if ftype.is_dir() || ftype.is_file() {
104                compress_path(dir.path(), root, z)?;
105            }
106        }
107    } else {
108        z.push_archive_entry(
109            entry,
110            Some(
111                File::open(path)
112                    .map_err(|e| Error::file_open(e, path.to_string_lossy().to_string()))?,
113            ),
114        )?;
115    }
116    Ok(())
117}
118
119impl<W: Write + Seek> SevenZWriter<W> {
120    /// Solid compression - compress all files in `path` with multiple files in one block.
121    pub fn push_source_path(
122        &mut self,
123        path: impl AsRef<Path>,
124        filter: impl Fn(&Path) -> bool,
125    ) -> Result<&mut Self, Error> {
126        encode_path(true, &path, self, filter)?;
127        Ok(self)
128    }
129
130    /// Non-solid compression - compresses all files into `path` with one file per block.
131    pub fn push_source_path_non_solid(
132        &mut self,
133        path: impl AsRef<Path>,
134        filter: impl Fn(&Path) -> bool,
135    ) -> Result<&mut Self, Error> {
136        encode_path(false, &path, self, filter)?;
137        Ok(self)
138    }
139}
140
141fn collect_file_paths(
142    src: impl AsRef<Path>,
143    paths: &mut Vec<PathBuf>,
144    filter: &dyn Fn(&Path) -> bool,
145) -> std::io::Result<()> {
146    let path = src.as_ref();
147    if !filter(path) {
148        return Ok(());
149    }
150    if path.is_dir() {
151        for dir in path.read_dir()? {
152            let dir = dir?;
153            let ftype = dir.file_type()?;
154            if ftype.is_file() || ftype.is_dir() {
155                collect_file_paths(dir.path(), paths, filter)?;
156            }
157        }
158    } else {
159        paths.push(path.to_path_buf())
160    }
161    Ok(())
162}
163
164const MAX_BLOCK_SIZE: u64 = 4 * 1024 * 1024 * 1024; // 4 GiB
165
166fn encode_path<W: Write + Seek>(
167    solid: bool,
168    src: impl AsRef<Path>,
169    zip: &mut SevenZWriter<W>,
170    filter: impl Fn(&Path) -> bool,
171) -> Result<(), Error> {
172    let mut entries = Vec::new();
173    let mut paths = Vec::new();
174    collect_file_paths(&src, &mut paths, &filter).map_err(|e| {
175        Error::io_msg(
176            e,
177            format!("Failed to collect entries from path:{:?}", src.as_ref()),
178        )
179    })?;
180    if !solid {
181        for ele in paths.into_iter() {
182            let name = ele
183                .strip_prefix(&src)
184                .unwrap()
185                .to_string_lossy()
186                .to_string();
187            zip.push_archive_entry(
188                SevenZArchiveEntry::from_path(ele.as_path(), name),
189                Some(File::open(ele.as_path()).map_err(Error::io)?),
190            )?;
191        }
192        return Ok(());
193    }
194    let mut files = Vec::new();
195    let mut file_size = 0;
196    for ele in paths.into_iter() {
197        let size = ele.metadata()?.len();
198        let name = ele
199            .strip_prefix(&src)
200            .unwrap()
201            .to_string_lossy()
202            .to_string();
203        if size >= MAX_BLOCK_SIZE {
204            zip.push_archive_entry(
205                SevenZArchiveEntry::from_path(ele.as_path(), name),
206                Some(File::open(ele.as_path()).map_err(Error::io)?),
207            )?;
208            continue;
209        }
210        if file_size + size >= MAX_BLOCK_SIZE {
211            zip.push_archive_entries(entries, SeqReader::new(files))?;
212            entries = Vec::new();
213            files = Vec::new();
214            file_size = 0;
215        }
216        file_size += size;
217        entries.push(SevenZArchiveEntry::from_path(ele.as_path(), name));
218        files.push(LazyFileReader::new(ele).into());
219    }
220    if !entries.is_empty() {
221        zip.push_archive_entries(entries, SeqReader::new(files))?;
222    }
223
224    Ok(())
225}