1mod builder;
2mod frozen;
3mod request_generator;
4
5use std::{
6 fmt,
7 ops::RangeInclusive,
8 sync::{Arc, RwLock as StdRwLock},
9};
10
11use eyeball::{SharedObservable, Subscriber};
12use futures_core::Stream;
13use ruma::{api::client::sync::sync_events::v5 as http, assign, events::StateEventType};
14use serde::{Deserialize, Serialize};
15use tokio::sync::broadcast::Sender;
16use tracing::{instrument, warn};
17
18pub use self::builder::*;
19pub(super) use self::{frozen::FrozenSlidingSyncList, request_generator::*};
20use super::{Error, PollTimeout, SlidingSyncInternalMessage};
21use crate::Result;
22
23#[derive(Clone, Copy, Debug)]
26pub(crate) enum SlidingSyncListCachePolicy {
27 Enabled,
29 Disabled,
31}
32
33pub type Bound = u32;
36
37pub type Range = RangeInclusive<Bound>;
39
40pub type Ranges = Vec<Range>;
42
43#[derive(Clone, Debug)]
47pub struct SlidingSyncList {
48 inner: Arc<SlidingSyncListInner>,
49}
50
51impl SlidingSyncList {
52 pub fn builder(name: impl Into<String>) -> SlidingSyncListBuilder {
54 SlidingSyncListBuilder::new(name)
55 }
56
57 pub fn name(&self) -> &str {
59 self.inner.name.as_str()
60 }
61
62 pub fn set_sync_mode<M>(&self, sync_mode: M)
73 where
74 M: Into<SlidingSyncMode>,
75 {
76 self.inner.set_sync_mode(sync_mode.into());
77
78 self.inner.internal_channel_send_if_possible(
81 SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration,
82 );
83 }
84
85 pub fn state(&self) -> SlidingSyncListLoadingState {
87 self.inner.state.get()
88 }
89
90 pub(super) fn requires_timeout(&self) -> PollTimeout {
95 let request_generator = &*self.inner.request_generator.read().unwrap();
96
97 (self.inner.requires_timeout)(request_generator)
98 }
99
100 pub fn state_stream(
111 &self,
112 ) -> (SlidingSyncListLoadingState, impl Stream<Item = SlidingSyncListLoadingState>) {
113 (self.inner.state.get(), self.inner.state.subscribe())
114 }
115
116 pub fn timeline_limit(&self) -> Bound {
118 *self.inner.timeline_limit.read().unwrap()
119 }
120
121 pub fn set_timeline_limit(&self, timeline: Bound) {
123 *self.inner.timeline_limit.write().unwrap() = timeline;
124 }
125
126 pub fn maximum_number_of_rooms(&self) -> Option<u32> {
129 self.inner.maximum_number_of_rooms.get()
130 }
131
132 pub fn maximum_number_of_rooms_stream(&self) -> Subscriber<Option<u32>> {
140 self.inner.maximum_number_of_rooms.subscribe()
141 }
142
143 pub(super) fn next_request(&self) -> Result<http::request::List, Error> {
148 self.inner.next_request()
149 }
150
151 pub(super) fn cache_policy(&self) -> SlidingSyncListCachePolicy {
153 self.inner.cache_policy
154 }
155
156 #[instrument(skip(self), fields(name = self.name()))]
164 pub(super) fn update(&mut self, maximum_number_of_rooms: Option<u32>) -> Result<bool, Error> {
165 if let Some(maximum_number_of_rooms) = maximum_number_of_rooms {
169 self.inner.update_request_generator_state(maximum_number_of_rooms)?;
170 }
171
172 let new_changes = self.inner.update_room_list(maximum_number_of_rooms)?;
173
174 Ok(new_changes)
175 }
176
177 #[cfg(feature = "testing")]
179 pub fn sync_mode(&self) -> SlidingSyncMode {
180 self.inner.sync_mode.read().unwrap().clone()
181 }
182
183 pub(super) fn set_maximum_number_of_rooms(&self, maximum_number_of_rooms: Option<u32>) {
185 self.inner.maximum_number_of_rooms.set(maximum_number_of_rooms);
186 }
187}
188
189pub(super) struct SlidingSyncListInner {
190 name: String,
192
193 state: SharedObservable<SlidingSyncListLoadingState>,
195
196 #[cfg(not(target_family = "wasm"))]
198 requires_timeout: Arc<dyn Fn(&SlidingSyncListRequestGenerator) -> PollTimeout + Send + Sync>,
199 #[cfg(target_family = "wasm")]
200 requires_timeout: Arc<dyn Fn(&SlidingSyncListRequestGenerator) -> PollTimeout>,
201
202 filters: Option<http::request::ListFilters>,
204
205 required_state: Vec<(StateEventType, String)>,
207
208 timeline_limit: StdRwLock<Bound>,
210
211 maximum_number_of_rooms: SharedObservable<Option<u32>>,
218
219 request_generator: StdRwLock<SlidingSyncListRequestGenerator>,
222
223 cache_policy: SlidingSyncListCachePolicy,
225
226 sliding_sync_internal_channel_sender: Sender<SlidingSyncInternalMessage>,
229
230 #[cfg(any(test, feature = "testing"))]
231 sync_mode: StdRwLock<SlidingSyncMode>,
232}
233
234impl fmt::Debug for SlidingSyncListInner {
235 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236 f.debug_struct("SlidingSyncListInner")
237 .field("name", &self.name)
238 .field("state", &self.state)
239 .finish()
240 }
241}
242
243impl SlidingSyncListInner {
244 pub fn set_sync_mode(&self, sync_mode: SlidingSyncMode) {
251 #[cfg(any(test, feature = "testing"))]
252 {
253 *self.sync_mode.write().unwrap() = sync_mode.clone();
254 }
255
256 {
257 let mut request_generator = self.request_generator.write().unwrap();
258 *request_generator = SlidingSyncListRequestGenerator::new(sync_mode);
259 }
260
261 self.state.update(|state| {
262 *state = match state {
263 SlidingSyncListLoadingState::NotLoaded => SlidingSyncListLoadingState::NotLoaded,
264 SlidingSyncListLoadingState::Preloaded => SlidingSyncListLoadingState::Preloaded,
265 SlidingSyncListLoadingState::PartiallyLoaded
266 | SlidingSyncListLoadingState::FullyLoaded => {
267 SlidingSyncListLoadingState::PartiallyLoaded
268 }
269 }
270 });
271 }
272
273 fn next_request(&self) -> Result<http::request::List, Error> {
275 let ranges = {
276 let mut request_generator = self.request_generator.write().unwrap();
279 request_generator.generate_next_ranges(self.maximum_number_of_rooms.get())?
280 };
281
282 Ok(self.request(ranges))
284 }
285
286 #[instrument(skip(self), fields(name = self.name))]
289 fn request(&self, ranges: Ranges) -> http::request::List {
290 let ranges = ranges.into_iter().map(|r| ((*r.start()).into(), (*r.end()).into())).collect();
291
292 let mut request = assign!(http::request::List::default(), { ranges });
293 request.room_details.timeline_limit = (*self.timeline_limit.read().unwrap()).into();
294 request.filters = self.filters.clone();
295 request.room_details.required_state = self.required_state.clone();
296
297 request
298 }
299
300 fn update_room_list(&self, maximum_number_of_rooms: Option<u32>) -> Result<bool, Error> {
305 let mut new_changes = false;
306
307 if maximum_number_of_rooms.is_some() {
308 if self.maximum_number_of_rooms.set_if_not_eq(maximum_number_of_rooms).is_some() {
310 new_changes = true;
311 }
312 }
313
314 Ok(new_changes)
315 }
316
317 fn update_request_generator_state(&self, maximum_number_of_rooms: u32) -> Result<(), Error> {
320 let mut request_generator = self.request_generator.write().unwrap();
321
322 let new_state = request_generator.handle_response(&self.name, maximum_number_of_rooms)?;
323 self.state.set_if_not_eq(new_state);
324
325 Ok(())
326 }
327
328 #[instrument]
331 fn internal_channel_send_if_possible(&self, message: SlidingSyncInternalMessage) {
332 let _ = self.sliding_sync_internal_channel_sender.send(message);
334 }
335}
336
337#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
347pub enum SlidingSyncListLoadingState {
348 #[default]
350 NotLoaded,
351
352 Preloaded,
354
355 PartiallyLoaded,
358
359 FullyLoaded,
362}
363
364#[cfg(test)]
365impl SlidingSyncListLoadingState {
366 fn is_fully_loaded(&self) -> bool {
368 matches!(self, Self::FullyLoaded)
369 }
370}
371
372#[derive(Clone, Debug, Default)]
376pub struct SlidingSyncSelectiveModeBuilder {
377 ranges: Ranges,
378}
379
380impl SlidingSyncSelectiveModeBuilder {
381 fn new() -> Self {
383 Self::default()
384 }
385
386 pub fn add_range(mut self, range: Range) -> Self {
388 self.ranges.push(range);
389 self
390 }
391
392 pub fn add_ranges(mut self, ranges: Ranges) -> Self {
394 self.ranges.extend(ranges);
395 self
396 }
397}
398
399impl From<SlidingSyncSelectiveModeBuilder> for SlidingSyncMode {
400 fn from(builder: SlidingSyncSelectiveModeBuilder) -> Self {
401 Self::Selective { ranges: builder.ranges }
402 }
403}
404
405#[derive(Clone, Debug)]
406enum WindowedModeBuilderKind {
407 Paging,
408 Growing,
409}
410
411#[derive(Clone, Debug)]
413pub struct SlidingSyncWindowedModeBuilder {
414 mode: WindowedModeBuilderKind,
415 batch_size: u32,
416 maximum_number_of_rooms_to_fetch: Option<u32>,
417}
418
419impl SlidingSyncWindowedModeBuilder {
420 fn new(mode: WindowedModeBuilderKind, batch_size: u32) -> Self {
421 Self { mode, batch_size, maximum_number_of_rooms_to_fetch: None }
422 }
423
424 pub fn maximum_number_of_rooms_to_fetch(mut self, num: u32) -> Self {
426 self.maximum_number_of_rooms_to_fetch = Some(num);
427 self
428 }
429}
430
431impl From<SlidingSyncWindowedModeBuilder> for SlidingSyncMode {
432 fn from(builder: SlidingSyncWindowedModeBuilder) -> Self {
433 match builder.mode {
434 WindowedModeBuilderKind::Paging => Self::Paging {
435 batch_size: builder.batch_size,
436 maximum_number_of_rooms_to_fetch: builder.maximum_number_of_rooms_to_fetch,
437 },
438 WindowedModeBuilderKind::Growing => Self::Growing {
439 batch_size: builder.batch_size,
440 maximum_number_of_rooms_to_fetch: builder.maximum_number_of_rooms_to_fetch,
441 },
442 }
443 }
444}
445
446#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
448pub enum SlidingSyncMode {
449 Selective {
451 ranges: Ranges,
453 },
454
455 Paging {
459 batch_size: u32,
461
462 maximum_number_of_rooms_to_fetch: Option<u32>,
465 },
466
467 Growing {
471 batch_size: u32,
473
474 maximum_number_of_rooms_to_fetch: Option<u32>,
477 },
478}
479
480impl Default for SlidingSyncMode {
481 fn default() -> Self {
482 Self::Selective { ranges: Vec::new() }
483 }
484}
485
486impl SlidingSyncMode {
487 pub fn new_selective() -> SlidingSyncSelectiveModeBuilder {
489 SlidingSyncSelectiveModeBuilder::new()
490 }
491
492 pub fn new_paging(batch_size: u32) -> SlidingSyncWindowedModeBuilder {
494 SlidingSyncWindowedModeBuilder::new(WindowedModeBuilderKind::Paging, batch_size)
495 }
496
497 pub fn new_growing(batch_size: u32) -> SlidingSyncWindowedModeBuilder {
499 SlidingSyncWindowedModeBuilder::new(WindowedModeBuilderKind::Growing, batch_size)
500 }
501}
502
503#[cfg(test)]
504mod tests {
505 use std::{
506 cell::Cell,
507 ops::Not,
508 sync::{Arc, Mutex},
509 };
510
511 use assert_matches::assert_matches;
512 use matrix_sdk_test::async_test;
513 use ruma::uint;
514 use serde_json::json;
515 use tokio::sync::broadcast::{channel, error::TryRecvError};
516
517 use super::{PollTimeout, SlidingSyncList, SlidingSyncListLoadingState, SlidingSyncMode};
518 use crate::sliding_sync::SlidingSyncInternalMessage;
519
520 macro_rules! assert_json_roundtrip {
521 (from $type:ty: $rust_value:expr => $json_value:expr) => {
522 let json = serde_json::to_value(&$rust_value).unwrap();
523 assert_eq!(json, $json_value);
524
525 let rust: $type = serde_json::from_value(json).unwrap();
526 assert_eq!(rust, $rust_value);
527 };
528 }
529
530 #[async_test]
531 async fn test_sliding_sync_list_selective_mode() {
532 let (sender, mut receiver) = channel(1);
533
534 let list = SlidingSyncList::builder("foo")
536 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=1).add_range(2..=3))
537 .build(sender);
538
539 {
540 let mut generator = list.inner.request_generator.write().unwrap();
541 assert_eq!(generator.requested_ranges(), &[0..=1, 2..=3]);
542
543 let ranges = generator.generate_next_ranges(None).unwrap();
544 assert_eq!(ranges, &[0..=1, 2..=3]);
545 }
546
547 assert!(matches!(receiver.try_recv(), Err(TryRecvError::Empty)));
549
550 list.set_sync_mode(SlidingSyncMode::new_selective().add_range(4..=5));
551
552 {
553 let mut generator = list.inner.request_generator.write().unwrap();
554 assert_eq!(generator.requested_ranges(), &[4..=5]);
555
556 let ranges = generator.generate_next_ranges(None).unwrap();
557 assert_eq!(ranges, &[4..=5]);
558 }
559
560 assert!(matches!(
562 receiver.try_recv(),
563 Ok(SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration)
564 ));
565 assert!(matches!(receiver.try_recv(), Err(TryRecvError::Empty)));
566 }
567
568 #[test]
569 fn test_sliding_sync_list_timeline_limit() {
570 let (sender, _receiver) = channel(1);
571
572 let list = SlidingSyncList::builder("foo")
573 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=1))
574 .timeline_limit(7)
575 .build(sender);
576
577 assert_eq!(list.timeline_limit(), 7);
578
579 list.set_timeline_limit(42);
580 assert_eq!(list.timeline_limit(), 42);
581 }
582
583 macro_rules! assert_ranges {
584 (
585 list = $list:ident,
586 list_state = $first_list_state:ident,
587 maximum_number_of_rooms = $maximum_number_of_rooms:expr,
588 timeout = $initial_timeout:ident,
589 $(
590 next => {
591 ranges = $( $range_start:literal ..= $range_end:literal ),* ,
592 is_fully_loaded = $is_fully_loaded:expr,
593 list_state = $list_state:ident,
594 timeout = $timeout:ident,
595 }
596 ),*
597 $(,)*
598 ) => {
599 assert_eq!($list.state(), SlidingSyncListLoadingState::$first_list_state, "first state");
600 assert_matches!(
601 $list.requires_timeout(),
602 PollTimeout:: $initial_timeout,
603 "initial timeout",
604 );
605
606 $(
607 {
608 let request = $list.next_request().unwrap();
610
611 assert_eq!(
612 request.ranges,
613 [
614 $( (uint!( $range_start ), uint!( $range_end )) ),*
615 ],
616 "ranges",
617 );
618
619 let _ = $list.update(Some($maximum_number_of_rooms));
621
622 assert_eq!(
623 $list.inner.request_generator.read().unwrap().is_fully_loaded(),
624 $is_fully_loaded,
625 "is fully loaded",
626 );
627 assert_eq!(
628 $list.state(),
629 SlidingSyncListLoadingState::$list_state,
630 "state",
631 );
632 assert_matches!(
633 $list.requires_timeout(),
634 PollTimeout:: $timeout,
635 "timeout",
636 );
637 }
638 )*
639 };
640 }
641
642 #[test]
643 fn test_generator_paging_full_sync() {
644 let (sender, _receiver) = channel(1);
645
646 let mut list = SlidingSyncList::builder("testing")
647 .sync_mode(SlidingSyncMode::new_paging(10))
648 .build(sender);
649
650 assert_ranges! {
651 list = list,
652 list_state = NotLoaded,
653 maximum_number_of_rooms = 25,
654 timeout = None,
655 next => {
656 ranges = 0..=9,
657 is_fully_loaded = false,
658 list_state = PartiallyLoaded,
659 timeout = None,
660 },
661 next => {
662 ranges = 10..=19,
663 is_fully_loaded = false,
664 list_state = PartiallyLoaded,
665 timeout = None,
666 },
667 next => {
669 ranges = 20..=24,
670 is_fully_loaded = true,
671 list_state = FullyLoaded,
672 timeout = Default,
673 },
674 next => {
676 ranges = 0..=24, is_fully_loaded = true,
678 list_state = FullyLoaded,
679 timeout = Default,
680 },
681 next => {
682 ranges = 0..=24,
683 is_fully_loaded = true,
684 list_state = FullyLoaded,
685 timeout = Default,
686 },
687 };
688 }
689
690 #[test]
691 fn test_generator_paging_full_sync_with_a_maximum_number_of_rooms_to_fetch() {
692 let (sender, _receiver) = channel(1);
693
694 let mut list = SlidingSyncList::builder("testing")
695 .sync_mode(SlidingSyncMode::new_paging(10).maximum_number_of_rooms_to_fetch(22))
696 .build(sender);
697
698 assert_ranges! {
699 list = list,
700 list_state = NotLoaded,
701 maximum_number_of_rooms = 25,
702 timeout = None,
703 next => {
704 ranges = 0..=9,
705 is_fully_loaded = false,
706 list_state = PartiallyLoaded,
707 timeout = None,
708 },
709 next => {
710 ranges = 10..=19,
711 is_fully_loaded = false,
712 list_state = PartiallyLoaded,
713 timeout = None,
714 },
715 next => {
717 ranges = 20..=21,
718 is_fully_loaded = true,
719 list_state = FullyLoaded,
720 timeout = Default,
721 },
722 next => {
724 ranges = 0..=21, is_fully_loaded = true,
726 list_state = FullyLoaded,
727 timeout = Default,
728 },
729 next => {
730 ranges = 0..=21,
731 is_fully_loaded = true,
732 list_state = FullyLoaded,
733 timeout = Default,
734 },
735 };
736 }
737
738 #[test]
739 fn test_generator_growing_full_sync() {
740 let (sender, _receiver) = channel(1);
741
742 let mut list = SlidingSyncList::builder("testing")
743 .sync_mode(SlidingSyncMode::new_growing(10))
744 .build(sender);
745
746 assert_ranges! {
747 list = list,
748 list_state = NotLoaded,
749 maximum_number_of_rooms = 25,
750 timeout = None,
751 next => {
752 ranges = 0..=9,
753 is_fully_loaded = false,
754 list_state = PartiallyLoaded,
755 timeout = None,
756 },
757 next => {
758 ranges = 0..=19,
759 is_fully_loaded = false,
760 list_state = PartiallyLoaded,
761 timeout = None,
762 },
763 next => {
765 ranges = 0..=24,
766 is_fully_loaded = true,
767 list_state = FullyLoaded,
768 timeout = Default,
769 },
770 next => {
772 ranges = 0..=24,
773 is_fully_loaded = true,
774 list_state = FullyLoaded,
775 timeout = Default,
776 },
777 next => {
778 ranges = 0..=24,
779 is_fully_loaded = true,
780 list_state = FullyLoaded,
781 timeout = Default,
782 },
783 };
784 }
785
786 #[test]
787 fn test_generator_growing_full_sync_with_a_maximum_number_of_rooms_to_fetch() {
788 let (sender, _receiver) = channel(1);
789
790 let mut list = SlidingSyncList::builder("testing")
791 .sync_mode(SlidingSyncMode::new_growing(10).maximum_number_of_rooms_to_fetch(22))
792 .build(sender);
793
794 assert_ranges! {
795 list = list,
796 list_state = NotLoaded,
797 maximum_number_of_rooms = 25,
798 timeout = None,
799 next => {
800 ranges = 0..=9,
801 is_fully_loaded = false,
802 list_state = PartiallyLoaded,
803 timeout = None,
804 },
805 next => {
806 ranges = 0..=19,
807 is_fully_loaded = false,
808 list_state = PartiallyLoaded,
809 timeout = None,
810 },
811 next => {
813 ranges = 0..=21,
814 is_fully_loaded = true,
815 list_state = FullyLoaded,
816 timeout = Default,
817 },
818 next => {
820 ranges = 0..=21,
821 is_fully_loaded = true,
822 list_state = FullyLoaded,
823 timeout = Default,
824 },
825 next => {
826 ranges = 0..=21,
827 is_fully_loaded = true,
828 list_state = FullyLoaded,
829 timeout = Default,
830 },
831 };
832 }
833
834 #[test]
835 fn test_generator_selective() {
836 let (sender, _receiver) = channel(1);
837
838 let mut list = SlidingSyncList::builder("testing")
839 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10).add_range(42..=153))
840 .build(sender);
841
842 assert_ranges! {
843 list = list,
844 list_state = NotLoaded,
845 maximum_number_of_rooms = 25,
846 timeout = Default,
847 next => {
849 ranges = 0..=10, 42..=153,
850 is_fully_loaded = true,
851 list_state = FullyLoaded,
852 timeout = Default,
853 },
854 next => {
856 ranges = 0..=10, 42..=153,
857 is_fully_loaded = true,
858 list_state = FullyLoaded,
859 timeout = Default,
860 },
861 next => {
862 ranges = 0..=10, 42..=153,
863 is_fully_loaded = true,
864 list_state = FullyLoaded,
865 timeout = Default,
866 }
867 };
868 }
869
870 #[async_test]
871 async fn test_generator_selective_with_modifying_ranges_on_the_fly() {
872 let (sender, _receiver) = channel(4);
873
874 let mut list = SlidingSyncList::builder("testing")
875 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10).add_range(42..=153))
876 .build(sender);
877
878 assert_ranges! {
879 list = list,
880 list_state = NotLoaded,
881 maximum_number_of_rooms = 25,
882 timeout = Default,
883 next => {
885 ranges = 0..=10, 42..=153,
886 is_fully_loaded = true,
887 list_state = FullyLoaded,
888 timeout = Default,
889 },
890 next => {
892 ranges = 0..=10, 42..=153,
893 is_fully_loaded = true,
894 list_state = FullyLoaded,
895 timeout = Default,
896 },
897 next => {
898 ranges = 0..=10, 42..=153,
899 is_fully_loaded = true,
900 list_state = FullyLoaded,
901 timeout = Default,
902 }
903 };
904
905 list.set_sync_mode(SlidingSyncMode::new_selective().add_range(3..=7));
906
907 assert_ranges! {
908 list = list,
909 list_state = PartiallyLoaded,
910 maximum_number_of_rooms = 25,
911 timeout = Default,
912 next => {
913 ranges = 3..=7,
914 is_fully_loaded = true,
915 list_state = FullyLoaded,
916 timeout = Default,
917 },
918 };
919
920 list.set_sync_mode(SlidingSyncMode::new_selective().add_range(42..=77));
921
922 assert_ranges! {
923 list = list,
924 list_state = PartiallyLoaded,
925 maximum_number_of_rooms = 25,
926 timeout = Default,
927 next => {
928 ranges = 42..=77,
929 is_fully_loaded = true,
930 list_state = FullyLoaded,
931 timeout = Default,
932 },
933 };
934
935 list.set_sync_mode(SlidingSyncMode::new_selective());
936
937 assert_ranges! {
938 list = list,
939 list_state = PartiallyLoaded,
940 maximum_number_of_rooms = 25,
941 timeout = Default,
942 next => {
943 ranges = ,
944 is_fully_loaded = true,
945 list_state = FullyLoaded,
946 timeout = Default,
947 },
948 };
949 }
950
951 #[async_test]
952 async fn test_generator_changing_sync_mode_to_various_modes() {
953 let (sender, _receiver) = channel(4);
954
955 let mut list = SlidingSyncList::builder("testing")
956 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10).add_range(42..=153))
957 .build(sender);
958
959 assert_ranges! {
960 list = list,
961 list_state = NotLoaded,
962 maximum_number_of_rooms = 25,
963 timeout = Default,
964 next => {
966 ranges = 0..=10, 42..=153,
967 is_fully_loaded = true,
968 list_state = FullyLoaded,
969 timeout = Default,
970 },
971 next => {
973 ranges = 0..=10, 42..=153,
974 is_fully_loaded = true,
975 list_state = FullyLoaded,
976 timeout = Default,
977 },
978 next => {
979 ranges = 0..=10, 42..=153,
980 is_fully_loaded = true,
981 list_state = FullyLoaded,
982 timeout = Default,
983 }
984 };
985
986 list.set_sync_mode(SlidingSyncMode::new_growing(10));
988
989 assert_ranges! {
990 list = list,
991 list_state = PartiallyLoaded, maximum_number_of_rooms = 25,
993 timeout = None,
994 next => {
995 ranges = 0..=9,
996 is_fully_loaded = false,
997 list_state = PartiallyLoaded,
998 timeout = None,
999 },
1000 next => {
1001 ranges = 0..=19,
1002 is_fully_loaded = false,
1003 list_state = PartiallyLoaded,
1004 timeout = None,
1005 },
1006 next => {
1008 ranges = 0..=24,
1009 is_fully_loaded = true,
1010 list_state = FullyLoaded,
1011 timeout = Default,
1012 },
1013 next => {
1015 ranges = 0..=24,
1016 is_fully_loaded = true,
1017 list_state = FullyLoaded,
1018 timeout = Default,
1019 },
1020 next => {
1021 ranges = 0..=24,
1022 is_fully_loaded = true,
1023 list_state = FullyLoaded,
1024 timeout = Default,
1025 },
1026 };
1027
1028 list.set_sync_mode(SlidingSyncMode::new_paging(10));
1030
1031 assert_ranges! {
1032 list = list,
1033 list_state = PartiallyLoaded, maximum_number_of_rooms = 25,
1035 timeout = None,
1036 next => {
1037 ranges = 0..=9,
1038 is_fully_loaded = false,
1039 list_state = PartiallyLoaded,
1040 timeout = None,
1041 },
1042 next => {
1043 ranges = 10..=19,
1044 is_fully_loaded = false,
1045 list_state = PartiallyLoaded,
1046 timeout = None,
1047 },
1048 next => {
1050 ranges = 20..=24,
1051 is_fully_loaded = true,
1052 list_state = FullyLoaded,
1053 timeout = Default,
1054 },
1055 next => {
1057 ranges = 0..=24, is_fully_loaded = true,
1059 list_state = FullyLoaded,
1060 timeout = Default,
1061 },
1062 next => {
1063 ranges = 0..=24,
1064 is_fully_loaded = true,
1065 list_state = FullyLoaded,
1066 timeout = Default,
1067 },
1068 };
1069
1070 list.set_sync_mode(SlidingSyncMode::new_selective());
1072
1073 assert_eq!(list.state(), SlidingSyncListLoadingState::PartiallyLoaded); list.set_sync_mode(SlidingSyncMode::new_selective().add_range(0..=100));
1079
1080 assert_ranges! {
1081 list = list,
1082 list_state = PartiallyLoaded, maximum_number_of_rooms = 25,
1084 timeout = Default,
1085 next => {
1087 ranges = 0..=100,
1088 is_fully_loaded = true,
1089 list_state = FullyLoaded,
1090 timeout = Default,
1091 },
1092 next => {
1094 ranges = 0..=100,
1095 is_fully_loaded = true,
1096 list_state = FullyLoaded,
1097 timeout = Default,
1098 },
1099 next => {
1100 ranges = 0..=100,
1101 is_fully_loaded = true,
1102 list_state = FullyLoaded,
1103 timeout = Default,
1104 }
1105 };
1106 }
1107
1108 #[async_test]
1109 #[allow(clippy::await_holding_lock)]
1110 async fn test_inner_update_maximum_number_of_rooms() {
1111 let (sender, _receiver) = channel(1);
1112
1113 let mut list = SlidingSyncList::builder("foo")
1114 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=3))
1115 .build(sender);
1116
1117 assert!(list.maximum_number_of_rooms().is_none());
1118
1119 let _ = list.next_request();
1121 let new_changes = list.update(Some(5)).unwrap();
1122 assert!(new_changes);
1123
1124 assert_eq!(list.maximum_number_of_rooms(), Some(5));
1126
1127 let _ = list.next_request();
1129 let new_changes = list.update(Some(5)).unwrap();
1130 assert!(!new_changes);
1131
1132 assert_eq!(list.maximum_number_of_rooms(), Some(5));
1134 }
1135
1136 #[test]
1137 fn test_sliding_sync_mode_serialization() {
1138 assert_json_roundtrip!(
1139 from SlidingSyncMode: SlidingSyncMode::from(SlidingSyncMode::new_paging(1).maximum_number_of_rooms_to_fetch(2)) => json!({
1140 "Paging": {
1141 "batch_size": 1,
1142 "maximum_number_of_rooms_to_fetch": 2
1143 }
1144 })
1145 );
1146 assert_json_roundtrip!(
1147 from SlidingSyncMode: SlidingSyncMode::from(SlidingSyncMode::new_growing(1).maximum_number_of_rooms_to_fetch(2)) => json!({
1148 "Growing": {
1149 "batch_size": 1,
1150 "maximum_number_of_rooms_to_fetch": 2
1151 }
1152 })
1153 );
1154 assert_json_roundtrip!(from SlidingSyncMode: SlidingSyncMode::from(SlidingSyncMode::new_selective()) => json!({
1155 "Selective": {
1156 "ranges": []
1157 }
1158 })
1159 );
1160 }
1161
1162 #[test]
1163 fn test_sliding_sync_list_loading_state_serialization() {
1164 assert_json_roundtrip!(from SlidingSyncListLoadingState: SlidingSyncListLoadingState::NotLoaded => json!("NotLoaded"));
1165 assert_json_roundtrip!(from SlidingSyncListLoadingState: SlidingSyncListLoadingState::Preloaded => json!("Preloaded"));
1166 assert_json_roundtrip!(from SlidingSyncListLoadingState: SlidingSyncListLoadingState::PartiallyLoaded => json!("PartiallyLoaded"));
1167 assert_json_roundtrip!(from SlidingSyncListLoadingState: SlidingSyncListLoadingState::FullyLoaded => json!("FullyLoaded"));
1168 }
1169
1170 #[test]
1171 fn test_sliding_sync_list_loading_state_is_fully_loaded() {
1172 assert!(SlidingSyncListLoadingState::NotLoaded.is_fully_loaded().not());
1173 assert!(SlidingSyncListLoadingState::Preloaded.is_fully_loaded().not());
1174 assert!(SlidingSyncListLoadingState::PartiallyLoaded.is_fully_loaded().not());
1175 assert!(SlidingSyncListLoadingState::FullyLoaded.is_fully_loaded());
1176 }
1177
1178 #[test]
1179 fn test_once_built() {
1180 let (sender, _receiver) = channel(1);
1181
1182 let probe = Arc::new(Mutex::new(Cell::new(false)));
1183 let probe_clone = probe.clone();
1184
1185 let _list = SlidingSyncList::builder("testing")
1186 .once_built(move |list| {
1187 let mut probe_lock = probe.lock().unwrap();
1188 *probe_lock.get_mut() = true;
1189
1190 list
1191 })
1192 .build(sender);
1193
1194 let probe_lock = probe_clone.lock().unwrap();
1195 assert!(probe_lock.get());
1196 }
1197}