1use serde::{Deserialize, Serialize};
2use std::collections::HashSet;
3use std::path::PathBuf;
4
5#[derive(Debug, Default, Serialize, Deserialize)]
6pub struct InboxState {
7 pub read: HashSet<String>,
8 pub archived: HashSet<String>,
9}
10
11impl InboxState {
12 pub fn path() -> PathBuf {
14 dirs::data_dir()
15 .unwrap_or_else(|| PathBuf::from("."))
16 .join("thor")
17 .join("state.json")
18 }
19
20 pub async fn load() -> Self {
22 let path = Self::path();
23 if path.exists() {
24 tokio::fs::read_to_string(&path)
25 .await
26 .ok()
27 .and_then(|s| serde_json::from_str(&s).ok())
28 .unwrap_or_default()
29 } else {
30 Self::default()
31 }
32 }
33
34 pub async fn save(&self) -> std::io::Result<()> {
36 let path = Self::path();
37 if let Some(parent) = path.parent() {
38 tokio::fs::create_dir_all(parent).await?;
39 }
40 let json = serde_json::to_string_pretty(self)?;
41 tokio::fs::write(path, json).await
42 }
43
44 pub fn mark_read(&mut self, id: &str) {
46 self.read.insert(id.to_string());
47 }
48
49 pub fn mark_archived(&mut self, id: &str) {
51 self.archived.insert(id.to_string());
52 }
53
54 pub fn is_read(&self, id: &str) -> bool {
56 self.read.contains(id)
57 }
58
59 pub fn is_archived(&self, id: &str) -> bool {
61 self.archived.contains(id)
62 }
63
64 pub fn unread_count(&self, ids: &[String]) -> usize {
66 ids.iter().filter(|id| !self.is_read(id) && !self.is_archived(id)).count()
67 }
68}