bbc_news_cli/
image_cache.rs

1use image::{DynamicImage, ImageReader, Rgba, RgbaImage};
2use std::collections::HashMap;
3use std::io::Cursor;
4use std::sync::{Arc, Mutex};
5
6/// Simple in-memory image cache
7pub struct ImageCache {
8    cache: HashMap<String, DynamicImage>,
9    max_size: usize,
10}
11
12impl ImageCache {
13    pub fn new() -> Self {
14        Self {
15            cache: HashMap::new(),
16            max_size: 50, // Keep up to 50 images in cache
17        }
18    }
19
20    /// Get image from cache or download it
21    pub fn get_or_load(&mut self, url: &str) -> DynamicImage {
22        // Check cache first
23        if let Some(img) = self.cache.get(url) {
24            return img.clone();
25        }
26
27        // Try to download
28        match Self::download_image(url) {
29            Ok(img) => {
30                // Add to cache (simple, no LRU for now)
31                if self.cache.len() >= self.max_size {
32                    // Clear cache if too large (simple strategy)
33                    self.cache.clear();
34                }
35                self.cache.insert(url.to_string(), img.clone());
36                img
37            }
38            Err(_) => {
39                // Return BBC logo placeholder on error
40                Self::create_bbc_logo()
41            }
42        }
43    }
44
45    /// Download image from URL
46    fn download_image(url: &str) -> anyhow::Result<DynamicImage> {
47        // Use BBC-compatible user agent and timeout
48        let client = reqwest::blocking::Client::builder()
49            .user_agent("Mozilla/5.0 (Linux; Android 10; SM-A307G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36")
50            .timeout(std::time::Duration::from_secs(15))
51            .build()?;
52
53        let response = client.get(url).send()?;
54        let bytes = response.bytes()?;
55        let img = ImageReader::new(Cursor::new(bytes))
56            .with_guessed_format()?
57            .decode()?;
58        Ok(img)
59    }
60
61    /// Create BBC logo placeholder (red rectangle with white BBC text effect)
62    pub fn create_bbc_logo() -> DynamicImage {
63        let width = 240;
64        let height = 135;
65
66        let mut img = RgbaImage::new(width, height);
67
68        // BBC red background
69        let bbc_red = Rgba([234, 68, 57, 255]);
70
71        // Fill with red
72        for pixel in img.pixels_mut() {
73            *pixel = bbc_red;
74        }
75
76        // Create simple BBC text pattern (simplified blocks)
77        // This creates a minimalist "BBC" appearance
78        let white = Rgba([255, 255, 255, 255]);
79
80        // Draw simplified "BBC" blocks in center
81        let center_x = width / 2;
82        let center_y = height / 2;
83        let block_width = 15;
84        let block_height = 30;
85        let spacing = 5;
86
87        // B (left)
88        let x1 = center_x - block_width * 2 - spacing * 2;
89        draw_rect(&mut img, x1, center_y - block_height / 2, block_width, block_height, white);
90
91        // B (middle)
92        let x2 = center_x - block_width - spacing;
93        draw_rect(&mut img, x2, center_y - block_height / 2, block_width, block_height, white);
94
95        // C (right)
96        let x3 = center_x + spacing;
97        draw_rect(&mut img, x3, center_y - block_height / 2, block_width, block_height, white);
98
99        DynamicImage::ImageRgba8(img)
100    }
101
102    /// Clear the cache
103    pub fn clear(&mut self) {
104        self.cache.clear();
105    }
106}
107
108// Helper to draw a rectangle
109fn draw_rect(img: &mut RgbaImage, x: u32, y: u32, width: u32, height: u32, color: Rgba<u8>) {
110    for dy in 0..height {
111        for dx in 0..width {
112            let px = x + dx;
113            let py = y + dy;
114            if px < img.width() && py < img.height() {
115                img.put_pixel(px, py, color);
116            }
117        }
118    }
119}
120
121// Global cache instance using lazy_static
122lazy_static::lazy_static! {
123    pub static ref GLOBAL_IMAGE_CACHE: Arc<Mutex<ImageCache>> = Arc::new(Mutex::new(ImageCache::new()));
124}
125
126/// Get image from global cache
127pub fn get_image(url: Option<&str>) -> DynamicImage {
128    match url {
129        Some(url_str) => {
130            let mut cache = GLOBAL_IMAGE_CACHE.lock().unwrap();
131            cache.get_or_load(url_str)
132        }
133        None => {
134            ImageCache::create_bbc_logo()
135        }
136    }
137}