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 ruma::{api::client::sync::sync_events::v5 as http, assign, TransactionId};
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 sticky_parameters::{LazyTransactionId, SlidingSyncStickyManager},
24 Error, SlidingSyncInternalMessage,
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)
79 where
80 M: Into<SlidingSyncMode>,
81 {
82 self.inner.set_sync_mode(sync_mode.into());
83
84 self.inner.internal_channel_send_if_possible(
87 SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration,
88 );
89 }
90
91 pub fn state(&self) -> SlidingSyncListLoadingState {
93 self.inner.state.read().unwrap().clone()
94 }
95
96 pub(super) fn requires_timeout(&self) -> bool {
109 self.inner.request_generator.read().unwrap().is_selective()
110 || self.state().is_fully_loaded()
111 }
112
113 pub fn state_stream(
124 &self,
125 ) -> (SlidingSyncListLoadingState, impl Stream<Item = SlidingSyncListLoadingState>) {
126 let read_lock = self.inner.state.read().unwrap();
127 let previous_value = (*read_lock).clone();
128 let subscriber = Observable::subscribe(&read_lock);
129
130 (previous_value, subscriber)
131 }
132
133 pub fn timeline_limit(&self) -> Bound {
135 *self.inner.timeline_limit.read().unwrap()
136 }
137
138 pub fn set_timeline_limit(&self, timeline: Bound) {
140 *self.inner.timeline_limit.write().unwrap() = timeline;
141 }
142
143 pub fn maximum_number_of_rooms(&self) -> Option<u32> {
146 self.inner.maximum_number_of_rooms.get()
147 }
148
149 pub fn maximum_number_of_rooms_stream(&self) -> Subscriber<Option<u32>> {
157 self.inner.maximum_number_of_rooms.subscribe()
158 }
159
160 pub(super) fn next_request(
165 &self,
166 txn_id: &mut LazyTransactionId,
167 ) -> Result<http::request::List, Error> {
168 self.inner.next_request(txn_id)
169 }
170
171 pub(super) fn cache_policy(&self) -> SlidingSyncListCachePolicy {
173 self.inner.cache_policy
174 }
175
176 #[instrument(skip(self), fields(name = self.name()))]
184 pub(super) fn update(&mut self, maximum_number_of_rooms: Option<u32>) -> Result<bool, Error> {
185 if let Some(maximum_number_of_rooms) = maximum_number_of_rooms {
188 self.inner.update_request_generator_state(maximum_number_of_rooms)?;
189 }
190
191 let new_changes = self.inner.update_room_list(maximum_number_of_rooms)?;
192
193 Ok(new_changes)
194 }
195
196 pub fn maybe_commit_sticky(&mut self, txn_id: &TransactionId) {
198 self.inner.sticky.write().unwrap().maybe_commit(txn_id);
199 }
200
201 pub(super) fn invalidate_sticky_data(&self) {
204 let _ = self.inner.sticky.write().unwrap().data_mut();
205 }
206
207 #[cfg(feature = "testing")]
209 pub fn sync_mode(&self) -> SlidingSyncMode {
210 self.inner.sync_mode.read().unwrap().clone()
211 }
212
213 #[cfg(test)]
215 pub(super) fn set_maximum_number_of_rooms(&self, maximum_number_of_rooms: Option<u32>) {
216 self.inner.maximum_number_of_rooms.set(maximum_number_of_rooms);
217 }
218}
219
220#[derive(Debug)]
221pub(super) struct SlidingSyncListInner {
222 name: String,
224
225 state: StdRwLock<Observable<SlidingSyncListLoadingState>>,
227
228 sticky: StdRwLock<SlidingSyncStickyManager<SlidingSyncListStickyParameters>>,
232
233 timeline_limit: StdRwLock<Bound>,
235
236 maximum_number_of_rooms: SharedObservable<Option<u32>>,
244
245 request_generator: StdRwLock<SlidingSyncListRequestGenerator>,
248
249 cache_policy: SlidingSyncListCachePolicy,
251
252 sliding_sync_internal_channel_sender: Sender<SlidingSyncInternalMessage>,
255
256 #[cfg(any(test, feature = "testing"))]
257 sync_mode: StdRwLock<SlidingSyncMode>,
258}
259
260impl SlidingSyncListInner {
261 pub fn set_sync_mode(&self, sync_mode: SlidingSyncMode) {
268 #[cfg(any(test, feature = "testing"))]
269 {
270 *self.sync_mode.write().unwrap() = sync_mode.clone();
271 }
272
273 {
274 let mut request_generator = self.request_generator.write().unwrap();
275 *request_generator = SlidingSyncListRequestGenerator::new(sync_mode);
276 }
277
278 {
279 let mut state = self.state.write().unwrap();
280
281 let next_state = match **state {
282 SlidingSyncListLoadingState::NotLoaded => SlidingSyncListLoadingState::NotLoaded,
283 SlidingSyncListLoadingState::Preloaded => SlidingSyncListLoadingState::Preloaded,
284 SlidingSyncListLoadingState::PartiallyLoaded
285 | SlidingSyncListLoadingState::FullyLoaded => {
286 SlidingSyncListLoadingState::PartiallyLoaded
287 }
288 };
289
290 Observable::set(&mut state, next_state);
291 }
292 }
293
294 fn next_request(&self, txn_id: &mut LazyTransactionId) -> Result<http::request::List, Error> {
296 let ranges = {
297 let mut request_generator = self.request_generator.write().unwrap();
299 request_generator.generate_next_ranges(self.maximum_number_of_rooms.get())?
300 };
301
302 Ok(self.request(ranges, txn_id))
304 }
305
306 #[instrument(skip(self), fields(name = self.name))]
309 fn request(&self, ranges: Ranges, txn_id: &mut LazyTransactionId) -> http::request::List {
310 let ranges = ranges.into_iter().map(|r| ((*r.start()).into(), (*r.end()).into())).collect();
311
312 let mut request = assign!(http::request::List::default(), { ranges });
313 request.room_details.timeline_limit = (*self.timeline_limit.read().unwrap()).into();
314
315 {
316 let mut sticky = self.sticky.write().unwrap();
317 sticky.maybe_apply(&mut request, txn_id);
318 }
319
320 request
321 }
322
323 fn update_room_list(&self, maximum_number_of_rooms: Option<u32>) -> Result<bool, Error> {
328 let mut new_changes = false;
329
330 if maximum_number_of_rooms.is_some() {
331 if self.maximum_number_of_rooms.set_if_not_eq(maximum_number_of_rooms).is_some() {
333 new_changes = true;
334 }
335 }
336
337 Ok(new_changes)
338 }
339
340 fn update_request_generator_state(&self, maximum_number_of_rooms: u32) -> Result<(), Error> {
343 let mut request_generator = self.request_generator.write().unwrap();
344 let new_state = request_generator.handle_response(&self.name, maximum_number_of_rooms)?;
345 Observable::set_if_not_eq(&mut self.state.write().unwrap(), new_state);
346 Ok(())
347 }
348
349 #[instrument]
352 fn internal_channel_send_if_possible(&self, message: SlidingSyncInternalMessage) {
353 let _ = self.sliding_sync_internal_channel_sender.send(message);
355 }
356}
357
358#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
368pub enum SlidingSyncListLoadingState {
369 #[default]
371 NotLoaded,
372
373 Preloaded,
375
376 PartiallyLoaded,
379
380 FullyLoaded,
383}
384
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::{sticky_parameters::LazyTransactionId, SlidingSyncInternalMessage};
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}