use std::fs; use std::path::Path; use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; use super::file::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, pub files: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub v: Option, } impl DirSnapshot { pub fn build_root(path: &Path) -> Result { let mut node = Self::build_node(path)?; node.v = Some(META_VERSION.to_string()); Ok(node) } fn build_node(path: &Path) -> Result { 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::, _>>() .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, }) } }