use std::fs::{self, File};
use std::path::Path;
use crate::archive::types::*;
use crate::archive::types::ensure_path_within_dir;
pub struct SevenZEngine;
impl SevenZEngine {
pub fn list(path: &str) -> Result<ArchiveInfo, String> {
let file = File::open(path).map_err(|e| format!("打开文件失败: {}", e))?;
let len = file.metadata().map_err(|e| format!("读取元数据失败: {}", e))?.len();
let archive = sevenz_rust::SevenZReader::new(file, len, sevenz_rust::Password::empty())
.map_err(|e| format!("读取7z失败: {}", e))?;
let files = &archive.archive().files;
let mut entries = Vec::new();
let mut total_size = 0u64;
let mut total_compressed_size = 0u64;
let mut has_password = false;
for entry in files {
let name = entry.name().to_string();
let is_dir = entry.is_directory();
let size = entry.size();
let compressed_size = entry.compressed_size;
total_size += size;
total_compressed_size += compressed_size;
let is_encrypted = !entry.has_stream() && size > 0;
if is_encrypted {
has_password = true;
}
entries.push(ArchiveEntry {
name: name.clone(),
path: name,
size,
compressed_size,
is_dir,
modified: None,
is_encrypted,
});
}
Ok(ArchiveInfo {
format: ArchiveFormat::SevenZ,
path: path.to_string(),
file_count: entries.iter().filter(|e| !e.is_dir).count(),
entries,
total_size,
total_compressed_size,
has_password,
})
}
pub fn extract(path: &str, options: &ExtractOptions, progress_cb: &dyn Fn(ProgressInfo)) -> Result<TaskResult, String> {
let output_dir = Path::new(&options.output_dir);
fs::create_dir_all(output_dir).map_err(|e| format!("创建目录失败: {}", e))?;
let task_id = uuid::Uuid::new_v4().to_string();
let file_count = {
let file = File::open(path).map_err(|e| format!("打开文件失败: {}", e))?;
let len = file.metadata().map_err(|e| format!("读取元数据失败: {}", e))?.len();
let archive = sevenz_rust::SevenZReader::new(file, len, sevenz_rust::Password::empty())
.map_err(|e| format!("读取7z失败: {}", e))?;
let all_files: Vec<_> = archive.archive().files.iter().filter(|f| !f.is_directory()).collect();
if let Some(ref selected) = options.selected_entries {
all_files.into_iter().filter(|f| selected.contains(&f.name().to_string())).count()
} else {
all_files.len()
}
};
let pwd = match options.password {
Some(ref password) => sevenz_rust::Password::from(password.as_str()),
None => sevenz_rust::Password::empty(),
};
let mut processed: usize = 0;
let selected = &options.selected_entries;
sevenz_rust::SevenZReader::new(
File::open(path).map_err(|e| format!("打开文件失败: {}", e))?,
std::fs::metadata(path).map_err(|e| format!("读取元数据失败: {}", e))?.len(),
pwd,
).map_err(|e| format!("读取7z失败: {}", e))?
.for_each_entries(|entry, reader| {
let name = entry.name().to_string();
if !entry.is_directory() {
if let Some(ref selected_list) = selected {
if !selected_list.contains(&name) {
return Ok(true); }
}
let out_path = match ensure_path_within_dir(output_dir, &name) {
Ok(p) => p,
Err(e) => return Err(sevenz_rust::Error::other(e)),
};
if let Some(parent) = out_path.parent() {
fs::create_dir_all(parent)?;
}
let mut out_file = File::create(&out_path)?;
std::io::copy(reader, &mut out_file)?;
processed += 1;
progress_cb(ProgressInfo {
task_id: task_id.clone(),
current_file: name,
processed_bytes: processed as u64,
total_bytes: file_count as u64,
percent: processed as f64 / file_count.max(1) as f64 * 100.0,
speed: 0.0,
elapsed_secs: 0.0,
remaining_secs: 0.0,
});
}
Ok(true) }).map_err(|e| format!("解压7z失败: {}", e))?;
Ok(TaskResult {
task_id,
success: true,
message: "解压完成".to_string(),
output_path: Some(options.output_dir.clone()),
})
}
pub fn compress(files: &[String], options: &CompressOptions, progress_cb: &dyn Fn(ProgressInfo)) -> Result<TaskResult, String> {
if let Some(parent) = Path::new(&options.output_path).parent() {
fs::create_dir_all(parent).map_err(|e| format!("创建输出目录失败: {}", e))?;
}
let task_id = uuid::Uuid::new_v4().to_string();
let mut writer = sevenz_rust::SevenZWriter::create(&options.output_path)
.map_err(|e| format!("创建7z文件失败: {}", e))?;
if let Some(ref password) = options.password {
let pwd = sevenz_rust::Password::from(password.as_str());
writer.set_content_methods(vec![
sevenz_rust::AesEncoderOptions::new(pwd).into(),
sevenz_rust::SevenZMethod::LZMA2.into(),
]);
}
let mut all_entries: Vec<(std::path::PathBuf, String, bool)> = Vec::new(); for file_path in files {
let path = Path::new(file_path);
if !path.exists() {
continue;
}
if path.is_dir() {
collect_sevenz_entries(path, path, &mut all_entries)?;
} else {
let name = path.file_name().unwrap_or_default().to_string_lossy().to_string();
all_entries.push((path.to_path_buf(), name, false));
}
}
let total = all_entries.len() as u64;
for (i, (path, name, is_dir)) in all_entries.iter().enumerate() {
if *is_dir {
let entry = sevenz_rust::SevenZArchiveEntry::from_path(path, name.clone());
writer.push_archive_entry(entry, None::<File>)
.map_err(|e| format!("添加目录失败: {}", e))?;
} else {
let entry = sevenz_rust::SevenZArchiveEntry::from_path(path, name.clone());
let f = File::open(path).map_err(|e| format!("打开文件失败: {}", e))?;
writer.push_archive_entry(entry, Some(f))
.map_err(|e| format!("添加文件失败: {}", e))?;
}
progress_cb(ProgressInfo {
task_id: task_id.clone(),
current_file: format!("入队: {}", name),
processed_bytes: i as u64 + 1,
total_bytes: total,
percent: (i as f64 + 1.0) / total as f64 * 50.0, speed: 0.0,
elapsed_secs: 0.0,
remaining_secs: 0.0,
});
}
progress_cb(ProgressInfo {
task_id: task_id.clone(),
current_file: "正在压缩...".to_string(),
processed_bytes: total,
total_bytes: total,
percent: 50.0,
speed: 0.0,
elapsed_secs: 0.0,
remaining_secs: 0.0,
});
writer.finish().map_err(|e| format!("完成压缩失败: {}", e))?;
Ok(TaskResult {
task_id,
success: true,
message: "压缩完成".to_string(),
output_path: Some(options.output_path.clone()),
})
}
}
fn collect_sevenz_entries(
base: &Path,
dir: &Path,
result: &mut Vec<(std::path::PathBuf, String, bool)>,
) -> Result<(), String> {
let entries = fs::read_dir(dir).map_err(|e| format!("读取目录失败: {}", e))?;
for entry in entries {
let entry = entry.map_err(|e| format!("读取条目失败: {}", e))?;
let path = entry.path();
let rel = path.strip_prefix(base).unwrap_or(&path);
let name = rel.to_string_lossy().to_string();
if path.is_dir() {
result.push((path.clone(), name, true));
collect_sevenz_entries(base, &path, result)?;
} else {
result.push((path, name, false));
}
}
Ok(())
}