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}