quickcfg/
state.rs

1//! Model for state file.
2
3use crate::config::Config;
4use crate::Timestamp;
5use anyhow::Error;
6use fxhash::FxHasher64;
7use serde::{Deserialize, Serialize};
8use std::collections::BTreeMap;
9use std::hash::{Hash, Hasher};
10
11#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)]
12#[serde(deny_unknown_fields)]
13pub struct Hashed {
14    /// The last calculated hash.
15    pub hash: u64,
16    /// When it was last updated.
17    pub updated: Timestamp,
18}
19
20/// The way the state is serialized.
21#[derive(Deserialize, Serialize, Default, Debug, PartialEq, Eq)]
22#[serde(deny_unknown_fields)]
23pub struct DiskState {
24    /// Last time git was updated.
25    #[serde(default)]
26    pub last_update: BTreeMap<String, Timestamp>,
27    /// Things that should only happen once.
28    #[serde(default)]
29    pub once: BTreeMap<String, Timestamp>,
30    #[serde(default)]
31    pub hashes: BTreeMap<String, Hashed>,
32}
33
34impl DiskState {
35    /// Convert into a state.
36    pub fn into_state(self, config: &Config, now: Timestamp) -> State<'_> {
37        State {
38            dirty: false,
39            last_update: self.last_update,
40            once: self.once,
41            hashes: self.hashes,
42            config,
43            now,
44        }
45    }
46}
47
48/// State model.
49/// This keeps track of any changes with the dirty flag, which is an indication whether it should
50/// be serialized or not.
51#[derive(Debug, PartialEq, Eq)]
52pub struct State<'a> {
53    pub dirty: bool,
54    /// Last time git was updated.
55    pub last_update: BTreeMap<String, Timestamp>,
56    /// Things that should only happen once.
57    pub once: BTreeMap<String, Timestamp>,
58    /// Things that have been tested against a hash.
59    pub hashes: BTreeMap<String, Hashed>,
60    /// The current configuration.
61    pub config: &'a Config,
62    /// Current timestamp.
63    pub now: Timestamp,
64}
65
66impl<'a> State<'a> {
67    pub fn new(config: &'a Config, now: Timestamp) -> Self {
68        State {
69            dirty: Default::default(),
70            last_update: Default::default(),
71            once: Default::default(),
72            hashes: Default::default(),
73            config,
74            now,
75        }
76    }
77
78    /// Get the last update timestamp for the given thing named `name`.
79    pub fn last_update<'time>(&'time self, name: &str) -> Option<&'time Timestamp> {
80        self.last_update.get(name)
81    }
82
83    /// Touch the thing with the given name.
84    pub fn touch(&mut self, name: &str) {
85        self.dirty = true;
86        self.last_update.insert(name.to_string(), Timestamp::now());
87    }
88
89    /// Check if the given ID has run once.
90    pub fn has_run_once(&self, id: &str) -> bool {
91        self.once.contains_key(id)
92    }
93
94    /// Mark that something has happened once.
95    pub fn touch_once(&mut self, id: &str) {
96        self.dirty = true;
97        self.once.insert(id.to_string(), Timestamp::now());
98    }
99
100    /// Touch the hashed item.
101    pub fn is_hash_fresh<H: Hash>(&self, id: &str, hash: H) -> Result<bool, Error> {
102        let hashed = match self.hashes.get(id) {
103            Some(hashed) => hashed,
104            None => return Ok(false),
105        };
106
107        let mut state = FxHasher64::default();
108        hash.hash(&mut state);
109
110        if hashed.hash != state.finish() {
111            return Ok(false);
112        }
113
114        let age = self.now.duration_since(hashed.updated)?;
115        Ok(age < self.config.package_refresh)
116    }
117
118    /// Touch the hashed item.
119    pub fn touch_hash<H: Hash>(&mut self, id: &str, hash: H) -> Result<(), Error> {
120        let mut state = FxHasher64::default();
121        hash.hash(&mut state);
122
123        self.dirty = true;
124
125        self.hashes.insert(
126            id.to_string(),
127            Hashed {
128                hash: state.finish(),
129                updated: Timestamp::now(),
130            },
131        );
132
133        Ok(())
134    }
135
136    /// Extend this state with another.
137    pub fn extend(&mut self, other: State) {
138        // nothing to extend.
139        if !other.dirty {
140            return;
141        }
142
143        self.dirty = true;
144        self.last_update.extend(other.last_update);
145        self.once.extend(other.once);
146        self.hashes.extend(other.hashes);
147    }
148
149    /// Serialize the state, returning `None` unless it is dirty.
150    pub fn serialize(self) -> Option<DiskState> {
151        if !self.dirty {
152            return None;
153        }
154
155        Some(DiskState {
156            last_update: self.last_update,
157            once: self.once,
158            hashes: self.hashes,
159        })
160    }
161}