1use std::collections::HashMap;
2use std::path::PathBuf;
3
4pub struct SnapshotManager {
5 originals: HashMap<PathBuf, Option<String>>,
6}
7
8impl Default for SnapshotManager {
9 fn default() -> Self {
10 Self::new()
11 }
12}
13
14impl SnapshotManager {
15 pub fn new() -> Self {
16 Self {
17 originals: HashMap::new(),
18 }
19 }
20
21 pub fn before_write(&mut self, path: &str) {
22 let path = PathBuf::from(path);
23 if self.originals.contains_key(&path) {
24 return;
25 }
26 let content = std::fs::read_to_string(&path).ok();
27 self.originals.insert(path, content);
28 }
29
30 pub fn list_changes(&self) -> Vec<(String, ChangeKind)> {
31 let mut changes = Vec::new();
32 for (path, original) in &self.originals {
33 let current = std::fs::read_to_string(path).ok();
34 let kind = match (original, ¤t) {
35 (None, Some(_)) => ChangeKind::Created,
36 (Some(_), None) => ChangeKind::Deleted,
37 (Some(old), Some(new)) if old != new => ChangeKind::Modified,
38 (None, None) => continue,
39 _ => continue,
40 };
41 changes.push((path.display().to_string(), kind));
42 }
43 changes.sort_by(|a, b| a.0.cmp(&b.0));
44 changes
45 }
46
47 pub fn restore(&self, path: &str) -> anyhow::Result<String> {
48 let key = PathBuf::from(path);
49 let original = self
50 .originals
51 .get(&key)
52 .ok_or_else(|| anyhow::anyhow!("no snapshot for {}", path))?;
53 match original {
54 None => {
55 if key.exists() {
56 std::fs::remove_file(&key)?;
57 Ok(format!("deleted {} (was created this session)", path))
58 } else {
59 Ok(format!("{} already gone", path))
60 }
61 }
62 Some(content) => {
63 std::fs::write(&key, content)?;
64 Ok(format!("restored {}", path))
65 }
66 }
67 }
68
69 pub fn restore_all(&self) -> anyhow::Result<Vec<String>> {
70 let mut restored = Vec::new();
71 for (path, original) in &self.originals {
72 match original {
73 None => {
74 if path.exists() {
75 std::fs::remove_file(path)?;
76 restored.push(format!("deleted {}", path.display()));
77 }
78 }
79 Some(content) => {
80 std::fs::write(path, content)?;
81 restored.push(format!("restored {}", path.display()));
82 }
83 }
84 }
85 Ok(restored)
86 }
87
88 pub fn file_count(&self) -> usize {
89 self.originals.len()
90 }
91
92 pub fn clear(&mut self) {
93 self.originals.clear();
94 }
95}
96
97pub enum ChangeKind {
98 Created,
99 Modified,
100 Deleted,
101}
102
103impl ChangeKind {
104 pub fn label(&self) -> &'static str {
105 match self {
106 Self::Created => "created",
107 Self::Modified => "modified",
108 Self::Deleted => "deleted",
109 }
110 }
111
112 pub fn icon(&self) -> &'static str {
113 match self {
114 Self::Created => "+",
115 Self::Modified => "~",
116 Self::Deleted => "-",
117 }
118 }
119}