Skip to main content

okc/scanner/
changes.rs

1//! Change detection between file system scans.
2//!
3//! [`ChangeDetector`] compares current and previous file listings to identify
4//! added, modified, deleted, and unchanged files for incremental indexing.
5
6use std::collections::HashSet;
7
8use crate::model::document::FileRecord;
9
10/// Result of change detection between two scans.
11#[derive(Debug)]
12pub struct FileChanges {
13    /// New files not present in previous scan.
14    pub added: Vec<FileRecord>,
15    /// Files with changed size or modification time.
16    pub modified: Vec<FileRecord>,
17    /// Files present in previous scan but not current.
18    pub deleted: Vec<String>,
19    /// Files unchanged since previous scan (paths only).
20    pub unchanged: Vec<String>,
21}
22
23/// Detects file system changes between scans.
24///
25/// Compares current and previous file records by path, using size and
26/// modification time to detect modifications.
27pub struct ChangeDetector;
28
29impl ChangeDetector {
30    /// Detect changes between current and previous file listings.
31    ///
32    /// Files are matched by path. A file is considered modified if either
33    /// its size or modification timestamp differs from the previous scan.
34    pub fn detect(current: &[FileRecord], previous: &[FileRecord]) -> FileChanges {
35        let prev_map: std::collections::HashMap<&str, &FileRecord> =
36            previous.iter().map(|f| (f.path.as_str(), f)).collect();
37
38        let current_paths: HashSet<&str> = current.iter().map(|f| f.path.as_str()).collect();
39
40        let mut added = Vec::new();
41        let mut modified = Vec::new();
42        let mut unchanged = Vec::new();
43
44        for file in current {
45            match prev_map.get(file.path.as_str()) {
46                Some(prev) => {
47                    if prev.modified_at == file.modified_at && prev.size == file.size {
48                        unchanged.push(file.path.clone());
49                    } else {
50                        modified.push(file.clone());
51                    }
52                }
53                None => {
54                    added.push(file.clone());
55                }
56            }
57        }
58
59        let deleted: Vec<String> = previous
60            .iter()
61            .map(|f| f.path.clone())
62            .filter(|p| !current_paths.contains(p.as_str()))
63            .collect();
64
65        FileChanges {
66            added,
67            modified,
68            deleted,
69            unchanged,
70        }
71    }
72}