1use 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 Some(super::record::sealed::Door(
109 storage.0.section(&self.settings) as Rc<dyn RecordDoor>,
110 ))
111 }
112}
113
114pub(crate) struct ViewStorage {
117 storage: AppStorage,
118 followed: RefCell<Vec<(Weak<()>, Refresh)>>,
119 person: Option<super::person::PersonState>,
122 device: Option<String>,
124 sections: RefCell<HashMap<&'static str, Rc<SectionDoor>>>,
126 held_sections: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
128 section_doors: RefCell<Vec<Rc<dyn RecordDoor>>>,
130 kept_by: RefCell<HashMap<String, &'static str>>,
134 records: Option<super::chain::WorkspaceRecords>,
137 chains: super::chain::Chains,
139 workspace: Option<Rc<super::workspace::WorkspaceState>>,
142 emoji: Option<Rc<super::workspace_emoji::WindowEmoji>>,
144}
145
146#[derive(Default)]
149pub(crate) struct Hosted {
150 pub(crate) person: Option<ui_core::record::person::PersonRecords>,
151 pub(crate) device: Option<String>,
152 pub(crate) records: Option<(
155 ui_core::record::RecordStore,
156 String,
157 ui_core::permissions::LivePermissions,
158 )>,
159 pub(crate) workspace: Option<(
162 ui_core::record::workspace::WorkspaceRecords,
163 ui_core::wake::WakeHandle,
164 )>,
165}
166
167pub(crate) struct Attached {
169 person: Option<super::person::PersonState>,
170 device: Option<String>,
171 records: Option<super::chain::WorkspaceRecords>,
172 workspace: Option<Rc<super::workspace::WorkspaceState>>,
173 emoji: Option<Rc<super::workspace_emoji::WindowEmoji>>,
174}
175
176impl Hosted {
177 pub(crate) fn attach(self, app_id: &'static str, app_title: &'static str) -> Attached {
178 let permissions = self
179 .records
180 .as_ref()
181 .map(|(_, _, permissions)| permissions.clone());
182 let workspace = self.workspace.map(|(records, wake)| {
183 let state = Rc::new(super::workspace::WorkspaceState::new(
184 records,
185 app_id,
186 app_title,
187 permissions,
188 Some(wake.clone()),
189 ));
190 let emoji = super::workspace_emoji::register(&wake, Rc::clone(&state));
191 (state, emoji)
192 });
193 let (workspace, emoji) = workspace.unzip();
194 Attached {
195 workspace,
196 emoji,
197 person: self.person.map(|records| {
200 let own = self.records.as_ref().and_then(|(store, _, _)| {
201 let (_, workspace) = store.package_and_home();
202 records
203 .in_workspace(&workspace)
204 .map_err(|error| {
205 tracing::warn!(%error, "the app's records of a kind are unavailable");
206 })
207 .ok()
208 });
209 super::person::PersonState::new(records, own, app_id, app_title)
210 }),
211 device: self.device,
212 records: self
213 .records
214 .map(|(store, me, permissions)| super::chain::WorkspaceRecords {
215 store,
216 me,
217 permissions: Some(permissions),
218 app_id,
219 app_title,
220 }),
221 }
222 }
223}
224
225impl ViewStorage {
226 pub(crate) fn new(storage: AppStorage, hosted: Attached) -> Rc<Self> {
227 Rc::new(Self {
228 storage,
229 followed: RefCell::default(),
230 person: hosted.person,
231 device: hosted.device,
232 sections: RefCell::default(),
233 held_sections: RefCell::default(),
234 section_doors: RefCell::default(),
235 kept_by: RefCell::default(),
236 records: hosted.records,
237 chains: super::chain::Chains::default(),
238 workspace: hosted.workspace,
239 emoji: hosted.emoji,
240 })
241 }
242
243 pub(crate) fn workspace_handle(&self) -> Option<super::WorkspaceHandle> {
247 self.workspace
248 .as_ref()
249 .map(super::workspace::WorkspaceState::handle)
250 }
251
252 pub(crate) fn chain<P: super::Chained>(
256 &self,
257 base: impl Fn() -> Rc<<P::Base as tilo_resource::Resource>::State> + 'static,
258 ) -> Option<Rc<super::chain::ChainState<P>>> {
259 super::controls::opened_record::<P>();
260 self.chains.open::<P>(self.records.as_ref(), base)
261 }
262
263 pub(crate) fn chain_beside<P: super::Chained>(
267 &self,
268 base: &ui_core::record::RecordId,
269 base_state: impl Fn() -> Rc<<P::Base as tilo_resource::Resource>::State> + 'static,
270 ) -> Option<Rc<super::chain::ChainState<P>>> {
271 super::controls::opened_record::<P>();
272 self.chains
273 .open_beside::<P>(self.records.as_ref(), base, base_state)
274 }
275
276 pub(crate) fn children<C: super::children::ChildRecord>(
280 &self,
281 parent: impl Fn() -> Rc<<C::Parent as tilo_resource::Resource>::State> + 'static,
282 ) -> Option<Rc<super::children::ChildrenState<C>>> {
283 super::controls::opened_record::<C>();
284 self.chains
285 .open_children::<C>(self.records.as_ref(), parent)
286 }
287
288 pub(crate) fn chain_action(
291 &self,
292 me: Option<&str>,
293 payload: &[u8],
294 live: &mut ui_core::widget::Edits<'_>,
295 ) -> Result<tilo_actions::ActionOutput, String> {
296 self.chains.answer(me, payload, live)
297 }
298
299 pub(crate) fn storage(&self) -> &AppStorage {
300 &self.storage
301 }
302
303 pub(crate) fn workspace_store(&self) -> Option<&ui_core::record::RecordStore> {
306 self.records.as_ref().map(|records| &records.store)
307 }
308
309 pub(crate) fn device(&self) -> Option<&str> {
311 self.device.as_deref()
312 }
313
314 pub(crate) fn person<P: tilo_resource::Resource>(
316 &self,
317 ) -> Option<super::person::PersonRecord<P>> {
318 super::controls::opened_record::<P>();
319 self.person.as_ref()?.open::<P>()
320 }
321
322 pub(crate) fn workspace_record<P: tilo_resource::Resource>(&self) {
325 if let Some(workspace) = &self.workspace {
326 let _ = workspace.open::<P>(workspace.app_id());
327 }
328 }
329
330 pub(crate) fn shared_workspace<P: tilo_resource::Resource>(
333 &self,
334 owner: &'static str,
335 ) -> Option<super::workspace::WorkspaceRecord<P>> {
336 self.workspace.as_ref()?.open::<P>(owner)
337 }
338
339 pub(crate) fn shared_record<P: tilo_resource::Resource>(
341 &self,
342 owner: &'static str,
343 id: &str,
344 ) -> Option<super::workspace::WorkspaceRecord<P>> {
345 self.workspace.as_ref()?.open_at::<P>(owner, id)
346 }
347
348 pub(crate) fn shared_person<P: tilo_resource::SharedRecord>(
350 &self,
351 ) -> Option<super::person::PersonRecord<P>> {
352 super::controls::opened_record::<P>();
353 self.person.as_ref()?.open_shared::<P>()
354 }
355
356 pub(crate) fn records_key(&self) -> Vec<(String, u64)> {
361 let mut doors = self
362 .person
363 .as_ref()
364 .map(super::person::PersonState::doors)
365 .unwrap_or_default();
366 doors.extend(self.section_doors.borrow().iter().cloned());
367 doors.extend(self.chains.doors());
368 doors
369 .iter()
370 .map(|door| (door.id().to_owned(), door.revision()))
371 .collect()
372 }
373
374 pub(crate) fn unavailable_records(&self) -> Vec<tilo_actions::ResourceSpec> {
377 self.person
378 .as_ref()
379 .map(super::person::PersonState::unavailable)
380 .unwrap_or_default()
381 }
382
383 pub(crate) fn record_resources(&self) -> Vec<tilo_actions::ResourceSpec> {
384 let mut resources = self
385 .person
386 .as_ref()
387 .map(super::person::PersonState::resources)
388 .unwrap_or_default();
389 resources.extend(
390 self.section_doors
391 .borrow()
392 .iter()
393 .map(|door| door.publish()),
394 );
395 resources.extend(self.chains.doors().iter().map(|door| door.publish()));
396 let kept_by = self.kept_by.borrow();
397 if !kept_by.is_empty() {
398 for resource in &mut resources {
399 if let Some(provider) = kept_by.get(resource.id.as_str()) {
400 run_by(resource, provider);
401 }
402 }
403 }
404 resources
405 }
406
407 pub(crate) fn keep_by(&self, id: &str, provider: &'static str) {
410 self.kept_by.borrow_mut().insert(id.to_owned(), provider);
411 }
412
413 pub(crate) fn kept_provider(&self, id: &str) -> Option<&'static str> {
415 self.kept_by.borrow().get(id).copied()
416 }
417
418 pub(crate) fn record_action(
421 &self,
422 me: Option<&str>,
423 payload: &[u8],
424 ) -> Result<tilo_actions::ActionOutput, String> {
425 let mut doors = self
426 .person
427 .as_ref()
428 .map(super::person::PersonState::doors)
429 .unwrap_or_default();
430 doors.extend(self.section_doors.borrow().iter().cloned());
431 let kept_by = self.kept_by.borrow();
433 doors.retain(|door| !kept_by.contains_key(door.id()));
434 let me = self
435 .person
436 .as_ref()
437 .map(super::person::PersonState::me)
438 .or(me);
439 super::record::answer(&doors, me, payload)
440 }
441
442 pub(crate) fn section<T>(&self, settings: &Settings<T>) -> Rc<SectionDoor>
446 where
447 T: tilo_resource::Resource<State = T> + tilo_resource::Fields + SettingsSchema,
448 {
449 super::controls::opened_section::<T>();
450 if let Some(open) = self.sections.borrow().get(T::KIND) {
451 return Rc::clone(open);
452 }
453 self.open_section(SectionKind::of(settings))
454 }
455
456 fn open_section(&self, kind: Rc<SectionKind>) -> Rc<SectionDoor> {
458 let name = kind.kind;
459 let door = if let Some(person) = &self.person {
460 person.open_section(kind)
461 } else {
462 let door = Rc::new(SectionDoor::new(kind, String::new(), Vec::new()));
463 self.section_doors
464 .borrow_mut()
465 .push(Rc::clone(&door) as Rc<dyn RecordDoor>);
466 door
467 };
468 self.sections.borrow_mut().insert(name, Rc::clone(&door));
469 door
470 }
471
472 pub(crate) fn section_held<T>(&self, settings: &Settings<T>) -> Option<Rc<Held<T>>>
475 where
476 T: tilo_resource::Resource<State = T> + tilo_resource::Fields + SettingsSchema,
477 {
478 if let Some(held) = self.held_sections.borrow().get(&TypeId::of::<T>()) {
479 return Rc::clone(held).downcast::<Held<T>>().ok();
480 }
481 let held = Rc::new(self.section(settings).held::<T>());
482 self.held_sections
483 .borrow_mut()
484 .insert(TypeId::of::<T>(), Rc::clone(&held) as Rc<dyn Any>);
485 Some(held)
486 }
487
488 pub(crate) fn follow_with(&self, alive: Weak<()>, refresh: impl FnMut() + 'static) {
490 self.followed.borrow_mut().push((alive, Box::new(refresh)));
491 }
492
493 pub(crate) fn person_history(
495 &self,
496 payload: &[u8],
497 ) -> Result<tilo_actions::ActionOutput, String> {
498 self.person
499 .as_ref()
500 .ok_or_else(|| "this window keeps no records of yours".to_owned())?
501 .answer(payload)
502 }
503
504 pub(crate) fn person_owners(&self) -> Vec<ui_core::history_mode::HistoryOwner> {
507 let mut owners = self
508 .person
509 .as_ref()
510 .map(super::person::PersonState::owners)
511 .unwrap_or_default();
512 if let Some(workspace) = &self.workspace {
513 owners.extend(workspace.owners());
514 }
515 owners
516 }
517
518 pub(crate) fn follow<T>(&self, alive: Weak<()>) -> Setting<T>
520 where
521 T: SettingsSchema + Clone + PartialEq + Send + Sync,
522 {
523 let settings = self.storage.settings::<T>();
524 let mut seen: SettingsRevision = settings.revision();
525 let value = LocalState::new(settings.get());
526 let setting = Setting {
527 settings: settings.clone(),
528 value: value.clone(),
529 };
530 self.followed.borrow_mut().push((
531 alive,
532 Box::new(move || {
533 let revision = settings.revision();
534 if revision == seen {
535 return;
536 }
537 seen = revision;
538 let stored = settings.get();
539 if value.with(|shown| *shown != stored) {
540 value.set(stored);
541 }
542 }),
543 ));
544 setting
545 }
546
547 pub(crate) fn settle(&self) {
551 for chain in self.chains.doors() {
552 let _ = chain.revision();
553 }
554 }
555
556 pub(crate) fn refresh(&self) {
559 let _profile = tilo_resource::profiling::Phase::start("framework.view_storage_refresh");
560 if let Some(person) = &self.person {
561 person.refresh();
562 }
563 if let Some(workspace) = &self.workspace {
564 workspace.refresh();
565 }
566 if let Some(emoji) = &self.emoji {
567 emoji.settle();
568 }
569 for chain in self.chains.doors() {
570 let _ = chain.revision();
571 }
572 let mut current = std::mem::take(&mut *self.followed.borrow_mut());
575 current.retain_mut(|(alive, refresh)| {
576 if alive.strong_count() == 0 {
577 return false;
578 }
579 refresh();
580 true
581 });
582 let mut list = self.followed.borrow_mut();
583 current.append(&mut list);
584 *list = current;
585 }
586}
587
588fn run_by(resource: &mut tilo_actions::ResourceSpec, provider: &'static str) {
590 resource.actions = std::mem::take(&mut resource.actions)
591 .into_iter()
592 .map(|spec| spec.run_by(provider))
593 .collect();
594 for child in &mut resource.children {
595 run_by(child, provider);
596 }
597}