feat: [#1932] l-s v0.5.1 原子写入保护及文档更新

This commit is contained in:
2026-05-08 00:18:30 +08:00
parent e821dcab14
commit 30186c23fb
8 changed files with 193 additions and 95 deletions
+44 -6
View File
@@ -4,10 +4,10 @@ mod head_hash;
mod meta;
mod utils;
use std::fs::{self, File};
use std::fs::{self, File, OpenOptions};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::time::Instant;
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use anyhow::{Context, Result};
use clap::Parser;
@@ -57,7 +57,7 @@ fn process_file(path: &Path) -> Result<()> {
tracker.finish("处理完成");
let json = meta.to_pretty_json()?;
println!("{}", json);
fs::write(&save_path, json)?;
write_atomic(&save_path, &json)?;
return Ok(());
}
@@ -98,9 +98,7 @@ fn process_dir(path: &Path) -> Result<()> {
if !meta_path.exists() {
let snapshot = DirSnapshot::build_root(path)?;
let json = serde_json::to_string_pretty(&snapshot)?;
let mut file = File::create(&meta_path)
.with_context(|| format!("无法写入: {}", meta_path.display()))?;
file.write_all(json.as_bytes())?;
write_atomic(&meta_path, &json)?;
return Ok(());
}
@@ -151,3 +149,43 @@ fn process_dir(path: &Path) -> Result<()> {
Ok(())
}
fn write_atomic(path: &Path, contents: &str) -> Result<()> {
let parent = path
.parent()
.map(Path::to_path_buf)
.unwrap_or_else(|| PathBuf::from("."));
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_nanos())
.unwrap_or(0);
let tmp_path = parent.join(format!(".l-s-tmp-{}-{nanos}", std::process::id()));
let result = (|| -> Result<()> {
let mut file = OpenOptions::new()
.write(true)
.create_new(true)
.open(&tmp_path)
.with_context(|| format!("无法创建临时文件: {}", tmp_path.display()))?;
file.write_all(contents.as_bytes())
.with_context(|| format!("无法写入临时文件: {}", tmp_path.display()))?;
file.sync_all()
.with_context(|| format!("无法同步临时文件: {}", tmp_path.display()))?;
drop(file);
fs::rename(&tmp_path, path).with_context(|| {
format!(
"无法将临时文件重命名为目标文件: {} -> {}",
tmp_path.display(),
path.display()
)
})?;
Ok(())
})();
if result.is_err() {
let _ = fs::remove_file(&tmp_path);
}
result
}
+10 -2
View File
@@ -32,7 +32,11 @@ pub struct FileMeta {
}
impl FileMeta {
pub fn from_path_with_callback<F1, F2>(path: &Path, mut on_bytes_read: F1, mut on_iop: F2) -> Result<Self>
pub fn from_path_with_callback<F1, F2>(
path: &Path,
mut on_bytes_read: F1,
mut on_iop: F2,
) -> Result<Self>
where
F1: FnMut(u64),
F2: FnMut(),
@@ -120,7 +124,11 @@ impl FileMeta {
}
}
pub fn calc_xxh128_with_callback<F1, F2>(path: &Path, mut on_bytes_read: F1, mut on_iop: F2) -> Result<String>
pub fn calc_xxh128_with_callback<F1, F2>(
path: &Path,
mut on_bytes_read: F1,
mut on_iop: F2,
) -> Result<String>
where
F1: FnMut(u64),
F2: FnMut(),
+13 -7
View File
@@ -9,11 +9,11 @@ use crate::utils::friendly_size;
/// 进度跟踪器,封装进度条和 IO 统计信息
pub struct ProgressTracker {
multi: Option<MultiProgress>,
file_progress_bar: Option<ProgressBar>, // 文件数量进度条
current_file_bar: Option<ProgressBar>, // 当前文件进度条
file_progress_bar: Option<ProgressBar>, // 文件数量进度条
current_file_bar: Option<ProgressBar>, // 当前文件进度条
bytes_read: Arc<AtomicU64>,
current_file_bytes: Arc<AtomicU64>, // 当前文件已读字节数
current_file_size: Arc<AtomicU64>, // 当前文件总大小
current_file_bytes: Arc<AtomicU64>, // 当前文件已读字节数
current_file_size: Arc<AtomicU64>, // 当前文件总大小
iops: Arc<AtomicU64>,
start_time: Instant,
last_update: Arc<AtomicU64>,
@@ -44,7 +44,7 @@ impl ProgressTracker {
.unwrap()
.progress_chars("=>-"),
);
current_pb.set_length(0); // 设置为0长度来隐藏
current_pb.set_length(0); // 设置为0长度来隐藏
(Some(multi), Some(file_pb), Some(current_pb))
} else {
@@ -163,7 +163,10 @@ impl ProgressTracker {
String::new()
};
pb.set_message(format!("IO速度: {}/s | IOPS: {:.0} | {}", speed_str, iops, eta_str));
pb.set_message(format!(
"IO速度: {}/s | IOPS: {:.0} | {}",
speed_str, iops, eta_str
));
}
}
}
@@ -246,7 +249,10 @@ impl ProgressTracker {
String::new()
};
pb.set_message(format!("IO速度: {}/s | IOPS: {:.0} | {}", speed_str, iops_value, eta_str));
pb.set_message(format!(
"IO速度: {}/s | IOPS: {:.0} | {}",
speed_str, iops_value, eta_str
));
}
}
+10 -9
View File
@@ -83,9 +83,7 @@ impl DirSnapshot {
}
// 获取文件大小并开始跟踪
let file_size = entry.metadata()
.map(|m| m.len())
.unwrap_or(0);
let file_size = entry.metadata().map(|m| m.len()).unwrap_or(0);
tracker.start_file(file_size, &name);
let on_bytes = tracker.bytes_callback();
@@ -124,9 +122,9 @@ impl DirSnapshot {
/// 校验通过则返回已有的 DirSnapshot,否则返回 Err 终止流程。
fn verify_and_load(path: &Path, tracker: &ProgressTracker) -> Result<Self> {
let meta_path = path.join("meta.json");
let meta_file = File::open(&meta_path)
.with_context(|| format!("无法读取: {}", meta_path.display()))?;
let snapshot: Self = serde_json::from_reader(meta_file)
let meta_file =
File::open(&meta_path).with_context(|| format!("无法读取: {}", meta_path.display()))?;
let mut snapshot: Self = serde_json::from_reader(meta_file)
.with_context(|| format!("无法解析: {}", meta_path.display()))?;
let mut stored = snapshot.collect_file_map(path);
@@ -162,6 +160,11 @@ impl DirSnapshot {
} else {
eprintln!("{msg}");
}
snapshot.dir_name = path
.file_name()
.map(basename)
.unwrap_or_else(|| path.to_string_lossy().to_string());
snapshot.v = None;
Ok(snapshot)
}
}
@@ -252,9 +255,7 @@ fn walk_dir_with_progress(
}
// 获取文件大小并开始跟踪
let file_size = entry.metadata()
.map(|m| m.len())
.unwrap_or(0);
let file_size = entry.metadata().map(|m| m.len()).unwrap_or(0);
tracker.start_file(file_size, &name);
let on_bytes = tracker.bytes_callback();
+7 -1
View File
@@ -44,6 +44,7 @@ pub fn should_skip_file(name: &str) -> bool {
.any(|item| item.eq_ignore_ascii_case(name))
|| name.starts_with("._")
|| name.starts_with("Thumb_")
|| name.starts_with(".l-s-tmp-")
}
pub fn hex_upper(bytes: impl AsRef<[u8]>) -> String {
@@ -56,7 +57,7 @@ pub fn hex_upper(bytes: impl AsRef<[u8]>) -> String {
#[cfg(test)]
mod tests {
use super::friendly_size;
use super::{friendly_size, should_skip_file};
#[test]
fn friendly_size_formats_units() {
@@ -65,4 +66,9 @@ mod tests {
assert_eq!(friendly_size(1024), "1.00KB");
assert_eq!(friendly_size(1024 * 1024), "1.00MB");
}
#[test]
fn should_skip_atomic_write_temp_files() {
assert!(should_skip_file(".l-s-tmp-123-456"));
}
}