Skip to main content

ferrex_model/image/
request.rs

1use std::hash::{Hash, Hasher};
2
3use uuid::Uuid;
4
5use crate::image::ImageSize;
6
7/// Priority hint for unified image loading.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum Priority {
10    /// Image should be fetched immediately because it is on-screen.
11    Visible,
12    /// Image will become visible soon; preload with elevated priority.
13    Preload,
14    /// Low-priority background prefetch.
15    Background,
16}
17
18impl Priority {
19    /// Convert the priority to a queue weight (higher is more urgent).
20    pub fn weight(&self) -> u8 {
21        match self {
22            Priority::Visible => 3,
23            Priority::Preload => 2,
24            Priority::Background => 1,
25        }
26    }
27}
28
29/// Unified image request shared between the player and server components.
30///
31/// Equality and hashing deliberately ignore `priority`. The cache uses
32/// `ImageRequest` as its key and should treat requests for the same media
33/// (including alternate indices) as identical regardless of urgency.
34#[derive(Debug, Clone)]
35pub struct ImageRequest {
36    /// `tmdb_image_variants.id` (UUID) for the selected image.
37    ///
38    /// This is the canonical identifier for images across server and player.
39    pub iid: Uuid,
40    pub size: ImageSize,
41    pub priority: Priority,
42}
43
44impl Hash for ImageRequest {
45    fn hash<H: Hasher>(&self, state: &mut H) {
46        self.iid.hash(state);
47        self.size.hash(state);
48    }
49}
50
51impl PartialEq for ImageRequest {
52    fn eq(&self, other: &Self) -> bool {
53        self.iid == other.iid && self.size == other.size
54    }
55}
56
57impl Eq for ImageRequest {}
58
59impl ImageRequest {
60    /// Create a new image request for a specific TMDB image variant id.
61    pub fn new(iid: Uuid, size: ImageSize) -> Self {
62        Self {
63            iid,
64            size,
65            priority: Priority::Visible,
66        }
67    }
68
69    pub fn with_priority(mut self, priority: Priority) -> Self {
70        self.priority = priority;
71        self
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::{ImageRequest, ImageSize, Priority};
78    use crate::image::{BackdropSize, PosterSize};
79    use std::collections::hash_map::DefaultHasher;
80    use std::hash::{Hash, Hasher};
81    use uuid::Uuid;
82
83    fn hash_of(request: &ImageRequest) -> u64 {
84        let mut hasher = DefaultHasher::new();
85        request.hash(&mut hasher);
86        hasher.finish()
87    }
88
89    #[test]
90    fn requests_ignore_priority_for_identity() {
91        let iid = Uuid::now_v7();
92        let base = ImageRequest::new(iid, ImageSize::Poster(PosterSize::W185));
93        let visible = base.clone().with_priority(Priority::Visible);
94        let preload = base.clone().with_priority(Priority::Preload);
95
96        assert_eq!(visible, preload);
97        assert_eq!(hash_of(&visible), hash_of(&preload));
98    }
99
100    #[test]
101    fn requests_hash_by_iid_and_size() {
102        let iid = Uuid::now_v7();
103        let poster =
104            ImageRequest::new(iid, ImageSize::Poster(PosterSize::W185));
105        let backdrop =
106            ImageRequest::new(iid, ImageSize::Backdrop(BackdropSize::W780));
107
108        assert_ne!(hash_of(&poster), hash_of(&backdrop));
109        assert!(matches!(poster.size, ImageSize::Poster(PosterSize::W185)));
110    }
111}