Skip to main content

wallswitch/core/
state.rs

1use crate::{
2    AtomicWriteExt as _, Dimension, Environment, WallSwitchError, WallSwitchResult, get_config_path,
3};
4use serde::{Deserialize, Serialize};
5use std::{
6    collections::HashMap,
7    fs,
8    io::{BufWriter, Write},
9    path::PathBuf,
10};
11
12const MAX_ITENS: usize = 10_000;
13
14/// Represents cached metadata of an image file to prevent redundant hashing and dimension probing.
15#[derive(Debug, Serialize, Deserialize, Default, Clone)]
16pub struct CacheEntry {
17    pub size: u64,
18    pub mtime: u64,
19    pub hash: String,
20    #[serde(default)]
21    pub dimension: Option<Dimension>,
22}
23
24/// Manages the persistence of the wallpaper history loop and the smart file cache.
25#[derive(Debug, Serialize, Deserialize, Default)]
26pub struct State {
27    pub history: Vec<PathBuf>,
28    pub hashes: HashMap<PathBuf, CacheEntry>,
29}
30
31impl State {
32    /// Loads the persistent state file from the system configuration path.
33    pub fn load(env: &Environment) -> Self {
34        if let Ok(path) = Self::get_path(env)
35            && let Ok(content) = fs::read_to_string(&path)
36            && let Ok(state) = serde_json::from_str(&content)
37        {
38            return state;
39        }
40        State::default()
41    }
42
43    /// Atomically persists the history loops and image metadata cache to disk.
44    ///
45    /// # Errors
46    ///
47    /// Returns a [`WallSwitchResult`] if writing the file fails.
48    pub fn save(&mut self, env: &Environment) -> WallSwitchResult<()> {
49        if self.history.len() > MAX_ITENS {
50            let start = self.history.len() - MAX_ITENS;
51            self.history = self.history[start..].to_vec();
52        }
53
54        let path = Self::get_path(env)?;
55
56        path.atomic_write(|temp_path| {
57            let file =
58                fs::File::create(temp_path).map_err(|io_error| WallSwitchError::IOError {
59                    path: temp_path.to_path_buf(),
60                    io_error,
61                })?;
62            let mut writer = BufWriter::new(file);
63            serde_json::to_writer_pretty(&mut writer, self)?;
64            writer.flush()?;
65            Ok(())
66        })?;
67
68        Ok(())
69    }
70
71    /// Removes untracked paths that no longer exist on the current filesystem from the cache.
72    pub fn garbage_collect(&mut self) {
73        self.hashes.retain(|path, _| path.exists());
74        self.hashes.shrink_to_fit();
75    }
76
77    /// Resolves the absolute path to the application state JSON file.
78    fn get_path(env: &Environment) -> WallSwitchResult<PathBuf> {
79        let mut path = get_config_path(env)?;
80        path.set_file_name("wallswitch-state.json");
81        Ok(path)
82    }
83}