om_context/
session.rs

1use serde::{Deserialize, Serialize};
2use sha2::{Digest, Sha256};
3use std::collections::HashMap;
4use std::fs;
5use std::path::PathBuf;
6use std::time::{SystemTime, UNIX_EPOCH};
7
8#[derive(Debug, Serialize, Deserialize)]
9pub struct Session {
10    pub name: String,
11    pub files: HashMap<String, String>,
12    #[serde(skip)]
13    path: PathBuf,
14}
15
16impl Session {
17    pub fn generate_id() -> String {
18        let timestamp = SystemTime::now()
19            .duration_since(UNIX_EPOCH)
20            .unwrap()
21            .as_secs();
22        format!("sess-{}", timestamp)
23    }
24
25    pub fn load(name: &str) -> Result<Self, Box<dyn std::error::Error>> {
26        let sessions_dir = Self::sessions_dir()?;
27        let path = sessions_dir.join(format!("{}.json", name));
28
29        if path.exists() {
30            let content = fs::read_to_string(&path)?;
31            let mut session: Session = serde_json::from_str(&content)?;
32            session.path = path;
33            Ok(session)
34        } else {
35            Ok(Session {
36                name: name.to_string(),
37                files: HashMap::new(),
38                path,
39            })
40        }
41    }
42
43    pub fn save(&self) -> Result<(), Box<dyn std::error::Error>> {
44        let sessions_dir = Self::sessions_dir()?;
45        fs::create_dir_all(&sessions_dir)?;
46
47        let content = serde_json::to_string_pretty(&self)?;
48        fs::write(&self.path, content)?;
49
50        Ok(())
51    }
52
53    pub fn was_read(&self, path: &str, hash: &str) -> bool {
54        self.files.get(path).map(|h| h == hash).unwrap_or(false)
55    }
56
57    pub fn mark_read(&mut self, path: &str, hash: &str) {
58        self.files.insert(path.to_string(), hash.to_string());
59    }
60
61    pub fn compute_hash(content: &[u8]) -> String {
62        let mut hasher = Sha256::new();
63        hasher.update(content);
64        format!("{:x}", hasher.finalize())
65    }
66
67    pub fn clear(name: &str) -> Result<(), Box<dyn std::error::Error>> {
68        let sessions_dir = Self::sessions_dir()?;
69        let path = sessions_dir.join(format!("{}.json", name));
70
71        if path.exists() {
72            fs::remove_file(&path)?;
73        }
74
75        Ok(())
76    }
77
78    fn sessions_dir() -> Result<PathBuf, Box<dyn std::error::Error>> {
79        let home = dirs::home_dir().ok_or("Could not determine home directory")?;
80        Ok(home.join(".om").join("sessions"))
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn test_compute_hash() {
90        let content = b"hello world";
91        let hash = Session::compute_hash(content);
92        assert_eq!(hash.len(), 64);
93    }
94
95    #[test]
96    fn test_was_read() {
97        let mut session = Session {
98            name: "test".to_string(),
99            files: HashMap::new(),
100            path: PathBuf::from("/tmp/test.json"),
101        };
102
103        let hash = "abc123";
104        session.mark_read("file.rs", hash);
105        assert!(session.was_read("file.rs", hash));
106        assert!(!session.was_read("file.rs", "different"));
107        assert!(!session.was_read("other.rs", hash));
108    }
109
110    #[test]
111    fn test_generate_id() {
112        let id = Session::generate_id();
113        assert!(id.starts_with("sess-"));
114    }
115}