window: prefetch images and queue offline read/unread actions
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.
This commit is contained in:
parent
3f759bce2e
commit
9bed643023
4 changed files with 123 additions and 5 deletions
65
src/pending_actions.rs
Normal file
65
src/pending_actions.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue