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 matrix_sdk_base::sliding_sync::http;
11use ruma::events::StateEventType;
12use tokio::sync::broadcast::Sender;
13
14use super::{
15    super::SlidingSyncInternalMessage, Bound, SlidingSyncList, SlidingSyncListCachePolicy,
16    SlidingSyncListInner, SlidingSyncListLoadingState, SlidingSyncListRequestGenerator,
17    SlidingSyncListStickyParameters, SlidingSyncMode,
18};
19use crate::{
20    sliding_sync::{cache::restore_sliding_sync_list, sticky_parameters::SlidingSyncStickyManager},
21    Client,
22};
23
24/// Data that might have been read from the cache.
25#[derive(Clone)]
26struct SlidingSyncListCachedData {
27    /// Total number of rooms that is possible to interact with the given list.
28    /// See also comment of [`SlidingSyncList::maximum_number_of_rooms`].
29    /// May be reloaded from the cache.
30    maximum_number_of_rooms: Option<u32>,
31}
32
33/// Builder for [`SlidingSyncList`].
34#[derive(Clone)]
35pub struct SlidingSyncListBuilder {
36    sync_mode: SlidingSyncMode,
37    required_state: Vec<(StateEventType, String)>,
38    include_heroes: Option<bool>,
39    filters: Option<http::request::ListFilters>,
40    timeline_limit: Bound,
41    pub(crate) name: String,
42
43    /// Should this list be cached and reloaded from the cache?
44    cache_policy: SlidingSyncListCachePolicy,
45
46    /// If set, temporary data that's been read from the cache, reloaded from a
47    /// `FrozenSlidingSyncList`.
48    reloaded_cached_data: Option<SlidingSyncListCachedData>,
49
50    once_built: Arc<Box<dyn Fn(SlidingSyncList) -> SlidingSyncList + Send + Sync>>,
51}
52
53#[cfg(not(tarpaulin_include))]
54impl fmt::Debug for SlidingSyncListBuilder {
55    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
56        // Print debug values for the builder, except `once_built` which is ignored.
57        formatter
58            .debug_struct("SlidingSyncListBuilder")
59            .field("sync_mode", &self.sync_mode)
60            .field("required_state", &self.required_state)
61            .field("include_heroes", &self.include_heroes)
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            include_heroes: None,
78            filters: None,
79            timeline_limit: 1,
80            name: name.into(),
81            reloaded_cached_data: None,
82            cache_policy: SlidingSyncListCachePolicy::Disabled,
83            once_built: Arc::new(Box::new(identity)),
84        }
85    }
86
87    /// Runs a callback once the list has been built.
88    ///
89    /// If the list was cached, then the cached fields won't be available in
90    /// this callback. Use the streams to get published versions of the
91    /// cached fields, once they've been set.
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    /// Which SlidingSyncMode to start this list under.
101    pub fn sync_mode(mut self, value: impl Into<SlidingSyncMode>) -> Self {
102        self.sync_mode = value.into();
103        self
104    }
105
106    /// Required states to return per room.
107    pub fn required_state(mut self, value: Vec<(StateEventType, String)>) -> Self {
108        self.required_state = value;
109        self
110    }
111
112    /// Include heroes.
113    pub fn include_heroes(mut self, value: Option<bool>) -> Self {
114        self.include_heroes = value;
115        self
116    }
117
118    /// Any filters to apply to the query.
119    pub fn filters(mut self, value: Option<http::request::ListFilters>) -> Self {
120        self.filters = value;
121        self
122    }
123
124    /// Set the limit of regular events to fetch for the timeline.
125    pub fn timeline_limit(mut self, timeline_limit: Bound) -> Self {
126        self.timeline_limit = timeline_limit;
127        self
128    }
129
130    /// Set the limit of regular events to fetch for the timeline to 0.
131    pub fn no_timeline_limit(mut self) -> Self {
132        self.timeline_limit = 0;
133        self
134    }
135
136    /// Marks this list as sync'd from the cache, and attempts to reload it from
137    /// storage.
138    ///
139    /// Returns a mapping of the room's data read from the cache, to be
140    /// incorporated into the `SlidingSync` bookkeepping.
141    pub(in super::super) async fn set_cached_and_reload(
142        &mut self,
143        client: &Client,
144        storage_key: &str,
145    ) -> crate::Result<()> {
146        self.cache_policy = SlidingSyncListCachePolicy::Enabled;
147
148        if let Some(frozen_list) =
149            restore_sliding_sync_list(client.store(), storage_key, &self.name).await?
150        {
151            assert!(
152                self.reloaded_cached_data.is_none(),
153                "can't call `set_cached_and_reload` twice"
154            );
155            self.reloaded_cached_data = Some(SlidingSyncListCachedData {
156                maximum_number_of_rooms: frozen_list.maximum_number_of_rooms,
157            });
158            Ok(())
159        } else {
160            Ok(())
161        }
162    }
163
164    /// Build the list.
165    pub(in super::super) fn build(
166        self,
167        sliding_sync_internal_channel_sender: Sender<SlidingSyncInternalMessage>,
168    ) -> SlidingSyncList {
169        let list = SlidingSyncList {
170            inner: Arc::new(SlidingSyncListInner {
171                #[cfg(any(test, feature = "testing"))]
172                sync_mode: StdRwLock::new(self.sync_mode.clone()),
173
174                // From the builder
175                sticky: StdRwLock::new(SlidingSyncStickyManager::new(
176                    SlidingSyncListStickyParameters::new(
177                        self.required_state,
178                        self.include_heroes,
179                        self.filters,
180                    ),
181                )),
182                timeline_limit: StdRwLock::new(self.timeline_limit),
183                name: self.name,
184                cache_policy: self.cache_policy,
185
186                // Computed from the builder.
187                request_generator: StdRwLock::new(SlidingSyncListRequestGenerator::new(
188                    self.sync_mode,
189                )),
190
191                // Values read from deserialization, or that are still equal to the default values
192                // otherwise.
193                state: StdRwLock::new(Observable::new(Default::default())),
194                maximum_number_of_rooms: SharedObservable::new(None),
195
196                // Internal data.
197                sliding_sync_internal_channel_sender,
198            }),
199        };
200
201        let once_built = self.once_built;
202
203        let list = once_built(list);
204
205        // If we reloaded from the cache, update values in the list here.
206        //
207        // Note about ordering: because of the contract with the observables, the
208        // initial values, if filled, have to be observable in the `once_built`
209        // callback. That's why we're doing this here *after* constructing the
210        // list, and not a few lines above.
211
212        if let Some(SlidingSyncListCachedData { maximum_number_of_rooms }) =
213            self.reloaded_cached_data
214        {
215            // Mark state as preloaded.
216            Observable::set(
217                &mut list.inner.state.write().unwrap(),
218                SlidingSyncListLoadingState::Preloaded,
219            );
220
221            // Reload the maximum number of rooms.
222            list.inner.maximum_number_of_rooms.set(maximum_number_of_rooms);
223        }
224
225        list
226    }
227}