use std::path::PathBuf; use crate::api::Api; #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "lowercase")] pub enum Action { Read, Unread, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct PendingAction { pub action: Action, pub id: String, } fn path() -> PathBuf { glib::user_cache_dir() .join("net.jeena.FeedTheMonkey") .join("pending_sync.json") } fn load() -> Vec { let Ok(data) = std::fs::read_to_string(path()) else { return Vec::new() }; serde_json::from_str(&data).unwrap_or_default() } fn save(actions: &[PendingAction]) { let dir = path(); std::fs::create_dir_all(dir.parent().unwrap()).ok(); if let Ok(s) = serde_json::to_string(actions) { std::fs::write(path(), s).ok(); } } /// Queue an action for an article. If the same article already has a pending /// action, it is replaced with the new one (last writer wins). pub fn add(action: Action, id: &str) { let mut actions = load(); actions.retain(|a| a.id != id); actions.push(PendingAction { action, id: id.to_string() }); save(&actions); } /// Send all queued actions to the server. Successfully synced actions are /// removed; failed ones remain in the queue for the next attempt. pub async fn flush(api: &Api, write_token: &str) { let actions = load(); if actions.is_empty() { return; } let mut remaining = Vec::new(); for pending in actions { let result = match pending.action { Action::Read => api.mark_read(write_token, &pending.id).await, Action::Unread => api.mark_unread(write_token, &pending.id).await, }; if result.is_err() { remaining.push(pending); } } save(&remaining); }