tilo_app/framework/
settings.rs1use std::any::{Any, TypeId};
6use std::cell::RefCell;
7use std::collections::HashMap;
8use std::rc::{Rc, Weak};
9
10use ui_core::LocalState;
11use ui_core::storage::{AppStorage, Settings, SettingsRevision, SettingsSchema};
12
13use super::record::{Held, RecordDoor};
14use super::section::{SectionDoor, SectionKind};
15
16pub struct Setting<T> {
32 settings: Settings<T>,
33 value: LocalState<T>,
34}
35
36impl<T> Clone for Setting<T> {
37 fn clone(&self) -> Self {
38 Self {
39 settings: self.settings.clone(),
40 value: self.value.clone(),
41 }
42 }
43}
44
45impl<T: SettingsSchema + Clone + PartialEq + Send + Sync> Setting<T> {
46 #[must_use]
48 pub fn get(&self) -> T {
49 self.value.with(Clone::clone)
50 }
51
52 #[must_use]
54 pub fn state(&self) -> LocalState<T> {
55 self.value.clone()
56 }
57
58 pub fn set(&self, value: T) -> std::io::Result<()> {
65 self.settings.set(&value)?;
66 self.value.set(value);
67 Ok(())
68 }
69
70 pub fn update(&self, change: impl FnOnce(&mut T)) -> std::io::Result<()> {
76 let value = self.settings.update(change)?;
77 self.value.set(value);
78 Ok(())
79 }
80
81 #[must_use]
83 pub fn settings(&self) -> &Settings<T> {
84 &self.settings
85 }
86}
87
88type Refresh = Box<dyn FnMut()>;
89
90impl<T> super::record::sealed::Hold<T> for Setting<T>
91where
92 T: tilo_resource::Resource<State = T>
93 + tilo_resource::Fields
94 + SettingsSchema
95 + Clone
96 + PartialEq
97 + Send
98 + Sync,
99{
100 fn held(&self, storage: super::record::sealed::Storage<'_>) -> Option<Rc<Held<T>>> {
101 storage.0.section_held(&self.settings)
102 }
103
104 fn door(
105 &self,
106 storage: super::record::sealed::Storage<'_>,
107 ) -> Option<super::record::sealed::Door> {
108 storage
109 .0
110 .section(&self.settings)
111 .map(|door| super::record::sealed::Door(door as Rc<dyn RecordDoor>))
112 }
113}
114
115pub(crate) struct ViewStorage {
118 storage: AppStorage,
119 followed: RefCell<Vec<(Weak<()>, Refresh)>>,
120 person: Option<super::person::PersonState>,
123 device: Option<String>,
125 sections: RefCell<HashMap<&'static str, Rc<SectionDoor>>>,
127 held_sections: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
129 section_doors: RefCell<Vec<Rc<dyn RecordDoor>>>,
131}
132
133#[derive(Default)]
136pub(crate) struct Hosted {
137 pub(crate) person: Option<ui_core::record::person::PersonRecords>,
138 pub(crate) device: Option<String>,
139}
140
141pub(crate) struct Attached {
143 person: Option<super::person::PersonState>,
144 device: Option<String>,
145}
146
147impl Hosted {
148 pub(crate) fn attach(self, app_id: &'static str, app_title: &'static str) -> Attached {
149 Attached {
150 person: self
151 .person
152 .map(|records| super::person::PersonState::new(records, app_id, app_title)),
153 device: self.device,
154 }
155 }
156}
157
158impl ViewStorage {
159 pub(crate) fn new(storage: AppStorage, hosted: Attached) -> Rc<Self> {
160 Rc::new(Self {
161 storage,
162 followed: RefCell::default(),
163 person: hosted.person,
164 device: hosted.device,
165 sections: RefCell::default(),
166 held_sections: RefCell::default(),
167 section_doors: RefCell::default(),
168 })
169 }
170
171 pub(crate) fn storage(&self) -> &AppStorage {
172 &self.storage
173 }
174
175 pub(crate) fn device(&self) -> Option<&str> {
177 self.device.as_deref()
178 }
179
180 pub(crate) fn person<P: tilo_resource::Resource>(
182 &self,
183 ) -> Option<super::person::PersonRecord<P>> {
184 super::controls::opened::<P>();
185 self.person.as_ref()?.open::<P>()
186 }
187
188 pub(crate) fn shared_person<P: tilo_resource::SharedRecord>(
190 &self,
191 ) -> Option<super::person::PersonRecord<P>> {
192 super::controls::opened::<P>();
193 self.person.as_ref()?.open_shared::<P>()
194 }
195
196 pub(crate) fn records_key(&self) -> Vec<(String, u64)> {
201 let mut doors = self
202 .person
203 .as_ref()
204 .map(super::person::PersonState::doors)
205 .unwrap_or_default();
206 doors.extend(self.section_doors.borrow().iter().cloned());
207 doors
208 .iter()
209 .map(|door| (door.id().to_owned(), door.revision()))
210 .collect()
211 }
212
213 pub(crate) fn record_resources(&self) -> Vec<tilo_actions::Resource> {
214 let mut resources = self
215 .person
216 .as_ref()
217 .map(super::person::PersonState::resources)
218 .unwrap_or_default();
219 resources.extend(
220 self.section_doors
221 .borrow()
222 .iter()
223 .map(|door| door.publish()),
224 );
225 resources
226 }
227
228 pub(crate) fn record_action(
231 &self,
232 me: Option<&str>,
233 payload: &[u8],
234 ) -> Result<tilo_actions::ActionOutput, String> {
235 let mut doors = self
236 .person
237 .as_ref()
238 .map(super::person::PersonState::doors)
239 .unwrap_or_default();
240 doors.extend(self.section_doors.borrow().iter().cloned());
241 let me = self
242 .person
243 .as_ref()
244 .map(super::person::PersonState::me)
245 .or(me);
246 super::record::answer(&doors, me, payload)
247 }
248
249 pub(crate) fn section<T>(&self, settings: &Settings<T>) -> Option<Rc<SectionDoor>>
254 where
255 T: tilo_resource::Resource<State = T> + tilo_resource::Fields + SettingsSchema,
256 {
257 super::controls::opened::<T>();
258 if let Some(open) = self.sections.borrow().get(T::KIND) {
259 return Some(Rc::clone(open));
260 }
261 self.open_section(SectionKind::of(settings))
262 }
263
264 fn open_section(&self, kind: Rc<SectionKind>) -> Option<Rc<SectionDoor>> {
266 let name = kind.kind;
267 let door = if let Some(person) = &self.person {
268 person.open_section(kind, self.device())?
269 } else {
270 let door = SectionDoor::window(kind, true)
271 .map_err(
272 |error| tracing::warn!(kind = name, %error, "settings section unavailable"),
273 )
274 .ok()?;
275 let door = Rc::new(door);
276 self.section_doors
277 .borrow_mut()
278 .push(Rc::clone(&door) as Rc<dyn RecordDoor>);
279 door
280 };
281 self.sections.borrow_mut().insert(name, Rc::clone(&door));
282 Some(door)
283 }
284
285 pub(crate) fn section_held<T>(&self, settings: &Settings<T>) -> Option<Rc<Held<T>>>
288 where
289 T: tilo_resource::Resource<State = T> + tilo_resource::Fields + SettingsSchema,
290 {
291 if let Some(held) = self.held_sections.borrow().get(&TypeId::of::<T>()) {
292 return Rc::clone(held).downcast::<Held<T>>().ok();
293 }
294 let held = self
295 .section(settings)?
296 .held::<T>()
297 .map_err(|error| tracing::warn!(kind = T::KIND, %error, "settings section unavailable"))
298 .ok()?;
299 let held = Rc::new(held);
300 self.held_sections
301 .borrow_mut()
302 .insert(TypeId::of::<T>(), Rc::clone(&held) as Rc<dyn Any>);
303 Some(held)
304 }
305
306 pub(crate) fn follow_with(&self, alive: Weak<()>, refresh: impl FnMut() + 'static) {
308 self.followed.borrow_mut().push((alive, Box::new(refresh)));
309 }
310
311 pub(crate) fn person_history(
313 &self,
314 payload: &[u8],
315 ) -> Result<tilo_actions::ActionOutput, String> {
316 self.person
317 .as_ref()
318 .ok_or_else(|| "this window keeps no records of yours".to_owned())?
319 .answer(payload)
320 }
321
322 pub(crate) fn person_owners(&self) -> Vec<ui_core::history_mode::HistoryOwner> {
324 self.person
325 .as_ref()
326 .map(super::person::PersonState::owners)
327 .unwrap_or_default()
328 }
329
330 pub(crate) fn follow<T>(&self, alive: Weak<()>) -> Setting<T>
332 where
333 T: SettingsSchema + Clone + PartialEq + Send + Sync,
334 {
335 let settings = self.storage.settings::<T>();
336 let mut seen: SettingsRevision = settings.revision();
337 let value = LocalState::new(settings.get());
338 let setting = Setting {
339 settings: settings.clone(),
340 value: value.clone(),
341 };
342 self.followed.borrow_mut().push((
343 alive,
344 Box::new(move || {
345 let revision = settings.revision();
346 if revision == seen {
347 return;
348 }
349 seen = revision;
350 let stored = settings.get();
351 if value.with(|shown| *shown != stored) {
352 value.set(stored);
353 }
354 }),
355 ));
356 setting
357 }
358
359 pub(crate) fn refresh(&self) {
362 let _profile = tilo_resource::profiling::Phase::start("framework.view_storage_refresh");
363 if let Some(person) = &self.person {
364 person.refresh();
365 }
366 let mut current = std::mem::take(&mut *self.followed.borrow_mut());
369 current.retain_mut(|(alive, refresh)| {
370 if alive.strong_count() == 0 {
371 return false;
372 }
373 refresh();
374 true
375 });
376 let mut list = self.followed.borrow_mut();
377 current.append(&mut list);
378 *list = current;
379 }
380}