use std::path::Path;
use crate::archive::types::*;
pub struct RarEngine;
impl RarEngine {
pub fn list(path: &str) -> Result<ArchiveInfo, String> {
let archive = unrar_ng::Archive::new(path);
let open_archive = archive
.open_for_listing()
.map_err(|e| format!("打开RAR失败: {}", e))?;
let mut entries = Vec::new();
let mut total_size = 0u64;
let mut total_compressed_size = 0u64;
let mut has_password = false;
for entry_result in open_archive {
let entry = entry_result.map_err(|e| format!("读取条目失败: {}", e))?;
let is_dir = entry.is_directory();
let size = entry.unpacked_size;
let compressed_size = 0u64;
let is_encrypted = entry.is_encrypted();
if is_encrypted {
has_password = true;
}
total_size += size;
total_compressed_size += compressed_size;
let filename = entry.filename.to_string_lossy().to_string();
entries.push(ArchiveEntry {
name: filename.clone(),
path: filename,
size,
compressed_size,
is_dir,
modified: None,
is_encrypted,
});
}
Ok(ArchiveInfo {
format: ArchiveFormat::Rar,
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);
std::fs::create_dir_all(output_dir).map_err(|e| format!("创建目录失败: {}", e))?;
let task_id = uuid::Uuid::new_v4().to_string();
let file_count = {
let list_archive = unrar_ng::Archive::new(path);
let entries: Vec<_> = list_archive.open_for_listing()
.map_err(|e| format!("列出条目失败: {}", e))?
.collect::<Result<Vec<_>, _>>()
.map_err(|e| format!("读取条目失败: {}", e))?;
let all_files: Vec<_> = entries.into_iter().filter(|e| !e.is_directory()).collect();
if let Some(ref selected) = options.selected_entries {
all_files.into_iter().filter(|e| selected.contains(&e.filename.to_string_lossy().to_string())).count()
} else {
all_files.len()
}
};
let archive = if let Some(ref password) = options.password {
unrar_ng::Archive::with_password(path, password.as_str())
} else {
unrar_ng::Archive::new(path)
};
let mut open_archive = archive.open_for_processing()
.map_err(|e| format!("打开RAR失败: {}", e))?;
let mut processed: usize = 0;
loop {
let next = open_archive.read_header()
.map_err(|e| format!("读取头部失败: {}", e))?;
match next {
Some(before_file) => {
let filename = before_file.entry().filename.to_string_lossy().to_string();
let is_dir = before_file.entry().is_directory();
if is_dir {
open_archive = before_file.skip().map_err(|e| format!("跳过目录失败: {}", e))?;
continue;
}
if let Some(ref selected) = options.selected_entries {
if !selected.contains(&filename) {
open_archive = before_file.skip().map_err(|e| format!("跳过条目失败: {}", e))?;
continue;
}
}
open_archive = before_file.extract_with_base(&options.output_dir)
.map_err(|e| format!("解压 {} 失败: {}", filename, e))?;
processed += 1;
progress_cb(ProgressInfo {
task_id: task_id.clone(),
current_file: filename,
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,
});
}
None => break, }
}
Ok(TaskResult {
task_id,
success: true,
message: "解压完成".to_string(),
output_path: Some(options.output_dir.clone()),
})
}
pub fn find_winrar() -> RarSupport {
let common_paths = [
r"C:\Program Files\WinRAR\rar.exe",
r"C:\Program Files (x86)\WinRAR\rar.exe",
r"C:\Program Files\WinRAR\WinRAR.exe",
r"C:\Program Files (x86)\WinRAR\WinRAR.exe",
];
let winrar_path = common_paths.iter().find(|p| Path::new(p).exists());
RarSupport {
can_decompress: true, can_compress: winrar_path.is_some(),
winrar_path: winrar_path.map(|p| p.to_string()),
}
}
pub fn compress_with_winrar(
files: &[String],
options: &CompressOptions,
winrar_path: &str,
progress_cb: &dyn Fn(ProgressInfo),
) -> Result<TaskResult, String> {
let task_id = uuid::Uuid::new_v4().to_string();
let mut cmd = std::process::Command::new(winrar_path);
cmd.arg("a");
let level = match options.compression_level {
CompressionLevel::Store => "-m0",
CompressionLevel::Fastest => "-m1",
CompressionLevel::Fast => "-m2",
CompressionLevel::Normal => "-m3",
CompressionLevel::Good => "-m4",
CompressionLevel::Best => "-m5",
};
cmd.arg(level);
if let Some(ref password) = options.password {
cmd.arg(format!("-p{}", password));
}
if let Some(volume_size) = options.split_volume {
let size_str = if volume_size >= 1_073_741_824 {
format!("{}g", volume_size / 1_073_741_824)
} else if volume_size >= 1_048_576 {
format!("{}m", volume_size / 1_048_576)
} else if volume_size >= 1024 {
format!("{}k", volume_size / 1024)
} else {
format!("{}b", volume_size)
};
cmd.arg(format!("-v{}", size_str));
}
cmd.arg(&options.output_path);
for file in files {
cmd.arg(file);
}
progress_cb(ProgressInfo {
task_id: task_id.clone(),
current_file: "正在压缩...".to_string(),
processed_bytes: 0,
total_bytes: 1,
percent: 0.0,
speed: 0.0,
elapsed_secs: 0.0,
remaining_secs: 0.0,
});
let output = cmd
.output()
.map_err(|e| format!("执行WinRAR失败: {}", e))?;
progress_cb(ProgressInfo {
task_id: task_id.clone(),
current_file: "完成".to_string(),
processed_bytes: 1,
total_bytes: 1,
percent: 100.0,
speed: 0.0,
elapsed_secs: 0.0,
remaining_secs: 0.0,
});
if output.status.success() {
Ok(TaskResult {
task_id,
success: true,
message: "压缩完成".to_string(),
output_path: Some(options.output_path.clone()),
})
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
Err(format!("WinRAR 压缩失败: {}", stderr))
}
}
}