diff --git a/gui/src/app.rs b/gui/src/app.rs index c8cb979..0d8aa3f 100644 --- a/gui/src/app.rs +++ b/gui/src/app.rs @@ -4,7 +4,7 @@ use dioxus::prelude::*; use mime_guess::Mime; use mumble_web2_common::{ClientConfig, ServerStatus}; use ordermap::OrderSet; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use crate::imp; @@ -54,14 +54,6 @@ pub enum Command { use Command::*; use ConnectionState::*; -#[derive(Default)] -pub struct ChannelState { - pub name: String, - pub children: OrderSet, - pub users: OrderSet, - pub parent: Option, -} - #[derive(Default)] pub struct UserState { pub name: String, @@ -94,8 +86,123 @@ pub struct Chat { } #[derive(Default)] -pub struct ServerState { +pub struct ChannelState { + pub name: String, + pub children: OrderSet, + pub users: OrderSet, + pub parent: Option, + pub position: i32, +} + +impl ChannelState { + pub fn update_from_ChannelState( + &mut self, + channel_state: &mumble_protocol::control::msgs::ChannelState, + ) { + if channel_state.has_position() { + self.position = channel_state.get_position(); + } + if channel_state.has_parent() { + self.parent = Some(channel_state.get_parent()); + } + if channel_state.has_name() { + self.name = channel_state.get_name().to_string(); + } + } +} + +#[derive(Default)] +pub struct ChannelsState { pub channels: HashMap, +} + +impl ChannelsState { + pub fn update_from_ChannelState( + &mut self, + channel_state: &mumble_protocol::control::msgs::ChannelState, + ) { + self.channels + .entry(channel_state.get_channel_id()) + .or_default() + .update_from_ChannelState(channel_state); + + self.update_channel_parents(); + } + + pub fn update_from_ChannelRemove( + &mut self, + channel_remove: &mumble_protocol::control::msgs::ChannelRemove, + ) { + self.channels.remove(&channel_remove.get_channel_id()); + self.update_channel_parents(); + } + + pub fn update_channel_parents(&mut self) { + // Zero out existing children + for (id, state) in self.channels.iter_mut() { + state.children = OrderSet::new(); + } + + let mut channels_updated: HashSet = HashSet::new(); + channels_updated.insert(0); // insert root channel + + let mut channels_to_parse: Vec<(ChannelId, ChannelId, i32)> = Vec::new(); + for (id, state) in self.channels.iter() { + // Skip root channel + if *id == 0 { + continue; + } + + // Ignore channels with no parent + let Some(parent_id) = state.parent else { + continue; + }; + + // If a channel has a parent that we haven't gotten a channel + // state packet for, ignore it + if !self.channels.contains_key(&parent_id) { + continue; + } + + channels_to_parse.push((*id, parent_id, state.position)); + } + + let mut channel_positions = HashMap::new(); + for (id, state) in self.channels.iter() { + channel_positions.insert(*id, state.position); + } + + while channels_updated.len() < channels_to_parse.len() { + for (id, parent_id, position) in &channels_to_parse { + if channels_updated.contains(&id) { + continue; + } + + // Skip channels whose parent we haven't seen yet + if !channels_updated.contains(&parent_id) { + continue; + } + + let parent = self.channels.get_mut(&parent_id).unwrap(); + + let mut after_index = 0; + for (index, child_id) in parent.children.iter().enumerate() { + after_index = index; + if channel_positions[child_id] > *position { + continue; + } + } + parent.children.insert_before(after_index, *id); + + channels_updated.insert(*id); + } + } + } +} + +#[derive(Default)] +pub struct ServerState { + pub channels_state: ChannelsState, pub users: HashMap, pub chat: Vec, pub session: Option, @@ -182,7 +289,7 @@ pub fn Channel(id: ChannelId) -> Element { let net: Coroutine = use_coroutine_handle(); let server = STATE.server.read(); let user = server.session.unwrap(); - let Some(state) = server.channels.get(&id) else { + let Some(state) = server.channels_state.channels.get(&id) else { return rsx!("missing channel {id}"); }; @@ -336,7 +443,7 @@ pub fn ControlView(config: Resource) -> Element { return rsx!(); }; - let current_channel_name = server.channels[&channel].name.clone(); + let current_channel_name = server.channels_state.channels[&channel].name.clone(); let proxy_url = config .read_unchecked() @@ -528,7 +635,7 @@ pub fn ServerView(config: Resource) -> Element { class: "server_grid", div { class: "server_channel_box", - for (id, state) in server.channels.iter() { + for (id, state) in server.channels_state.channels.iter() { if state.parent.is_none() { Channel { id: *id } } diff --git a/gui/src/lib.rs b/gui/src/lib.rs index d21a432..edf1096 100644 --- a/gui/src/lib.rs +++ b/gui/src/lib.rs @@ -335,41 +335,11 @@ fn accept_packet( } ControlPacket::ChannelState(u) => { let mut server = STATE.server.write(); - let id = u.get_channel_id(); - - let state = server.channels.entry(id).or_default(); - let new_parent = if u.has_parent() { - if let Some(parent) = state.parent.and_then(|p| server.channels.get_mut(&p)) { - parent.children.remove(&id); - } - - let parent_id = u.get_parent(); - let parent = server.channels.entry(parent_id).or_default(); - if u.has_position() && u.get_position() as usize <= parent.children.len() { - // TODO: what if positions are received out of order? we need to sort afterwards? - parent.children.insert_before(u.get_position() as usize, id); - } else { - parent.children.insert(id); - } - Some(parent_id) - } else { - None - }; - - let state = server.channels.entry(id).or_default(); - state.parent = new_parent; - if u.has_name() { - state.name = u.get_name().to_string(); - } + server.channels_state.update_from_ChannelState(&u); } ControlPacket::ChannelRemove(u) => { let mut server = STATE.server.write(); - let id = u.get_channel_id(); - if let Some(channel) = server.channels.remove(&id) { - if let Some(parent) = channel.parent.and_then(|p| server.channels.get_mut(&p)) { - parent.children.remove(&id); - } - } + server.channels_state.update_from_ChannelRemove(&u); } ControlPacket::UserState(u) => { let mut server = STATE.server.write(); @@ -381,12 +351,13 @@ fn accept_packet( let state = state_entry.or_default(); // the server might now send a channel_id if the user is in channel=0 if u.has_channel_id() || new { - if let Some(parent) = server.channels.get_mut(&state.channel) { + if let Some(parent) = server.channels_state.channels.get_mut(&state.channel) { parent.users.remove(&id); } let channel_id = u.get_channel_id(); server + .channels_state .channels .entry(channel_id) .or_default() @@ -418,7 +389,7 @@ fn accept_packet( let mut server = STATE.server.write(); let id = u.get_session(); if let Some(state) = server.users.remove(&id) { - if let Some(parent) = server.channels.get_mut(&state.channel) { + if let Some(parent) = server.channels_state.channels.get_mut(&state.channel) { parent.users.remove(&id); } }