matrix_sdk/sliding_sync/list/
builder.rs

1//! Builder for [`SlidingSyncList`].
2
3use std::{
4    convert::identity,
5    fmt,
6    sync::{Arc, RwLock as StdRwLock},
7};
8
9use eyeball::SharedObservable;
10use ruma::{api::client::sync::sync_events::v5 as http, events::StateEventType};
11use tokio::sync::broadcast::Sender;
12
13use super::{
14    super::SlidingSyncInternalMessage, Bound, SlidingSyncList, SlidingSyncListCachePolicy,
15    SlidingSyncListInner, SlidingSyncListLoadingState, SlidingSyncListRequestGenerator,
16    SlidingSyncListStickyParameters, SlidingSyncMode,
17};
18use crate::{
19    Client,
20    sliding_sync::{cache::restore_sliding_sync_list, sticky_parameters::SlidingSyncStickyManager},
21};
22
23/// 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.
29    maximum_number_of_rooms: Option<u32>,
30}
31
32/// Builder for [`SlidingSyncList`].
33#[derive(Clone)]
34pub struct SlidingSyncListBuilder {
35    sync_mode: SlidingSyncMode,
36    #[cfg(not(target_family = "wasm"))]
37    requires_timeout: Arc<dyn Fn(&SlidingSyncListRequestGenerator) -> bool + Send + Sync>,
38    #[cfg(target_family = "wasm")]
39    requires_timeout: Arc<dyn Fn(&SlidingSyncListRequestGenerator) -> bool>,
40    required_state: Vec<(StateEventType, String)>,
41    filters: Option<http::request::ListFilters>,
42    timeline_limit: Bound,
43    pub(crate) name: String,
44
45    /// Should this list be cached and reloaded from the cache?
46    cache_policy: SlidingSyncListCachePolicy,
47
48    /// If set, temporary data that's been read from the cache, reloaded from a
49    /// `FrozenSlidingSyncList`.
50    reloaded_cached_data: Option<SlidingSyncListCachedData>,
51
52    #[cfg(not(target_family = "wasm"))]
53    once_built: Arc<Box<dyn Fn(SlidingSyncList) -> SlidingSyncList + Send + Sync>>,
54    #[cfg(target_family = "wasm")]
55    once_built: Arc<Box<dyn Fn(SlidingSyncList) -> SlidingSyncList>>,
56}
57
58#[cfg(not(tarpaulin_include))]
59impl fmt::Debug for SlidingSyncListBuilder {
60    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
61        // Print debug values for the builder, except `once_built` which is ignored.
62        formatter
63            .debug_struct("SlidingSyncListBuilder")
64            .field("sync_mode", &self.sync_mode)
65            .field("required_state", &self.required_state)
66            .field("filters", &self.filters)
67            .field("timeline_limit", &self.timeline_limit)
68            .field("name", &self.name)
69            .finish_non_exhaustive()
70    }
71}
72
73impl SlidingSyncListBuilder {
74    pub(super) fn new(name: impl Into<String>) -> Self {
75        Self {
76            sync_mode: SlidingSyncMode::default(),
77            requires_timeout: Arc::new(|request_generator| request_generator.is_fully_loaded()),
78            required_state: vec![
79                (StateEventType::RoomEncryption, "".to_owned()),
80                (StateEventType::RoomTombstone, "".to_owned()),
81            ],
82            filters: None,
83            timeline_limit: 1,
84            name: name.into(),
85            reloaded_cached_data: None,
86            cache_policy: SlidingSyncListCachePolicy::Disabled,
87            once_built: Arc::new(Box::new(identity)),
88        }
89    }
90
91    /// Runs a callback once the list has been built.
92    ///
93    /// If the list was cached, then the cached fields won't be available in
94    /// this callback. Use the streams to get published versions of the
95    /// cached fields, once they've been set.
96    #[cfg(not(target_family = "wasm"))]
97    pub fn once_built<C>(mut self, callback: C) -> Self
98    where
99        C: Fn(SlidingSyncList) -> SlidingSyncList + Send + Sync + 'static,
100    {
101        self.once_built = Arc::new(Box::new(callback));
102        self
103    }
104
105    /// Runs a callback once the list has been built.
106    ///
107    /// If the list was cached, then the cached fields won't be available in
108    /// this callback. Use the streams to get published versions of the
109    /// cached fields, once they've been set.
110    #[cfg(target_family = "wasm")]
111    pub fn once_built<C>(mut self, callback: C) -> Self
112    where
113        C: Fn(SlidingSyncList) -> SlidingSyncList + 'static,
114    {
115        self.once_built = Arc::new(Box::new(callback));
116        self
117    }
118
119    /// Which SlidingSyncMode to start this list under.
120    pub fn sync_mode(mut self, value: impl Into<SlidingSyncMode>) -> Self {
121        self.sync_mode = value.into();
122        self
123    }
124
125    /// Custom function to decide whether this list requires a
126    /// [`http::Request::timeout`] value.
127    ///
128    /// A list requires a `timeout` query if and only if we want the server to
129    /// wait on new updates, i.e. to do a long-polling.
130    #[cfg(not(target_family = "wasm"))]
131    pub fn requires_timeout<F>(mut self, f: F) -> Self
132    where
133        F: Fn(&SlidingSyncListRequestGenerator) -> bool + Send + Sync + 'static,
134    {
135        self.requires_timeout = Arc::new(f);
136        self
137    }
138
139    /// Custom function to decide whether this list requires a
140    /// [`http::Request::timeout`] value.
141    ///
142    /// A list requires a `timeout` query if and only if we want the server to
143    /// wait on new updates, i.e. to do a long-polling.
144    #[cfg(target_family = "wasm")]
145    pub fn requires_timeout<F>(mut self, f: F) -> Self
146    where
147        F: Fn(&SlidingSyncListRequestGenerator) -> bool + 'static,
148    {
149        self.requires_timeout = Arc::new(f);
150        self
151    }
152
153    /// Required states to return per room.
154    pub fn required_state(mut self, value: Vec<(StateEventType, String)>) -> Self {
155        self.required_state = value;
156        self
157    }
158
159    /// Any filters to apply to the query.
160    pub fn filters(mut self, value: Option<http::request::ListFilters>) -> Self {
161        self.filters = value;
162        self
163    }
164
165    /// Set the limit of regular events to fetch for the timeline.
166    pub fn timeline_limit(mut self, timeline_limit: Bound) -> Self {
167        self.timeline_limit = timeline_limit;
168        self
169    }
170
171    /// Set the limit of regular events to fetch for the timeline to 0.
172    pub fn no_timeline_limit(mut self) -> Self {
173        self.timeline_limit = 0;
174        self
175    }
176
177    /// Marks this list as sync'd from the cache, and attempts to reload it from
178    /// storage.
179    ///
180    /// Returns a mapping of the room's data read from the cache, to be
181    /// incorporated into the `SlidingSync` bookkeepping.
182    pub(in super::super) async fn set_cached_and_reload(
183        &mut self,
184        client: &Client,
185        storage_key: &str,
186    ) -> crate::Result<()> {
187        self.cache_policy = SlidingSyncListCachePolicy::Enabled;
188
189        if let Some(frozen_list) =
190            restore_sliding_sync_list(client.state_store(), storage_key, &self.name).await?
191        {
192            assert!(
193                self.reloaded_cached_data.is_none(),
194                "can't call `set_cached_and_reload` twice"
195            );
196            self.reloaded_cached_data = Some(SlidingSyncListCachedData {
197                maximum_number_of_rooms: frozen_list.maximum_number_of_rooms,
198            });
199            Ok(())
200        } else {
201            Ok(())
202        }
203    }
204
205    /// Build the list.
206    pub(in super::super) fn build(
207        self,
208        sliding_sync_internal_channel_sender: Sender<SlidingSyncInternalMessage>,
209    ) -> SlidingSyncList {
210        let list = SlidingSyncList {
211            inner: Arc::new(SlidingSyncListInner {
212                #[cfg(any(test, feature = "testing"))]
213                sync_mode: StdRwLock::new(self.sync_mode.clone()),
214
215                // From the builder
216                sticky: StdRwLock::new(SlidingSyncStickyManager::new(
217                    SlidingSyncListStickyParameters::new(self.required_state, self.filters),
218                )),
219                timeline_limit: StdRwLock::new(self.timeline_limit),
220                name: self.name,
221                cache_policy: self.cache_policy,
222                requires_timeout: self.requires_timeout,
223
224                // Computed from the builder.
225                request_generator: StdRwLock::new(SlidingSyncListRequestGenerator::new(
226                    self.sync_mode,
227                )),
228
229                // Values read from deserialization, or that are still equal to the default values
230                // otherwise.
231                state: SharedObservable::new(Default::default()),
232                maximum_number_of_rooms: SharedObservable::new(None),
233
234                // Internal data.
235                sliding_sync_internal_channel_sender,
236            }),
237        };
238
239        let once_built = self.once_built;
240
241        let list = once_built(list);
242
243        // If we reloaded from the cache, update values in the list here.
244        //
245        // Note about ordering: because of the contract with the observables, the
246        // initial values, if filled, have to be observable in the `once_built`
247        // callback. That's why we're doing this here *after* constructing the
248        // list, and not a few lines above.
249
250        if let Some(SlidingSyncListCachedData { maximum_number_of_rooms }) =
251            self.reloaded_cached_data
252        {
253            // Mark state as preloaded.
254            list.inner.state.set(SlidingSyncListLoadingState::Preloaded);
255
256            // Reload the maximum number of rooms.
257            list.inner.maximum_number_of_rooms.set(maximum_number_of_rooms);
258        }
259
260        list
261    }
262}