//! 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 { 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) }