1//! Builder for [`SlidingSyncList`].
23use std::{
4 convert::identity,
5 fmt,
6 sync::{Arc, RwLock as StdRwLock},
7};
89use eyeball::{Observable, SharedObservable};
10use ruma::{api::client::sync::sync_events::v5 as http, events::StateEventType};
11use tokio::sync::broadcast::Sender;
1213use super::{
14super::SlidingSyncInternalMessage, Bound, SlidingSyncList, SlidingSyncListCachePolicy,
15 SlidingSyncListInner, SlidingSyncListLoadingState, SlidingSyncListRequestGenerator,
16 SlidingSyncListStickyParameters, SlidingSyncMode,
17};
18use crate::{
19 sliding_sync::{cache::restore_sliding_sync_list, sticky_parameters::SlidingSyncStickyManager},
20 Client,
21};
2223/// Data that might have been read from the cache.
24#[derive(Clone)]
25struct SlidingSyncListCachedData {
26/// Total number of rooms that is possible to interact with the given list.
27 /// See also comment of [`SlidingSyncList::maximum_number_of_rooms`].
28 /// May be reloaded from the cache.
29maximum_number_of_rooms: Option<u32>,
30}
3132/// Builder for [`SlidingSyncList`].
33#[derive(Clone)]
34pub struct SlidingSyncListBuilder {
35 sync_mode: SlidingSyncMode,
36 required_state: Vec<(StateEventType, String)>,
37 include_heroes: Option<bool>,
38 filters: Option<http::request::ListFilters>,
39 timeline_limit: Bound,
40pub(crate) name: String,
4142/// Should this list be cached and reloaded from the cache?
43cache_policy: SlidingSyncListCachePolicy,
4445/// If set, temporary data that's been read from the cache, reloaded from a
46 /// `FrozenSlidingSyncList`.
47reloaded_cached_data: Option<SlidingSyncListCachedData>,
4849 once_built: Arc<Box<dyn Fn(SlidingSyncList) -> SlidingSyncList + Send + Sync>>,
50}
5152#[cfg(not(tarpaulin_include))]
53impl fmt::Debug for SlidingSyncListBuilder {
54fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
55// Print debug values for the builder, except `once_built` which is ignored.
56formatter
57 .debug_struct("SlidingSyncListBuilder")
58 .field("sync_mode", &self.sync_mode)
59 .field("required_state", &self.required_state)
60 .field("include_heroes", &self.include_heroes)
61 .field("filters", &self.filters)
62 .field("timeline_limit", &self.timeline_limit)
63 .field("name", &self.name)
64 .finish_non_exhaustive()
65 }
66}
6768impl SlidingSyncListBuilder {
69pub(super) fn new(name: impl Into<String>) -> Self {
70Self {
71 sync_mode: SlidingSyncMode::default(),
72 required_state: vec![
73 (StateEventType::RoomEncryption, "".to_owned()),
74 (StateEventType::RoomTombstone, "".to_owned()),
75 ],
76 include_heroes: None,
77 filters: None,
78 timeline_limit: 1,
79 name: name.into(),
80 reloaded_cached_data: None,
81 cache_policy: SlidingSyncListCachePolicy::Disabled,
82 once_built: Arc::new(Box::new(identity)),
83 }
84 }
8586/// Runs a callback once the list has been built.
87 ///
88 /// If the list was cached, then the cached fields won't be available in
89 /// this callback. Use the streams to get published versions of the
90 /// cached fields, once they've been set.
91pub fn once_built<C>(mut self, callback: C) -> Self
92where
93C: Fn(SlidingSyncList) -> SlidingSyncList + Send + Sync + 'static,
94 {
95self.once_built = Arc::new(Box::new(callback));
96self
97}
9899/// Which SlidingSyncMode to start this list under.
100pub fn sync_mode(mut self, value: impl Into<SlidingSyncMode>) -> Self {
101self.sync_mode = value.into();
102self
103}
104105/// Required states to return per room.
106pub fn required_state(mut self, value: Vec<(StateEventType, String)>) -> Self {
107self.required_state = value;
108self
109}
110111/// Include heroes.
112pub fn include_heroes(mut self, value: Option<bool>) -> Self {
113self.include_heroes = value;
114self
115}
116117/// Any filters to apply to the query.
118pub fn filters(mut self, value: Option<http::request::ListFilters>) -> Self {
119self.filters = value;
120self
121}
122123/// Set the limit of regular events to fetch for the timeline.
124pub fn timeline_limit(mut self, timeline_limit: Bound) -> Self {
125self.timeline_limit = timeline_limit;
126self
127}
128129/// Set the limit of regular events to fetch for the timeline to 0.
130pub fn no_timeline_limit(mut self) -> Self {
131self.timeline_limit = 0;
132self
133}
134135/// Marks this list as sync'd from the cache, and attempts to reload it from
136 /// storage.
137 ///
138 /// Returns a mapping of the room's data read from the cache, to be
139 /// incorporated into the `SlidingSync` bookkeepping.
140pub(in super::super) async fn set_cached_and_reload(
141&mut self,
142 client: &Client,
143 storage_key: &str,
144 ) -> crate::Result<()> {
145self.cache_policy = SlidingSyncListCachePolicy::Enabled;
146147if let Some(frozen_list) =
148 restore_sliding_sync_list(client.state_store(), storage_key, &self.name).await?
149{
150assert!(
151self.reloaded_cached_data.is_none(),
152"can't call `set_cached_and_reload` twice"
153);
154self.reloaded_cached_data = Some(SlidingSyncListCachedData {
155 maximum_number_of_rooms: frozen_list.maximum_number_of_rooms,
156 });
157Ok(())
158 } else {
159Ok(())
160 }
161 }
162163/// Build the list.
164pub(in super::super) fn build(
165self,
166 sliding_sync_internal_channel_sender: Sender<SlidingSyncInternalMessage>,
167 ) -> SlidingSyncList {
168let list = SlidingSyncList {
169 inner: Arc::new(SlidingSyncListInner {
170#[cfg(any(test, feature = "testing"))]
171sync_mode: StdRwLock::new(self.sync_mode.clone()),
172173// From the builder
174sticky: StdRwLock::new(SlidingSyncStickyManager::new(
175 SlidingSyncListStickyParameters::new(
176self.required_state,
177self.include_heroes,
178self.filters,
179 ),
180 )),
181 timeline_limit: StdRwLock::new(self.timeline_limit),
182 name: self.name,
183 cache_policy: self.cache_policy,
184185// Computed from the builder.
186request_generator: StdRwLock::new(SlidingSyncListRequestGenerator::new(
187self.sync_mode,
188 )),
189190// Values read from deserialization, or that are still equal to the default values
191 // otherwise.
192state: StdRwLock::new(Observable::new(Default::default())),
193 maximum_number_of_rooms: SharedObservable::new(None),
194195// Internal data.
196sliding_sync_internal_channel_sender,
197 }),
198 };
199200let once_built = self.once_built;
201202let list = once_built(list);
203204// If we reloaded from the cache, update values in the list here.
205 //
206 // Note about ordering: because of the contract with the observables, the
207 // initial values, if filled, have to be observable in the `once_built`
208 // callback. That's why we're doing this here *after* constructing the
209 // list, and not a few lines above.
210211if let Some(SlidingSyncListCachedData { maximum_number_of_rooms }) =
212self.reloaded_cached_data
213 {
214// Mark state as preloaded.
215Observable::set(
216&mut list.inner.state.write().unwrap(),
217 SlidingSyncListLoadingState::Preloaded,
218 );
219220// Reload the maximum number of rooms.
221list.inner.maximum_number_of_rooms.set(maximum_number_of_rooms);
222 }
223224 list
225 }
226}