use std::collections::{BTreeMap, BTreeSet};
use std::path::Path;
use super::{state::Baseline, LeanResult, CONTROL_DIR, STATE_DIR};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ScanEntry {
pub size: u64,
pub mtime_unix: i64,
pub mode: u32,
}
pub fn scan(root: &Path) -> LeanResult<BTreeMap<String, ScanEntry>> {
let mut out = BTreeMap::new();
walk(root, root, &mut out)?;
Ok(out)
}
fn walk(root: &Path, dir: &Path, out: &mut BTreeMap<String, ScanEntry>) -> LeanResult<()> {
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
let name = entry.file_name();
if dir == root {
let n = name.to_string_lossy();
if n == STATE_DIR || n == CONTROL_DIR {
continue;
}
}
let meta = std::fs::symlink_metadata(&path)?;
if meta.file_type().is_symlink() {
continue;
}
if meta.is_dir() {
walk(root, &path, out)?;
} else if meta.is_file() {
let rel = path
.strip_prefix(root)
.expect("walk stays under root")
.to_string_lossy()
.replace('\\', "/");
#[cfg(unix)]
let mode = {
use std::os::unix::fs::MetadataExt;
meta.mode()
};
#[cfg(not(unix))]
let mode = 0o644;
let mtime_unix = meta
.modified()
.ok()
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
.map(|d| d.as_secs() as i64)
.unwrap_or(0);
out.insert(rel, ScanEntry { size: meta.len(), mtime_unix, mode });
}
}
Ok(())
}
#[derive(Debug, Default)]
pub struct Classified {
pub uploads: BTreeSet<String>,
pub deletes: BTreeSet<String>,
pub first_absence: BTreeSet<String>,
}
pub fn is_control_path(path: &str) -> bool {
path == CONTROL_DIR || path.strip_prefix(CONTROL_DIR).map(|r| r.starts_with('/')).unwrap_or(false)
}
pub fn classify(scan: &BTreeMap<String, ScanEntry>, baseline: &Baseline) -> Classified {
let mut c = Classified::default();
for (path, s) in scan {
match baseline.entries.get(path) {
None => {
c.uploads.insert(path.clone());
}
Some(b) => {
if b.size != s.size || b.mtime_unix != s.mtime_unix {
c.uploads.insert(path.clone());
}
}
}
}
for path in baseline.entries.keys() {
if scan.contains_key(path) {
continue;
}
if is_control_path(path) {
continue;
}
if baseline.prev_scan.contains(path) {
c.first_absence.insert(path.clone());
} else {
c.deletes.insert(path.clone());
}
}
c
}