1mod builder;
2mod frozen;
3mod request_generator;
4mod sticky;
5
6use std::{
7 fmt,
8 ops::RangeInclusive,
9 sync::{Arc, RwLock as StdRwLock},
10};
11
12use eyeball::{SharedObservable, Subscriber};
13use futures_core::Stream;
14use ruma::{TransactionId, api::client::sync::sync_events::v5 as http, assign};
15use serde::{Deserialize, Serialize};
16use tokio::sync::broadcast::Sender;
17use tracing::{instrument, warn};
18
19pub use self::builder::*;
20use self::sticky::SlidingSyncListStickyParameters;
21pub(super) use self::{frozen::FrozenSlidingSyncList, request_generator::*};
22use super::{
23 Error, SlidingSyncInternalMessage,
24 sticky_parameters::{LazyTransactionId, SlidingSyncStickyManager},
25};
26use crate::Result;
27
28#[derive(Clone, Copy, Debug)]
31pub(crate) enum SlidingSyncListCachePolicy {
32 Enabled,
34 Disabled,
36}
37
38pub type Bound = u32;
41
42pub type Range = RangeInclusive<Bound>;
44
45pub type Ranges = Vec<Range>;
47
48#[derive(Clone, Debug)]
52pub struct SlidingSyncList {
53 inner: Arc<SlidingSyncListInner>,
54}
55
56impl SlidingSyncList {
57 pub fn builder(name: impl Into<String>) -> SlidingSyncListBuilder {
59 SlidingSyncListBuilder::new(name)
60 }
61
62 pub fn name(&self) -> &str {
64 self.inner.name.as_str()
65 }
66
67 pub fn set_sync_mode<M>(&self, sync_mode: M)
78 where
79 M: Into<SlidingSyncMode>,
80 {
81 self.inner.set_sync_mode(sync_mode.into());
82
83 self.inner.internal_channel_send_if_possible(
86 SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration,
87 );
88 }
89
90 pub fn state(&self) -> SlidingSyncListLoadingState {
92 self.inner.state.get()
93 }
94
95 pub(super) fn requires_timeout(&self) -> bool {
100 let request_generator = &*self.inner.request_generator.read().unwrap();
101
102 (self.inner.requires_timeout)(request_generator)
103 }
104
105 pub fn state_stream(
116 &self,
117 ) -> (SlidingSyncListLoadingState, impl Stream<Item = SlidingSyncListLoadingState>) {
118 (self.inner.state.get(), self.inner.state.subscribe())
119 }
120
121 pub fn timeline_limit(&self) -> Bound {
123 *self.inner.timeline_limit.read().unwrap()
124 }
125
126 pub fn set_timeline_limit(&self, timeline: Bound) {
128 *self.inner.timeline_limit.write().unwrap() = timeline;
129 }
130
131 pub fn maximum_number_of_rooms(&self) -> Option<u32> {
134 self.inner.maximum_number_of_rooms.get()
135 }
136
137 pub fn maximum_number_of_rooms_stream(&self) -> Subscriber<Option<u32>> {
145 self.inner.maximum_number_of_rooms.subscribe()
146 }
147
148 pub(super) fn next_request(
153 &self,
154 txn_id: &mut LazyTransactionId,
155 ) -> Result<http::request::List, Error> {
156 self.inner.next_request(txn_id)
157 }
158
159 pub(super) fn cache_policy(&self) -> SlidingSyncListCachePolicy {
161 self.inner.cache_policy
162 }
163
164 #[instrument(skip(self), fields(name = self.name()))]
172 pub(super) fn update(&mut self, maximum_number_of_rooms: Option<u32>) -> Result<bool, Error> {
173 if let Some(maximum_number_of_rooms) = maximum_number_of_rooms {
176 self.inner.update_request_generator_state(maximum_number_of_rooms)?;
177 }
178
179 let new_changes = self.inner.update_room_list(maximum_number_of_rooms)?;
180
181 Ok(new_changes)
182 }
183
184 pub fn maybe_commit_sticky(&mut self, txn_id: &TransactionId) {
186 self.inner.sticky.write().unwrap().maybe_commit(txn_id);
187 }
188
189 pub(super) fn invalidate_sticky_data(&self) {
192 let _ = self.inner.sticky.write().unwrap().data_mut();
193 }
194
195 #[cfg(feature = "testing")]
197 pub fn sync_mode(&self) -> SlidingSyncMode {
198 self.inner.sync_mode.read().unwrap().clone()
199 }
200
201 pub(super) fn set_maximum_number_of_rooms(&self, maximum_number_of_rooms: Option<u32>) {
203 self.inner.maximum_number_of_rooms.set(maximum_number_of_rooms);
204 }
205}
206
207pub(super) struct SlidingSyncListInner {
208 name: String,
210
211 state: SharedObservable<SlidingSyncListLoadingState>,
213
214 #[cfg(not(target_family = "wasm"))]
216 requires_timeout: Arc<dyn Fn(&SlidingSyncListRequestGenerator) -> bool + Send + Sync>,
217 #[cfg(target_family = "wasm")]
218 requires_timeout: Arc<dyn Fn(&SlidingSyncListRequestGenerator) -> bool>,
219
220 sticky: StdRwLock<SlidingSyncStickyManager<SlidingSyncListStickyParameters>>,
224
225 timeline_limit: StdRwLock<Bound>,
227
228 maximum_number_of_rooms: SharedObservable<Option<u32>>,
236
237 request_generator: StdRwLock<SlidingSyncListRequestGenerator>,
240
241 cache_policy: SlidingSyncListCachePolicy,
243
244 sliding_sync_internal_channel_sender: Sender<SlidingSyncInternalMessage>,
247
248 #[cfg(any(test, feature = "testing"))]
249 sync_mode: StdRwLock<SlidingSyncMode>,
250}
251
252impl fmt::Debug for SlidingSyncListInner {
253 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254 f.debug_struct("SlidingSyncListInner")
255 .field("name", &self.name)
256 .field("state", &self.state)
257 .finish()
258 }
259}
260
261impl SlidingSyncListInner {
262 pub fn set_sync_mode(&self, sync_mode: SlidingSyncMode) {
269 #[cfg(any(test, feature = "testing"))]
270 {
271 *self.sync_mode.write().unwrap() = sync_mode.clone();
272 }
273
274 {
275 let mut request_generator = self.request_generator.write().unwrap();
276 *request_generator = SlidingSyncListRequestGenerator::new(sync_mode);
277 }
278
279 self.state.update(|state| {
280 *state = match state {
281 SlidingSyncListLoadingState::NotLoaded => SlidingSyncListLoadingState::NotLoaded,
282 SlidingSyncListLoadingState::Preloaded => SlidingSyncListLoadingState::Preloaded,
283 SlidingSyncListLoadingState::PartiallyLoaded
284 | SlidingSyncListLoadingState::FullyLoaded => {
285 SlidingSyncListLoadingState::PartiallyLoaded
286 }
287 }
288 });
289 }
290
291 fn next_request(&self, txn_id: &mut LazyTransactionId) -> Result<http::request::List, Error> {
293 let ranges = {
294 let mut request_generator = self.request_generator.write().unwrap();
296 request_generator.generate_next_ranges(self.maximum_number_of_rooms.get())?
297 };
298
299 Ok(self.request(ranges, txn_id))
301 }
302
303 #[instrument(skip(self), fields(name = self.name))]
306 fn request(&self, ranges: Ranges, txn_id: &mut LazyTransactionId) -> http::request::List {
307 let ranges = ranges.into_iter().map(|r| ((*r.start()).into(), (*r.end()).into())).collect();
308
309 let mut request = assign!(http::request::List::default(), { ranges });
310 request.room_details.timeline_limit = (*self.timeline_limit.read().unwrap()).into();
311
312 {
313 let mut sticky = self.sticky.write().unwrap();
314 sticky.maybe_apply(&mut request, txn_id);
315 }
316
317 request
318 }
319
320 fn update_room_list(&self, maximum_number_of_rooms: Option<u32>) -> Result<bool, Error> {
325 let mut new_changes = false;
326
327 if maximum_number_of_rooms.is_some() {
328 if self.maximum_number_of_rooms.set_if_not_eq(maximum_number_of_rooms).is_some() {
330 new_changes = true;
331 }
332 }
333
334 Ok(new_changes)
335 }
336
337 fn update_request_generator_state(&self, maximum_number_of_rooms: u32) -> Result<(), Error> {
340 let mut request_generator = self.request_generator.write().unwrap();
341
342 let new_state = request_generator.handle_response(&self.name, maximum_number_of_rooms)?;
343 self.state.set_if_not_eq(new_state);
344
345 Ok(())
346 }
347
348 #[instrument]
351 fn internal_channel_send_if_possible(&self, message: SlidingSyncInternalMessage) {
352 let _ = self.sliding_sync_internal_channel_sender.send(message);
354 }
355}
356
357#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
367pub enum SlidingSyncListLoadingState {
368 #[default]
370 NotLoaded,
371
372 Preloaded,
374
375 PartiallyLoaded,
378
379 FullyLoaded,
382}
383
384#[cfg(test)]
385impl SlidingSyncListLoadingState {
386 fn is_fully_loaded(&self) -> bool {
388 matches!(self, Self::FullyLoaded)
389 }
390}
391
392#[derive(Clone, Debug, Default)]
396pub struct SlidingSyncSelectiveModeBuilder {
397 ranges: Ranges,
398}
399
400impl SlidingSyncSelectiveModeBuilder {
401 fn new() -> Self {
403 Self::default()
404 }
405
406 pub fn add_range(mut self, range: Range) -> Self {
408 self.ranges.push(range);
409 self
410 }
411
412 pub fn add_ranges(mut self, ranges: Ranges) -> Self {
414 self.ranges.extend(ranges);
415 self
416 }
417}
418
419impl From<SlidingSyncSelectiveModeBuilder> for SlidingSyncMode {
420 fn from(builder: SlidingSyncSelectiveModeBuilder) -> Self {
421 Self::Selective { ranges: builder.ranges }
422 }
423}
424
425#[derive(Clone, Debug)]
426enum WindowedModeBuilderKind {
427 Paging,
428 Growing,
429}
430
431#[derive(Clone, Debug)]
433pub struct SlidingSyncWindowedModeBuilder {
434 mode: WindowedModeBuilderKind,
435 batch_size: u32,
436 maximum_number_of_rooms_to_fetch: Option<u32>,
437}
438
439impl SlidingSyncWindowedModeBuilder {
440 fn new(mode: WindowedModeBuilderKind, batch_size: u32) -> Self {
441 Self { mode, batch_size, maximum_number_of_rooms_to_fetch: None }
442 }
443
444 pub fn maximum_number_of_rooms_to_fetch(mut self, num: u32) -> Self {
446 self.maximum_number_of_rooms_to_fetch = Some(num);
447 self
448 }
449}
450
451impl From<SlidingSyncWindowedModeBuilder> for SlidingSyncMode {
452 fn from(builder: SlidingSyncWindowedModeBuilder) -> Self {
453 match builder.mode {
454 WindowedModeBuilderKind::Paging => Self::Paging {
455 batch_size: builder.batch_size,
456 maximum_number_of_rooms_to_fetch: builder.maximum_number_of_rooms_to_fetch,
457 },
458 WindowedModeBuilderKind::Growing => Self::Growing {
459 batch_size: builder.batch_size,
460 maximum_number_of_rooms_to_fetch: builder.maximum_number_of_rooms_to_fetch,
461 },
462 }
463 }
464}
465
466#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
468pub enum SlidingSyncMode {
469 Selective {
471 ranges: Ranges,
473 },
474
475 Paging {
479 batch_size: u32,
481
482 maximum_number_of_rooms_to_fetch: Option<u32>,
485 },
486
487 Growing {
491 batch_size: u32,
493
494 maximum_number_of_rooms_to_fetch: Option<u32>,
497 },
498}
499
500impl Default for SlidingSyncMode {
501 fn default() -> Self {
502 Self::Selective { ranges: Vec::new() }
503 }
504}
505
506impl SlidingSyncMode {
507 pub fn new_selective() -> SlidingSyncSelectiveModeBuilder {
509 SlidingSyncSelectiveModeBuilder::new()
510 }
511
512 pub fn new_paging(batch_size: u32) -> SlidingSyncWindowedModeBuilder {
514 SlidingSyncWindowedModeBuilder::new(WindowedModeBuilderKind::Paging, batch_size)
515 }
516
517 pub fn new_growing(batch_size: u32) -> SlidingSyncWindowedModeBuilder {
519 SlidingSyncWindowedModeBuilder::new(WindowedModeBuilderKind::Growing, batch_size)
520 }
521}
522
523#[cfg(test)]
524mod tests {
525 use std::{
526 cell::Cell,
527 ops::Not,
528 sync::{Arc, Mutex},
529 };
530
531 use matrix_sdk_test::async_test;
532 use ruma::uint;
533 use serde_json::json;
534 use tokio::sync::broadcast::{channel, error::TryRecvError};
535
536 use super::{SlidingSyncList, SlidingSyncListLoadingState, SlidingSyncMode};
537 use crate::sliding_sync::{SlidingSyncInternalMessage, sticky_parameters::LazyTransactionId};
538
539 macro_rules! assert_json_roundtrip {
540 (from $type:ty: $rust_value:expr => $json_value:expr) => {
541 let json = serde_json::to_value(&$rust_value).unwrap();
542 assert_eq!(json, $json_value);
543
544 let rust: $type = serde_json::from_value(json).unwrap();
545 assert_eq!(rust, $rust_value);
546 };
547 }
548
549 #[async_test]
550 async fn test_sliding_sync_list_selective_mode() {
551 let (sender, mut receiver) = channel(1);
552
553 let list = SlidingSyncList::builder("foo")
555 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=1).add_range(2..=3))
556 .build(sender);
557
558 {
559 let mut generator = list.inner.request_generator.write().unwrap();
560 assert_eq!(generator.requested_ranges(), &[0..=1, 2..=3]);
561
562 let ranges = generator.generate_next_ranges(None).unwrap();
563 assert_eq!(ranges, &[0..=1, 2..=3]);
564 }
565
566 assert!(matches!(receiver.try_recv(), Err(TryRecvError::Empty)));
568
569 list.set_sync_mode(SlidingSyncMode::new_selective().add_range(4..=5));
570
571 {
572 let mut generator = list.inner.request_generator.write().unwrap();
573 assert_eq!(generator.requested_ranges(), &[4..=5]);
574
575 let ranges = generator.generate_next_ranges(None).unwrap();
576 assert_eq!(ranges, &[4..=5]);
577 }
578
579 assert!(matches!(
581 receiver.try_recv(),
582 Ok(SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration)
583 ));
584 assert!(matches!(receiver.try_recv(), Err(TryRecvError::Empty)));
585 }
586
587 #[test]
588 fn test_sliding_sync_list_timeline_limit() {
589 let (sender, _receiver) = channel(1);
590
591 let list = SlidingSyncList::builder("foo")
592 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=1))
593 .timeline_limit(7)
594 .build(sender);
595
596 assert_eq!(list.timeline_limit(), 7);
597
598 list.set_timeline_limit(42);
599 assert_eq!(list.timeline_limit(), 42);
600 }
601
602 macro_rules! assert_ranges {
603 (
604 list = $list:ident,
605 list_state = $first_list_state:ident,
606 maximum_number_of_rooms = $maximum_number_of_rooms:expr,
607 requires_timeout = $initial_requires_timeout:literal,
608 $(
609 next => {
610 ranges = $( $range_start:literal ..= $range_end:literal ),* ,
611 is_fully_loaded = $is_fully_loaded:expr,
612 list_state = $list_state:ident,
613 requires_timeout = $requires_timeout:literal,
614 }
615 ),*
616 $(,)*
617 ) => {
618 assert_eq!($list.state(), SlidingSyncListLoadingState::$first_list_state, "first state");
619 assert_eq!(
620 $list.requires_timeout(),
621 $initial_requires_timeout,
622 "initial requires_timeout",
623 );
624
625 $(
626 {
627 let request = $list.next_request(&mut LazyTransactionId::new()).unwrap();
629
630 assert_eq!(
631 request.ranges,
632 [
633 $( (uint!( $range_start ), uint!( $range_end )) ),*
634 ],
635 "ranges",
636 );
637
638 let _ = $list.update(Some($maximum_number_of_rooms));
640
641 assert_eq!(
642 $list.inner.request_generator.read().unwrap().is_fully_loaded(),
643 $is_fully_loaded,
644 "is fully loaded",
645 );
646 assert_eq!(
647 $list.state(),
648 SlidingSyncListLoadingState::$list_state,
649 "state",
650 );
651 assert_eq!(
652 $list.requires_timeout(),
653 $requires_timeout,
654 "requires_timeout",
655 );
656 }
657 )*
658 };
659 }
660
661 #[test]
662 fn test_generator_paging_full_sync() {
663 let (sender, _receiver) = channel(1);
664
665 let mut list = SlidingSyncList::builder("testing")
666 .sync_mode(SlidingSyncMode::new_paging(10))
667 .build(sender);
668
669 assert_ranges! {
670 list = list,
671 list_state = NotLoaded,
672 maximum_number_of_rooms = 25,
673 requires_timeout = false,
674 next => {
675 ranges = 0..=9,
676 is_fully_loaded = false,
677 list_state = PartiallyLoaded,
678 requires_timeout = false,
679 },
680 next => {
681 ranges = 10..=19,
682 is_fully_loaded = false,
683 list_state = PartiallyLoaded,
684 requires_timeout = false,
685 },
686 next => {
688 ranges = 20..=24,
689 is_fully_loaded = true,
690 list_state = FullyLoaded,
691 requires_timeout = true,
692 },
693 next => {
695 ranges = 0..=24, is_fully_loaded = true,
697 list_state = FullyLoaded,
698 requires_timeout = true,
699 },
700 next => {
701 ranges = 0..=24,
702 is_fully_loaded = true,
703 list_state = FullyLoaded,
704 requires_timeout = true,
705 },
706 };
707 }
708
709 #[test]
710 fn test_generator_paging_full_sync_with_a_maximum_number_of_rooms_to_fetch() {
711 let (sender, _receiver) = channel(1);
712
713 let mut list = SlidingSyncList::builder("testing")
714 .sync_mode(SlidingSyncMode::new_paging(10).maximum_number_of_rooms_to_fetch(22))
715 .build(sender);
716
717 assert_ranges! {
718 list = list,
719 list_state = NotLoaded,
720 maximum_number_of_rooms = 25,
721 requires_timeout = false,
722 next => {
723 ranges = 0..=9,
724 is_fully_loaded = false,
725 list_state = PartiallyLoaded,
726 requires_timeout = false,
727 },
728 next => {
729 ranges = 10..=19,
730 is_fully_loaded = false,
731 list_state = PartiallyLoaded,
732 requires_timeout = false,
733 },
734 next => {
736 ranges = 20..=21,
737 is_fully_loaded = true,
738 list_state = FullyLoaded,
739 requires_timeout = true,
740 },
741 next => {
743 ranges = 0..=21, is_fully_loaded = true,
745 list_state = FullyLoaded,
746 requires_timeout = true,
747 },
748 next => {
749 ranges = 0..=21,
750 is_fully_loaded = true,
751 list_state = FullyLoaded,
752 requires_timeout = true,
753 },
754 };
755 }
756
757 #[test]
758 fn test_generator_growing_full_sync() {
759 let (sender, _receiver) = channel(1);
760
761 let mut list = SlidingSyncList::builder("testing")
762 .sync_mode(SlidingSyncMode::new_growing(10))
763 .build(sender);
764
765 assert_ranges! {
766 list = list,
767 list_state = NotLoaded,
768 maximum_number_of_rooms = 25,
769 requires_timeout = false,
770 next => {
771 ranges = 0..=9,
772 is_fully_loaded = false,
773 list_state = PartiallyLoaded,
774 requires_timeout = false,
775 },
776 next => {
777 ranges = 0..=19,
778 is_fully_loaded = false,
779 list_state = PartiallyLoaded,
780 requires_timeout = false,
781 },
782 next => {
784 ranges = 0..=24,
785 is_fully_loaded = true,
786 list_state = FullyLoaded,
787 requires_timeout = true,
788 },
789 next => {
791 ranges = 0..=24,
792 is_fully_loaded = true,
793 list_state = FullyLoaded,
794 requires_timeout = true,
795 },
796 next => {
797 ranges = 0..=24,
798 is_fully_loaded = true,
799 list_state = FullyLoaded,
800 requires_timeout = true,
801 },
802 };
803 }
804
805 #[test]
806 fn test_generator_growing_full_sync_with_a_maximum_number_of_rooms_to_fetch() {
807 let (sender, _receiver) = channel(1);
808
809 let mut list = SlidingSyncList::builder("testing")
810 .sync_mode(SlidingSyncMode::new_growing(10).maximum_number_of_rooms_to_fetch(22))
811 .build(sender);
812
813 assert_ranges! {
814 list = list,
815 list_state = NotLoaded,
816 maximum_number_of_rooms = 25,
817 requires_timeout = false,
818 next => {
819 ranges = 0..=9,
820 is_fully_loaded = false,
821 list_state = PartiallyLoaded,
822 requires_timeout = false,
823 },
824 next => {
825 ranges = 0..=19,
826 is_fully_loaded = false,
827 list_state = PartiallyLoaded,
828 requires_timeout = false,
829 },
830 next => {
832 ranges = 0..=21,
833 is_fully_loaded = true,
834 list_state = FullyLoaded,
835 requires_timeout = true,
836 },
837 next => {
839 ranges = 0..=21,
840 is_fully_loaded = true,
841 list_state = FullyLoaded,
842 requires_timeout = true,
843 },
844 next => {
845 ranges = 0..=21,
846 is_fully_loaded = true,
847 list_state = FullyLoaded,
848 requires_timeout = true,
849 },
850 };
851 }
852
853 #[test]
854 fn test_generator_selective() {
855 let (sender, _receiver) = channel(1);
856
857 let mut list = SlidingSyncList::builder("testing")
858 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10).add_range(42..=153))
859 .build(sender);
860
861 assert_ranges! {
862 list = list,
863 list_state = NotLoaded,
864 maximum_number_of_rooms = 25,
865 requires_timeout = true,
866 next => {
868 ranges = 0..=10, 42..=153,
869 is_fully_loaded = true,
870 list_state = FullyLoaded,
871 requires_timeout = true,
872 },
873 next => {
875 ranges = 0..=10, 42..=153,
876 is_fully_loaded = true,
877 list_state = FullyLoaded,
878 requires_timeout = true,
879 },
880 next => {
881 ranges = 0..=10, 42..=153,
882 is_fully_loaded = true,
883 list_state = FullyLoaded,
884 requires_timeout = true,
885 }
886 };
887 }
888
889 #[async_test]
890 async fn test_generator_selective_with_modifying_ranges_on_the_fly() {
891 let (sender, _receiver) = channel(4);
892
893 let mut list = SlidingSyncList::builder("testing")
894 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10).add_range(42..=153))
895 .build(sender);
896
897 assert_ranges! {
898 list = list,
899 list_state = NotLoaded,
900 maximum_number_of_rooms = 25,
901 requires_timeout = true,
902 next => {
904 ranges = 0..=10, 42..=153,
905 is_fully_loaded = true,
906 list_state = FullyLoaded,
907 requires_timeout = true,
908 },
909 next => {
911 ranges = 0..=10, 42..=153,
912 is_fully_loaded = true,
913 list_state = FullyLoaded,
914 requires_timeout = true,
915 },
916 next => {
917 ranges = 0..=10, 42..=153,
918 is_fully_loaded = true,
919 list_state = FullyLoaded,
920 requires_timeout = true,
921 }
922 };
923
924 list.set_sync_mode(SlidingSyncMode::new_selective().add_range(3..=7));
925
926 assert_ranges! {
927 list = list,
928 list_state = PartiallyLoaded,
929 maximum_number_of_rooms = 25,
930 requires_timeout = true,
931 next => {
932 ranges = 3..=7,
933 is_fully_loaded = true,
934 list_state = FullyLoaded,
935 requires_timeout = true,
936 },
937 };
938
939 list.set_sync_mode(SlidingSyncMode::new_selective().add_range(42..=77));
940
941 assert_ranges! {
942 list = list,
943 list_state = PartiallyLoaded,
944 maximum_number_of_rooms = 25,
945 requires_timeout = true,
946 next => {
947 ranges = 42..=77,
948 is_fully_loaded = true,
949 list_state = FullyLoaded,
950 requires_timeout = true,
951 },
952 };
953
954 list.set_sync_mode(SlidingSyncMode::new_selective());
955
956 assert_ranges! {
957 list = list,
958 list_state = PartiallyLoaded,
959 maximum_number_of_rooms = 25,
960 requires_timeout = true,
961 next => {
962 ranges = ,
963 is_fully_loaded = true,
964 list_state = FullyLoaded,
965 requires_timeout = true,
966 },
967 };
968 }
969
970 #[async_test]
971 async fn test_generator_changing_sync_mode_to_various_modes() {
972 let (sender, _receiver) = channel(4);
973
974 let mut list = SlidingSyncList::builder("testing")
975 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10).add_range(42..=153))
976 .build(sender);
977
978 assert_ranges! {
979 list = list,
980 list_state = NotLoaded,
981 maximum_number_of_rooms = 25,
982 requires_timeout = true,
983 next => {
985 ranges = 0..=10, 42..=153,
986 is_fully_loaded = true,
987 list_state = FullyLoaded,
988 requires_timeout = true,
989 },
990 next => {
992 ranges = 0..=10, 42..=153,
993 is_fully_loaded = true,
994 list_state = FullyLoaded,
995 requires_timeout = true,
996 },
997 next => {
998 ranges = 0..=10, 42..=153,
999 is_fully_loaded = true,
1000 list_state = FullyLoaded,
1001 requires_timeout = true,
1002 }
1003 };
1004
1005 list.set_sync_mode(SlidingSyncMode::new_growing(10));
1007
1008 assert_ranges! {
1009 list = list,
1010 list_state = PartiallyLoaded, maximum_number_of_rooms = 25,
1012 requires_timeout = false,
1013 next => {
1014 ranges = 0..=9,
1015 is_fully_loaded = false,
1016 list_state = PartiallyLoaded,
1017 requires_timeout = false,
1018 },
1019 next => {
1020 ranges = 0..=19,
1021 is_fully_loaded = false,
1022 list_state = PartiallyLoaded,
1023 requires_timeout = false,
1024 },
1025 next => {
1027 ranges = 0..=24,
1028 is_fully_loaded = true,
1029 list_state = FullyLoaded,
1030 requires_timeout = true,
1031 },
1032 next => {
1034 ranges = 0..=24,
1035 is_fully_loaded = true,
1036 list_state = FullyLoaded,
1037 requires_timeout = true,
1038 },
1039 next => {
1040 ranges = 0..=24,
1041 is_fully_loaded = true,
1042 list_state = FullyLoaded,
1043 requires_timeout = true,
1044 },
1045 };
1046
1047 list.set_sync_mode(SlidingSyncMode::new_paging(10));
1049
1050 assert_ranges! {
1051 list = list,
1052 list_state = PartiallyLoaded, maximum_number_of_rooms = 25,
1054 requires_timeout = false,
1055 next => {
1056 ranges = 0..=9,
1057 is_fully_loaded = false,
1058 list_state = PartiallyLoaded,
1059 requires_timeout = false,
1060 },
1061 next => {
1062 ranges = 10..=19,
1063 is_fully_loaded = false,
1064 list_state = PartiallyLoaded,
1065 requires_timeout = false,
1066 },
1067 next => {
1069 ranges = 20..=24,
1070 is_fully_loaded = true,
1071 list_state = FullyLoaded,
1072 requires_timeout = true,
1073 },
1074 next => {
1076 ranges = 0..=24, is_fully_loaded = true,
1078 list_state = FullyLoaded,
1079 requires_timeout = true,
1080 },
1081 next => {
1082 ranges = 0..=24,
1083 is_fully_loaded = true,
1084 list_state = FullyLoaded,
1085 requires_timeout = true,
1086 },
1087 };
1088
1089 list.set_sync_mode(SlidingSyncMode::new_selective());
1091
1092 assert_eq!(list.state(), SlidingSyncListLoadingState::PartiallyLoaded); list.set_sync_mode(SlidingSyncMode::new_selective().add_range(0..=100));
1098
1099 assert_ranges! {
1100 list = list,
1101 list_state = PartiallyLoaded, maximum_number_of_rooms = 25,
1103 requires_timeout = true,
1104 next => {
1106 ranges = 0..=100,
1107 is_fully_loaded = true,
1108 list_state = FullyLoaded,
1109 requires_timeout = true,
1110 },
1111 next => {
1113 ranges = 0..=100,
1114 is_fully_loaded = true,
1115 list_state = FullyLoaded,
1116 requires_timeout = true,
1117 },
1118 next => {
1119 ranges = 0..=100,
1120 is_fully_loaded = true,
1121 list_state = FullyLoaded,
1122 requires_timeout = true,
1123 }
1124 };
1125 }
1126
1127 #[async_test]
1128 #[allow(clippy::await_holding_lock)]
1129 async fn test_inner_update_maximum_number_of_rooms() {
1130 let (sender, _receiver) = channel(1);
1131
1132 let mut list = SlidingSyncList::builder("foo")
1133 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=3))
1134 .build(sender);
1135
1136 assert!(list.maximum_number_of_rooms().is_none());
1137
1138 let _ = list.next_request(&mut LazyTransactionId::new());
1140 let new_changes = list.update(Some(5)).unwrap();
1141 assert!(new_changes);
1142
1143 assert_eq!(list.maximum_number_of_rooms(), Some(5));
1145
1146 let _ = list.next_request(&mut LazyTransactionId::new());
1148 let new_changes = list.update(Some(5)).unwrap();
1149 assert!(!new_changes);
1150
1151 assert_eq!(list.maximum_number_of_rooms(), Some(5));
1153 }
1154
1155 #[test]
1156 fn test_sliding_sync_mode_serialization() {
1157 assert_json_roundtrip!(
1158 from SlidingSyncMode: SlidingSyncMode::from(SlidingSyncMode::new_paging(1).maximum_number_of_rooms_to_fetch(2)) => json!({
1159 "Paging": {
1160 "batch_size": 1,
1161 "maximum_number_of_rooms_to_fetch": 2
1162 }
1163 })
1164 );
1165 assert_json_roundtrip!(
1166 from SlidingSyncMode: SlidingSyncMode::from(SlidingSyncMode::new_growing(1).maximum_number_of_rooms_to_fetch(2)) => json!({
1167 "Growing": {
1168 "batch_size": 1,
1169 "maximum_number_of_rooms_to_fetch": 2
1170 }
1171 })
1172 );
1173 assert_json_roundtrip!(from SlidingSyncMode: SlidingSyncMode::from(SlidingSyncMode::new_selective()) => json!({
1174 "Selective": {
1175 "ranges": []
1176 }
1177 })
1178 );
1179 }
1180
1181 #[test]
1182 fn test_sliding_sync_list_loading_state_serialization() {
1183 assert_json_roundtrip!(from SlidingSyncListLoadingState: SlidingSyncListLoadingState::NotLoaded => json!("NotLoaded"));
1184 assert_json_roundtrip!(from SlidingSyncListLoadingState: SlidingSyncListLoadingState::Preloaded => json!("Preloaded"));
1185 assert_json_roundtrip!(from SlidingSyncListLoadingState: SlidingSyncListLoadingState::PartiallyLoaded => json!("PartiallyLoaded"));
1186 assert_json_roundtrip!(from SlidingSyncListLoadingState: SlidingSyncListLoadingState::FullyLoaded => json!("FullyLoaded"));
1187 }
1188
1189 #[test]
1190 fn test_sliding_sync_list_loading_state_is_fully_loaded() {
1191 assert!(SlidingSyncListLoadingState::NotLoaded.is_fully_loaded().not());
1192 assert!(SlidingSyncListLoadingState::Preloaded.is_fully_loaded().not());
1193 assert!(SlidingSyncListLoadingState::PartiallyLoaded.is_fully_loaded().not());
1194 assert!(SlidingSyncListLoadingState::FullyLoaded.is_fully_loaded());
1195 }
1196
1197 #[test]
1198 fn test_once_built() {
1199 let (sender, _receiver) = channel(1);
1200
1201 let probe = Arc::new(Mutex::new(Cell::new(false)));
1202 let probe_clone = probe.clone();
1203
1204 let _list = SlidingSyncList::builder("testing")
1205 .once_built(move |list| {
1206 let mut probe_lock = probe.lock().unwrap();
1207 *probe_lock.get_mut() = true;
1208
1209 list
1210 })
1211 .build(sender);
1212
1213 let probe_lock = probe_clone.lock().unwrap();
1214 assert!(probe_lock.get());
1215 }
1216}