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}