First working POC
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
//! 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user