Skip to main content

gecol_core/
cache.rs

1use std::{
2    collections::HashMap,
3    hash::{DefaultHasher, Hash, Hasher},
4    path::{Path, PathBuf},
5};
6
7use serde::{Deserialize, Serialize};
8
9use crate::{Error, extract::ExtractionConfig};
10
11/// Allows caching extraction results.
12///
13/// Using cache improves performance a lot, so it is recommended to use. First
14/// time you extract color from the image, it can take a bit longer, since just
15/// opening a high resolution image takes a bit of time. To improve this, the
16/// extracted color is stored in the cache (when used), so the next time you
17/// want to extract color from the same image, it is pretty much instant.
18///
19/// # Usage
20///
21/// Generaly you don't need to use the cache directly, you can use the specific
22/// [`Extractor`](crate::extract::Extractor) methods:
23/// - [`Extractor::extract_cached`](crate::extract::Extractor::extract_cached)
24/// - [`Extractor::extract_cached_with_progress](crate::extract::Extractor::extract_cached_with_progress)
25#[derive(Debug, Clone, Serialize, Deserialize, Default)]
26pub struct Cache {
27    pub entries: HashMap<String, (u8, u8, u8)>,
28}
29
30impl Cache {
31    /// Loads the cache from the default cache file path.
32    ///
33    /// Default cache file path is given by the [`Cache::file`].
34    ///
35    /// If it fails to load or find the cache file, it returns default cache.
36    pub fn load_default() -> Self {
37        Self::load(Self::file())
38    }
39
40    /// Loads the cache from the given file path.
41    ///
42    /// If it fails to load or find the cache file, it returns default cache.
43    pub fn load<P>(file: P) -> Self
44    where
45        P: AsRef<Path>,
46    {
47        match std::fs::read(file) {
48            Ok(bytes) => postcard::from_bytes(&bytes).unwrap_or_default(),
49            Err(_) => Self::default(),
50        }
51    }
52
53    /// Saves the current cache to the default cache file path.
54    ///
55    /// Default cache file path is given by the [`Cache::file`].
56    pub fn save_default(&self) -> Result<(), Error> {
57        self.save(Self::file())
58    }
59
60    /// Saves the current cache to the given file path.
61    pub fn save<P>(&self, file: P) -> Result<(), Error>
62    where
63        P: AsRef<Path>,
64    {
65        let file = file.as_ref();
66        if let Some(parent) = file.parent() {
67            std::fs::create_dir_all(parent)?;
68        }
69        let encoded = postcard::to_allocvec(self)?;
70        std::fs::write(file, encoded)?;
71        Ok(())
72    }
73
74    /// Generates the cache key for the given config and image.
75    ///
76    /// This is done so that any change in config, that would effect the
77    /// extracted color, is detected. It also can detect the image being
78    /// modified.
79    pub fn key<P>(config: &ExtractionConfig, image: P) -> Result<String, Error>
80    where
81        P: AsRef<Path>,
82    {
83        let mut hasher = DefaultHasher::new();
84
85        let image = std::fs::canonicalize(image)?;
86        image.hash(&mut hasher);
87
88        if let Ok(Ok(modifier)) =
89            std::fs::metadata(image).map(|v| v.modified())
90        {
91            modifier.hash(&mut hasher);
92        }
93
94        config.hash(&mut hasher);
95        Ok(format!("{:x}", hasher.finish()))
96    }
97
98    /// Gets the default cache directory.
99    pub fn dir() -> PathBuf {
100        dirs::cache_dir()
101            .unwrap_or_else(|| ".".into())
102            .join("gecol")
103    }
104
105    /// Gets the default cache file path.
106    ///
107    /// It uses the [`Cache::dir`] to get the cache directory, followed by the
108    /// `colors.bin`.
109    pub fn file() -> PathBuf {
110        Self::dir().join("colors.bin")
111    }
112}