1use 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 pub hash: u64,
16 pub updated: Timestamp,
18}
19
20#[derive(Deserialize, Serialize, Default, Debug, PartialEq, Eq)]
22#[serde(deny_unknown_fields)]
23pub struct DiskState {
24 #[serde(default)]
26 pub last_update: BTreeMap<String, Timestamp>,
27 #[serde(default)]
29 pub once: BTreeMap<String, Timestamp>,
30 #[serde(default)]
31 pub hashes: BTreeMap<String, Hashed>,
32}
33
34impl DiskState {
35 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#[derive(Debug, PartialEq, Eq)]
52pub struct State<'a> {
53 pub dirty: bool,
54 pub last_update: BTreeMap<String, Timestamp>,
56 pub once: BTreeMap<String, Timestamp>,
58 pub hashes: BTreeMap<String, Hashed>,
60 pub config: &'a Config,
62 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 pub fn last_update<'time>(&'time self, name: &str) -> Option<&'time Timestamp> {
80 self.last_update.get(name)
81 }
82
83 pub fn touch(&mut self, name: &str) {
85 self.dirty = true;
86 self.last_update.insert(name.to_string(), Timestamp::now());
87 }
88
89 pub fn has_run_once(&self, id: &str) -> bool {
91 self.once.contains_key(id)
92 }
93
94 pub fn touch_once(&mut self, id: &str) {
96 self.dirty = true;
97 self.once.insert(id.to_string(), Timestamp::now());
98 }
99
100 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 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 pub fn extend(&mut self, other: State) {
138 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 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}