Skip to main content

mimick/api_client/
features.rs

1use serde_json::Value;
2
3use super::ImmichApiClient;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub struct LibraryFeatures {
7    pub trash: bool,
8    pub people: bool,
9    pub smart_search: bool,
10    pub ocr: bool,
11    pub places: bool,
12    pub search: bool,
13    pub tags: bool,
14    pub folders: bool,
15    pub shared_links: bool,
16    pub ratings: bool,
17}
18
19impl Default for LibraryFeatures {
20    fn default() -> Self {
21        Self::from_responses(None, None)
22    }
23}
24
25impl LibraryFeatures {
26    pub fn from_responses(server: Option<&Value>, preferences: Option<&Value>) -> Self {
27        let flag = |value: Option<&Value>, pointer: &str| {
28            value
29                .and_then(|value| value.pointer(pointer))
30                .and_then(Value::as_bool)
31                .unwrap_or(true)
32        };
33        let server_flag = |pointer| flag(server, pointer);
34        let preference = |pointer| flag(preferences, pointer);
35        Self {
36            trash: server_flag("/trash"),
37            people: server_flag("/facialRecognition") && preference("/people/enabled"),
38            smart_search: server_flag("/smartSearch"),
39            ocr: server_flag("/ocr"),
40            places: server_flag("/reverseGeocoding"),
41            search: server_flag("/search"),
42            tags: preference("/tags/enabled"),
43            folders: preference("/folders/enabled"),
44            shared_links: preference("/sharedLinks/enabled"),
45            ratings: preference("/ratings/enabled"),
46        }
47    }
48}
49
50impl ImmichApiClient {
51    pub async fn fetch_library_features(&self) -> LibraryFeatures {
52        let server = self.fetch_json("/api/server/features").await;
53        let preferences = self.fetch_json("/api/users/me/preferences").await;
54        LibraryFeatures::from_responses(server.as_ref(), preferences.as_ref())
55    }
56
57    async fn fetch_json(&self, path: &str) -> Option<Value> {
58        match self.send_json(reqwest::Method::GET, path, None).await {
59            Ok(resp) => resp.json().await.ok(),
60            Err(err) => {
61                log::debug!("Could not read {path}: {err}");
62                None
63            }
64        }
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn missing_responses_enable_everything() {
74        let features = LibraryFeatures::default();
75        assert!(features.trash && features.people && features.tags && features.ratings);
76    }
77
78    #[test]
79    fn server_and_account_flags_turn_features_off() {
80        let server = serde_json::json!({
81            "trash": false,
82            "facialRecognition": true,
83            "smartSearch": false,
84            "ocr": false,
85            "reverseGeocoding": true,
86            "search": true
87        });
88        let preferences = serde_json::json!({
89            "people": { "enabled": false, "sidebarWeb": true },
90            "tags": { "enabled": true, "sidebarWeb": false },
91            "ratings": { "enabled": false }
92        });
93        let features = LibraryFeatures::from_responses(Some(&server), Some(&preferences));
94        assert!(!features.trash);
95        assert!(!features.people);
96        assert!(!features.smart_search);
97        assert!(!features.ocr);
98        assert!(features.places);
99        assert!(features.tags);
100        assert!(features.folders);
101        assert!(!features.ratings);
102    }
103}