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 pub(super) fn set_maximum_number_of_rooms(&self, maximum_number_of_rooms: Option<u32>) {
215 self.inner.maximum_number_of_rooms.set(maximum_number_of_rooms);
216 }
217}
218
219#[derive(Debug)]
220pub(super) struct SlidingSyncListInner {
221 name: String,
223
224 state: StdRwLock<Observable<SlidingSyncListLoadingState>>,
226
227 sticky: StdRwLock<SlidingSyncStickyManager<SlidingSyncListStickyParameters>>,
231
232 timeline_limit: StdRwLock<Bound>,
234
235 maximum_number_of_rooms: SharedObservable<Option<u32>>,
243
244 request_generator: StdRwLock<SlidingSyncListRequestGenerator>,
247
248 cache_policy: SlidingSyncListCachePolicy,
250
251 sliding_sync_internal_channel_sender: Sender<SlidingSyncInternalMessage>,
254
255 #[cfg(any(test, feature = "testing"))]
256 sync_mode: StdRwLock<SlidingSyncMode>,
257}
258
259impl SlidingSyncListInner {
260 pub fn set_sync_mode(&self, sync_mode: SlidingSyncMode) {
267 #[cfg(any(test, feature = "testing"))]
268 {
269 *self.sync_mode.write().unwrap() = sync_mode.clone();
270 }
271
272 {
273 let mut request_generator = self.request_generator.write().unwrap();
274 *request_generator = SlidingSyncListRequestGenerator::new(sync_mode);
275 }
276
277 {
278 let mut state = self.state.write().unwrap();
279
280 let next_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 Observable::set(&mut state, next_state);
290 }
291 }
292
293 fn next_request(&self, txn_id: &mut LazyTransactionId) -> Result<http::request::List, Error> {
295 let ranges = {
296 let mut request_generator = self.request_generator.write().unwrap();
298 request_generator.generate_next_ranges(self.maximum_number_of_rooms.get())?
299 };
300
301 Ok(self.request(ranges, txn_id))
303 }
304
305 #[instrument(skip(self), fields(name = self.name))]
308 fn request(&self, ranges: Ranges, txn_id: &mut LazyTransactionId) -> http::request::List {
309 let ranges = ranges.into_iter().map(|r| ((*r.start()).into(), (*r.end()).into())).collect();
310
311 let mut request = assign!(http::request::List::default(), { ranges });
312 request.room_details.timeline_limit = (*self.timeline_limit.read().unwrap()).into();
313
314 {
315 let mut sticky = self.sticky.write().unwrap();
316 sticky.maybe_apply(&mut request, txn_id);
317 }
318
319 request
320 }
321
322 fn update_room_list(&self, maximum_number_of_rooms: Option<u32>) -> Result<bool, Error> {
327 let mut new_changes = false;
328
329 if maximum_number_of_rooms.is_some() {
330 if self.maximum_number_of_rooms.set_if_not_eq(maximum_number_of_rooms).is_some() {
332 new_changes = true;
333 }
334 }
335
336 Ok(new_changes)
337 }
338
339 fn update_request_generator_state(&self, maximum_number_of_rooms: u32) -> Result<(), Error> {
342 let mut request_generator = self.request_generator.write().unwrap();
343 let new_state = request_generator.handle_response(&self.name, maximum_number_of_rooms)?;
344 Observable::set_if_not_eq(&mut self.state.write().unwrap(), new_state);
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
384impl SlidingSyncListLoadingState {
385 fn is_fully_loaded(&self) -> bool {
387 matches!(self, Self::FullyLoaded)
388 }
389}
390
391#[derive(Clone, Debug, Default)]
395pub struct SlidingSyncSelectiveModeBuilder {
396 ranges: Ranges,
397}
398
399impl SlidingSyncSelectiveModeBuilder {
400 fn new() -> Self {
402 Self::default()
403 }
404
405 pub fn add_range(mut self, range: Range) -> Self {
407 self.ranges.push(range);
408 self
409 }
410
411 pub fn add_ranges(mut self, ranges: Ranges) -> Self {
413 self.ranges.extend(ranges);
414 self
415 }
416}
417
418impl From<SlidingSyncSelectiveModeBuilder> for SlidingSyncMode {
419 fn from(builder: SlidingSyncSelectiveModeBuilder) -> Self {
420 Self::Selective { ranges: builder.ranges }
421 }
422}
423
424#[derive(Clone, Debug)]
425enum WindowedModeBuilderKind {
426 Paging,
427 Growing,
428}
429
430#[derive(Clone, Debug)]
432pub struct SlidingSyncWindowedModeBuilder {
433 mode: WindowedModeBuilderKind,
434 batch_size: u32,
435 maximum_number_of_rooms_to_fetch: Option<u32>,
436}
437
438impl SlidingSyncWindowedModeBuilder {
439 fn new(mode: WindowedModeBuilderKind, batch_size: u32) -> Self {
440 Self { mode, batch_size, maximum_number_of_rooms_to_fetch: None }
441 }
442
443 pub fn maximum_number_of_rooms_to_fetch(mut self, num: u32) -> Self {
445 self.maximum_number_of_rooms_to_fetch = Some(num);
446 self
447 }
448}
449
450impl From<SlidingSyncWindowedModeBuilder> for SlidingSyncMode {
451 fn from(builder: SlidingSyncWindowedModeBuilder) -> Self {
452 match builder.mode {
453 WindowedModeBuilderKind::Paging => Self::Paging {
454 batch_size: builder.batch_size,
455 maximum_number_of_rooms_to_fetch: builder.maximum_number_of_rooms_to_fetch,
456 },
457 WindowedModeBuilderKind::Growing => Self::Growing {
458 batch_size: builder.batch_size,
459 maximum_number_of_rooms_to_fetch: builder.maximum_number_of_rooms_to_fetch,
460 },
461 }
462 }
463}
464
465#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
467pub enum SlidingSyncMode {
468 Selective {
470 ranges: Ranges,
472 },
473
474 Paging {
478 batch_size: u32,
480
481 maximum_number_of_rooms_to_fetch: Option<u32>,
484 },
485
486 Growing {
490 batch_size: u32,
492
493 maximum_number_of_rooms_to_fetch: Option<u32>,
496 },
497}
498
499impl Default for SlidingSyncMode {
500 fn default() -> Self {
501 Self::Selective { ranges: Vec::new() }
502 }
503}
504
505impl SlidingSyncMode {
506 pub fn new_selective() -> SlidingSyncSelectiveModeBuilder {
508 SlidingSyncSelectiveModeBuilder::new()
509 }
510
511 pub fn new_paging(batch_size: u32) -> SlidingSyncWindowedModeBuilder {
513 SlidingSyncWindowedModeBuilder::new(WindowedModeBuilderKind::Paging, batch_size)
514 }
515
516 pub fn new_growing(batch_size: u32) -> SlidingSyncWindowedModeBuilder {
518 SlidingSyncWindowedModeBuilder::new(WindowedModeBuilderKind::Growing, batch_size)
519 }
520}
521
522#[cfg(test)]
523mod tests {
524 use std::{
525 cell::Cell,
526 ops::Not,
527 sync::{Arc, Mutex},
528 };
529
530 use matrix_sdk_test::async_test;
531 use ruma::uint;
532 use serde_json::json;
533 use tokio::sync::broadcast::{channel, error::TryRecvError};
534
535 use super::{SlidingSyncList, SlidingSyncListLoadingState, SlidingSyncMode};
536 use crate::sliding_sync::{sticky_parameters::LazyTransactionId, SlidingSyncInternalMessage};
537
538 macro_rules! assert_json_roundtrip {
539 (from $type:ty: $rust_value:expr => $json_value:expr) => {
540 let json = serde_json::to_value(&$rust_value).unwrap();
541 assert_eq!(json, $json_value);
542
543 let rust: $type = serde_json::from_value(json).unwrap();
544 assert_eq!(rust, $rust_value);
545 };
546 }
547
548 #[async_test]
549 async fn test_sliding_sync_list_selective_mode() {
550 let (sender, mut receiver) = channel(1);
551
552 let list = SlidingSyncList::builder("foo")
554 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=1).add_range(2..=3))
555 .build(sender);
556
557 {
558 let mut generator = list.inner.request_generator.write().unwrap();
559 assert_eq!(generator.requested_ranges(), &[0..=1, 2..=3]);
560
561 let ranges = generator.generate_next_ranges(None).unwrap();
562 assert_eq!(ranges, &[0..=1, 2..=3]);
563 }
564
565 assert!(matches!(receiver.try_recv(), Err(TryRecvError::Empty)));
567
568 list.set_sync_mode(SlidingSyncMode::new_selective().add_range(4..=5));
569
570 {
571 let mut generator = list.inner.request_generator.write().unwrap();
572 assert_eq!(generator.requested_ranges(), &[4..=5]);
573
574 let ranges = generator.generate_next_ranges(None).unwrap();
575 assert_eq!(ranges, &[4..=5]);
576 }
577
578 assert!(matches!(
580 receiver.try_recv(),
581 Ok(SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration)
582 ));
583 assert!(matches!(receiver.try_recv(), Err(TryRecvError::Empty)));
584 }
585
586 #[test]
587 fn test_sliding_sync_list_timeline_limit() {
588 let (sender, _receiver) = channel(1);
589
590 let list = SlidingSyncList::builder("foo")
591 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=1))
592 .timeline_limit(7)
593 .build(sender);
594
595 assert_eq!(list.timeline_limit(), 7);
596
597 list.set_timeline_limit(42);
598 assert_eq!(list.timeline_limit(), 42);
599 }
600
601 macro_rules! assert_ranges {
602 (
603 list = $list:ident,
604 list_state = $first_list_state:ident,
605 maximum_number_of_rooms = $maximum_number_of_rooms:expr,
606 requires_timeout = $initial_requires_timeout:literal,
607 $(
608 next => {
609 ranges = $( $range_start:literal ..= $range_end:literal ),* ,
610 is_fully_loaded = $is_fully_loaded:expr,
611 list_state = $list_state:ident,
612 requires_timeout = $requires_timeout:literal,
613 }
614 ),*
615 $(,)*
616 ) => {
617 assert_eq!($list.state(), SlidingSyncListLoadingState::$first_list_state, "first state");
618 assert_eq!(
619 $list.requires_timeout(),
620 $initial_requires_timeout,
621 "initial requires_timeout",
622 );
623
624 $(
625 {
626 let request = $list.next_request(&mut LazyTransactionId::new()).unwrap();
628
629 assert_eq!(
630 request.ranges,
631 [
632 $( (uint!( $range_start ), uint!( $range_end )) ),*
633 ],
634 "ranges",
635 );
636
637 let _ = $list.update(Some($maximum_number_of_rooms));
639
640 assert_eq!(
641 $list.inner.request_generator.read().unwrap().is_fully_loaded(),
642 $is_fully_loaded,
643 "is fully loaded",
644 );
645 assert_eq!(
646 $list.state(),
647 SlidingSyncListLoadingState::$list_state,
648 "state",
649 );
650 assert_eq!(
651 $list.requires_timeout(),
652 $requires_timeout,
653 "requires_timeout",
654 );
655 }
656 )*
657 };
658 }
659
660 #[test]
661 fn test_generator_paging_full_sync() {
662 let (sender, _receiver) = channel(1);
663
664 let mut list = SlidingSyncList::builder("testing")
665 .sync_mode(SlidingSyncMode::new_paging(10))
666 .build(sender);
667
668 assert_ranges! {
669 list = list,
670 list_state = NotLoaded,
671 maximum_number_of_rooms = 25,
672 requires_timeout = false,
673 next => {
674 ranges = 0..=9,
675 is_fully_loaded = false,
676 list_state = PartiallyLoaded,
677 requires_timeout = false,
678 },
679 next => {
680 ranges = 10..=19,
681 is_fully_loaded = false,
682 list_state = PartiallyLoaded,
683 requires_timeout = false,
684 },
685 next => {
687 ranges = 20..=24,
688 is_fully_loaded = true,
689 list_state = FullyLoaded,
690 requires_timeout = true,
691 },
692 next => {
694 ranges = 0..=24, is_fully_loaded = true,
696 list_state = FullyLoaded,
697 requires_timeout = true,
698 },
699 next => {
700 ranges = 0..=24,
701 is_fully_loaded = true,
702 list_state = FullyLoaded,
703 requires_timeout = true,
704 },
705 };
706 }
707
708 #[test]
709 fn test_generator_paging_full_sync_with_a_maximum_number_of_rooms_to_fetch() {
710 let (sender, _receiver) = channel(1);
711
712 let mut list = SlidingSyncList::builder("testing")
713 .sync_mode(SlidingSyncMode::new_paging(10).maximum_number_of_rooms_to_fetch(22))
714 .build(sender);
715
716 assert_ranges! {
717 list = list,
718 list_state = NotLoaded,
719 maximum_number_of_rooms = 25,
720 requires_timeout = false,
721 next => {
722 ranges = 0..=9,
723 is_fully_loaded = false,
724 list_state = PartiallyLoaded,
725 requires_timeout = false,
726 },
727 next => {
728 ranges = 10..=19,
729 is_fully_loaded = false,
730 list_state = PartiallyLoaded,
731 requires_timeout = false,
732 },
733 next => {
735 ranges = 20..=21,
736 is_fully_loaded = true,
737 list_state = FullyLoaded,
738 requires_timeout = true,
739 },
740 next => {
742 ranges = 0..=21, is_fully_loaded = true,
744 list_state = FullyLoaded,
745 requires_timeout = true,
746 },
747 next => {
748 ranges = 0..=21,
749 is_fully_loaded = true,
750 list_state = FullyLoaded,
751 requires_timeout = true,
752 },
753 };
754 }
755
756 #[test]
757 fn test_generator_growing_full_sync() {
758 let (sender, _receiver) = channel(1);
759
760 let mut list = SlidingSyncList::builder("testing")
761 .sync_mode(SlidingSyncMode::new_growing(10))
762 .build(sender);
763
764 assert_ranges! {
765 list = list,
766 list_state = NotLoaded,
767 maximum_number_of_rooms = 25,
768 requires_timeout = false,
769 next => {
770 ranges = 0..=9,
771 is_fully_loaded = false,
772 list_state = PartiallyLoaded,
773 requires_timeout = false,
774 },
775 next => {
776 ranges = 0..=19,
777 is_fully_loaded = false,
778 list_state = PartiallyLoaded,
779 requires_timeout = false,
780 },
781 next => {
783 ranges = 0..=24,
784 is_fully_loaded = true,
785 list_state = FullyLoaded,
786 requires_timeout = true,
787 },
788 next => {
790 ranges = 0..=24,
791 is_fully_loaded = true,
792 list_state = FullyLoaded,
793 requires_timeout = true,
794 },
795 next => {
796 ranges = 0..=24,
797 is_fully_loaded = true,
798 list_state = FullyLoaded,
799 requires_timeout = true,
800 },
801 };
802 }
803
804 #[test]
805 fn test_generator_growing_full_sync_with_a_maximum_number_of_rooms_to_fetch() {
806 let (sender, _receiver) = channel(1);
807
808 let mut list = SlidingSyncList::builder("testing")
809 .sync_mode(SlidingSyncMode::new_growing(10).maximum_number_of_rooms_to_fetch(22))
810 .build(sender);
811
812 assert_ranges! {
813 list = list,
814 list_state = NotLoaded,
815 maximum_number_of_rooms = 25,
816 requires_timeout = false,
817 next => {
818 ranges = 0..=9,
819 is_fully_loaded = false,
820 list_state = PartiallyLoaded,
821 requires_timeout = false,
822 },
823 next => {
824 ranges = 0..=19,
825 is_fully_loaded = false,
826 list_state = PartiallyLoaded,
827 requires_timeout = false,
828 },
829 next => {
831 ranges = 0..=21,
832 is_fully_loaded = true,
833 list_state = FullyLoaded,
834 requires_timeout = true,
835 },
836 next => {
838 ranges = 0..=21,
839 is_fully_loaded = true,
840 list_state = FullyLoaded,
841 requires_timeout = true,
842 },
843 next => {
844 ranges = 0..=21,
845 is_fully_loaded = true,
846 list_state = FullyLoaded,
847 requires_timeout = true,
848 },
849 };
850 }
851
852 #[test]
853 fn test_generator_selective() {
854 let (sender, _receiver) = channel(1);
855
856 let mut list = SlidingSyncList::builder("testing")
857 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10).add_range(42..=153))
858 .build(sender);
859
860 assert_ranges! {
861 list = list,
862 list_state = NotLoaded,
863 maximum_number_of_rooms = 25,
864 requires_timeout = true,
865 next => {
867 ranges = 0..=10, 42..=153,
868 is_fully_loaded = true,
869 list_state = FullyLoaded,
870 requires_timeout = true,
871 },
872 next => {
874 ranges = 0..=10, 42..=153,
875 is_fully_loaded = true,
876 list_state = FullyLoaded,
877 requires_timeout = true,
878 },
879 next => {
880 ranges = 0..=10, 42..=153,
881 is_fully_loaded = true,
882 list_state = FullyLoaded,
883 requires_timeout = true,
884 }
885 };
886 }
887
888 #[async_test]
889 async fn test_generator_selective_with_modifying_ranges_on_the_fly() {
890 let (sender, _receiver) = channel(4);
891
892 let mut list = SlidingSyncList::builder("testing")
893 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10).add_range(42..=153))
894 .build(sender);
895
896 assert_ranges! {
897 list = list,
898 list_state = NotLoaded,
899 maximum_number_of_rooms = 25,
900 requires_timeout = true,
901 next => {
903 ranges = 0..=10, 42..=153,
904 is_fully_loaded = true,
905 list_state = FullyLoaded,
906 requires_timeout = true,
907 },
908 next => {
910 ranges = 0..=10, 42..=153,
911 is_fully_loaded = true,
912 list_state = FullyLoaded,
913 requires_timeout = true,
914 },
915 next => {
916 ranges = 0..=10, 42..=153,
917 is_fully_loaded = true,
918 list_state = FullyLoaded,
919 requires_timeout = true,
920 }
921 };
922
923 list.set_sync_mode(SlidingSyncMode::new_selective().add_range(3..=7));
924
925 assert_ranges! {
926 list = list,
927 list_state = PartiallyLoaded,
928 maximum_number_of_rooms = 25,
929 requires_timeout = true,
930 next => {
931 ranges = 3..=7,
932 is_fully_loaded = true,
933 list_state = FullyLoaded,
934 requires_timeout = true,
935 },
936 };
937
938 list.set_sync_mode(SlidingSyncMode::new_selective().add_range(42..=77));
939
940 assert_ranges! {
941 list = list,
942 list_state = PartiallyLoaded,
943 maximum_number_of_rooms = 25,
944 requires_timeout = true,
945 next => {
946 ranges = 42..=77,
947 is_fully_loaded = true,
948 list_state = FullyLoaded,
949 requires_timeout = true,
950 },
951 };
952
953 list.set_sync_mode(SlidingSyncMode::new_selective());
954
955 assert_ranges! {
956 list = list,
957 list_state = PartiallyLoaded,
958 maximum_number_of_rooms = 25,
959 requires_timeout = true,
960 next => {
961 ranges = ,
962 is_fully_loaded = true,
963 list_state = FullyLoaded,
964 requires_timeout = true,
965 },
966 };
967 }
968
969 #[async_test]
970 async fn test_generator_changing_sync_mode_to_various_modes() {
971 let (sender, _receiver) = channel(4);
972
973 let mut list = SlidingSyncList::builder("testing")
974 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10).add_range(42..=153))
975 .build(sender);
976
977 assert_ranges! {
978 list = list,
979 list_state = NotLoaded,
980 maximum_number_of_rooms = 25,
981 requires_timeout = true,
982 next => {
984 ranges = 0..=10, 42..=153,
985 is_fully_loaded = true,
986 list_state = FullyLoaded,
987 requires_timeout = true,
988 },
989 next => {
991 ranges = 0..=10, 42..=153,
992 is_fully_loaded = true,
993 list_state = FullyLoaded,
994 requires_timeout = true,
995 },
996 next => {
997 ranges = 0..=10, 42..=153,
998 is_fully_loaded = true,
999 list_state = FullyLoaded,
1000 requires_timeout = true,
1001 }
1002 };
1003
1004 list.set_sync_mode(SlidingSyncMode::new_growing(10));
1006
1007 assert_ranges! {
1008 list = list,
1009 list_state = PartiallyLoaded, maximum_number_of_rooms = 25,
1011 requires_timeout = false,
1012 next => {
1013 ranges = 0..=9,
1014 is_fully_loaded = false,
1015 list_state = PartiallyLoaded,
1016 requires_timeout = false,
1017 },
1018 next => {
1019 ranges = 0..=19,
1020 is_fully_loaded = false,
1021 list_state = PartiallyLoaded,
1022 requires_timeout = false,
1023 },
1024 next => {
1026 ranges = 0..=24,
1027 is_fully_loaded = true,
1028 list_state = FullyLoaded,
1029 requires_timeout = true,
1030 },
1031 next => {
1033 ranges = 0..=24,
1034 is_fully_loaded = true,
1035 list_state = FullyLoaded,
1036 requires_timeout = true,
1037 },
1038 next => {
1039 ranges = 0..=24,
1040 is_fully_loaded = true,
1041 list_state = FullyLoaded,
1042 requires_timeout = true,
1043 },
1044 };
1045
1046 list.set_sync_mode(SlidingSyncMode::new_paging(10));
1048
1049 assert_ranges! {
1050 list = list,
1051 list_state = PartiallyLoaded, maximum_number_of_rooms = 25,
1053 requires_timeout = false,
1054 next => {
1055 ranges = 0..=9,
1056 is_fully_loaded = false,
1057 list_state = PartiallyLoaded,
1058 requires_timeout = false,
1059 },
1060 next => {
1061 ranges = 10..=19,
1062 is_fully_loaded = false,
1063 list_state = PartiallyLoaded,
1064 requires_timeout = false,
1065 },
1066 next => {
1068 ranges = 20..=24,
1069 is_fully_loaded = true,
1070 list_state = FullyLoaded,
1071 requires_timeout = true,
1072 },
1073 next => {
1075 ranges = 0..=24, is_fully_loaded = true,
1077 list_state = FullyLoaded,
1078 requires_timeout = true,
1079 },
1080 next => {
1081 ranges = 0..=24,
1082 is_fully_loaded = true,
1083 list_state = FullyLoaded,
1084 requires_timeout = true,
1085 },
1086 };
1087
1088 list.set_sync_mode(SlidingSyncMode::new_selective());
1090
1091 assert_eq!(list.state(), SlidingSyncListLoadingState::PartiallyLoaded); list.set_sync_mode(SlidingSyncMode::new_selective().add_range(0..=100));
1097
1098 assert_ranges! {
1099 list = list,
1100 list_state = PartiallyLoaded, maximum_number_of_rooms = 25,
1102 requires_timeout = true,
1103 next => {
1105 ranges = 0..=100,
1106 is_fully_loaded = true,
1107 list_state = FullyLoaded,
1108 requires_timeout = true,
1109 },
1110 next => {
1112 ranges = 0..=100,
1113 is_fully_loaded = true,
1114 list_state = FullyLoaded,
1115 requires_timeout = true,
1116 },
1117 next => {
1118 ranges = 0..=100,
1119 is_fully_loaded = true,
1120 list_state = FullyLoaded,
1121 requires_timeout = true,
1122 }
1123 };
1124 }
1125
1126 #[async_test]
1127 #[allow(clippy::await_holding_lock)]
1128 async fn test_inner_update_maximum_number_of_rooms() {
1129 let (sender, _receiver) = channel(1);
1130
1131 let mut list = SlidingSyncList::builder("foo")
1132 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=3))
1133 .build(sender);
1134
1135 assert!(list.maximum_number_of_rooms().is_none());
1136
1137 let _ = list.next_request(&mut LazyTransactionId::new());
1139 let new_changes = list.update(Some(5)).unwrap();
1140 assert!(new_changes);
1141
1142 assert_eq!(list.maximum_number_of_rooms(), Some(5));
1144
1145 let _ = list.next_request(&mut LazyTransactionId::new());
1147 let new_changes = list.update(Some(5)).unwrap();
1148 assert!(!new_changes);
1149
1150 assert_eq!(list.maximum_number_of_rooms(), Some(5));
1152 }
1153
1154 #[test]
1155 fn test_sliding_sync_mode_serialization() {
1156 assert_json_roundtrip!(
1157 from SlidingSyncMode: SlidingSyncMode::from(SlidingSyncMode::new_paging(1).maximum_number_of_rooms_to_fetch(2)) => json!({
1158 "Paging": {
1159 "batch_size": 1,
1160 "maximum_number_of_rooms_to_fetch": 2
1161 }
1162 })
1163 );
1164 assert_json_roundtrip!(
1165 from SlidingSyncMode: SlidingSyncMode::from(SlidingSyncMode::new_growing(1).maximum_number_of_rooms_to_fetch(2)) => json!({
1166 "Growing": {
1167 "batch_size": 1,
1168 "maximum_number_of_rooms_to_fetch": 2
1169 }
1170 })
1171 );
1172 assert_json_roundtrip!(from SlidingSyncMode: SlidingSyncMode::from(SlidingSyncMode::new_selective()) => json!({
1173 "Selective": {
1174 "ranges": []
1175 }
1176 })
1177 );
1178 }
1179
1180 #[test]
1181 fn test_sliding_sync_list_loading_state_serialization() {
1182 assert_json_roundtrip!(from SlidingSyncListLoadingState: SlidingSyncListLoadingState::NotLoaded => json!("NotLoaded"));
1183 assert_json_roundtrip!(from SlidingSyncListLoadingState: SlidingSyncListLoadingState::Preloaded => json!("Preloaded"));
1184 assert_json_roundtrip!(from SlidingSyncListLoadingState: SlidingSyncListLoadingState::PartiallyLoaded => json!("PartiallyLoaded"));
1185 assert_json_roundtrip!(from SlidingSyncListLoadingState: SlidingSyncListLoadingState::FullyLoaded => json!("FullyLoaded"));
1186 }
1187
1188 #[test]
1189 fn test_sliding_sync_list_loading_state_is_fully_loaded() {
1190 assert!(SlidingSyncListLoadingState::NotLoaded.is_fully_loaded().not());
1191 assert!(SlidingSyncListLoadingState::Preloaded.is_fully_loaded().not());
1192 assert!(SlidingSyncListLoadingState::PartiallyLoaded.is_fully_loaded().not());
1193 assert!(SlidingSyncListLoadingState::FullyLoaded.is_fully_loaded());
1194 }
1195
1196 #[test]
1197 fn test_once_built() {
1198 let (sender, _receiver) = channel(1);
1199
1200 let probe = Arc::new(Mutex::new(Cell::new(false)));
1201 let probe_clone = probe.clone();
1202
1203 let _list = SlidingSyncList::builder("testing")
1204 .once_built(move |list| {
1205 let mut probe_lock = probe.lock().unwrap();
1206 *probe_lock.get_mut() = true;
1207
1208 list
1209 })
1210 .build(sender);
1211
1212 let probe_lock = probe_clone.lock().unwrap();
1213 assert!(probe_lock.get());
1214 }
1215}