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::{Observable, 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    sliding_sync::{cache::restore_sliding_sync_list, sticky_parameters::SlidingSyncStickyManager},
20    Client,
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    required_state: Vec<(StateEventType, String)>,
37    filters: Option<http::request::ListFilters>,
38    timeline_limit: Bound,
39    pub(crate) name: String,
40
41    /// Should this list be cached and reloaded from the cache?
42    cache_policy: SlidingSyncListCachePolicy,
43
44    /// If set, temporary data that's been read from the cache, reloaded from a
45    /// `FrozenSlidingSyncList`.
46    reloaded_cached_data: Option<SlidingSyncListCachedData>,
47
48    #[cfg(not(target_family = "wasm"))]
49    once_built: Arc<Box<dyn Fn(SlidingSyncList) -> SlidingSyncList + Send + Sync>>,
50    #[cfg(target_family = "wasm")]
51    once_built: Arc<Box<dyn Fn(SlidingSyncList) -> SlidingSyncList>>,
52}
53
54#[cfg(not(tarpaulin_include))]
55impl fmt::Debug for SlidingSyncListBuilder {
56    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
57        // Print debug values for the builder, except `once_built` which is ignored.
58        formatter
59            .debug_struct("SlidingSyncListBuilder")
60            .field("sync_mode", &self.sync_mode)
61            .field("required_state", &self.required_state)
62            .field("filters", &self.filters)
63            .field("timeline_limit", &self.timeline_limit)
64            .field("name", &self.name)
65            .finish_non_exhaustive()
66    }
67}
68
69impl SlidingSyncListBuilder {
70    pub(super) fn new(name: impl Into<String>) -> Self {
71        Self {
72            sync_mode: SlidingSyncMode::default(),
73            required_state: vec![
74                (StateEventType::RoomEncryption, "".to_owned()),
75                (StateEventType::RoomTombstone, "".to_owned()),
76            ],
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    }
85
86    /// 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.
91    #[cfg(not(target_family = "wasm"))]
92    pub fn once_built<C>(mut self, callback: C) -> Self
93    where
94        C: Fn(SlidingSyncList) -> SlidingSyncList + Send + Sync + 'static,
95    {
96        self.once_built = Arc::new(Box::new(callback));
97        self
98    }
99
100    /// Runs a callback once the list has been built.
101    ///
102    /// If the list was cached, then the cached fields won't be available in
103    /// this callback. Use the streams to get published versions of the
104    /// cached fields, once they've been set.
105    #[cfg(target_family = "wasm")]
106    pub fn once_built<C>(mut self, callback: C) -> Self
107    where
108        C: Fn(SlidingSyncList) -> SlidingSyncList + 'static,
109    {
110        self.once_built = Arc::new(Box::new(callback));
111        self
112    }
113
114    /// Which SlidingSyncMode to start this list under.
115    pub fn sync_mode(mut self, value: impl Into<SlidingSyncMode>) -> Self {
116        self.sync_mode = value.into();
117        self
118    }
119
120    /// Required states to return per room.
121    pub fn required_state(mut self, value: Vec<(StateEventType, String)>) -> Self {
122        self.required_state = value;
123        self
124    }
125
126    /// Any filters to apply to the query.
127    pub fn filters(mut self, value: Option<http::request::ListFilters>) -> Self {
128        self.filters = value;
129        self
130    }
131
132    /// Set the limit of regular events to fetch for the timeline.
133    pub fn timeline_limit(mut self, timeline_limit: Bound) -> Self {
134        self.timeline_limit = timeline_limit;
135        self
136    }
137
138    /// Set the limit of regular events to fetch for the timeline to 0.
139    pub fn no_timeline_limit(mut self) -> Self {
140        self.timeline_limit = 0;
141        self
142    }
143
144    /// Marks this list as sync'd from the cache, and attempts to reload it from
145    /// storage.
146    ///
147    /// Returns a mapping of the room's data read from the cache, to be
148    /// incorporated into the `SlidingSync` bookkeepping.
149    pub(in super::super) async fn set_cached_and_reload(
150        &mut self,
151        client: &Client,
152        storage_key: &str,
153    ) -> crate::Result<()> {
154        self.cache_policy = SlidingSyncListCachePolicy::Enabled;
155
156        if let Some(frozen_list) =
157            restore_sliding_sync_list(client.state_store(), storage_key, &self.name).await?
158        {
159            assert!(
160                self.reloaded_cached_data.is_none(),
161                "can't call `set_cached_and_reload` twice"
162            );
163            self.reloaded_cached_data = Some(SlidingSyncListCachedData {
164                maximum_number_of_rooms: frozen_list.maximum_number_of_rooms,
165            });
166            Ok(())
167        } else {
168            Ok(())
169        }
170    }
171
172    /// Build the list.
173    pub(in super::super) fn build(
174        self,
175        sliding_sync_internal_channel_sender: Sender<SlidingSyncInternalMessage>,
176    ) -> SlidingSyncList {
177        let list = SlidingSyncList {
178            inner: Arc::new(SlidingSyncListInner {
179                #[cfg(any(test, feature = "testing"))]
180                sync_mode: StdRwLock::new(self.sync_mode.clone()),
181
182                // From the builder
183                sticky: StdRwLock::new(SlidingSyncStickyManager::new(
184                    SlidingSyncListStickyParameters::new(self.required_state, self.filters),
185                )),
186                timeline_limit: StdRwLock::new(self.timeline_limit),
187                name: self.name,
188                cache_policy: self.cache_policy,
189
190                // Computed from the builder.
191                request_generator: StdRwLock::new(SlidingSyncListRequestGenerator::new(
192                    self.sync_mode,
193                )),
194
195                // Values read from deserialization, or that are still equal to the default values
196                // otherwise.
197                state: StdRwLock::new(Observable::new(Default::default())),
198                maximum_number_of_rooms: SharedObservable::new(None),
199
200                // Internal data.
201                sliding_sync_internal_channel_sender,
202            }),
203        };
204
205        let once_built = self.once_built;
206
207        let list = once_built(list);
208
209        // If we reloaded from the cache, update values in the list here.
210        //
211        // Note about ordering: because of the contract with the observables, the
212        // initial values, if filled, have to be observable in the `once_built`
213        // callback. That's why we're doing this here *after* constructing the
214        // list, and not a few lines above.
215
216        if let Some(SlidingSyncListCachedData { maximum_number_of_rooms }) =
217            self.reloaded_cached_data
218        {
219            // Mark state as preloaded.
220            Observable::set(
221                &mut list.inner.state.write().unwrap(),
222                SlidingSyncListLoadingState::Preloaded,
223            );
224
225            // Reload the maximum number of rooms.
226            list.inner.maximum_number_of_rooms.set(maximum_number_of_rooms);
227        }
228
229        list
230    }
231}