thor-notify 0.2.1

Notification schema and inbox management for Thor
Documentation
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::path::PathBuf;

#[derive(Debug, Default, Serialize, Deserialize)]
pub struct InboxState {
    pub read: HashSet<String>,
    pub archived: HashSet<String>,
}

impl InboxState {
    /// Get state file path
    pub fn path() -> PathBuf {
        dirs::data_dir()
            .unwrap_or_else(|| PathBuf::from("."))
            .join("thor")
            .join("state.json")
    }

    /// Load state from disk
    pub async fn load() -> Self {
        let path = Self::path();
        if path.exists() {
            tokio::fs::read_to_string(&path)
                .await
                .ok()
                .and_then(|s| serde_json::from_str(&s).ok())
                .unwrap_or_default()
        } else {
            Self::default()
        }
    }

    /// Save state to disk
    pub async fn save(&self) -> std::io::Result<()> {
        let path = Self::path();
        if let Some(parent) = path.parent() {
            tokio::fs::create_dir_all(parent).await?;
        }
        let json = serde_json::to_string_pretty(self)?;
        tokio::fs::write(path, json).await
    }

    /// Mark notification as read
    pub fn mark_read(&mut self, id: &str) {
        self.read.insert(id.to_string());
    }

    /// Mark notification as archived
    pub fn mark_archived(&mut self, id: &str) {
        self.archived.insert(id.to_string());
    }

    /// Check if notification is read
    pub fn is_read(&self, id: &str) -> bool {
        self.read.contains(id)
    }

    /// Check if notification is archived
    pub fn is_archived(&self, id: &str) -> bool {
        self.archived.contains(id)
    }

    /// Get unread count from a list of notification IDs
    pub fn unread_count(&self, ids: &[String]) -> usize {
        ids.iter().filter(|id| !self.is_read(id) && !self.is_archived(id)).count()
    }
}