feat: Tauri commands, concurrent refresh, download manager

Refresh fetches 8 channel feeds concurrently and mirrors thumbnails to
disk so the offline feed still renders. Downloads are capped at 2 and
shell out to yt-dlp with an absolute binary path, since GUI apps do not
inherit a login shell PATH.
This commit is contained in:
vincent
2026-08-29 02:28:26 +02:00
parent b724e9d56f
commit a30423e4b5
9 changed files with 651 additions and 9 deletions
+55
View File
@@ -0,0 +1,55 @@
//! Local thumbnail cache.
//!
//! Feed thumbnails are remote `ytimg.com` URLs. Offline — the whole point of
//! this app — those fail to load and the feed becomes a wall of broken images.
//! So every thumbnail we learn about gets mirrored to disk.
use std::path::{Path, PathBuf};
pub fn cache_dir(app_data: &Path) -> PathBuf {
app_data.join("thumbs")
}
/// Downloads one thumbnail if it isn't already cached. Returns its path.
pub async fn cache_one(
client: &reqwest::Client,
video_id: &str,
url: &str,
dir: &Path,
) -> Result<PathBuf, String> {
let dest = dir.join(format!("{video_id}.jpg"));
if dest.exists() {
return Ok(dest);
}
tokio::fs::create_dir_all(dir)
.await
.map_err(|e| format!("Cannot create thumbnail cache dir: {e}"))?;
let resp = client
.get(url)
.send()
.await
.map_err(|e| format!("Thumbnail request failed: {e}"))?;
if !resp.status().is_success() {
return Err(format!("Thumbnail returned HTTP {}", resp.status()));
}
let bytes = resp
.bytes()
.await
.map_err(|e| format!("Cannot read thumbnail body: {e}"))?;
// Write to a temp name then rename, so an interrupted download never leaves
// a half-written JPEG that later looks cached.
let tmp = dir.join(format!("{video_id}.jpg.part"));
tokio::fs::write(&tmp, &bytes)
.await
.map_err(|e| format!("Cannot write thumbnail: {e}"))?;
tokio::fs::rename(&tmp, &dest)
.await
.map_err(|e| format!("Cannot finalize thumbnail: {e}"))?;
Ok(dest)
}