Files
l-s/src/meta/tree.rs
2025-11-21 00:52:15 +08:00

139 lines
4.1 KiB
Rust

use std::collections::BTreeMap;
use std::fs;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use super::file::{calc_xxh128, FileMeta};
use crate::constants::META_VERSION;
use crate::utils::{basename, should_skip_dir, should_skip_file};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DirSnapshot {
pub dir_name: String,
pub dirs: Vec<DirSnapshot>,
pub files: Vec<FileMeta>,
#[serde(skip_serializing_if = "Option::is_none")]
pub v: Option<String>,
}
impl DirSnapshot {
pub fn build_root(path: &Path) -> Result<Self> {
let mut node = Self::build_node(path)?;
node.v = Some(META_VERSION.to_string());
Ok(node)
}
pub fn from_reader<R: std::io::Read>(reader: R) -> Result<Self> {
Ok(serde_json::from_reader(reader)?)
}
fn build_node(path: &Path) -> Result<Self> {
let dir_name = path
.file_name()
.map(basename)
.unwrap_or_else(|| path.to_string_lossy().to_string());
let mut dirs = Vec::new();
let mut files = Vec::new();
let mut entries = fs::read_dir(path)
.with_context(|| format!("无法遍历目录: {}", path.display()))?
.collect::<Result<Vec<_>, _>>()
.with_context(|| format!("读取目录失败: {}", path.display()))?;
entries.sort_by(|a, b| a.file_name().cmp(&b.file_name()));
for entry in entries {
let file_name = entry.file_name();
let name = file_name.to_string_lossy().to_string();
let full_path = entry.path();
let file_type = entry
.file_type()
.with_context(|| format!("无法读取类型: {}", full_path.display()))?;
if file_type.is_dir() {
if should_skip_dir(&name) {
continue;
}
println!("目录: {}", full_path.display());
dirs.push(Self::build_node(&full_path)?);
continue;
}
if should_skip_file(&name) {
continue;
}
let meta = FileMeta::from_path(&full_path)?;
println!("文件: {} {}", meta.friendly_size, full_path.display());
files.push(meta);
}
Ok(Self {
dir_name,
dirs,
files,
v: None,
})
}
pub fn collect_file_map(&self, root: &Path) -> BTreeMap<PathBuf, FileMeta> {
let mut map = BTreeMap::new();
self.collect_into(root.to_path_buf(), &mut map);
map
}
fn collect_into(&self, current: PathBuf, map: &mut BTreeMap<PathBuf, FileMeta>) {
for file in &self.files {
map.insert(current.join(&file.basename), file.clone());
}
for dir in &self.dirs {
let next = current.join(&dir.dir_name);
dir.collect_into(next, map);
}
}
}
pub fn scan_dir_xxh128(path: &Path) -> Result<BTreeMap<PathBuf, String>> {
let mut map = BTreeMap::new();
walk_dir(path, &mut map)?;
Ok(map)
}
fn walk_dir(path: &Path, map: &mut BTreeMap<PathBuf, String>) -> Result<()> {
let mut entries = fs::read_dir(path)
.with_context(|| format!("无法遍历目录: {}", path.display()))?
.collect::<Result<Vec<_>, _>>()
.with_context(|| format!("读取目录失败: {}", path.display()))?;
entries.sort_by(|a, b| a.file_name().cmp(&b.file_name()));
for entry in entries {
let file_name = entry.file_name();
let name = file_name.to_string_lossy().to_string();
let full_path = entry.path();
let file_type = entry
.file_type()
.with_context(|| format!("无法读取类型: {}", full_path.display()))?;
if file_type.is_dir() {
if should_skip_dir(&name) {
continue;
}
walk_dir(&full_path, map)?;
continue;
}
if should_skip_file(&name) {
continue;
}
let hash = calc_xxh128(&full_path)?;
map.insert(full_path, hash);
}
Ok(())
}