tauri_plugin_matrix_svelte/matrix/
sync.rs

1use anyhow::bail;
2use futures::{pin_mut, StreamExt};
3use matrix_sdk::{Client, RoomState};
4use matrix_sdk_ui::{
5    eyeball_im::{Vector, VectorDiff},
6    sync_service::SyncService,
7};
8use tauri::{AppHandle, Runtime};
9
10use crate::matrix::{
11    room::rooms_list::{
12        enqueue_rooms_list_update, handle_room_list_service_loading_state, RoomsListUpdate,
13    },
14    rooms::{add_new_room, remove_room, update_room},
15    singletons::{ALL_JOINED_ROOMS, LOG_ROOM_LIST_DIFFS},
16};
17
18use super::{rooms::RoomListServiceRoomInfo, singletons::SYNC_SERVICE};
19
20pub async fn sync<R: Runtime>(_app_handle: &AppHandle<R>, client: Client) -> anyhow::Result<()> {
21    let sync_service = SyncService::builder(client).build().await?;
22
23    // Start the sync service
24    sync_service.start().await;
25    let room_list_service = sync_service.room_list_service();
26    SYNC_SERVICE
27        .set(sync_service)
28        .unwrap_or_else(|_| panic!("BUG: SYNC_SERVICE already set!"));
29
30    let all_rooms_list = room_list_service.all_rooms().await?;
31    handle_room_list_service_loading_state(all_rooms_list.loading_state());
32
33    let (room_diff_stream, room_list_dynamic_entries_controller) =
34        all_rooms_list.entries_with_dynamic_adapters(usize::MAX);
35
36    // Handle only joined rooms for the moment TODO: handle all room types
37    room_list_dynamic_entries_controller.set_filter(Box::new(|room| match room.state() {
38        RoomState::Joined => true,
39        _ => false,
40    }));
41
42    let mut all_known_rooms: Vector<RoomListServiceRoomInfo> = Vector::new();
43
44    pin_mut!(room_diff_stream);
45    while let Some(batch) = room_diff_stream.next().await {
46        let mut peekable_diffs = batch.into_iter().peekable();
47        while let Some(diff) = peekable_diffs.next() {
48            match diff {
49                VectorDiff::Append { values: new_rooms } => {
50                    let _num_new_rooms = new_rooms.len();
51                    if LOG_ROOM_LIST_DIFFS {
52                        println!("room_list: diff Append {_num_new_rooms}");
53                    }
54                    for new_room in new_rooms {
55                        add_new_room(&new_room, &room_list_service).await?;
56                        all_known_rooms.push_back(new_room.into());
57                    }
58                }
59                VectorDiff::Clear => {
60                    if LOG_ROOM_LIST_DIFFS {
61                        println!("room_list: diff Clear");
62                    }
63                    all_known_rooms.clear();
64                    ALL_JOINED_ROOMS.lock().unwrap().clear();
65                    enqueue_rooms_list_update(RoomsListUpdate::ClearRooms);
66                }
67                VectorDiff::PushFront { value: new_room } => {
68                    if LOG_ROOM_LIST_DIFFS {
69                        println!("room_list: diff PushFront");
70                    }
71                    add_new_room(&new_room, &room_list_service).await?;
72                    all_known_rooms.push_front(new_room.into());
73                }
74                VectorDiff::PushBack { value: new_room } => {
75                    if LOG_ROOM_LIST_DIFFS {
76                        println!("room_list: diff PushBack");
77                    }
78                    add_new_room(&new_room, &room_list_service).await?;
79                    all_known_rooms.push_back(new_room.into());
80                }
81                VectorDiff::PopFront => {
82                    if LOG_ROOM_LIST_DIFFS {
83                        println!("room_list: diff PopFront");
84                    }
85                    if let Some(room) = all_known_rooms.pop_front() {
86                        if LOG_ROOM_LIST_DIFFS {
87                            println!("PopFront: removing {}", room.room_id);
88                        }
89                        remove_room(&room);
90                    }
91                }
92                VectorDiff::PopBack => {
93                    if LOG_ROOM_LIST_DIFFS {
94                        println!("room_list: diff PopBack");
95                    }
96                    if let Some(room) = all_known_rooms.pop_back() {
97                        if LOG_ROOM_LIST_DIFFS {
98                            println!("PopBack: removing {}", room.room_id);
99                        }
100                        remove_room(&room);
101                    }
102                }
103                VectorDiff::Insert {
104                    index,
105                    value: new_room,
106                } => {
107                    if LOG_ROOM_LIST_DIFFS {
108                        println!("room_list: diff Insert at {index}");
109                    }
110                    add_new_room(&new_room, &room_list_service).await?;
111                    all_known_rooms.insert(index, new_room.into());
112                }
113                VectorDiff::Set {
114                    index,
115                    value: changed_room,
116                } => {
117                    if LOG_ROOM_LIST_DIFFS {
118                        println!("room_list: diff Set at {index}");
119                    }
120                    if let Some(old_room) = all_known_rooms.get(index) {
121                        update_room(old_room, &changed_room, &room_list_service).await?;
122                    } else {
123                        eprintln!("BUG: room list diff: Set index {index} was out of bounds.");
124                    }
125                    all_known_rooms.set(index, changed_room.into());
126                }
127                VectorDiff::Remove {
128                    index: remove_index,
129                } => {
130                    if LOG_ROOM_LIST_DIFFS {
131                        println!("room_list: diff Remove at {remove_index}");
132                    }
133                    if remove_index < all_known_rooms.len() {
134                        let room = all_known_rooms.remove(remove_index);
135                        // Try to optimize a common operation, in which a `Remove` diff
136                        // is immediately followed by an `Insert` diff for the same room,
137                        // which happens frequently in order to "sort" the room list
138                        // by changing its positional order.
139                        // We treat this as a simple `Set` operation (`update_room()`),
140                        // which is way more efficient.
141                        let mut next_diff_was_handled = false;
142                        if let Some(VectorDiff::Insert {
143                            index: insert_index,
144                            value: new_room,
145                        }) = peekable_diffs.peek()
146                        {
147                            if room.room_id == new_room.room_id() {
148                                if LOG_ROOM_LIST_DIFFS {
149                                    println!("Optimizing Remove({remove_index}) + Insert({insert_index}) into Set (update) for room {}", room.room_id);
150                                }
151                                update_room(&room, new_room, &room_list_service).await?;
152                                all_known_rooms.insert(*insert_index, new_room.clone().into());
153                                next_diff_was_handled = true;
154                            }
155                        }
156                        if next_diff_was_handled {
157                            peekable_diffs.next(); // consume the next diff
158                        } else {
159                            println!("UNTESTED SCENARIO: room_list: diff Remove({remove_index}) was NOT followed by an Insert. Removed room: {}", room.room_id);
160                            remove_room(&room);
161                        }
162                    } else {
163                        eprintln!("BUG: room_list: diff Remove index {remove_index} out of bounds, len {}", all_known_rooms.len());
164                    }
165                }
166                VectorDiff::Truncate { length } => {
167                    if LOG_ROOM_LIST_DIFFS {
168                        println!("room_list: diff Truncate to {length}");
169                    }
170                    // Iterate manually so we can know which rooms are being removed.
171                    while all_known_rooms.len() > length {
172                        if let Some(room) = all_known_rooms.pop_back() {
173                            remove_room(&room);
174                        }
175                    }
176                    all_known_rooms.truncate(length); // sanity check
177                }
178                VectorDiff::Reset { values: new_rooms } => {
179                    // We implement this by clearing all rooms and then adding back the new values.
180                    if LOG_ROOM_LIST_DIFFS {
181                        println!(
182                            "room_list: diff Reset, old length {}, new length {}",
183                            all_known_rooms.len(),
184                            new_rooms.len()
185                        );
186                    }
187                    // Iterate manually so we can know which rooms are being removed.
188                    while let Some(room) = all_known_rooms.pop_back() {
189                        remove_room(&room);
190                    }
191                    // ALL_JOINED_ROOMS should already be empty due to successive calls to `remove_room()`,
192                    // so this is just a sanity check.
193                    ALL_JOINED_ROOMS.lock().unwrap().clear();
194                    enqueue_rooms_list_update(RoomsListUpdate::ClearRooms);
195                    for room in &new_rooms {
196                        add_new_room(room, &room_list_service).await?;
197                    }
198                    all_known_rooms = new_rooms.into_iter().map(|r| r.into()).collect();
199                }
200            }
201        }
202    }
203
204    bail!("room list service sync loop ended unexpectedly")
205}