arminius_lib_sync/syncv4/plan/
apply_request.rs1mod tests;
8
9use std::collections::{BTreeMap, HashMap};
10
11use arminius_lib_component::error::ResultExt as _;
12use arminius_lib_db_sync::syncv4::RoomInfo;
13use arminius_lib_model::syncv4::RoomConfig;
14use itertools::Itertools as _;
15use ruma::{OwnedRoomId, api::client::sync::sync_events};
16
17use crate::syncv4::error::SyncV4Error;
18
19#[derive(Debug)]
21pub struct MatchedRoom {
22 pub(crate) room_config: RoomConfig,
24 pub(crate) room_info: RoomInfo,
26}
27
28pub struct FilteredRooms {
30 pub(crate) lists_counts: HashMap<String, usize>,
32
33 pub(crate) matched_rooms: HashMap<OwnedRoomId, MatchedRoom>,
35}
36
37pub(super) fn apply_request(
39 user_rooms: &HashMap<OwnedRoomId, RoomInfo>,
40 req_lists: &BTreeMap<String, sync_events::v5::request::List>,
41 req_subscriptions: &BTreeMap<OwnedRoomId, sync_events::v5::request::RoomSubscription>,
42) -> Result<FilteredRooms, SyncV4Error> {
43 let mut matched_rooms = HashMap::new();
44
45 let lists_counts = apply_lists(user_rooms, req_lists, &mut matched_rooms)?;
46 apply_subscriptions(user_rooms, req_subscriptions, &mut matched_rooms);
47
48 Ok(FilteredRooms { lists_counts, matched_rooms })
49}
50
51fn apply_lists(
57 user_rooms: &HashMap<OwnedRoomId, RoomInfo>,
58 lists: &BTreeMap<String, sync_events::v5::request::List>,
59 result: &mut HashMap<OwnedRoomId, MatchedRoom>,
60) -> Result<HashMap<String, usize>, SyncV4Error> {
61 let mut lists_counts = HashMap::new();
62
63 let sorted_rooms: Vec<(&OwnedRoomId, &RoomInfo)> = user_rooms
66 .iter()
67 .sorted_by_key(|(_room_id, room_info)| std::cmp::Reverse(room_info.max_event_stream_id))
68 .collect();
69
70 for (list_name, list) in lists {
71 let room_config: RoomConfig = list.room_details.clone().into();
72
73 let filtered_rooms = user_rooms;
75
76 lists_counts.insert(list_name.clone(), filtered_rooms.len());
78
79 if list.ranges.is_empty() && list.filters.is_none() {
81 tracing::warn!("This list doesn't list anything: {}", list_name);
82 continue;
83 }
84
85 if list.ranges.is_empty() {
87 for (room_id, room_info) in filtered_rooms {
88 result
89 .entry(room_id.to_owned())
90 .and_modify(|entry| entry.room_config.combine(&room_config))
91 .or_insert_with(|| MatchedRoom {
92 room_config: room_config.clone(),
93 room_info: room_info.clone(),
94 });
95 }
96 }
97 else {
99 for (range_start, range_end) in list.ranges.clone() {
100 let (range_start, range_end): (usize, usize) = (
101 range_start.try_into().unrecoverable_error("Converting UInt to usize")?,
102 range_end.try_into().unrecoverable_error("Converting UInt to usize")?,
103 );
104
105 for (room_id, room_info) in sorted_rooms
106 .iter()
107 .skip(range_start)
108 .take(range_end.saturating_sub(range_start) + 1)
109 {
110 result
111 .entry((**room_id).clone())
112 .and_modify(|entry| entry.room_config.combine(&room_config))
113 .or_insert_with(|| MatchedRoom {
114 room_config: room_config.clone(),
115 room_info: (**room_info).clone(),
116 });
117 }
118 }
119 }
120 }
121
122 Ok(lists_counts)
123}
124
125fn apply_subscriptions(
129 user_rooms: &HashMap<OwnedRoomId, RoomInfo>,
130 subscriptions: &BTreeMap<OwnedRoomId, sync_events::v5::request::RoomSubscription>,
131 result: &mut HashMap<OwnedRoomId, MatchedRoom>,
132) {
133 for (room_id, room_subscription) in subscriptions {
134 let Some(room_info) = user_rooms.get(room_id) else {
135 tracing::warn!(
138 %room_id,
139 "User tried to subscribe to room {room_id} without being a member of it. Ignoring it.",
140 );
141 continue;
142 };
143
144 let room_config: RoomConfig = room_subscription.clone().into();
145
146 result
147 .entry(room_id.clone())
148 .and_modify(|entry| {
149 entry.room_config.combine(&room_config);
150 })
151 .or_insert_with(|| MatchedRoom { room_config, room_info: room_info.clone() });
152 }
153}