jellytau_lib/thumbnail/
worker.rs1use std::time::Duration;
4
5pub struct ThumbnailWorker {
7 client: reqwest::Client,
8}
9
10impl ThumbnailWorker {
11 pub fn new() -> Self {
13 let client = reqwest::Client::builder()
14 .timeout(Duration::from_secs(30))
15 .https_only(true)
16 .build()
17 .expect("Failed to create HTTP client");
18
19 Self { client }
20 }
21
22 pub async fn download(&self, url: &str) -> Result<Vec<u8>, ThumbnailDownloadError> {
24 let response = self
25 .client
26 .get(url)
27 .send()
28 .await
29 .map_err(|e| ThumbnailDownloadError::Network(e.to_string()))?;
30
31 let status = response.status();
32 if !status.is_success() {
33 return Err(ThumbnailDownloadError::Http(status.as_u16()));
34 }
35
36 let bytes = response
37 .bytes()
38 .await
39 .map_err(|e| ThumbnailDownloadError::Network(e.to_string()))?;
40
41 Ok(bytes.to_vec())
42 }
43
44 pub async fn download_with_retry(
46 &self,
47 url: &str,
48 max_retries: u32,
49 ) -> Result<Vec<u8>, ThumbnailDownloadError> {
50 let mut last_error = ThumbnailDownloadError::Network("No attempts made".to_string());
51
52 for attempt in 0..=max_retries {
53 match self.download(url).await {
54 Ok(data) => return Ok(data),
55 Err(e) if e.is_retryable() && attempt < max_retries => {
56 last_error = e;
57 let delay = Duration::from_millis(100 * (1 << attempt));
59 tokio::time::sleep(delay).await;
60 }
61 Err(e) => return Err(e),
62 }
63 }
64
65 Err(last_error)
66 }
67}
68
69impl Default for ThumbnailWorker {
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75#[derive(Debug)]
77pub enum ThumbnailDownloadError {
78 Network(String),
79 Http(u16),
80}
81
82impl ThumbnailDownloadError {
83 pub fn is_retryable(&self) -> bool {
85 match self {
86 ThumbnailDownloadError::Network(_) => true,
87 ThumbnailDownloadError::Http(status) => *status >= 500,
88 }
89 }
90}
91
92impl std::fmt::Display for ThumbnailDownloadError {
93 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94 match self {
95 ThumbnailDownloadError::Network(msg) => write!(f, "Network error: {}", msg),
96 ThumbnailDownloadError::Http(status) => write!(f, "HTTP error: {}", status),
97 }
98 }
99}
100
101impl std::error::Error for ThumbnailDownloadError {}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn test_error_retryable() {
109 assert!(ThumbnailDownloadError::Network("timeout".to_string()).is_retryable());
110 assert!(ThumbnailDownloadError::Http(500).is_retryable());
111 assert!(ThumbnailDownloadError::Http(503).is_retryable());
112 assert!(!ThumbnailDownloadError::Http(404).is_retryable());
113 assert!(!ThumbnailDownloadError::Http(400).is_retryable());
114 }
115
116 #[test]
117 fn test_worker_creation() {
118 let _worker = ThumbnailWorker::new();
119 let _default = ThumbnailWorker::default();
121 }
122}