Skip to main content

dot/
snapshot.rs

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, &current) {
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}