1mod builder;
2mod frozen;
3mod request_generator;
4mod sticky;
5
6use std::{
7 fmt::Debug,
8 ops::RangeInclusive,
9 sync::{Arc, RwLock as StdRwLock},
10};
11
12use eyeball::{Observable, SharedObservable, Subscriber};
13use futures_core::Stream;
14use matrix_sdk_base::sliding_sync::http;
15use ruma::{assign, TransactionId};
16use serde::{Deserialize, Serialize};
17use tokio::sync::broadcast::Sender;
18use tracing::{instrument, warn};
19
20pub use self::builder::*;
21use self::sticky::SlidingSyncListStickyParameters;
22pub(super) use self::{frozen::FrozenSlidingSyncList, request_generator::*};
23use super::{
24 sticky_parameters::{LazyTransactionId, SlidingSyncStickyManager},
25 Error, SlidingSyncInternalMessage,
26};
27use crate::Result;
28
29#[derive(Clone, Copy, Debug)]
32pub(crate) enum SlidingSyncListCachePolicy {
33 Enabled,
35 Disabled,
37}
38
39pub type Bound = u32;
42
43pub type Range = RangeInclusive<Bound>;
45
46pub type Ranges = Vec<Range>;
48
49#[derive(Clone, Debug)]
53pub struct SlidingSyncList {
54 inner: Arc<SlidingSyncListInner>,
55}
56
57impl SlidingSyncList {
58 pub fn builder(name: impl Into<String>) -> SlidingSyncListBuilder {
60 SlidingSyncListBuilder::new(name)
61 }
62
63 pub fn name(&self) -> &str {
65 self.inner.name.as_str()
66 }
67
68 pub fn set_sync_mode<M>(&self, sync_mode: M)
80 where
81 M: Into<SlidingSyncMode>,
82 {
83 self.inner.set_sync_mode(sync_mode.into());
84
85 self.inner.internal_channel_send_if_possible(
88 SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration,
89 );
90 }
91
92 pub fn state(&self) -> SlidingSyncListLoadingState {
94 self.inner.state.read().unwrap().clone()
95 }
96
97 pub(super) fn requires_timeout(&self) -> bool {
110 self.inner.request_generator.read().unwrap().is_selective()
111 || self.state().is_fully_loaded()
112 }
113
114 pub fn state_stream(
125 &self,
126 ) -> (SlidingSyncListLoadingState, impl Stream<Item = SlidingSyncListLoadingState>) {
127 let read_lock = self.inner.state.read().unwrap();
128 let previous_value = (*read_lock).clone();
129 let subscriber = Observable::subscribe(&read_lock);
130
131 (previous_value, subscriber)
132 }
133
134 pub fn timeline_limit(&self) -> Bound {
136 *self.inner.timeline_limit.read().unwrap()
137 }
138
139 pub fn set_timeline_limit(&self, timeline: Bound) {
141 *self.inner.timeline_limit.write().unwrap() = timeline;
142 }
143
144 pub fn maximum_number_of_rooms(&self) -> Option<u32> {
147 self.inner.maximum_number_of_rooms.get()
148 }
149
150 pub fn maximum_number_of_rooms_stream(&self) -> Subscriber<Option<u32>> {
158 self.inner.maximum_number_of_rooms.subscribe()
159 }
160
161 pub(super) fn next_request(
166 &self,
167 txn_id: &mut LazyTransactionId,
168 ) -> Result<http::request::List, Error> {
169 self.inner.next_request(txn_id)
170 }
171
172 pub(super) fn cache_policy(&self) -> SlidingSyncListCachePolicy {
174 self.inner.cache_policy
175 }
176
177 #[instrument(skip(self), fields(name = self.name()))]
185 pub(super) fn update(&mut self, maximum_number_of_rooms: Option<u32>) -> Result<bool, Error> {
186 if let Some(maximum_number_of_rooms) = maximum_number_of_rooms {
189 self.inner.update_request_generator_state(maximum_number_of_rooms)?;
190 }
191
192 let new_changes = self.inner.update_room_list(maximum_number_of_rooms)?;
193
194 Ok(new_changes)
195 }
196
197 pub fn maybe_commit_sticky(&mut self, txn_id: &TransactionId) {
199 self.inner.sticky.write().unwrap().maybe_commit(txn_id);
200 }
201
202 pub(super) fn invalidate_sticky_data(&self) {
205 let _ = self.inner.sticky.write().unwrap().data_mut();
206 }
207
208 #[cfg(feature = "testing")]
210 pub fn sync_mode(&self) -> SlidingSyncMode {
211 self.inner.sync_mode.read().unwrap().clone()
212 }
213
214 #[cfg(test)]
216 pub(super) fn set_maximum_number_of_rooms(&self, maximum_number_of_rooms: Option<u32>) {
217 self.inner.maximum_number_of_rooms.set(maximum_number_of_rooms);
218 }
219}
220
221#[derive(Debug)]
222pub(super) struct SlidingSyncListInner {
223 name: String,
225
226 state: StdRwLock<Observable<SlidingSyncListLoadingState>>,
228
229 sticky: StdRwLock<SlidingSyncStickyManager<SlidingSyncListStickyParameters>>,
233
234 timeline_limit: StdRwLock<Bound>,
236
237 maximum_number_of_rooms: SharedObservable<Option<u32>>,
245
246 request_generator: StdRwLock<SlidingSyncListRequestGenerator>,
249
250 cache_policy: SlidingSyncListCachePolicy,
252
253 sliding_sync_internal_channel_sender: Sender<SlidingSyncInternalMessage>,
256
257 #[cfg(any(test, feature = "testing"))]
258 sync_mode: StdRwLock<SlidingSyncMode>,
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 {
280 let mut state = self.state.write().unwrap();
281
282 let next_state = match **state {
283 SlidingSyncListLoadingState::NotLoaded => SlidingSyncListLoadingState::NotLoaded,
284 SlidingSyncListLoadingState::Preloaded => SlidingSyncListLoadingState::Preloaded,
285 SlidingSyncListLoadingState::PartiallyLoaded
286 | SlidingSyncListLoadingState::FullyLoaded => {
287 SlidingSyncListLoadingState::PartiallyLoaded
288 }
289 };
290
291 Observable::set(&mut state, next_state);
292 }
293 }
294
295 fn next_request(&self, txn_id: &mut LazyTransactionId) -> Result<http::request::List, Error> {
297 let ranges = {
298 let mut request_generator = self.request_generator.write().unwrap();
300 request_generator.generate_next_ranges(self.maximum_number_of_rooms.get())?
301 };
302
303 Ok(self.request(ranges, txn_id))
305 }
306
307 #[instrument(skip(self), fields(name = self.name))]
310 fn request(&self, ranges: Ranges, txn_id: &mut LazyTransactionId) -> http::request::List {
311 let ranges = ranges.into_iter().map(|r| ((*r.start()).into(), (*r.end()).into())).collect();
312
313 let mut request = assign!(http::request::List::default(), { ranges });
314 request.room_details.timeline_limit = (*self.timeline_limit.read().unwrap()).into();
315
316 {
317 let mut sticky = self.sticky.write().unwrap();
318 sticky.maybe_apply(&mut request, txn_id);
319 }
320
321 request
322 }
323
324 fn update_room_list(&self, maximum_number_of_rooms: Option<u32>) -> Result<bool, Error> {
329 let mut new_changes = false;
330
331 if maximum_number_of_rooms.is_some() {
332 if self.maximum_number_of_rooms.set_if_not_eq(maximum_number_of_rooms).is_some() {
334 new_changes = true;
335 }
336 }
337
338 Ok(new_changes)
339 }
340
341 fn update_request_generator_state(&self, maximum_number_of_rooms: u32) -> Result<(), Error> {
344 let mut request_generator = self.request_generator.write().unwrap();
345 let new_state = request_generator.handle_response(&self.name, maximum_number_of_rooms)?;
346 Observable::set_if_not_eq(&mut self.state.write().unwrap(), new_state);
347 Ok(())
348 }
349
350 #[instrument]
353 fn internal_channel_send_if_possible(&self, message: SlidingSyncInternalMessage) {
354 let _ = self.sliding_sync_internal_channel_sender.send(message);
356 }
357}
358
359#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
369pub enum SlidingSyncListLoadingState {
370 #[default]
372 NotLoaded,
373
374 Preloaded,
376
377 PartiallyLoaded,
380
381 FullyLoaded,
384}
385
386impl SlidingSyncListLoadingState {
387 fn is_fully_loaded(&self) -> bool {
389 matches!(self, Self::FullyLoaded)
390 }
391}
392
393#[derive(Clone, Debug, Default)]
397pub struct SlidingSyncSelectiveModeBuilder {
398 ranges: Ranges,
399}
400
401impl SlidingSyncSelectiveModeBuilder {
402 fn new() -> Self {
404 Self::default()
405 }
406
407 pub fn add_range(mut self, range: Range) -> Self {
409 self.ranges.push(range);
410 self
411 }
412
413 pub fn add_ranges(mut self, ranges: Ranges) -> Self {
415 self.ranges.extend(ranges);
416 self
417 }
418}
419
420impl From<SlidingSyncSelectiveModeBuilder> for SlidingSyncMode {
421 fn from(builder: SlidingSyncSelectiveModeBuilder) -> Self {
422 Self::Selective { ranges: builder.ranges }
423 }
424}
425
426#[derive(Clone, Debug)]
427enum WindowedModeBuilderKind {
428 Paging,
429 Growing,
430}
431
432#[derive(Clone, Debug)]
434pub struct SlidingSyncWindowedModeBuilder {
435 mode: WindowedModeBuilderKind,
436 batch_size: u32,
437 maximum_number_of_rooms_to_fetch: Option<u32>,
438}
439
440impl SlidingSyncWindowedModeBuilder {
441 fn new(mode: WindowedModeBuilderKind, batch_size: u32) -> Self {
442 Self { mode, batch_size, maximum_number_of_rooms_to_fetch: None }
443 }
444
445 pub fn maximum_number_of_rooms_to_fetch(mut self, num: u32) -> Self {
447 self.maximum_number_of_rooms_to_fetch = Some(num);
448 self
449 }
450}
451
452impl From<SlidingSyncWindowedModeBuilder> for SlidingSyncMode {
453 fn from(builder: SlidingSyncWindowedModeBuilder) -> Self {
454 match builder.mode {
455 WindowedModeBuilderKind::Paging => Self::Paging {
456 batch_size: builder.batch_size,
457 maximum_number_of_rooms_to_fetch: builder.maximum_number_of_rooms_to_fetch,
458 },
459 WindowedModeBuilderKind::Growing => Self::Growing {
460 batch_size: builder.batch_size,
461 maximum_number_of_rooms_to_fetch: builder.maximum_number_of_rooms_to_fetch,
462 },
463 }
464 }
465}
466
467#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
469pub enum SlidingSyncMode {
470 Selective {
472 ranges: Ranges,
474 },
475
476 Paging {
480 batch_size: u32,
482
483 maximum_number_of_rooms_to_fetch: Option<u32>,
486 },
487
488 Growing {
492 batch_size: u32,
494
495 maximum_number_of_rooms_to_fetch: Option<u32>,
498 },
499}
500
501impl Default for SlidingSyncMode {
502 fn default() -> Self {
503 Self::Selective { ranges: Vec::new() }
504 }
505}
506
507impl SlidingSyncMode {
508 pub fn new_selective() -> SlidingSyncSelectiveModeBuilder {
510 SlidingSyncSelectiveModeBuilder::new()
511 }
512
513 pub fn new_paging(batch_size: u32) -> SlidingSyncWindowedModeBuilder {
515 SlidingSyncWindowedModeBuilder::new(WindowedModeBuilderKind::Paging, batch_size)
516 }
517
518 pub fn new_growing(batch_size: u32) -> SlidingSyncWindowedModeBuilder {
520 SlidingSyncWindowedModeBuilder::new(WindowedModeBuilderKind::Growing, batch_size)
521 }
522}
523
524#[cfg(test)]
525mod tests {
526 use std::{
527 cell::Cell,
528 ops::Not,
529 sync::{Arc, Mutex},
530 };
531
532 use matrix_sdk_test::async_test;
533 use ruma::uint;
534 use serde_json::json;
535 use tokio::sync::broadcast::{channel, error::TryRecvError};
536
537 use super::{SlidingSyncList, SlidingSyncListLoadingState, SlidingSyncMode};
538 use crate::sliding_sync::{sticky_parameters::LazyTransactionId, SlidingSyncInternalMessage};
539
540 macro_rules! assert_json_roundtrip {
541 (from $type:ty: $rust_value:expr => $json_value:expr) => {
542 let json = serde_json::to_value(&$rust_value).unwrap();
543 assert_eq!(json, $json_value);
544
545 let rust: $type = serde_json::from_value(json).unwrap();
546 assert_eq!(rust, $rust_value);
547 };
548 }
549
550 #[async_test]
551 async fn test_sliding_sync_list_selective_mode() {
552 let (sender, mut receiver) = channel(1);
553
554 let list = SlidingSyncList::builder("foo")
556 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=1).add_range(2..=3))
557 .build(sender);
558
559 {
560 let mut generator = list.inner.request_generator.write().unwrap();
561 assert_eq!(generator.requested_ranges(), &[0..=1, 2..=3]);
562
563 let ranges = generator.generate_next_ranges(None).unwrap();
564 assert_eq!(ranges, &[0..=1, 2..=3]);
565 }
566
567 assert!(matches!(receiver.try_recv(), Err(TryRecvError::Empty)));
569
570 list.set_sync_mode(SlidingSyncMode::new_selective().add_range(4..=5));
571
572 {
573 let mut generator = list.inner.request_generator.write().unwrap();
574 assert_eq!(generator.requested_ranges(), &[4..=5]);
575
576 let ranges = generator.generate_next_ranges(None).unwrap();
577 assert_eq!(ranges, &[4..=5]);
578 }
579
580 assert!(matches!(
582 receiver.try_recv(),
583 Ok(SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration)
584 ));
585 assert!(matches!(receiver.try_recv(), Err(TryRecvError::Empty)));
586 }
587
588 #[test]
589 fn test_sliding_sync_list_timeline_limit() {
590 let (sender, _receiver) = channel(1);
591
592 let list = SlidingSyncList::builder("foo")
593 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=1))
594 .timeline_limit(7)
595 .build(sender);
596
597 assert_eq!(list.timeline_limit(), 7);
598
599 list.set_timeline_limit(42);
600 assert_eq!(list.timeline_limit(), 42);
601 }
602
603 macro_rules! assert_ranges {
604 (
605 list = $list:ident,
606 list_state = $first_list_state:ident,
607 maximum_number_of_rooms = $maximum_number_of_rooms:expr,
608 requires_timeout = $initial_requires_timeout:literal,
609 $(
610 next => {
611 ranges = $( $range_start:literal ..= $range_end:literal ),* ,
612 is_fully_loaded = $is_fully_loaded:expr,
613 list_state = $list_state:ident,
614 requires_timeout = $requires_timeout:literal,
615 }
616 ),*
617 $(,)*
618 ) => {
619 assert_eq!($list.state(), SlidingSyncListLoadingState::$first_list_state, "first state");
620 assert_eq!(
621 $list.requires_timeout(),
622 $initial_requires_timeout,
623 "initial requires_timeout",
624 );
625
626 $(
627 {
628 let request = $list.next_request(&mut LazyTransactionId::new()).unwrap();
630
631 assert_eq!(
632 request.ranges,
633 [
634 $( (uint!( $range_start ), uint!( $range_end )) ),*
635 ],
636 "ranges",
637 );
638
639 let _ = $list.update(Some($maximum_number_of_rooms));
641
642 assert_eq!(
643 $list.inner.request_generator.read().unwrap().is_fully_loaded(),
644 $is_fully_loaded,
645 "is fully loaded",
646 );
647 assert_eq!(
648 $list.state(),
649 SlidingSyncListLoadingState::$list_state,
650 "state",
651 );
652 assert_eq!(
653 $list.requires_timeout(),
654 $requires_timeout,
655 "requires_timeout",
656 );
657 }
658 )*
659 };
660 }
661
662 #[test]
663 fn test_generator_paging_full_sync() {
664 let (sender, _receiver) = channel(1);
665
666 let mut list = SlidingSyncList::builder("testing")
667 .sync_mode(SlidingSyncMode::new_paging(10))
668 .build(sender);
669
670 assert_ranges! {
671 list = list,
672 list_state = NotLoaded,
673 maximum_number_of_rooms = 25,
674 requires_timeout = false,
675 next => {
676 ranges = 0..=9,
677 is_fully_loaded = false,
678 list_state = PartiallyLoaded,
679 requires_timeout = false,
680 },
681 next => {
682 ranges = 10..=19,
683 is_fully_loaded = false,
684 list_state = PartiallyLoaded,
685 requires_timeout = false,
686 },
687 next => {
689 ranges = 20..=24,
690 is_fully_loaded = true,
691 list_state = FullyLoaded,
692 requires_timeout = true,
693 },
694 next => {
696 ranges = 0..=24, is_fully_loaded = true,
698 list_state = FullyLoaded,
699 requires_timeout = true,
700 },
701 next => {
702 ranges = 0..=24,
703 is_fully_loaded = true,
704 list_state = FullyLoaded,
705 requires_timeout = true,
706 },
707 };
708 }
709
710 #[test]
711 fn test_generator_paging_full_sync_with_a_maximum_number_of_rooms_to_fetch() {
712 let (sender, _receiver) = channel(1);
713
714 let mut list = SlidingSyncList::builder("testing")
715 .sync_mode(SlidingSyncMode::new_paging(10).maximum_number_of_rooms_to_fetch(22))
716 .build(sender);
717
718 assert_ranges! {
719 list = list,
720 list_state = NotLoaded,
721 maximum_number_of_rooms = 25,
722 requires_timeout = false,
723 next => {
724 ranges = 0..=9,
725 is_fully_loaded = false,
726 list_state = PartiallyLoaded,
727 requires_timeout = false,
728 },
729 next => {
730 ranges = 10..=19,
731 is_fully_loaded = false,
732 list_state = PartiallyLoaded,
733 requires_timeout = false,
734 },
735 next => {
737 ranges = 20..=21,
738 is_fully_loaded = true,
739 list_state = FullyLoaded,
740 requires_timeout = true,
741 },
742 next => {
744 ranges = 0..=21, is_fully_loaded = true,
746 list_state = FullyLoaded,
747 requires_timeout = true,
748 },
749 next => {
750 ranges = 0..=21,
751 is_fully_loaded = true,
752 list_state = FullyLoaded,
753 requires_timeout = true,
754 },
755 };
756 }
757
758 #[test]
759 fn test_generator_growing_full_sync() {
760 let (sender, _receiver) = channel(1);
761
762 let mut list = SlidingSyncList::builder("testing")
763 .sync_mode(SlidingSyncMode::new_growing(10))
764 .build(sender);
765
766 assert_ranges! {
767 list = list,
768 list_state = NotLoaded,
769 maximum_number_of_rooms = 25,
770 requires_timeout = false,
771 next => {
772 ranges = 0..=9,
773 is_fully_loaded = false,
774 list_state = PartiallyLoaded,
775 requires_timeout = false,
776 },
777 next => {
778 ranges = 0..=19,
779 is_fully_loaded = false,
780 list_state = PartiallyLoaded,
781 requires_timeout = false,
782 },
783 next => {
785 ranges = 0..=24,
786 is_fully_loaded = true,
787 list_state = FullyLoaded,
788 requires_timeout = true,
789 },
790 next => {
792 ranges = 0..=24,
793 is_fully_loaded = true,
794 list_state = FullyLoaded,
795 requires_timeout = true,
796 },
797 next => {
798 ranges = 0..=24,
799 is_fully_loaded = true,
800 list_state = FullyLoaded,
801 requires_timeout = true,
802 },
803 };
804 }
805
806 #[test]
807 fn test_generator_growing_full_sync_with_a_maximum_number_of_rooms_to_fetch() {
808 let (sender, _receiver) = channel(1);
809
810 let mut list = SlidingSyncList::builder("testing")
811 .sync_mode(SlidingSyncMode::new_growing(10).maximum_number_of_rooms_to_fetch(22))
812 .build(sender);
813
814 assert_ranges! {
815 list = list,
816 list_state = NotLoaded,
817 maximum_number_of_rooms = 25,
818 requires_timeout = false,
819 next => {
820 ranges = 0..=9,
821 is_fully_loaded = false,
822 list_state = PartiallyLoaded,
823 requires_timeout = false,
824 },
825 next => {
826 ranges = 0..=19,
827 is_fully_loaded = false,
828 list_state = PartiallyLoaded,
829 requires_timeout = false,
830 },
831 next => {
833 ranges = 0..=21,
834 is_fully_loaded = true,
835 list_state = FullyLoaded,
836 requires_timeout = true,
837 },
838 next => {
840 ranges = 0..=21,
841 is_fully_loaded = true,
842 list_state = FullyLoaded,
843 requires_timeout = true,
844 },
845 next => {
846 ranges = 0..=21,
847 is_fully_loaded = true,
848 list_state = FullyLoaded,
849 requires_timeout = true,
850 },
851 };
852 }
853
854 #[test]
855 fn test_generator_selective() {
856 let (sender, _receiver) = channel(1);
857
858 let mut list = SlidingSyncList::builder("testing")
859 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10).add_range(42..=153))
860 .build(sender);
861
862 assert_ranges! {
863 list = list,
864 list_state = NotLoaded,
865 maximum_number_of_rooms = 25,
866 requires_timeout = true,
867 next => {
869 ranges = 0..=10, 42..=153,
870 is_fully_loaded = true,
871 list_state = FullyLoaded,
872 requires_timeout = true,
873 },
874 next => {
876 ranges = 0..=10, 42..=153,
877 is_fully_loaded = true,
878 list_state = FullyLoaded,
879 requires_timeout = true,
880 },
881 next => {
882 ranges = 0..=10, 42..=153,
883 is_fully_loaded = true,
884 list_state = FullyLoaded,
885 requires_timeout = true,
886 }
887 };
888 }
889
890 #[async_test]
891 async fn test_generator_selective_with_modifying_ranges_on_the_fly() {
892 let (sender, _receiver) = channel(4);
893
894 let mut list = SlidingSyncList::builder("testing")
895 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10).add_range(42..=153))
896 .build(sender);
897
898 assert_ranges! {
899 list = list,
900 list_state = NotLoaded,
901 maximum_number_of_rooms = 25,
902 requires_timeout = true,
903 next => {
905 ranges = 0..=10, 42..=153,
906 is_fully_loaded = true,
907 list_state = FullyLoaded,
908 requires_timeout = true,
909 },
910 next => {
912 ranges = 0..=10, 42..=153,
913 is_fully_loaded = true,
914 list_state = FullyLoaded,
915 requires_timeout = true,
916 },
917 next => {
918 ranges = 0..=10, 42..=153,
919 is_fully_loaded = true,
920 list_state = FullyLoaded,
921 requires_timeout = true,
922 }
923 };
924
925 list.set_sync_mode(SlidingSyncMode::new_selective().add_range(3..=7));
926
927 assert_ranges! {
928 list = list,
929 list_state = PartiallyLoaded,
930 maximum_number_of_rooms = 25,
931 requires_timeout = true,
932 next => {
933 ranges = 3..=7,
934 is_fully_loaded = true,
935 list_state = FullyLoaded,
936 requires_timeout = true,
937 },
938 };
939
940 list.set_sync_mode(SlidingSyncMode::new_selective().add_range(42..=77));
941
942 assert_ranges! {
943 list = list,
944 list_state = PartiallyLoaded,
945 maximum_number_of_rooms = 25,
946 requires_timeout = true,
947 next => {
948 ranges = 42..=77,
949 is_fully_loaded = true,
950 list_state = FullyLoaded,
951 requires_timeout = true,
952 },
953 };
954
955 list.set_sync_mode(SlidingSyncMode::new_selective());
956
957 assert_ranges! {
958 list = list,
959 list_state = PartiallyLoaded,
960 maximum_number_of_rooms = 25,
961 requires_timeout = true,
962 next => {
963 ranges = ,
964 is_fully_loaded = true,
965 list_state = FullyLoaded,
966 requires_timeout = true,
967 },
968 };
969 }
970
971 #[async_test]
972 async fn test_generator_changing_sync_mode_to_various_modes() {
973 let (sender, _receiver) = channel(4);
974
975 let mut list = SlidingSyncList::builder("testing")
976 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10).add_range(42..=153))
977 .build(sender);
978
979 assert_ranges! {
980 list = list,
981 list_state = NotLoaded,
982 maximum_number_of_rooms = 25,
983 requires_timeout = true,
984 next => {
986 ranges = 0..=10, 42..=153,
987 is_fully_loaded = true,
988 list_state = FullyLoaded,
989 requires_timeout = true,
990 },
991 next => {
993 ranges = 0..=10, 42..=153,
994 is_fully_loaded = true,
995 list_state = FullyLoaded,
996 requires_timeout = true,
997 },
998 next => {
999 ranges = 0..=10, 42..=153,
1000 is_fully_loaded = true,
1001 list_state = FullyLoaded,
1002 requires_timeout = true,
1003 }
1004 };
1005
1006 list.set_sync_mode(SlidingSyncMode::new_growing(10));
1008
1009 assert_ranges! {
1010 list = list,
1011 list_state = PartiallyLoaded, maximum_number_of_rooms = 25,
1013 requires_timeout = false,
1014 next => {
1015 ranges = 0..=9,
1016 is_fully_loaded = false,
1017 list_state = PartiallyLoaded,
1018 requires_timeout = false,
1019 },
1020 next => {
1021 ranges = 0..=19,
1022 is_fully_loaded = false,
1023 list_state = PartiallyLoaded,
1024 requires_timeout = false,
1025 },
1026 next => {
1028 ranges = 0..=24,
1029 is_fully_loaded = true,
1030 list_state = FullyLoaded,
1031 requires_timeout = true,
1032 },
1033 next => {
1035 ranges = 0..=24,
1036 is_fully_loaded = true,
1037 list_state = FullyLoaded,
1038 requires_timeout = true,
1039 },
1040 next => {
1041 ranges = 0..=24,
1042 is_fully_loaded = true,
1043 list_state = FullyLoaded,
1044 requires_timeout = true,
1045 },
1046 };
1047
1048 list.set_sync_mode(SlidingSyncMode::new_paging(10));
1050
1051 assert_ranges! {
1052 list = list,
1053 list_state = PartiallyLoaded, maximum_number_of_rooms = 25,
1055 requires_timeout = false,
1056 next => {
1057 ranges = 0..=9,
1058 is_fully_loaded = false,
1059 list_state = PartiallyLoaded,
1060 requires_timeout = false,
1061 },
1062 next => {
1063 ranges = 10..=19,
1064 is_fully_loaded = false,
1065 list_state = PartiallyLoaded,
1066 requires_timeout = false,
1067 },
1068 next => {
1070 ranges = 20..=24,
1071 is_fully_loaded = true,
1072 list_state = FullyLoaded,
1073 requires_timeout = true,
1074 },
1075 next => {
1077 ranges = 0..=24, is_fully_loaded = true,
1079 list_state = FullyLoaded,
1080 requires_timeout = true,
1081 },
1082 next => {
1083 ranges = 0..=24,
1084 is_fully_loaded = true,
1085 list_state = FullyLoaded,
1086 requires_timeout = true,
1087 },
1088 };
1089
1090 list.set_sync_mode(SlidingSyncMode::new_selective());
1092
1093 assert_eq!(list.state(), SlidingSyncListLoadingState::PartiallyLoaded); list.set_sync_mode(SlidingSyncMode::new_selective().add_range(0..=100));
1099
1100 assert_ranges! {
1101 list = list,
1102 list_state = PartiallyLoaded, maximum_number_of_rooms = 25,
1104 requires_timeout = true,
1105 next => {
1107 ranges = 0..=100,
1108 is_fully_loaded = true,
1109 list_state = FullyLoaded,
1110 requires_timeout = true,
1111 },
1112 next => {
1114 ranges = 0..=100,
1115 is_fully_loaded = true,
1116 list_state = FullyLoaded,
1117 requires_timeout = true,
1118 },
1119 next => {
1120 ranges = 0..=100,
1121 is_fully_loaded = true,
1122 list_state = FullyLoaded,
1123 requires_timeout = true,
1124 }
1125 };
1126 }
1127
1128 #[async_test]
1129 #[allow(clippy::await_holding_lock)]
1130 async fn test_inner_update_maximum_number_of_rooms() {
1131 let (sender, _receiver) = channel(1);
1132
1133 let mut list = SlidingSyncList::builder("foo")
1134 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=3))
1135 .build(sender);
1136
1137 assert!(list.maximum_number_of_rooms().is_none());
1138
1139 let _ = list.next_request(&mut LazyTransactionId::new());
1141 let new_changes = list.update(Some(5)).unwrap();
1142 assert!(new_changes);
1143
1144 assert_eq!(list.maximum_number_of_rooms(), Some(5));
1146
1147 let _ = list.next_request(&mut LazyTransactionId::new());
1149 let new_changes = list.update(Some(5)).unwrap();
1150 assert!(!new_changes);
1151
1152 assert_eq!(list.maximum_number_of_rooms(), Some(5));
1154 }
1155
1156 #[test]
1157 fn test_sliding_sync_mode_serialization() {
1158 assert_json_roundtrip!(
1159 from SlidingSyncMode: SlidingSyncMode::from(SlidingSyncMode::new_paging(1).maximum_number_of_rooms_to_fetch(2)) => json!({
1160 "Paging": {
1161 "batch_size": 1,
1162 "maximum_number_of_rooms_to_fetch": 2
1163 }
1164 })
1165 );
1166 assert_json_roundtrip!(
1167 from SlidingSyncMode: SlidingSyncMode::from(SlidingSyncMode::new_growing(1).maximum_number_of_rooms_to_fetch(2)) => json!({
1168 "Growing": {
1169 "batch_size": 1,
1170 "maximum_number_of_rooms_to_fetch": 2
1171 }
1172 })
1173 );
1174 assert_json_roundtrip!(from SlidingSyncMode: SlidingSyncMode::from(SlidingSyncMode::new_selective()) => json!({
1175 "Selective": {
1176 "ranges": []
1177 }
1178 })
1179 );
1180 }
1181
1182 #[test]
1183 fn test_sliding_sync_list_loading_state_serialization() {
1184 assert_json_roundtrip!(from SlidingSyncListLoadingState: SlidingSyncListLoadingState::NotLoaded => json!("NotLoaded"));
1185 assert_json_roundtrip!(from SlidingSyncListLoadingState: SlidingSyncListLoadingState::Preloaded => json!("Preloaded"));
1186 assert_json_roundtrip!(from SlidingSyncListLoadingState: SlidingSyncListLoadingState::PartiallyLoaded => json!("PartiallyLoaded"));
1187 assert_json_roundtrip!(from SlidingSyncListLoadingState: SlidingSyncListLoadingState::FullyLoaded => json!("FullyLoaded"));
1188 }
1189
1190 #[test]
1191 fn test_sliding_sync_list_loading_state_is_fully_loaded() {
1192 assert!(SlidingSyncListLoadingState::NotLoaded.is_fully_loaded().not());
1193 assert!(SlidingSyncListLoadingState::Preloaded.is_fully_loaded().not());
1194 assert!(SlidingSyncListLoadingState::PartiallyLoaded.is_fully_loaded().not());
1195 assert!(SlidingSyncListLoadingState::FullyLoaded.is_fully_loaded());
1196 }
1197
1198 #[test]
1199 fn test_once_built() {
1200 let (sender, _receiver) = channel(1);
1201
1202 let probe = Arc::new(Mutex::new(Cell::new(false)));
1203 let probe_clone = probe.clone();
1204
1205 let _list = SlidingSyncList::builder("testing")
1206 .once_built(move |list| {
1207 let mut probe_lock = probe.lock().unwrap();
1208 *probe_lock.get_mut() = true;
1209
1210 list
1211 })
1212 .build(sender);
1213
1214 let probe_lock = probe_clone.lock().unwrap();
1215 assert!(probe_lock.get());
1216 }
1217}