|
|
|
|
@ -36,21 +36,18 @@ mod imp {
|
|
|
|
|
#[properties(wrapper_type = super::RoomList)] |
|
|
|
|
pub struct RoomList { |
|
|
|
|
/// The list of rooms.
|
|
|
|
|
pub list: RefCell<IndexMap<OwnedRoomId, Room>>, |
|
|
|
|
pub(super) list: RefCell<IndexMap<OwnedRoomId, Room>>, |
|
|
|
|
/// The list of rooms we are currently joining.
|
|
|
|
|
pub pending_rooms: RefCell<HashSet<OwnedRoomOrAliasId>>, |
|
|
|
|
pub(super) pending_rooms: RefCell<HashSet<OwnedRoomOrAliasId>>, |
|
|
|
|
/// The list of rooms that were upgraded and for which we haven't joined
|
|
|
|
|
/// the successor yet.
|
|
|
|
|
pub tombstoned_rooms: RefCell<HashSet<OwnedRoomId>>, |
|
|
|
|
tombstoned_rooms: RefCell<HashSet<OwnedRoomId>>, |
|
|
|
|
/// The current session.
|
|
|
|
|
#[property(get, construct_only)] |
|
|
|
|
pub session: glib::WeakRef<Session>, |
|
|
|
|
session: glib::WeakRef<Session>, |
|
|
|
|
/// The rooms metainfo that allow to restore this `RoomList` from its
|
|
|
|
|
/// previous state.
|
|
|
|
|
///
|
|
|
|
|
/// This is in a Mutex because updating the data in the store is async
|
|
|
|
|
/// and we don't want to overwrite newer data with older data.
|
|
|
|
|
pub metainfo: RoomListMetainfo, |
|
|
|
|
metainfo: RoomListMetainfo, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[glib::object_subclass] |
|
|
|
|
@ -91,349 +88,348 @@ mod imp {
|
|
|
|
|
.cloned() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
glib::wrapper! { |
|
|
|
|
/// List of all joined rooms of the user.
|
|
|
|
|
///
|
|
|
|
|
/// This is the parent ListModel of the sidebar from which all other models
|
|
|
|
|
/// are derived.
|
|
|
|
|
///
|
|
|
|
|
/// The `RoomList` also takes care of all so called *pending rooms*, i.e.
|
|
|
|
|
/// rooms the user requested to join, but received no response from the
|
|
|
|
|
/// server yet.
|
|
|
|
|
pub struct RoomList(ObjectSubclass<imp::RoomList>) |
|
|
|
|
@implements gio::ListModel; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl RoomList { |
|
|
|
|
pub fn new(session: &Session) -> Self { |
|
|
|
|
glib::Object::builder().property("session", session).build() |
|
|
|
|
} |
|
|
|
|
impl RoomList { |
|
|
|
|
/// Get the room with the given room ID, if any.
|
|
|
|
|
pub(super) fn get(&self, room_id: &RoomId) -> Option<Room> { |
|
|
|
|
self.list.borrow().get(room_id).cloned() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Get a snapshot of the rooms list.
|
|
|
|
|
pub fn snapshot(&self) -> Vec<Room> { |
|
|
|
|
self.imp().list.borrow().values().cloned().collect() |
|
|
|
|
} |
|
|
|
|
/// Whether this list contains the room with the given ID.
|
|
|
|
|
fn contains(&self, room_id: &RoomId) -> bool { |
|
|
|
|
self.list.borrow().contains_key(room_id) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Whether the room with the given identifier is pending.
|
|
|
|
|
pub fn is_pending_room(&self, identifier: &RoomOrAliasId) -> bool { |
|
|
|
|
self.imp().pending_rooms.borrow().contains(identifier) |
|
|
|
|
} |
|
|
|
|
/// Remove the given room identifier from the pending rooms.
|
|
|
|
|
fn remove_pending_room(&self, identifier: &RoomOrAliasId) { |
|
|
|
|
self.pending_rooms.borrow_mut().remove(identifier); |
|
|
|
|
self.obj().emit_by_name::<()>("pending-rooms-changed", &[]); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn pending_rooms_remove(&self, identifier: &RoomOrAliasId) { |
|
|
|
|
self.imp().pending_rooms.borrow_mut().remove(identifier); |
|
|
|
|
self.emit_by_name::<()>("pending-rooms-changed", &[]); |
|
|
|
|
} |
|
|
|
|
/// Add the given room identified to the pending rooms.
|
|
|
|
|
fn add_pending_room(&self, identifier: OwnedRoomOrAliasId) { |
|
|
|
|
self.pending_rooms.borrow_mut().insert(identifier); |
|
|
|
|
self.obj().emit_by_name::<()>("pending-rooms-changed", &[]); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn pending_rooms_insert(&self, identifier: OwnedRoomOrAliasId) { |
|
|
|
|
self.imp().pending_rooms.borrow_mut().insert(identifier); |
|
|
|
|
self.emit_by_name::<()>("pending-rooms-changed", &[]); |
|
|
|
|
} |
|
|
|
|
/// Add a room that was tombstoned but for which we haven't joined the
|
|
|
|
|
/// successor yet.
|
|
|
|
|
pub(super) fn add_tombstoned_room(&self, room_id: OwnedRoomId) { |
|
|
|
|
self.tombstoned_rooms.borrow_mut().insert(room_id); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn pending_rooms_replace_or_remove(&self, identifier: &RoomOrAliasId, room_id: &RoomId) { |
|
|
|
|
{ |
|
|
|
|
let mut pending_rooms = self.imp().pending_rooms.borrow_mut(); |
|
|
|
|
pending_rooms.remove(identifier); |
|
|
|
|
if !self.contains(room_id) { |
|
|
|
|
pending_rooms.insert(room_id.to_owned().into()); |
|
|
|
|
/// Remove the given room identifier from the pending rooms and replace
|
|
|
|
|
/// it with the given room ID if the room is not in the list yet.
|
|
|
|
|
fn remove_or_replace_pending_room(&self, identifier: &RoomOrAliasId, room_id: &RoomId) { |
|
|
|
|
{ |
|
|
|
|
let mut pending_rooms = self.pending_rooms.borrow_mut(); |
|
|
|
|
pending_rooms.remove(identifier); |
|
|
|
|
if !self.contains(room_id) { |
|
|
|
|
pending_rooms.insert(room_id.to_owned().into()); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
self.obj().emit_by_name::<()>("pending-rooms-changed", &[]); |
|
|
|
|
} |
|
|
|
|
self.emit_by_name::<()>("pending-rooms-changed", &[]); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Get the room with the given room ID, if any.
|
|
|
|
|
pub fn get(&self, room_id: &RoomId) -> Option<Room> { |
|
|
|
|
self.imp().list.borrow().get(room_id).cloned() |
|
|
|
|
} |
|
|
|
|
/// Handle when items were added to the list.
|
|
|
|
|
fn items_added(&self, added: usize) { |
|
|
|
|
let position = { |
|
|
|
|
let list = self.list.borrow(); |
|
|
|
|
|
|
|
|
|
/// Get the room with the given identifier, if any.
|
|
|
|
|
pub fn get_by_identifier(&self, identifier: &RoomOrAliasId) -> Option<Room> { |
|
|
|
|
match <&RoomId>::try_from(identifier) { |
|
|
|
|
Ok(room_id) => self.get(room_id), |
|
|
|
|
Err(room_alias) => { |
|
|
|
|
let mut matches = self |
|
|
|
|
.imp() |
|
|
|
|
.list |
|
|
|
|
.borrow() |
|
|
|
|
.iter() |
|
|
|
|
.filter(|(_, room)| { |
|
|
|
|
let matrix_room = room.matrix_room(); |
|
|
|
|
matrix_room.canonical_alias().as_deref() == Some(room_alias) |
|
|
|
|
|| matrix_room.alt_aliases().iter().any(|a| a == room_alias) |
|
|
|
|
}) |
|
|
|
|
.map(|(room_id, room)| (room_id.clone(), room.clone())) |
|
|
|
|
.collect::<HashMap<_, _>>(); |
|
|
|
|
|
|
|
|
|
if matches.len() <= 1 { |
|
|
|
|
return matches.into_values().next(); |
|
|
|
|
} |
|
|
|
|
let position = list.len().saturating_sub(added); |
|
|
|
|
|
|
|
|
|
// The alias is shared between upgraded rooms. We want the latest room, so
|
|
|
|
|
// filter out those that are predecessors.
|
|
|
|
|
let predecessors = matches |
|
|
|
|
.iter() |
|
|
|
|
.filter_map(|(_, room)| room.predecessor_id().cloned()) |
|
|
|
|
.collect::<Vec<_>>(); |
|
|
|
|
for room_id in predecessors { |
|
|
|
|
matches.remove(&room_id); |
|
|
|
|
let mut tombstoned_rooms_to_remove = Vec::new(); |
|
|
|
|
for (_room_id, room) in list.iter().skip(position) { |
|
|
|
|
room.connect_room_forgotten(clone!( |
|
|
|
|
#[weak(rename_to = imp)] |
|
|
|
|
self, |
|
|
|
|
move |room| { |
|
|
|
|
imp.remove(room.room_id()); |
|
|
|
|
} |
|
|
|
|
)); |
|
|
|
|
|
|
|
|
|
// Check if the new room is the successor to a tombstoned room.
|
|
|
|
|
if let Some(predecessor_id) = room.predecessor_id() { |
|
|
|
|
if self.tombstoned_rooms.borrow().contains(predecessor_id) { |
|
|
|
|
if let Some(room) = self.get(predecessor_id) { |
|
|
|
|
room.update_successor(); |
|
|
|
|
tombstoned_rooms_to_remove.push(predecessor_id.clone()); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if matches.len() <= 1 { |
|
|
|
|
return matches.into_values().next(); |
|
|
|
|
if !tombstoned_rooms_to_remove.is_empty() { |
|
|
|
|
let mut tombstoned_rooms = self.tombstoned_rooms.borrow_mut(); |
|
|
|
|
for room_id in tombstoned_rooms_to_remove { |
|
|
|
|
tombstoned_rooms.remove(&room_id); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Ideally this should not happen, return the one with the latest activity.
|
|
|
|
|
matches |
|
|
|
|
.into_values() |
|
|
|
|
.fold(None::<Room>, |latest_room, room| { |
|
|
|
|
latest_room |
|
|
|
|
.filter(|r| r.latest_activity() >= room.latest_activity()) |
|
|
|
|
.or(Some(room)) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
position |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
self.obj().items_changed(position as u32, 0, added as u32); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Wait till the room with the given ID becomes available.
|
|
|
|
|
pub async fn get_wait(&self, room_id: &RoomId) -> Option<Room> { |
|
|
|
|
if let Some(room) = self.get(room_id) { |
|
|
|
|
Some(room) |
|
|
|
|
} else { |
|
|
|
|
let (sender, receiver) = futures_channel::oneshot::channel(); |
|
|
|
|
|
|
|
|
|
let room_id = room_id.to_owned(); |
|
|
|
|
let sender = Cell::new(Some(sender)); |
|
|
|
|
// FIXME: add a timeout
|
|
|
|
|
let handler_id = self.connect_items_changed(move |obj, _, _, _| { |
|
|
|
|
if let Some(room) = obj.get(&room_id) { |
|
|
|
|
if let Some(sender) = sender.take() { |
|
|
|
|
sender.send(Some(room)).unwrap(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
/// Remove the room with the given ID.
|
|
|
|
|
fn remove(&self, room_id: &RoomId) { |
|
|
|
|
let removed = { |
|
|
|
|
let mut list = self.list.borrow_mut(); |
|
|
|
|
|
|
|
|
|
let room = receiver.await.unwrap(); |
|
|
|
|
self.disconnect(handler_id); |
|
|
|
|
room |
|
|
|
|
list.shift_remove_full(room_id) |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
self.tombstoned_rooms.borrow_mut().remove(room_id); |
|
|
|
|
|
|
|
|
|
if let Some((position, ..)) = removed { |
|
|
|
|
self.obj().items_changed(position as u32, 1, 0); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Whether this list contains the room with the given ID.
|
|
|
|
|
pub fn contains(&self, room_id: &RoomId) -> bool { |
|
|
|
|
self.imp().list.borrow().contains_key(room_id) |
|
|
|
|
} |
|
|
|
|
/// Load the list of rooms from the `Store`.
|
|
|
|
|
pub(super) async fn load(&self) { |
|
|
|
|
let rooms = self.metainfo.load_rooms().await; |
|
|
|
|
let added = rooms.len(); |
|
|
|
|
self.list.borrow_mut().extend(rooms); |
|
|
|
|
|
|
|
|
|
/// Remove the room with the given ID.
|
|
|
|
|
pub fn remove(&self, room_id: &RoomId) { |
|
|
|
|
let imp = self.imp(); |
|
|
|
|
self.items_added(added); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let removed = { |
|
|
|
|
let mut list = imp.list.borrow_mut(); |
|
|
|
|
/// Handle room updates received via sync.
|
|
|
|
|
pub(super) fn handle_room_updates(&self, rooms: RoomUpdates) { |
|
|
|
|
let Some(session) = self.session.upgrade() else { |
|
|
|
|
return; |
|
|
|
|
}; |
|
|
|
|
let client = session.client(); |
|
|
|
|
|
|
|
|
|
let mut new_rooms = HashMap::new(); |
|
|
|
|
|
|
|
|
|
for (room_id, left_room) in rooms.leave { |
|
|
|
|
let room = if let Some(room) = self.get(&room_id) { |
|
|
|
|
room |
|
|
|
|
} else if let Some(matrix_room) = client.get_room(&room_id) { |
|
|
|
|
new_rooms |
|
|
|
|
.entry(room_id.clone()) |
|
|
|
|
.or_insert_with(|| Room::new(&session, matrix_room, None)) |
|
|
|
|
.clone() |
|
|
|
|
} else { |
|
|
|
|
warn!("Could not find left room {room_id}"); |
|
|
|
|
continue; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
self.remove_pending_room((*room_id).into()); |
|
|
|
|
room.handle_ambiguity_changes(left_room.ambiguity_changes.values()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
list.shift_remove_full(room_id) |
|
|
|
|
}; |
|
|
|
|
for (room_id, joined_room) in rooms.join { |
|
|
|
|
let room = if let Some(room) = self.get(&room_id) { |
|
|
|
|
room |
|
|
|
|
} else if let Some(matrix_room) = client.get_room(&room_id) { |
|
|
|
|
new_rooms |
|
|
|
|
.entry(room_id.clone()) |
|
|
|
|
.or_insert_with(|| Room::new(&session, matrix_room, None)) |
|
|
|
|
.clone() |
|
|
|
|
} else { |
|
|
|
|
warn!("Could not find joined room {room_id}"); |
|
|
|
|
continue; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
self.remove_pending_room((*room_id).into()); |
|
|
|
|
self.metainfo.watch_room(&room); |
|
|
|
|
room.handle_ambiguity_changes(joined_room.ambiguity_changes.values()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
imp.tombstoned_rooms.borrow_mut().remove(room_id); |
|
|
|
|
for (room_id, _invited_room) in rooms.invite { |
|
|
|
|
let room = if let Some(room) = self.get(&room_id) { |
|
|
|
|
room |
|
|
|
|
} else if let Some(matrix_room) = client.get_room(&room_id) { |
|
|
|
|
new_rooms |
|
|
|
|
.entry(room_id.clone()) |
|
|
|
|
.or_insert_with(|| Room::new(&session, matrix_room, None)) |
|
|
|
|
.clone() |
|
|
|
|
} else { |
|
|
|
|
warn!("Could not find invited room {room_id}"); |
|
|
|
|
continue; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
self.remove_pending_room((*room_id).into()); |
|
|
|
|
self.metainfo.watch_room(&room); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if let Some((position, ..)) = removed { |
|
|
|
|
self.items_changed(position as u32, 1, 0); |
|
|
|
|
if !new_rooms.is_empty() { |
|
|
|
|
let added = new_rooms.len(); |
|
|
|
|
self.list.borrow_mut().extend(new_rooms); |
|
|
|
|
self.items_added(added); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn items_added(&self, added: usize) { |
|
|
|
|
let position = { |
|
|
|
|
let imp = self.imp(); |
|
|
|
|
let list = imp.list.borrow(); |
|
|
|
|
/// Join the room with the given identifier.
|
|
|
|
|
pub(super) async fn join_by_id_or_alias( |
|
|
|
|
&self, |
|
|
|
|
identifier: OwnedRoomOrAliasId, |
|
|
|
|
via: Vec<OwnedServerName>, |
|
|
|
|
) -> Result<OwnedRoomId, String> { |
|
|
|
|
let Some(session) = self.session.upgrade() else { |
|
|
|
|
return Err("Could not upgrade Session".to_owned()); |
|
|
|
|
}; |
|
|
|
|
let client = session.client(); |
|
|
|
|
let identifier_clone = identifier.clone(); |
|
|
|
|
|
|
|
|
|
let position = list.len().saturating_sub(added); |
|
|
|
|
self.add_pending_room(identifier.clone()); |
|
|
|
|
|
|
|
|
|
let mut tombstoned_rooms_to_remove = Vec::new(); |
|
|
|
|
for (_room_id, room) in list.iter().skip(position) { |
|
|
|
|
room.connect_room_forgotten(clone!( |
|
|
|
|
#[weak(rename_to = obj)] |
|
|
|
|
self, |
|
|
|
|
move |room| { |
|
|
|
|
obj.remove(room.room_id()); |
|
|
|
|
} |
|
|
|
|
)); |
|
|
|
|
|
|
|
|
|
// Check if the new room is the successor to a tombstoned room.
|
|
|
|
|
if let Some(predecessor_id) = room.predecessor_id() { |
|
|
|
|
if imp.tombstoned_rooms.borrow().contains(predecessor_id) { |
|
|
|
|
if let Some(room) = self.get(predecessor_id) { |
|
|
|
|
room.update_successor(); |
|
|
|
|
tombstoned_rooms_to_remove.push(predecessor_id.clone()); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
let handle = spawn_tokio!(async move { |
|
|
|
|
client |
|
|
|
|
.join_room_by_id_or_alias(&identifier_clone, &via) |
|
|
|
|
.await |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
if !tombstoned_rooms_to_remove.is_empty() { |
|
|
|
|
let mut tombstoned_rooms = imp.tombstoned_rooms.borrow_mut(); |
|
|
|
|
for room_id in tombstoned_rooms_to_remove { |
|
|
|
|
tombstoned_rooms.remove(&room_id); |
|
|
|
|
match handle.await.expect("task was not aborted") { |
|
|
|
|
Ok(matrix_room) => { |
|
|
|
|
self.remove_or_replace_pending_room(&identifier, matrix_room.room_id()); |
|
|
|
|
Ok(matrix_room.room_id().to_owned()) |
|
|
|
|
} |
|
|
|
|
Err(error) => { |
|
|
|
|
self.remove_pending_room(&identifier); |
|
|
|
|
error!("Joining room {identifier} failed: {error}"); |
|
|
|
|
|
|
|
|
|
let error = gettext_f( |
|
|
|
|
// Translators: Do NOT translate the content between '{' and '}', this is a
|
|
|
|
|
// variable name.
|
|
|
|
|
"Could not join room {room_name}", |
|
|
|
|
&[("room_name", identifier.as_str())], |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
Err(error) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
position |
|
|
|
|
}; |
|
|
|
|
glib::wrapper! { |
|
|
|
|
/// List of all rooms known by the user.
|
|
|
|
|
///
|
|
|
|
|
/// This is the parent `GListModel` of the sidebar from which all other models
|
|
|
|
|
/// are derived.
|
|
|
|
|
///
|
|
|
|
|
/// The `RoomList` also takes care of, so called *pending rooms*, i.e.
|
|
|
|
|
/// rooms the user requested to join, but received no response from the
|
|
|
|
|
/// server yet.
|
|
|
|
|
pub struct RoomList(ObjectSubclass<imp::RoomList>) |
|
|
|
|
@implements gio::ListModel; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
self.items_changed(position as u32, 0, added as u32); |
|
|
|
|
impl RoomList { |
|
|
|
|
pub fn new(session: &Session) -> Self { |
|
|
|
|
glib::Object::builder().property("session", session).build() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Loads the state from the `Store`.
|
|
|
|
|
///
|
|
|
|
|
/// Note that the `Store` currently doesn't store all events, therefore, we
|
|
|
|
|
/// aren't really loading much via this function.
|
|
|
|
|
pub async fn load(&self) { |
|
|
|
|
let imp = self.imp(); |
|
|
|
|
/// Load the list of rooms from the `Store`.
|
|
|
|
|
pub(crate) async fn load(&self) { |
|
|
|
|
self.imp().load().await; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let rooms = imp.metainfo.load_rooms().await; |
|
|
|
|
let added = rooms.len(); |
|
|
|
|
imp.list.borrow_mut().extend(rooms); |
|
|
|
|
/// Get a snapshot of the rooms list.
|
|
|
|
|
pub(crate) fn snapshot(&self) -> Vec<Room> { |
|
|
|
|
self.imp().list.borrow().values().cloned().collect() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
self.items_added(added); |
|
|
|
|
/// Whether the room with the given identifier is pending.
|
|
|
|
|
pub(crate) fn is_pending_room(&self, identifier: &RoomOrAliasId) -> bool { |
|
|
|
|
self.imp().pending_rooms.borrow().contains(identifier) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn handle_room_updates(&self, rooms: RoomUpdates) { |
|
|
|
|
let Some(session) = self.session() else { |
|
|
|
|
return; |
|
|
|
|
/// Get the room with the given room ID, if any.
|
|
|
|
|
pub(crate) fn get(&self, room_id: &RoomId) -> Option<Room> { |
|
|
|
|
self.imp().get(room_id) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Get the room with the given identifier, if any.
|
|
|
|
|
pub(crate) fn get_by_identifier(&self, identifier: &RoomOrAliasId) -> Option<Room> { |
|
|
|
|
let room_alias = match <&RoomId>::try_from(identifier) { |
|
|
|
|
Ok(room_id) => return self.get(room_id), |
|
|
|
|
Err(room_alias) => room_alias, |
|
|
|
|
}; |
|
|
|
|
let imp = self.imp(); |
|
|
|
|
let client = session.client(); |
|
|
|
|
|
|
|
|
|
let mut new_rooms = HashMap::new(); |
|
|
|
|
|
|
|
|
|
for (room_id, left_room) in rooms.leave { |
|
|
|
|
let room = if let Some(room) = self.get(&room_id) { |
|
|
|
|
room |
|
|
|
|
} else if let Some(matrix_room) = client.get_room(&room_id) { |
|
|
|
|
new_rooms |
|
|
|
|
.entry(room_id.clone()) |
|
|
|
|
.or_insert_with(|| Room::new(&session, matrix_room, None)) |
|
|
|
|
.clone() |
|
|
|
|
} else { |
|
|
|
|
warn!("Could not find left room {room_id}"); |
|
|
|
|
continue; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
self.pending_rooms_remove((*room_id).into()); |
|
|
|
|
room.handle_ambiguity_changes(left_room.ambiguity_changes.values()); |
|
|
|
|
} |
|
|
|
|
let mut matches = self |
|
|
|
|
.imp() |
|
|
|
|
.list |
|
|
|
|
.borrow() |
|
|
|
|
.iter() |
|
|
|
|
.filter(|(_, room)| { |
|
|
|
|
// We don't want a room that is not joined, it might not be the proper room for
|
|
|
|
|
// the given alias anymore.
|
|
|
|
|
if !room.is_joined() { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for (room_id, joined_room) in rooms.join { |
|
|
|
|
let room = if let Some(room) = self.get(&room_id) { |
|
|
|
|
room |
|
|
|
|
} else if let Some(matrix_room) = client.get_room(&room_id) { |
|
|
|
|
new_rooms |
|
|
|
|
.entry(room_id.clone()) |
|
|
|
|
.or_insert_with(|| Room::new(&session, matrix_room, None)) |
|
|
|
|
.clone() |
|
|
|
|
} else { |
|
|
|
|
warn!("Could not find joined room {room_id}"); |
|
|
|
|
continue; |
|
|
|
|
}; |
|
|
|
|
let matrix_room = room.matrix_room(); |
|
|
|
|
matrix_room.canonical_alias().as_deref() == Some(room_alias) |
|
|
|
|
|| matrix_room.alt_aliases().iter().any(|a| a == room_alias) |
|
|
|
|
}) |
|
|
|
|
.map(|(room_id, room)| (room_id.clone(), room.clone())) |
|
|
|
|
.collect::<HashMap<_, _>>(); |
|
|
|
|
|
|
|
|
|
self.pending_rooms_remove((*room_id).into()); |
|
|
|
|
imp.metainfo.watch_room(&room); |
|
|
|
|
room.handle_ambiguity_changes(joined_room.ambiguity_changes.values()); |
|
|
|
|
if matches.len() <= 1 { |
|
|
|
|
return matches.into_values().next(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for (room_id, _invited_room) in rooms.invite { |
|
|
|
|
let room = if let Some(room) = self.get(&room_id) { |
|
|
|
|
room |
|
|
|
|
} else if let Some(matrix_room) = client.get_room(&room_id) { |
|
|
|
|
new_rooms |
|
|
|
|
.entry(room_id.clone()) |
|
|
|
|
.or_insert_with(|| Room::new(&session, matrix_room, None)) |
|
|
|
|
.clone() |
|
|
|
|
} else { |
|
|
|
|
warn!("Could not find invited room {room_id}"); |
|
|
|
|
continue; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
self.pending_rooms_remove((*room_id).into()); |
|
|
|
|
imp.metainfo.watch_room(&room); |
|
|
|
|
// The alias is shared between upgraded rooms. We want the latest room, so
|
|
|
|
|
// filter out those that are predecessors.
|
|
|
|
|
let predecessors = matches |
|
|
|
|
.iter() |
|
|
|
|
.filter_map(|(_, room)| room.predecessor_id().cloned()) |
|
|
|
|
.collect::<Vec<_>>(); |
|
|
|
|
for room_id in predecessors { |
|
|
|
|
matches.remove(&room_id); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if !new_rooms.is_empty() { |
|
|
|
|
let added = new_rooms.len(); |
|
|
|
|
imp.list.borrow_mut().extend(new_rooms); |
|
|
|
|
self.items_added(added); |
|
|
|
|
if matches.len() <= 1 { |
|
|
|
|
return matches.into_values().next(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Ideally this should not happen, return the one with the latest activity.
|
|
|
|
|
matches |
|
|
|
|
.into_values() |
|
|
|
|
.fold(None::<Room>, |latest_room, room| { |
|
|
|
|
latest_room |
|
|
|
|
.filter(|r| r.latest_activity() >= room.latest_activity()) |
|
|
|
|
.or(Some(room)) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Join the room with the given identifier.
|
|
|
|
|
pub async fn join_by_id_or_alias( |
|
|
|
|
&self, |
|
|
|
|
identifier: OwnedRoomOrAliasId, |
|
|
|
|
via: Vec<OwnedServerName>, |
|
|
|
|
) -> Result<OwnedRoomId, String> { |
|
|
|
|
let Some(session) = self.session() else { |
|
|
|
|
return Err("Could not upgrade Session".to_owned()); |
|
|
|
|
}; |
|
|
|
|
let client = session.client(); |
|
|
|
|
let identifier_clone = identifier.clone(); |
|
|
|
|
/// Wait till the room with the given ID becomes available.
|
|
|
|
|
pub(crate) async fn get_wait(&self, room_id: &RoomId) -> Option<Room> { |
|
|
|
|
if let Some(room) = self.get(room_id) { |
|
|
|
|
return Some(room); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
self.pending_rooms_insert(identifier.clone()); |
|
|
|
|
let (sender, receiver) = futures_channel::oneshot::channel(); |
|
|
|
|
|
|
|
|
|
let handle = spawn_tokio!(async move { |
|
|
|
|
client |
|
|
|
|
.join_room_by_id_or_alias(&identifier_clone, &via) |
|
|
|
|
.await |
|
|
|
|
let room_id = room_id.to_owned(); |
|
|
|
|
let sender = Cell::new(Some(sender)); |
|
|
|
|
// FIXME: add a timeout
|
|
|
|
|
let handler_id = self.connect_items_changed(move |obj, _, _, _| { |
|
|
|
|
if let Some(room) = obj.get(&room_id) { |
|
|
|
|
if let Some(sender) = sender.take() { |
|
|
|
|
let _ = sender.send(Some(room)); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
match handle.await.unwrap() { |
|
|
|
|
Ok(matrix_room) => { |
|
|
|
|
self.pending_rooms_replace_or_remove(&identifier, matrix_room.room_id()); |
|
|
|
|
Ok(matrix_room.room_id().to_owned()) |
|
|
|
|
} |
|
|
|
|
Err(error) => { |
|
|
|
|
self.pending_rooms_remove(&identifier); |
|
|
|
|
error!("Joining room {identifier} failed: {error}"); |
|
|
|
|
|
|
|
|
|
let error = gettext_f( |
|
|
|
|
// Translators: Do NOT translate the content between '{' and '}', this is a
|
|
|
|
|
// variable name.
|
|
|
|
|
"Could not join room {room_name}", |
|
|
|
|
&[("room_name", identifier.as_str())], |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
Err(error) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
let room = receiver.await.ok().flatten(); |
|
|
|
|
|
|
|
|
|
pub fn connect_pending_rooms_changed<F: Fn(&Self) + 'static>( |
|
|
|
|
&self, |
|
|
|
|
f: F, |
|
|
|
|
) -> glib::SignalHandlerId { |
|
|
|
|
self.connect_closure( |
|
|
|
|
"pending-rooms-changed", |
|
|
|
|
true, |
|
|
|
|
closure_local!(move |obj: Self| { |
|
|
|
|
f(&obj); |
|
|
|
|
}), |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
self.disconnect(handler_id); |
|
|
|
|
|
|
|
|
|
/// Get the room with the given identifier, if it is joined.
|
|
|
|
|
pub fn joined_room(&self, identifier: &RoomOrAliasId) -> Option<Room> { |
|
|
|
|
self.get_by_identifier(identifier).filter(Room::is_joined) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Add a room that was tombstoned but for which we haven't joined the
|
|
|
|
|
/// successor yet.
|
|
|
|
|
pub fn add_tombstoned_room(&self, room_id: OwnedRoomId) { |
|
|
|
|
self.imp().tombstoned_rooms.borrow_mut().insert(room_id); |
|
|
|
|
room |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Get the joined room that is a direct chat with the user with the given
|
|
|
|
|
/// ID.
|
|
|
|
|
///
|
|
|
|
|
/// If several rooms are found, returns the room with the latest activity.
|
|
|
|
|
pub fn direct_chat(&self, user_id: &UserId) -> Option<Room> { |
|
|
|
|
pub(crate) fn direct_chat(&self, user_id: &UserId) -> Option<Room> { |
|
|
|
|
self.imp() |
|
|
|
|
.list |
|
|
|
|
.borrow() |
|
|
|
|
@ -446,4 +442,38 @@ impl RoomList {
|
|
|
|
|
.max_by(|x, y| x.latest_activity().cmp(&y.latest_activity())) |
|
|
|
|
.cloned() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Add a room that was tombstoned but for which we haven't joined the
|
|
|
|
|
/// successor yet.
|
|
|
|
|
pub(crate) fn add_tombstoned_room(&self, room_id: OwnedRoomId) { |
|
|
|
|
self.imp().add_tombstoned_room(room_id); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Handle room updates received via sync.
|
|
|
|
|
pub(crate) fn handle_room_updates(&self, rooms: RoomUpdates) { |
|
|
|
|
self.imp().handle_room_updates(rooms); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Join the room with the given identifier.
|
|
|
|
|
pub(crate) async fn join_by_id_or_alias( |
|
|
|
|
&self, |
|
|
|
|
identifier: OwnedRoomOrAliasId, |
|
|
|
|
via: Vec<OwnedServerName>, |
|
|
|
|
) -> Result<OwnedRoomId, String> { |
|
|
|
|
self.imp().join_by_id_or_alias(identifier, via).await |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Connect to the signal emitted when the pending rooms changed.
|
|
|
|
|
pub fn connect_pending_rooms_changed<F: Fn(&Self) + 'static>( |
|
|
|
|
&self, |
|
|
|
|
f: F, |
|
|
|
|
) -> glib::SignalHandlerId { |
|
|
|
|
self.connect_closure( |
|
|
|
|
"pending-rooms-changed", |
|
|
|
|
true, |
|
|
|
|
closure_local!(move |obj: Self| { |
|
|
|
|
f(&obj); |
|
|
|
|
}), |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|