1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
use super::*;

/// SnapShotDifference 结构体定义了快照之间的差异
/// 包括删除的项、新增的项以及修改的项。每个项都通过其对应的路径进行标识。
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
pub struct SnapShotDifference {
    /// 被删除的文件或目录路径集合
    pub deleted: BTreeSet<String>,
    /// 新增的文件或目录信息映射
    pub added: BTreeMap<String, DirectoryEntry>,
    /// 被修改的文件或目录信息映射
    pub modified: BTreeMap<String, DifferenceEntry>,
}

/// DifferenceEntry 枚举定义了差异条目的类型,可以是文件或目录。
/// 文件类型包含一个 ObjectID,目录类型包含一个嵌套的 SnapShotDifference 结构。
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
pub enum DifferenceEntry {
    /// 文件条目
    File(ObjectID),
    /// 目录条目
    Directory(Box<SnapShotDifference>),
}

/// DifferenceStackType 枚举定义了差异栈的操作类型,包括删除、添加和修改。
#[derive(Copy, Clone, Debug)]
pub enum DifferenceStackType {
    /// 被删除的路径
    Deleted,
    /// 新增的路径及条目信息
    Added,
    /// 被修改的路径及条目信息
    Modified,
}

/// DifferenceStackItem 枚举定义了差异栈的具体项,对应于不同类型的差异操作。
/// 包括被删除的路径、新增的路径及条目信息、被修改的路径及条目信息。
#[derive(Clone, Debug)]
pub enum DifferenceStackItem {
    /// 被删除的路径
    Deleted(PathBuf),
    /// 新增的路径及条目信息
    Added(PathBuf, DirectoryEntry),
    /// 被修改的路径及条目信息
    Modified(PathBuf, DifferenceEntry),
}

impl DirectoryEntry {
    pub fn difference(&self, other: &DirectoryEntry) -> Option<DifferenceEntry> {
        use crate::snapshot::directory::DirectoryEntry::*;
        match (self, other) {
            (File(id), File(id_)) => {
                if id != id_ {
                    Some(DifferenceEntry::File(*id_))
                }
                else {
                    None
                }
            }
            (Directory(_), File(id)) => Some(DifferenceEntry::File(*id)),
            (File(_), Directory(d)) => Some(DifferenceEntry::Directory(Box::new(SnapShotDifference {
                deleted: BTreeSet::new(),
                added: d.root.clone(),
                modified: BTreeMap::new(),
            }))),
            (Directory(d), Directory(d_)) => {
                if d == d_ {
                    None
                }
                else {
                    Some(DifferenceEntry::Directory(Box::new(d.difference(d_))))
                }
            }
        }
    }
}

impl SnapShotDirectory {
    /// Compute the diff between this directory structure and the one
    /// which is currently located at the path.
    pub fn difference(&self, other: &SnapShotDirectory) -> SnapShotDifference {
        let added: BTreeMap<String, DirectoryEntry> = other
            .root
            .iter()
            .filter(|(file_name, _dir_entry)| !self.root.contains_key(*file_name))
            .map(|(fname, dir_entry)| (fname.clone(), dir_entry.clone()))
            .collect();
        let deleted: BTreeSet<String> = self
            .root
            .iter()
            .filter(|(file_name, _dir_entry)| !other.root.contains_key(*file_name))
            .map(|(fname, _dir_entry)| fname.clone())
            .collect();
        let modified: BTreeMap<String, DifferenceEntry> = self
            .root
            .iter()
            .filter_map(|(file_name, dir_entry)| {
                other
                    .root
                    .get(file_name)
                    .and_then(|other_dir_entry| dir_entry.difference(other_dir_entry).map(|diff| (file_name.clone(), diff)))
            })
            .collect();
        SnapShotDifference { added, deleted, modified }
    }
}

impl Display for SnapShotDifference {
    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
        let mut stack: Vec<DifferenceStackItem> = vec![];

        for (path, dir_entry) in self.added.clone() {
            stack.push(DifferenceStackItem::Added(PathBuf::from(path), dir_entry));
        }
        for (path, diff_entry) in self.modified.clone() {
            stack.push(DifferenceStackItem::Modified(PathBuf::from(path), diff_entry));
        }
        for path in self.deleted.clone() {
            stack.push(DifferenceStackItem::Deleted(PathBuf::from(path)));
        }

        let mut diff_paths: BTreeMap<PathBuf, DifferenceStackType> = BTreeMap::new();

        while let Some(diff_stack_item) = stack.pop() {
            match diff_stack_item {
                DifferenceStackItem::Deleted(path) => {
                    diff_paths.insert(path, DifferenceStackType::Deleted);
                }
                DifferenceStackItem::Added(path, dir_entry) => match dir_entry {
                    DirectoryEntry::File(_) => {
                        diff_paths.insert(path, DifferenceStackType::Added);
                    }
                    DirectoryEntry::Directory(dir) => {
                        if dir.root.is_empty() {
                            diff_paths.insert(path, DifferenceStackType::Added);
                        }
                        else {
                            for (dir_name, dir_entry) in dir.root.clone() {
                                stack.push(DifferenceStackItem::Added(path.join(dir_name), dir_entry));
                            }
                        }
                    }
                },
                DifferenceStackItem::Modified(path, diff_entry) => match diff_entry {
                    DifferenceEntry::File(_) => {
                        diff_paths.insert(path, DifferenceStackType::Modified);
                    }
                    DifferenceEntry::Directory(diff) => {
                        for (dir_name, dir_entry) in diff.added.clone() {
                            stack.push(DifferenceStackItem::Added(path.join(dir_name), dir_entry))
                        }
                        for (dir_name, diff_entry) in diff.modified.clone() {
                            stack.push(DifferenceStackItem::Modified(path.join(dir_name), diff_entry))
                        }
                        for dir_name in diff.deleted.clone() {
                            stack.push(DifferenceStackItem::Deleted(path.join(dir_name)))
                        }
                    }
                },
            }
        }

        for (path, diff_item) in diff_paths {
            writeln!(f, "{}", diff_item.character_symbol())?;
            path.to_str().unwrap();
        }
        Ok(())
    }
}

impl DifferenceStackType {
    /// 特征符号
    pub fn character_symbol(&self) -> char {
        match self {
            Self::Deleted => 'D',
            Self::Added => 'A',
            Self::Modified => 'M',
        }
    }
}