egui_charts/ui/dialogs/templates/
store.rs1use crate::data::FeedError;
4use crate::data::inbox::Inbox;
5use crate::services::{Storage, StoreKind};
6use serde::{Serialize, de::DeserializeOwned};
7use std::collections::{BTreeMap, VecDeque};
8use std::sync::Arc;
9
10#[derive(serde::Serialize, serde::Deserialize)]
11struct Document<T> {
12 version: u32,
13 template: T,
14}
15
16enum Pending {
17 List,
18 Load(String),
19 Save,
20 Delete(String),
21}
22
23pub struct TemplateStore<T> {
28 entries: BTreeMap<String, T>,
29 kind: StoreKind,
30 storage: Option<Arc<dyn Storage>>,
31 inbox: Arc<Inbox>,
32 pending: Option<Pending>,
33 remaining: VecDeque<String>,
34 loaded: bool,
35 pub error: Option<String>,
37}
38
39impl<T: Clone + Serialize + DeserializeOwned> TemplateStore<T> {
40 pub fn new(kind: StoreKind) -> Self {
42 Self {
43 entries: BTreeMap::new(),
44 kind,
45 storage: None,
46 inbox: Arc::default(),
47 pending: None,
48 remaining: VecDeque::new(),
49 loaded: true,
50 error: None,
51 }
52 }
53
54 pub fn entries(&self) -> &BTreeMap<String, T> {
56 &self.entries
57 }
58
59 pub fn ready(&self) -> bool {
61 self.loaded && self.pending.is_none()
62 }
63
64 pub fn set_storage(&mut self, storage: Arc<dyn Storage>) {
66 self.storage = Some(storage);
67 self.pending = None;
68 self.reload();
69 }
70
71 pub fn reload(&mut self) {
74 if self.pending.is_some() {
75 return;
76 }
77 if let Some(storage) = &self.storage {
78 self.entries.clear();
79 self.remaining.clear();
80 self.inbox = Arc::default();
81 self.loaded = false;
82 self.error = None;
83 self.pending = Some(Pending::List);
84 storage.list(self.kind, self.inbox.reply(0));
85 }
86 }
87
88 pub fn poll(&mut self, ctx: &egui::Context) {
90 let repaint = ctx.clone();
91 self.inbox.set_waker(move || repaint.request_repaint());
92 for (_, answer) in self.inbox.drain().answers {
93 let Some(pending) = self.pending.take() else {
94 continue;
95 };
96 let result = match pending {
97 Pending::List => {
98 let result = *answer
99 .downcast::<Result<Vec<String>, FeedError>>()
100 .expect("template list");
101 result
102 .map(|names| {
103 self.remaining = names.into_iter().collect();
104 })
105 .map_err(|error| error.to_string())
106 }
107 Pending::Load(name) => {
108 let result = *answer
109 .downcast::<Result<Option<String>, FeedError>>()
110 .expect("template load");
111 result
112 .map_err(|error| error.to_string())
113 .and_then(|document| {
114 if let Some(document) = document {
115 let document: Document<T> = serde_json::from_str(&document)
116 .map_err(|error| error.to_string())?;
117 if document.version != 1 {
118 return Err("Unsupported template version".into());
119 }
120 self.entries.insert(name, document.template);
121 }
122 Ok(())
123 })
124 }
125 Pending::Save | Pending::Delete(_) => {
126 let result = *answer
127 .downcast::<Result<(), FeedError>>()
128 .expect("template write");
129 result
130 .map(|()| {
131 if let Pending::Delete(name) = pending {
132 self.entries.remove(&name);
133 }
134 })
135 .map_err(|error| error.to_string())
136 }
137 };
138 if let Err(error) = result {
139 self.error = Some(error);
140 self.remaining.clear();
141 } else if !self.loaded {
142 if let Some(name) = self.remaining.pop_front() {
143 self.storage.as_ref().expect("template storage").load(
144 self.kind,
145 &name,
146 self.inbox.reply(0),
147 );
148 self.pending = Some(Pending::Load(name));
149 } else {
150 self.loaded = true;
151 }
152 }
153 }
154 }
155
156 pub fn save(&mut self, name: &str, template: T) -> Result<(), String> {
158 if !self.ready() {
159 return Err("Templates are not ready".into());
160 }
161 let name = name.trim();
162 if name.is_empty() {
163 return Err("Enter a template name".into());
164 }
165 let document = serde_json::to_string(&Document {
166 version: 1,
167 template: template.clone(),
168 })
169 .map_err(|error| error.to_string())?;
170 self.entries.insert(name.into(), template);
171 self.error = None;
172 if let Some(storage) = &self.storage {
173 self.pending = Some(Pending::Save);
174 storage.save(self.kind, name, document, self.inbox.reply(0));
175 }
176 Ok(())
177 }
178
179 pub fn delete(&mut self, name: &str) -> Result<(), String> {
181 if !self.ready() {
182 return Err("Templates are not ready".into());
183 }
184 self.error = None;
185 if let Some(storage) = &self.storage {
186 self.pending = Some(Pending::Delete(name.into()));
187 storage.delete(self.kind, name, self.inbox.reply(0));
188 } else {
189 self.entries.remove(name);
190 }
191 Ok(())
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198 use crate::data::Reply;
199 use std::sync::Mutex;
200
201 #[derive(Default)]
202 struct Host {
203 documents: Mutex<BTreeMap<String, String>>,
204 fail_write: Mutex<bool>,
205 }
206
207 impl Storage for Host {
208 fn list(&self, _: StoreKind, reply: Reply<Vec<String>>) {
209 reply.send(Ok(self.documents.lock().unwrap().keys().cloned().collect()));
210 }
211 fn load(&self, _: StoreKind, name: &str, reply: Reply<Option<String>>) {
212 reply.send(Ok(self.documents.lock().unwrap().get(name).cloned()));
213 }
214 fn save(&self, _: StoreKind, name: &str, document: String, reply: Reply<()>) {
215 if *self.fail_write.lock().unwrap() {
216 reply.send(Err(FeedError::NotSupported));
217 } else {
218 self.documents.lock().unwrap().insert(name.into(), document);
219 reply.send(Ok(()));
220 }
221 }
222 fn delete(&self, _: StoreKind, name: &str, reply: Reply<()>) {
223 if *self.fail_write.lock().unwrap() {
224 reply.send(Err(FeedError::NotSupported));
225 } else {
226 self.documents.lock().unwrap().remove(name);
227 reply.send(Ok(()));
228 }
229 }
230 }
231
232 #[test]
233 fn named_templates_survive_restart_and_failed_or_unknown_documents_are_not_lost() {
234 let ctx = egui::Context::default();
235 let host = Arc::new(Host::default());
236 let mut store = TemplateStore::<Vec<u32>>::new(StoreKind::ChartTemplates);
237 store.set_storage(host.clone());
238 assert!(
239 store.save("Example", vec![7]).is_err(),
240 "wait for the initial read"
241 );
242 store.poll(&ctx);
243 assert!(store.save(" ", vec![7]).is_err());
244 store.save(" Example ", vec![7]).unwrap();
245 assert!(!store.ready(), "one write per document at a time");
246 store.reload();
247 assert_eq!(
248 store.entries()["Example"],
249 vec![7],
250 "reloading cannot discard an outstanding save"
251 );
252 store.poll(&ctx);
253 let mut restored = TemplateStore::<Vec<u32>>::new(StoreKind::ChartTemplates);
254 restored.set_storage(host.clone());
255 restored.poll(&ctx);
256 restored.poll(&ctx);
257 assert_eq!(restored.entries()["Example"], vec![7]);
258 *host.fail_write.lock().unwrap() = true;
259 restored.save("Example", vec![11]).unwrap();
260 restored.poll(&ctx);
261 assert!(restored.error.is_some());
262 assert_eq!(
263 restored.entries()["Example"],
264 vec![11],
265 "retain edits when save fails"
266 );
267 restored.delete("Example").unwrap();
268 restored.poll(&ctx);
269 assert!(
270 restored.entries().contains_key("Example"),
271 "failed delete retains the row"
272 );
273 *host.fail_write.lock().unwrap() = false;
274 restored.save("Example", vec![11]).unwrap();
275 restored.poll(&ctx);
276 restored.delete("Example").unwrap();
277 restored.poll(&ctx);
278 assert!(restored.entries().is_empty());
279 assert!(host.documents.lock().unwrap().is_empty());
280 for document in [r#"{"version":2,"template":[7]}"#, "invalid json"] {
281 host.documents
282 .lock()
283 .unwrap()
284 .insert("Future".into(), document.into());
285 restored.reload();
286 restored.poll(&ctx);
287 restored.poll(&ctx);
288 assert!(!restored.ready());
289 assert!(restored.error.is_some());
290 assert!(restored.save("Future", vec![9]).is_err());
291 assert!(restored.delete("Future").is_err());
292 assert_eq!(host.documents.lock().unwrap()["Future"], document);
293 }
294 }
295}