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