Skip to main content

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