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 {
168 self.inner.update_request_generator_state(maximum_number_of_rooms)?;
169 }
170
171 let new_changes = self.inner.update_room_list(maximum_number_of_rooms)?;
172
173 Ok(new_changes)
174 }
175
176 #[cfg(feature = "testing")]
178 pub fn sync_mode(&self) -> SlidingSyncMode {
179 self.inner.sync_mode.read().unwrap().clone()
180 }
181
182 pub(super) fn set_maximum_number_of_rooms(&self, maximum_number_of_rooms: Option<u32>) {
184 self.inner.maximum_number_of_rooms.set(maximum_number_of_rooms);
185 }
186}
187
188pub(super) struct SlidingSyncListInner {
189 name: String,
191
192 state: SharedObservable<SlidingSyncListLoadingState>,
194
195 #[cfg(not(target_family = "wasm"))]
197 requires_timeout: Arc<dyn Fn(&SlidingSyncListRequestGenerator) -> PollTimeout + Send + Sync>,
198 #[cfg(target_family = "wasm")]
199 requires_timeout: Arc<dyn Fn(&SlidingSyncListRequestGenerator) -> PollTimeout>,
200
201 filters: Option<http::request::ListFilters>,
203
204 required_state: Vec<(StateEventType, String)>,
206
207 timeline_limit: StdRwLock<Bound>,
209
210 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();
278 request_generator.generate_next_ranges(self.maximum_number_of_rooms.get())?
279 };
280
281 Ok(self.request(ranges))
283 }
284
285 #[instrument(skip(self), fields(name = self.name))]
288 fn request(&self, ranges: Ranges) -> http::request::List {
289 let ranges = ranges.into_iter().map(|r| ((*r.start()).into(), (*r.end()).into())).collect();
290
291 let mut request = assign!(http::request::List::default(), { ranges });
292 request.room_details.timeline_limit = (*self.timeline_limit.read().unwrap()).into();
293 request.filters = self.filters.clone();
294 request.room_details.required_state = self.required_state.clone();
295
296 request
297 }
298
299 fn update_room_list(&self, maximum_number_of_rooms: Option<u32>) -> Result<bool, Error> {
304 let mut new_changes = false;
305
306 if maximum_number_of_rooms.is_some() {
307 if self.maximum_number_of_rooms.set_if_not_eq(maximum_number_of_rooms).is_some() {
309 new_changes = true;
310 }
311 }
312
313 Ok(new_changes)
314 }
315
316 fn update_request_generator_state(&self, maximum_number_of_rooms: u32) -> Result<(), Error> {
319 let mut request_generator = self.request_generator.write().unwrap();
320
321 let new_state = request_generator.handle_response(&self.name, maximum_number_of_rooms)?;
322 self.state.set_if_not_eq(new_state);
323
324 Ok(())
325 }
326
327 #[instrument]
330 fn internal_channel_send_if_possible(&self, message: SlidingSyncInternalMessage) {
331 let _ = self.sliding_sync_internal_channel_sender.send(message);
333 }
334}
335
336#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
346pub enum SlidingSyncListLoadingState {
347 #[default]
349 NotLoaded,
350
351 Preloaded,
353
354 PartiallyLoaded,
357
358 FullyLoaded,
361}
362
363#[cfg(test)]
364impl SlidingSyncListLoadingState {
365 fn is_fully_loaded(&self) -> bool {
367 matches!(self, Self::FullyLoaded)
368 }
369}
370
371#[derive(Clone, Debug, Default)]
375pub struct SlidingSyncSelectiveModeBuilder {
376 ranges: Ranges,
377}
378
379impl SlidingSyncSelectiveModeBuilder {
380 fn new() -> Self {
382 Self::default()
383 }
384
385 pub fn add_range(mut self, range: Range) -> Self {
387 self.ranges.push(range);
388 self
389 }
390
391 pub fn add_ranges(mut self, ranges: Ranges) -> Self {
393 self.ranges.extend(ranges);
394 self
395 }
396}
397
398impl From<SlidingSyncSelectiveModeBuilder> for SlidingSyncMode {
399 fn from(builder: SlidingSyncSelectiveModeBuilder) -> Self {
400 Self::Selective { ranges: builder.ranges }
401 }
402}
403
404#[derive(Clone, Debug)]
405enum WindowedModeBuilderKind {
406 Paging,
407 Growing,
408}
409
410#[derive(Clone, Debug)]
412pub struct SlidingSyncWindowedModeBuilder {
413 mode: WindowedModeBuilderKind,
414 batch_size: u32,
415 maximum_number_of_rooms_to_fetch: Option<u32>,
416}
417
418impl SlidingSyncWindowedModeBuilder {
419 fn new(mode: WindowedModeBuilderKind, batch_size: u32) -> Self {
420 Self { mode, batch_size, maximum_number_of_rooms_to_fetch: None }
421 }
422
423 pub fn maximum_number_of_rooms_to_fetch(mut self, num: u32) -> Self {
425 self.maximum_number_of_rooms_to_fetch = Some(num);
426 self
427 }
428}
429
430impl From<SlidingSyncWindowedModeBuilder> for SlidingSyncMode {
431 fn from(builder: SlidingSyncWindowedModeBuilder) -> Self {
432 match builder.mode {
433 WindowedModeBuilderKind::Paging => Self::Paging {
434 batch_size: builder.batch_size,
435 maximum_number_of_rooms_to_fetch: builder.maximum_number_of_rooms_to_fetch,
436 },
437 WindowedModeBuilderKind::Growing => Self::Growing {
438 batch_size: builder.batch_size,
439 maximum_number_of_rooms_to_fetch: builder.maximum_number_of_rooms_to_fetch,
440 },
441 }
442 }
443}
444
445#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
447pub enum SlidingSyncMode {
448 Selective {
450 ranges: Ranges,
452 },
453
454 Paging {
458 batch_size: u32,
460
461 maximum_number_of_rooms_to_fetch: Option<u32>,
464 },
465
466 Growing {
470 batch_size: u32,
472
473 maximum_number_of_rooms_to_fetch: Option<u32>,
476 },
477}
478
479impl Default for SlidingSyncMode {
480 fn default() -> Self {
481 Self::Selective { ranges: Vec::new() }
482 }
483}
484
485impl SlidingSyncMode {
486 pub fn new_selective() -> SlidingSyncSelectiveModeBuilder {
488 SlidingSyncSelectiveModeBuilder::new()
489 }
490
491 pub fn new_paging(batch_size: u32) -> SlidingSyncWindowedModeBuilder {
493 SlidingSyncWindowedModeBuilder::new(WindowedModeBuilderKind::Paging, batch_size)
494 }
495
496 pub fn new_growing(batch_size: u32) -> SlidingSyncWindowedModeBuilder {
498 SlidingSyncWindowedModeBuilder::new(WindowedModeBuilderKind::Growing, batch_size)
499 }
500}
501
502#[cfg(test)]
503mod tests {
504 use std::{
505 cell::Cell,
506 ops::Not,
507 sync::{Arc, Mutex},
508 };
509
510 use assert_matches::assert_matches;
511 use matrix_sdk_test::async_test;
512 use ruma::uint;
513 use serde_json::json;
514 use tokio::sync::broadcast::{channel, error::TryRecvError};
515
516 use super::{PollTimeout, SlidingSyncList, SlidingSyncListLoadingState, SlidingSyncMode};
517 use crate::sliding_sync::SlidingSyncInternalMessage;
518
519 macro_rules! assert_json_roundtrip {
520 (from $type:ty: $rust_value:expr => $json_value:expr) => {
521 let json = serde_json::to_value(&$rust_value).unwrap();
522 assert_eq!(json, $json_value);
523
524 let rust: $type = serde_json::from_value(json).unwrap();
525 assert_eq!(rust, $rust_value);
526 };
527 }
528
529 #[async_test]
530 async fn test_sliding_sync_list_selective_mode() {
531 let (sender, mut receiver) = channel(1);
532
533 let list = SlidingSyncList::builder("foo")
535 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=1).add_range(2..=3))
536 .build(sender);
537
538 {
539 let mut generator = list.inner.request_generator.write().unwrap();
540 assert_eq!(generator.requested_ranges(), &[0..=1, 2..=3]);
541
542 let ranges = generator.generate_next_ranges(None).unwrap();
543 assert_eq!(ranges, &[0..=1, 2..=3]);
544 }
545
546 assert!(matches!(receiver.try_recv(), Err(TryRecvError::Empty)));
548
549 list.set_sync_mode(SlidingSyncMode::new_selective().add_range(4..=5));
550
551 {
552 let mut generator = list.inner.request_generator.write().unwrap();
553 assert_eq!(generator.requested_ranges(), &[4..=5]);
554
555 let ranges = generator.generate_next_ranges(None).unwrap();
556 assert_eq!(ranges, &[4..=5]);
557 }
558
559 assert!(matches!(
561 receiver.try_recv(),
562 Ok(SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration)
563 ));
564 assert!(matches!(receiver.try_recv(), Err(TryRecvError::Empty)));
565 }
566
567 #[test]
568 fn test_sliding_sync_list_timeline_limit() {
569 let (sender, _receiver) = channel(1);
570
571 let list = SlidingSyncList::builder("foo")
572 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=1))
573 .timeline_limit(7)
574 .build(sender);
575
576 assert_eq!(list.timeline_limit(), 7);
577
578 list.set_timeline_limit(42);
579 assert_eq!(list.timeline_limit(), 42);
580 }
581
582 macro_rules! assert_ranges {
583 (
584 list = $list:ident,
585 list_state = $first_list_state:ident,
586 maximum_number_of_rooms = $maximum_number_of_rooms:expr,
587 timeout = $initial_timeout:ident,
588 $(
589 next => {
590 ranges = $( $range_start:literal ..= $range_end:literal ),* ,
591 is_fully_loaded = $is_fully_loaded:expr,
592 list_state = $list_state:ident,
593 timeout = $timeout:ident,
594 }
595 ),*
596 $(,)*
597 ) => {
598 assert_eq!($list.state(), SlidingSyncListLoadingState::$first_list_state, "first state");
599 assert_matches!(
600 $list.requires_timeout(),
601 PollTimeout:: $initial_timeout,
602 "initial timeout",
603 );
604
605 $(
606 {
607 let request = $list.next_request().unwrap();
609
610 assert_eq!(
611 request.ranges,
612 [
613 $( (uint!( $range_start ), uint!( $range_end )) ),*
614 ],
615 "ranges",
616 );
617
618 let _ = $list.update(Some($maximum_number_of_rooms));
620
621 assert_eq!(
622 $list.inner.request_generator.read().unwrap().is_fully_loaded(),
623 $is_fully_loaded,
624 "is fully loaded",
625 );
626 assert_eq!(
627 $list.state(),
628 SlidingSyncListLoadingState::$list_state,
629 "state",
630 );
631 assert_matches!(
632 $list.requires_timeout(),
633 PollTimeout:: $timeout,
634 "timeout",
635 );
636 }
637 )*
638 };
639 }
640
641 #[test]
642 fn test_generator_paging_full_sync() {
643 let (sender, _receiver) = channel(1);
644
645 let mut list = SlidingSyncList::builder("testing")
646 .sync_mode(SlidingSyncMode::new_paging(10))
647 .build(sender);
648
649 assert_ranges! {
650 list = list,
651 list_state = NotLoaded,
652 maximum_number_of_rooms = 25,
653 timeout = None,
654 next => {
655 ranges = 0..=9,
656 is_fully_loaded = false,
657 list_state = PartiallyLoaded,
658 timeout = None,
659 },
660 next => {
661 ranges = 10..=19,
662 is_fully_loaded = false,
663 list_state = PartiallyLoaded,
664 timeout = None,
665 },
666 next => {
668 ranges = 20..=24,
669 is_fully_loaded = true,
670 list_state = FullyLoaded,
671 timeout = Default,
672 },
673 next => {
675 ranges = 0..=24, is_fully_loaded = true,
677 list_state = FullyLoaded,
678 timeout = Default,
679 },
680 next => {
681 ranges = 0..=24,
682 is_fully_loaded = true,
683 list_state = FullyLoaded,
684 timeout = Default,
685 },
686 };
687 }
688
689 #[test]
690 fn test_generator_paging_full_sync_with_a_maximum_number_of_rooms_to_fetch() {
691 let (sender, _receiver) = channel(1);
692
693 let mut list = SlidingSyncList::builder("testing")
694 .sync_mode(SlidingSyncMode::new_paging(10).maximum_number_of_rooms_to_fetch(22))
695 .build(sender);
696
697 assert_ranges! {
698 list = list,
699 list_state = NotLoaded,
700 maximum_number_of_rooms = 25,
701 timeout = None,
702 next => {
703 ranges = 0..=9,
704 is_fully_loaded = false,
705 list_state = PartiallyLoaded,
706 timeout = None,
707 },
708 next => {
709 ranges = 10..=19,
710 is_fully_loaded = false,
711 list_state = PartiallyLoaded,
712 timeout = None,
713 },
714 next => {
716 ranges = 20..=21,
717 is_fully_loaded = true,
718 list_state = FullyLoaded,
719 timeout = Default,
720 },
721 next => {
723 ranges = 0..=21, is_fully_loaded = true,
725 list_state = FullyLoaded,
726 timeout = Default,
727 },
728 next => {
729 ranges = 0..=21,
730 is_fully_loaded = true,
731 list_state = FullyLoaded,
732 timeout = Default,
733 },
734 };
735 }
736
737 #[test]
738 fn test_generator_growing_full_sync() {
739 let (sender, _receiver) = channel(1);
740
741 let mut list = SlidingSyncList::builder("testing")
742 .sync_mode(SlidingSyncMode::new_growing(10))
743 .build(sender);
744
745 assert_ranges! {
746 list = list,
747 list_state = NotLoaded,
748 maximum_number_of_rooms = 25,
749 timeout = None,
750 next => {
751 ranges = 0..=9,
752 is_fully_loaded = false,
753 list_state = PartiallyLoaded,
754 timeout = None,
755 },
756 next => {
757 ranges = 0..=19,
758 is_fully_loaded = false,
759 list_state = PartiallyLoaded,
760 timeout = None,
761 },
762 next => {
764 ranges = 0..=24,
765 is_fully_loaded = true,
766 list_state = FullyLoaded,
767 timeout = Default,
768 },
769 next => {
771 ranges = 0..=24,
772 is_fully_loaded = true,
773 list_state = FullyLoaded,
774 timeout = Default,
775 },
776 next => {
777 ranges = 0..=24,
778 is_fully_loaded = true,
779 list_state = FullyLoaded,
780 timeout = Default,
781 },
782 };
783 }
784
785 #[test]
786 fn test_generator_growing_full_sync_with_a_maximum_number_of_rooms_to_fetch() {
787 let (sender, _receiver) = channel(1);
788
789 let mut list = SlidingSyncList::builder("testing")
790 .sync_mode(SlidingSyncMode::new_growing(10).maximum_number_of_rooms_to_fetch(22))
791 .build(sender);
792
793 assert_ranges! {
794 list = list,
795 list_state = NotLoaded,
796 maximum_number_of_rooms = 25,
797 timeout = None,
798 next => {
799 ranges = 0..=9,
800 is_fully_loaded = false,
801 list_state = PartiallyLoaded,
802 timeout = None,
803 },
804 next => {
805 ranges = 0..=19,
806 is_fully_loaded = false,
807 list_state = PartiallyLoaded,
808 timeout = None,
809 },
810 next => {
812 ranges = 0..=21,
813 is_fully_loaded = true,
814 list_state = FullyLoaded,
815 timeout = Default,
816 },
817 next => {
819 ranges = 0..=21,
820 is_fully_loaded = true,
821 list_state = FullyLoaded,
822 timeout = Default,
823 },
824 next => {
825 ranges = 0..=21,
826 is_fully_loaded = true,
827 list_state = FullyLoaded,
828 timeout = Default,
829 },
830 };
831 }
832
833 #[test]
834 fn test_generator_selective() {
835 let (sender, _receiver) = channel(1);
836
837 let mut list = SlidingSyncList::builder("testing")
838 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10).add_range(42..=153))
839 .build(sender);
840
841 assert_ranges! {
842 list = list,
843 list_state = NotLoaded,
844 maximum_number_of_rooms = 25,
845 timeout = Default,
846 next => {
848 ranges = 0..=10, 42..=153,
849 is_fully_loaded = true,
850 list_state = FullyLoaded,
851 timeout = Default,
852 },
853 next => {
855 ranges = 0..=10, 42..=153,
856 is_fully_loaded = true,
857 list_state = FullyLoaded,
858 timeout = Default,
859 },
860 next => {
861 ranges = 0..=10, 42..=153,
862 is_fully_loaded = true,
863 list_state = FullyLoaded,
864 timeout = Default,
865 }
866 };
867 }
868
869 #[async_test]
870 async fn test_generator_selective_with_modifying_ranges_on_the_fly() {
871 let (sender, _receiver) = channel(4);
872
873 let mut list = SlidingSyncList::builder("testing")
874 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10).add_range(42..=153))
875 .build(sender);
876
877 assert_ranges! {
878 list = list,
879 list_state = NotLoaded,
880 maximum_number_of_rooms = 25,
881 timeout = Default,
882 next => {
884 ranges = 0..=10, 42..=153,
885 is_fully_loaded = true,
886 list_state = FullyLoaded,
887 timeout = Default,
888 },
889 next => {
891 ranges = 0..=10, 42..=153,
892 is_fully_loaded = true,
893 list_state = FullyLoaded,
894 timeout = Default,
895 },
896 next => {
897 ranges = 0..=10, 42..=153,
898 is_fully_loaded = true,
899 list_state = FullyLoaded,
900 timeout = Default,
901 }
902 };
903
904 list.set_sync_mode(SlidingSyncMode::new_selective().add_range(3..=7));
905
906 assert_ranges! {
907 list = list,
908 list_state = PartiallyLoaded,
909 maximum_number_of_rooms = 25,
910 timeout = Default,
911 next => {
912 ranges = 3..=7,
913 is_fully_loaded = true,
914 list_state = FullyLoaded,
915 timeout = Default,
916 },
917 };
918
919 list.set_sync_mode(SlidingSyncMode::new_selective().add_range(42..=77));
920
921 assert_ranges! {
922 list = list,
923 list_state = PartiallyLoaded,
924 maximum_number_of_rooms = 25,
925 timeout = Default,
926 next => {
927 ranges = 42..=77,
928 is_fully_loaded = true,
929 list_state = FullyLoaded,
930 timeout = Default,
931 },
932 };
933
934 list.set_sync_mode(SlidingSyncMode::new_selective());
935
936 assert_ranges! {
937 list = list,
938 list_state = PartiallyLoaded,
939 maximum_number_of_rooms = 25,
940 timeout = Default,
941 next => {
942 ranges = ,
943 is_fully_loaded = true,
944 list_state = FullyLoaded,
945 timeout = Default,
946 },
947 };
948 }
949
950 #[async_test]
951 async fn test_generator_changing_sync_mode_to_various_modes() {
952 let (sender, _receiver) = channel(4);
953
954 let mut list = SlidingSyncList::builder("testing")
955 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10).add_range(42..=153))
956 .build(sender);
957
958 assert_ranges! {
959 list = list,
960 list_state = NotLoaded,
961 maximum_number_of_rooms = 25,
962 timeout = Default,
963 next => {
965 ranges = 0..=10, 42..=153,
966 is_fully_loaded = true,
967 list_state = FullyLoaded,
968 timeout = Default,
969 },
970 next => {
972 ranges = 0..=10, 42..=153,
973 is_fully_loaded = true,
974 list_state = FullyLoaded,
975 timeout = Default,
976 },
977 next => {
978 ranges = 0..=10, 42..=153,
979 is_fully_loaded = true,
980 list_state = FullyLoaded,
981 timeout = Default,
982 }
983 };
984
985 list.set_sync_mode(SlidingSyncMode::new_growing(10));
987
988 assert_ranges! {
989 list = list,
990 list_state = PartiallyLoaded, maximum_number_of_rooms = 25,
992 timeout = None,
993 next => {
994 ranges = 0..=9,
995 is_fully_loaded = false,
996 list_state = PartiallyLoaded,
997 timeout = None,
998 },
999 next => {
1000 ranges = 0..=19,
1001 is_fully_loaded = false,
1002 list_state = PartiallyLoaded,
1003 timeout = None,
1004 },
1005 next => {
1007 ranges = 0..=24,
1008 is_fully_loaded = true,
1009 list_state = FullyLoaded,
1010 timeout = Default,
1011 },
1012 next => {
1014 ranges = 0..=24,
1015 is_fully_loaded = true,
1016 list_state = FullyLoaded,
1017 timeout = Default,
1018 },
1019 next => {
1020 ranges = 0..=24,
1021 is_fully_loaded = true,
1022 list_state = FullyLoaded,
1023 timeout = Default,
1024 },
1025 };
1026
1027 list.set_sync_mode(SlidingSyncMode::new_paging(10));
1029
1030 assert_ranges! {
1031 list = list,
1032 list_state = PartiallyLoaded, maximum_number_of_rooms = 25,
1034 timeout = None,
1035 next => {
1036 ranges = 0..=9,
1037 is_fully_loaded = false,
1038 list_state = PartiallyLoaded,
1039 timeout = None,
1040 },
1041 next => {
1042 ranges = 10..=19,
1043 is_fully_loaded = false,
1044 list_state = PartiallyLoaded,
1045 timeout = None,
1046 },
1047 next => {
1049 ranges = 20..=24,
1050 is_fully_loaded = true,
1051 list_state = FullyLoaded,
1052 timeout = Default,
1053 },
1054 next => {
1056 ranges = 0..=24, is_fully_loaded = true,
1058 list_state = FullyLoaded,
1059 timeout = Default,
1060 },
1061 next => {
1062 ranges = 0..=24,
1063 is_fully_loaded = true,
1064 list_state = FullyLoaded,
1065 timeout = Default,
1066 },
1067 };
1068
1069 list.set_sync_mode(SlidingSyncMode::new_selective());
1071
1072 assert_eq!(list.state(), SlidingSyncListLoadingState::PartiallyLoaded); list.set_sync_mode(SlidingSyncMode::new_selective().add_range(0..=100));
1078
1079 assert_ranges! {
1080 list = list,
1081 list_state = PartiallyLoaded, maximum_number_of_rooms = 25,
1083 timeout = Default,
1084 next => {
1086 ranges = 0..=100,
1087 is_fully_loaded = true,
1088 list_state = FullyLoaded,
1089 timeout = Default,
1090 },
1091 next => {
1093 ranges = 0..=100,
1094 is_fully_loaded = true,
1095 list_state = FullyLoaded,
1096 timeout = Default,
1097 },
1098 next => {
1099 ranges = 0..=100,
1100 is_fully_loaded = true,
1101 list_state = FullyLoaded,
1102 timeout = Default,
1103 }
1104 };
1105 }
1106
1107 #[async_test]
1108 #[allow(clippy::await_holding_lock)]
1109 async fn test_inner_update_maximum_number_of_rooms() {
1110 let (sender, _receiver) = channel(1);
1111
1112 let mut list = SlidingSyncList::builder("foo")
1113 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=3))
1114 .build(sender);
1115
1116 assert!(list.maximum_number_of_rooms().is_none());
1117
1118 let _ = list.next_request();
1120 let new_changes = list.update(Some(5)).unwrap();
1121 assert!(new_changes);
1122
1123 assert_eq!(list.maximum_number_of_rooms(), Some(5));
1125
1126 let _ = list.next_request();
1128 let new_changes = list.update(Some(5)).unwrap();
1129 assert!(!new_changes);
1130
1131 assert_eq!(list.maximum_number_of_rooms(), Some(5));
1133 }
1134
1135 #[test]
1136 fn test_sliding_sync_mode_serialization() {
1137 assert_json_roundtrip!(
1138 from SlidingSyncMode: SlidingSyncMode::from(SlidingSyncMode::new_paging(1).maximum_number_of_rooms_to_fetch(2)) => json!({
1139 "Paging": {
1140 "batch_size": 1,
1141 "maximum_number_of_rooms_to_fetch": 2
1142 }
1143 })
1144 );
1145 assert_json_roundtrip!(
1146 from SlidingSyncMode: SlidingSyncMode::from(SlidingSyncMode::new_growing(1).maximum_number_of_rooms_to_fetch(2)) => json!({
1147 "Growing": {
1148 "batch_size": 1,
1149 "maximum_number_of_rooms_to_fetch": 2
1150 }
1151 })
1152 );
1153 assert_json_roundtrip!(from SlidingSyncMode: SlidingSyncMode::from(SlidingSyncMode::new_selective()) => json!({
1154 "Selective": {
1155 "ranges": []
1156 }
1157 })
1158 );
1159 }
1160
1161 #[test]
1162 fn test_sliding_sync_list_loading_state_serialization() {
1163 assert_json_roundtrip!(from SlidingSyncListLoadingState: SlidingSyncListLoadingState::NotLoaded => json!("NotLoaded"));
1164 assert_json_roundtrip!(from SlidingSyncListLoadingState: SlidingSyncListLoadingState::Preloaded => json!("Preloaded"));
1165 assert_json_roundtrip!(from SlidingSyncListLoadingState: SlidingSyncListLoadingState::PartiallyLoaded => json!("PartiallyLoaded"));
1166 assert_json_roundtrip!(from SlidingSyncListLoadingState: SlidingSyncListLoadingState::FullyLoaded => json!("FullyLoaded"));
1167 }
1168
1169 #[test]
1170 fn test_sliding_sync_list_loading_state_is_fully_loaded() {
1171 assert!(SlidingSyncListLoadingState::NotLoaded.is_fully_loaded().not());
1172 assert!(SlidingSyncListLoadingState::Preloaded.is_fully_loaded().not());
1173 assert!(SlidingSyncListLoadingState::PartiallyLoaded.is_fully_loaded().not());
1174 assert!(SlidingSyncListLoadingState::FullyLoaded.is_fully_loaded());
1175 }
1176
1177 #[test]
1178 fn test_once_built() {
1179 let (sender, _receiver) = channel(1);
1180
1181 let probe = Arc::new(Mutex::new(Cell::new(false)));
1182 let probe_clone = probe.clone();
1183
1184 let _list = SlidingSyncList::builder("testing")
1185 .once_built(move |list| {
1186 let mut probe_lock = probe.lock().unwrap();
1187 *probe_lock.get_mut() = true;
1188
1189 list
1190 })
1191 .build(sender);
1192
1193 let probe_lock = probe_clone.lock().unwrap();
1194 assert!(probe_lock.get());
1195 }
1196}