在Rust(Tauri)中压缩解压时保留文件修改时间的跨平台方案
解决方案:跨平台保留游戏存档的修改时间(Tauri + Rust + zip)
一、压缩阶段:方案可靠性确认与文件夹递归处理
1. 单文件压缩的可靠性
你当前通过SimpleFileOptions::last_modified_time存储原始修改时间的方案是可靠的,zip规范原生支持存储文件最后修改时间,zip crate的实现完全符合标准。可以优化代码的内存效率:
- 无需将整个文件读入
Vec<u8>,改用流式写入避免大文件占用过多内存:
let mut original_file = File::open(&unit_path)?; let metadata = original_file.metadata()?; let last_modify_time = chrono::DateTime::<Local>::from(metadata.modified()?) .naive_local() .try_into() .unwrap(); zip.start_file( unit_path.file_name() .ok_or(BackupFileError::NonePathError)? .to_str() .ok_or(BackupFileError::NonePathError)?, SimpleFileOptions::default() .compression_method(zip::CompressionMethod::Bzip2) .last_modified_time(last_modify_time), )?; std::io::copy(&mut original_file, &mut zip)?; // 流式写入替代read_to_end
2. 文件夹递归压缩的正确姿势
处理文件夹时,需要递归遍历所有子文件并在zip中保留原目录结构:
use walkdir::WalkDir; // 添加walkdir依赖简化遍历 fn add_dir_to_zip(zip: &mut zip::ZipWriter<File>, dir_path: &Path) -> Result<(), BackupFileError> { for entry in WalkDir::new(dir_path) { let entry = entry?; let path = entry.path(); let relative_path = path.strip_prefix(dir_path)?; if path.is_dir() { // 在zip中创建对应目录 zip.add_directory( relative_path.to_str().ok_or(BackupFileError::NonePathError)?, Default::default() )?; } else { let mut file = File::open(path)?; let metadata = file.metadata()?; let last_modify_time = chrono::DateTime::<Local>::from(metadata.modified()?) .naive_local() .try_into() .unwrap(); zip.start_file( relative_path.to_str().ok_or(BackupFileError::NonePathError)?, SimpleFileOptions::default() .compression_method(zip::CompressionMethod::Bzip2) .last_modified_time(last_modify_time), )?; std::io::copy(&mut file, zip)?; } } Ok(()) }
二、解压阶段:解决Windows权限问题与跨平台时间设置
1. 权限错误的根源与修复
Windows下Permission denied的核心原因:
File::open(&original_path)默认是只读模式,无权限修改文件元数据;- 若文件被其他进程占用(如游戏未关闭),也会触发权限错误。
修改后的解压代码:
use filetime::FileTime; // 添加filetime依赖,跨平台处理文件时间 let option = fs_extra::file::CopyOptions::new().overwrite(true); // 获取zip条目时间信息 let zip_entry = zip.by_name( original_path.file_name().unwrap().to_str().unwrap() ).unwrap(); let last_modified = zip_entry.last_modified().unwrap(); // 转换为跨平台兼容的FileTime let file_time = FileTime::from_last_modification_time(&last_modified); // 先移动文件到最终路径 fs_extra::file::move_file(&original_path, &unit_path, &option)?; // 以可写模式打开文件并设置修改时间 let mut file = std::fs::OpenOptions::new() .write(true) .open(&unit_path)?; filetime::set_file_handle_mtime(&mut file, file_time)?;
关键优化:
- 先完成文件移动,再修改目标路径下的文件元数据,避免临时文件权限问题;
- 用
filetime替代手动时间转换,它原生支持Windows、Linux、macOS的文件时间设置; - 显式开启
write权限确保能修改元数据。
2. 文件夹解压的递归处理
对应压缩逻辑,解压时需先创建目录,再为每个文件恢复修改时间:
fn extract_zip_with_metadata(zip_path: &Path, dest_dir: &Path) -> Result<(), BackupFileError> { let file = File::open(zip_path)?; let mut zip = zip::ZipArchive::new(file)?; for i in 0..zip.len() { let mut entry = zip.by_index(i)?; let entry_path = dest_dir.join(entry.name()); if entry.is_dir() { std::fs::create_dir_all(&entry_path)?; } else { // 创建父目录 if let Some(parent) = entry_path.parent() { std::fs::create_dir_all(parent)?; } // 提取文件 let mut file = File::create(&entry_path)?; std::io::copy(&mut entry, &mut file)?; // 恢复修改时间 let last_modified = entry.last_modified().unwrap(); let file_time = FileTime::from_last_modification_time(&last_modified); filetime::set_file_mtime(&entry_path, file_time)?; } } Ok(()) }
三、跨平台额外注意事项
- 权限继承:Linux和macOS下,zip会存储文件权限位,解压时
zipcrate会自动恢复,无需额外处理; - Tauri配置:确保
tauri.conf.json中配置了必要的文件系统权限:
{ "tauri": { "allowlist": { "fs": { "readFile": true, "writeFile": true, "createDir": true, "copyFile": true, "scope": ["$APPDATA/*", "$HOME/*"] // 按需限定路径范围 } } } }
内容的提问来源于stack exchange,提问作者Sworld
相关产品推荐
相关产品推荐

