1 //! SevenZip archive format compress function
5 io::{self, Read, Seek, Write},
11 use same_file::Handle;
12 use sevenz_rust::SevenZArchiveEntry;
15 error::{Error, FinalError, Result},
19 logger::{info, warning},
20 Bytes, EscapedPathDisplay, FileVisibilityPolicy,
24 pub fn compress_sevenz<W>(
28 file_visibility_policy: FileVisibilityPolicy,
34 let mut writer = sevenz_rust::SevenZWriter::new(writer)?;
35 let output_handle = Handle::from_path(output_path);
37 for filename in files {
38 let previous_location = cd_into_same_dir_as(filename)?;
41 // paths should be canonicalized by now, and the root directory rejected.
42 let filename = filename.file_name().unwrap();
44 for entry in file_visibility_policy.build_walker(filename) {
46 let path = entry.path();
48 // If the output_path is the same as the input file, warn the user and skip the input (in order to avoid compression recursion)
49 if let Ok(handle) = &output_handle {
50 if matches!(Handle::from_path(path), Ok(x) if &x == handle) {
52 "Cannot compress `{}` into itself, skipping",
60 // This is printed for every file in `input_filenames` and has
61 // little importance for most users, but would generate lots of
62 // spoken text for users using screen readers, braille displays
65 info(format!("Compressing '{}'", EscapedPathDisplay::new(path)));
68 let metadata = match path.metadata() {
69 Ok(metadata) => metadata,
71 if e.kind() == std::io::ErrorKind::NotFound && path.is_symlink() {
72 // This path is for a broken symlink, ignore it
79 let entry_name = path.to_str().ok_or_else(|| {
80 FinalError::with_title("7z requires that all entry names are valid UTF-8")
81 .detail(format!("File at '{path:?}' has a non-UTF-8 name"))
84 let entry = sevenz_rust::SevenZArchiveEntry::from_path(path, entry_name.to_owned());
85 let entry_data = if metadata.is_dir() {
88 Some(fs::File::open(path)?)
91 writer.push_archive_entry::<fs::File>(entry, entry_data)?;
94 env::set_current_dir(previous_location)?;
97 let bytes = writer.finish()?;
101 pub fn decompress_sevenz<R>(reader: R, output_path: &Path, password: Option<&[u8]>, quiet: bool) -> crate::Result<usize>
105 let mut count: usize = 0;
107 let entry_extract_fn = |entry: &SevenZArchiveEntry, reader: &mut dyn Read, path: &PathBuf| {
109 // Manually handle writing all files from 7z archive, due to library exluding empty files
110 use std::io::BufWriter;
112 use filetime_creation as ft;
114 let file_path = output_path.join(entry.name());
116 if entry.is_directory() {
119 "File {} extracted to \"{}\"",
125 fs::create_dir_all(path)?;
130 "{:?} extracted. ({})",
132 Bytes::new(entry.size())
136 if let Some(parent) = path.parent() {
137 if !parent.exists() {
138 fs::create_dir_all(parent)?;
142 let file = fs::File::create(path)?;
143 let mut writer = BufWriter::new(file);
144 io::copy(reader, &mut writer)?;
146 ft::set_file_handle_times(
147 writer.get_ref().file(),
148 Some(ft::FileTime::from_system_time(entry.access_date().into())),
149 Some(ft::FileTime::from_system_time(entry.last_modified_date().into())),
150 Some(ft::FileTime::from_system_time(entry.creation_date().into())),
152 .unwrap_or_default();
159 Some(password) => sevenz_rust::decompress_with_extract_fn_and_password(
162 sevenz_rust::Password::from(password.to_str().map_err(|err| Error::InvalidPassword {
163 reason: err.to_string(),
167 None => sevenz_rust::decompress_with_extract_fn(reader, output_path, entry_extract_fn)?,
173 /// List contents of `archive_path`, returning a vector of archive entries
176 password: Option<&[u8]>,
177 ) -> Result<impl Iterator<Item = crate::Result<FileInArchive>>> {
178 let reader = fs::File::open(archive_path)?;
180 let mut files = Vec::new();
182 let entry_extract_fn = |entry: &SevenZArchiveEntry, _: &mut dyn Read, _: &PathBuf| {
183 files.push(Ok(FileInArchive {
184 path: entry.name().into(),
185 is_dir: entry.is_directory(),
192 let password = match password.to_str() {
195 return Err(Error::InvalidPassword {
196 reason: err.to_string(),
200 sevenz_rust::decompress_with_extract_fn_and_password(
203 sevenz_rust::Password::from(password),
207 None => sevenz_rust::decompress_with_extract_fn(reader, ".", entry_extract_fn)?,
210 Ok(files.into_iter())