122 lines
3.4 KiB
Rust
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();
|
|
}
|
|
}
|