Files
jellytau/src-tauri/src/thumbnail/worker.rs
T
2026-01-26 22:21:54 +01:00

122 lines
3.4 KiB
Rust

//! 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<Vec<u8>, 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<Vec<u8>, 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();
}
}