//! Thumbnail download worker use std::time::Duration; /// Worker for downloading thumbnails from Jellyfin server pub struct ThumbnailWorker { client: reqwest::Client, } impl ThumbnailWorker { /// Create a new thumbnail worker pub fn new() -> Self { let client = reqwest::Client::builder() .timeout(Duration::from_secs(30)) .build() .expect("Failed to create HTTP client"); Self { client } } /// Download a thumbnail from URL pub async fn download(&self, url: &str) -> Result, ThumbnailDownloadError> { let response = self .client .get(url) .send() .await .map_err(|e| ThumbnailDownloadError::Network(e.to_string()))?; let status = response.status(); if !status.is_success() { return Err(ThumbnailDownloadError::Http(status.as_u16())); } let bytes = response .bytes() .await .map_err(|e| ThumbnailDownloadError::Network(e.to_string()))?; Ok(bytes.to_vec()) } /// Download with retry logic pub async fn download_with_retry( &self, url: &str, max_retries: u32, ) -> Result, ThumbnailDownloadError> { let mut last_error = ThumbnailDownloadError::Network("No attempts made".to_string()); for attempt in 0..=max_retries { match self.download(url).await { Ok(data) => return Ok(data), Err(e) if e.is_retryable() && attempt < max_retries => { last_error = e; // Simple exponential backoff: 100ms, 200ms, 400ms let delay = Duration::from_millis(100 * (1 << attempt)); tokio::time::sleep(delay).await; } Err(e) => return Err(e), } } Err(last_error) } } impl Default for ThumbnailWorker { fn default() -> Self { Self::new() } } /// Errors that can occur during thumbnail download #[derive(Debug)] pub enum ThumbnailDownloadError { Network(String), Http(u16), } impl ThumbnailDownloadError { /// Check if this error is retryable pub fn is_retryable(&self) -> bool { match self { ThumbnailDownloadError::Network(_) => true, ThumbnailDownloadError::Http(status) => *status >= 500, } } } impl std::fmt::Display for ThumbnailDownloadError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ThumbnailDownloadError::Network(msg) => write!(f, "Network error: {}", msg), ThumbnailDownloadError::Http(status) => write!(f, "HTTP error: {}", status), } } } impl std::error::Error for ThumbnailDownloadError {} #[cfg(test)] mod tests { use super::*; #[test] fn test_error_retryable() { assert!(ThumbnailDownloadError::Network("timeout".to_string()).is_retryable()); assert!(ThumbnailDownloadError::Http(500).is_retryable()); assert!(ThumbnailDownloadError::Http(503).is_retryable()); assert!(!ThumbnailDownloadError::Http(404).is_retryable()); assert!(!ThumbnailDownloadError::Http(400).is_retryable()); } #[test] fn test_worker_creation() { let _worker = ThumbnailWorker::new(); // Just verify it doesn't panic let _default = ThumbnailWorker::default(); } }