After a successful article refresh, all images referenced in article content are downloaded in the background so articles can be read offline. The prefetch only runs when the cache-images setting is enabled and the connection is not metered. Read/unread state changes that fail to reach the server (e.g. when offline) are now persisted to a local queue in ~/.cache/net.jeena.FeedTheMonkey/pending_sync.json. The queue is flushed at the start of the next successful fetch.
65 lines
1.8 KiB
Rust
65 lines
1.8 KiB
Rust
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<PendingAction> {
|
|
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);
|
|
}
|