use std::fs::File;
use std::path::Path;
use crate::archive::types::*;
use crate::archive::types::ensure_path_within_dir;
pub struct TarEngine;
impl TarEngine {
pub fn list(path: &str, format: ArchiveFormat) -> Result<ArchiveInfo, String> {
let file = File::open(path).map_err(|e| format!("打开文件失败: {}", e))?;
let mut entries = Vec::new();
let mut total_size = 0u64;
let mut total_compressed_size = 0u64;
match format {
ArchiveFormat::TarGz => {
let gz = flate2::read::GzDecoder::new(file);
let mut archive = tar::Archive::new(gz);
list_tar_entries(&mut archive, &mut entries, &mut total_size, &mut total_compressed_size)?;
}
ArchiveFormat::TarBz2 => {
let bz2 = bzip2::read::BzDecoder::new(file);
let mut archive = tar::Archive::new(bz2);
list_tar_entries(&mut archive, &mut entries, &mut total_size, &mut total_compressed_size)?;
}
ArchiveFormat::TarXz => {
let xz = xz2::read::XzDecoder::new(file);
let mut archive = tar::Archive::new(xz);
list_tar_entries(&mut archive, &mut entries, &mut total_size, &mut total_compressed_size)?;
}
ArchiveFormat::Tar => {
let mut archive = tar::Archive::new(file);
list_tar_entries(&mut archive, &mut entries, &mut total_size, &mut total_compressed_size)?;
}
_ => return Err("不支持的格式".to_string()),
}
Ok(ArchiveInfo {
format,
path: path.to_string(),
file_count: entries.iter().filter(|e| !e.is_dir).count(),
entries,
total_size,
total_compressed_size,
has_password: false,
})
}
pub fn extract(path: &str, format: ArchiveFormat, options: &ExtractOptions, progress_cb: &dyn Fn(ProgressInfo)) -> Result<TaskResult, String> {
let output_dir = Path::new(&options.output_dir);
std::fs::create_dir_all(output_dir).map_err(|e| format!("创建目录失败: {}", e))?;
let file = File::open(path).map_err(|e| format!("打开文件失败: {}", e))?;
let task_id = uuid::Uuid::new_v4().to_string();
match format {
ArchiveFormat::TarGz => {
let gz = flate2::read::GzDecoder::new(file);
let mut archive = tar::Archive::new(gz);
extract_tar_entries(&mut archive, output_dir, &task_id, &options.selected_entries, progress_cb)?;
}
ArchiveFormat::TarBz2 => {
let bz2 = bzip2::read::BzDecoder::new(file);
let mut archive = tar::Archive::new(bz2);
extract_tar_entries(&mut archive, output_dir, &task_id, &options.selected_entries, progress_cb)?;
}
ArchiveFormat::TarXz => {
let xz = xz2::read::XzDecoder::new(file);
let mut archive = tar::Archive::new(xz);
extract_tar_entries(&mut archive, output_dir, &task_id, &options.selected_entries, progress_cb)?;
}
ArchiveFormat::Tar => {
let mut archive = tar::Archive::new(file);
extract_tar_entries(&mut archive, output_dir, &task_id, &options.selected_entries, progress_cb)?;
}
_ => return Err("不支持的格式".to_string()),
}
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() {
std::fs::create_dir_all(parent).map_err(|e| format!("创建输出目录失败: {}", e))?;
}
let task_id = uuid::Uuid::new_v4().to_string();
let file = File::create(&options.output_path).map_err(|e| format!("创建文件失败: {}", e))?;
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() {
let dir_name = path.file_name().unwrap_or_default().to_string_lossy().to_string();
collect_tar_entries(path, &dir_name, &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;
match options.format {
ArchiveFormat::TarGz => {
let gz = flate2::write::GzEncoder::new(file, flate2::Compression::default());
let mut archive = tar::Builder::new(gz);
add_entries_to_tar(&mut archive, &all_entries, &task_id, total, progress_cb)?;
let gz = archive.into_inner().map_err(|e| format!("完成压缩失败: {}", e))?;
gz.finish().map_err(|e| format!("完成压缩失败: {}", e))?;
}
ArchiveFormat::TarBz2 => {
let bz2 = bzip2::write::BzEncoder::new(file, bzip2::Compression::default());
let mut archive = tar::Builder::new(bz2);
add_entries_to_tar(&mut archive, &all_entries, &task_id, total, progress_cb)?;
let bz2 = archive.into_inner().map_err(|e| format!("完成压缩失败: {}", e))?;
bz2.finish().map_err(|e| format!("完成压缩失败: {}", e))?;
}
ArchiveFormat::TarXz => {
let xz = xz2::write::XzEncoder::new(file, 6);
let mut archive = tar::Builder::new(xz);
add_entries_to_tar(&mut archive, &all_entries, &task_id, total, progress_cb)?;
let xz = archive.into_inner().map_err(|e| format!("完成压缩失败: {}", e))?;
xz.finish().map_err(|e| format!("完成压缩失败: {}", e))?;
}
ArchiveFormat::Tar => {
let mut archive = tar::Builder::new(file);
add_entries_to_tar(&mut archive, &all_entries, &task_id, total, progress_cb)?;
archive.finish().map_err(|e| format!("完成压缩失败: {}", e))?;
}
_ => return Err("不支持的格式".to_string()),
}
Ok(TaskResult {
task_id,
success: true,
message: "压缩完成".to_string(),
output_path: Some(options.output_path.clone()),
})
}
}
fn list_tar_entries<R: std::io::Read>(
archive: &mut tar::Archive<R>,
entries: &mut Vec<ArchiveEntry>,
total_size: &mut u64,
total_compressed_size: &mut u64,
) -> Result<(), String> {
for entry_result in archive.entries().map_err(|e| format!("读取条目失败: {}", e))? {
let entry = entry_result.map_err(|e| format!("读取条目失败: {}", e))?;
let path = entry.path().map_err(|e| format!("读取路径失败: {}", e))?;
let name = path.to_string_lossy().to_string();
let size = entry.size();
let is_dir = entry.header().entry_type().is_dir();
*total_size += size;
*total_compressed_size += size;
entries.push(ArchiveEntry {
name: name.clone(),
path: name,
size,
compressed_size: size,
is_dir,
modified: entry.header().mtime().ok().map(|t| t.to_string()),
is_encrypted: false,
});
}
Ok(())
}
fn extract_tar_entries<R: std::io::Read>(
archive: &mut tar::Archive<R>,
output_dir: &Path,
task_id: &str,
selected_entries: &Option<Vec<String>>,
progress_cb: &dyn Fn(ProgressInfo),
) -> Result<(), String> {
let entries: Vec<tar::Entry<'_, R>> = archive.entries().map_err(|e| format!("读取条目失败: {}", e))?.collect::<Result<Vec<_>, _>>().map_err(|e| format!("读取条目失败: {}", e))?;
let total = if let Some(ref selected) = selected_entries {
entries.iter().filter(|e| {
let name = e.path().map(|p| p.to_string_lossy().to_string()).unwrap_or_default();
selected.contains(&name)
}).count() as u64
} else {
entries.len() as u64
};
let mut processed: u64 = 0;
for mut entry in entries.into_iter() {
let name = entry.path().map_err(|e| format!("读取路径失败: {}", e))?.to_string_lossy().to_string();
if let Some(ref selected) = selected_entries {
if !selected.contains(&name) {
continue;
}
}
let out_path = ensure_path_within_dir(output_dir, &name)?;
if entry.header().entry_type().is_dir() {
std::fs::create_dir_all(&out_path).map_err(|e| format!("创建目录失败: {}", e))?;
} else {
if let Some(parent) = out_path.parent() {
std::fs::create_dir_all(parent).map_err(|e| format!("创建目录失败: {}", e))?;
}
let mut out_file = std::fs::File::create(&out_path).map_err(|e| format!("创建文件失败: {}", e))?;
std::io::copy(&mut entry, &mut out_file).map_err(|e| format!("写入文件失败: {}", e))?;
}
processed += 1;
progress_cb(ProgressInfo {
task_id: task_id.to_string(),
current_file: name,
processed_bytes: processed,
total_bytes: total,
percent: processed as f64 / total.max(1) as f64 * 100.0,
speed: 0.0,
elapsed_secs: 0.0,
remaining_secs: 0.0,
});
}
Ok(())
}
fn collect_tar_entries(
dir: &Path,
prefix: &str,
result: &mut Vec<(std::path::PathBuf, String, bool)>,
) -> Result<(), String> {
result.push((dir.to_path_buf(), prefix.to_string(), true));
let entries = std::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 name = path.file_name().unwrap_or_default().to_string_lossy().to_string();
let archive_path = format!("{}/{}", prefix, name);
if path.is_dir() {
collect_tar_entries(&path, &archive_path, result)?;
} else {
result.push((path, archive_path, false));
}
}
Ok(())
}
fn add_entries_to_tar<W: std::io::Write>(
archive: &mut tar::Builder<W>,
entries: &[(std::path::PathBuf, String, bool)],
task_id: &str,
total: u64,
progress_cb: &dyn Fn(ProgressInfo),
) -> Result<(), String> {
for (i, (path, name, is_dir)) in entries.iter().enumerate() {
if *is_dir {
archive.append_dir(name, path)
.map_err(|e| format!("添加目录失败: {}", e))?;
} else {
let mut f = File::open(path).map_err(|e| format!("打开文件失败: {}", e))?;
archive.append_file(name, &mut f)
.map_err(|e| format!("添加文件失败: {}", e))?;
}
progress_cb(ProgressInfo {
task_id: task_id.to_string(),
current_file: name.clone(),
processed_bytes: i as u64 + 1,
total_bytes: total,
percent: (i as f64 + 1.0) / total as f64 * 100.0,
speed: 0.0,
elapsed_secs: 0.0,
remaining_secs: 0.0,
});
}
Ok(())
}