From b630f1855ec194703bfb0fdac22de9e18eaa5237 Mon Sep 17 00:00:00 2001 From: Max Audron Date: Sun, 23 Feb 2025 17:04:26 +0100 Subject: add basic network syncables --- .../signalproxy/objects/highlightrulemanager.rs | 3 - src/message/signalproxy/objects/ircuser.rs | 2 +- src/message/signalproxy/objects/network.rs | 361 ++++++++++++++++++++- src/message/signalproxy/objects/networkinfo.rs | 39 ++- 4 files changed, 388 insertions(+), 17 deletions(-) (limited to 'src/message/signalproxy/objects') diff --git a/src/message/signalproxy/objects/highlightrulemanager.rs b/src/message/signalproxy/objects/highlightrulemanager.rs index abcc2c5..9083686 100644 --- a/src/message/signalproxy/objects/highlightrulemanager.rs +++ b/src/message/signalproxy/objects/highlightrulemanager.rs @@ -10,9 +10,6 @@ use crate::message::StatefulSyncableServer; use crate::message::Syncable; use crate::primitive::Variant; -#[cfg(feature = "server")] -use crate::message::signalproxy::translation::NetworkMap; - #[derive(Default, Debug, Clone, PartialEq, NetworkList, NetworkMap)] pub struct HighlightRuleManager { #[network(rename = "HighlightRuleList", variant = "VariantMap", network = "map")] diff --git a/src/message/signalproxy/objects/ircuser.rs b/src/message/signalproxy/objects/ircuser.rs index 93ae013..72f98be 100644 --- a/src/message/signalproxy/objects/ircuser.rs +++ b/src/message/signalproxy/objects/ircuser.rs @@ -65,7 +65,7 @@ impl IrcUser { sync!("removeUserModes", [modes]); } - pub fn update_hostmask(&mut self, mask: String) {} + pub fn update_hostmask(&mut self, _mask: String) {} pub fn join_channel(&mut self, channel: String) { if !self.channels.contains(&channel) { diff --git a/src/message/signalproxy/objects/network.rs b/src/message/signalproxy/objects/network.rs index 33d2b33..a8a882d 100644 --- a/src/message/signalproxy/objects/network.rs +++ b/src/message/signalproxy/objects/network.rs @@ -1,30 +1,44 @@ use std::collections::HashMap; +use itertools::Itertools; +use log::{error, warn}; use num_derive::{FromPrimitive, ToPrimitive}; -use num_traits::FromPrimitive; +use num_traits::{FromPrimitive, ToPrimitive}; -use libquassel_derive::{NetworkList, NetworkMap}; +use libquassel_derive::{sync, NetworkList, NetworkMap, Setters}; +use crate::error::ProtocolError; use crate::message::signalproxy::translation::NetworkMap; +use crate::message::{Class, Syncable}; use crate::primitive::{Variant, VariantList, VariantMap}; use super::{ircchannel::IrcChannel, ircuser::IrcUser, networkinfo::NetworkInfo}; -#[derive(Default, Debug, Clone, PartialEq)] +#[derive(Default, Debug, Clone, PartialEq, Setters)] pub struct Network { pub my_nick: String, pub latency: i32, pub current_server: String, + #[setter(name = "connected")] pub is_connected: bool, pub connection_state: ConnectionState, + #[setter(skip)] pub prefixes: Vec, + #[setter(skip)] pub prefix_modes: Vec, + #[setter(skip)] pub channel_modes: HashMap, + #[setter(skip)] pub irc_users: HashMap, + #[setter(skip)] pub irc_channels: HashMap, + #[setter(skip)] pub supports: HashMap, + #[setter(skip)] pub caps: HashMap, + #[setter(skip)] pub caps_enabled: Vec, + #[setter(skip)] pub network_info: NetworkInfo, } @@ -78,6 +92,189 @@ impl Network { pub fn add_channel(&mut self, name: &str, channel: IrcChannel) { self.irc_channels.insert(name.to_owned(), channel); } + + pub fn connect(&self) { + #[cfg(feature = "client")] + sync!("requestConnect", []) + } + + pub fn disconnect(&self) { + #[cfg(feature = "client")] + sync!("requestDisconnect", []) + } + + pub fn set_network_info(&mut self, network_info: NetworkInfo) { + #[cfg(feature = "client")] + sync!("requestSetNetworkInfo", [network_info.to_network_map()]); + + self.network_info = network_info; + } + + /// Enable the capability `cap` if it is not already enabled + pub fn acknowledge_cap(&mut self, cap: String) { + #[cfg(feature = "server")] + sync!("acknowledgeCap", [cap.clone()]); + + if !self.caps_enabled.contains(&cap) { + self.caps_enabled.push(cap); + } else { + warn!("Capability {} already enabled", cap) + } + } + + /// Add a new capability supported by the server + pub fn add_cap(&mut self, cap: String, value: String) { + #[cfg(feature = "server")] + sync!("addCap", [cap.clone(), value.clone()]); + + self.caps.insert(cap, value); + } + + /// Clear `caps` and `caps_enabled` + pub fn clear_caps(&mut self) { + #[cfg(feature = "server")] + sync!("clearCaps", []); + + self.caps.clear(); + self.caps_enabled.clear(); + } + + /// Remove a capability from `caps` and `caps_enabled` + pub fn remove_cap(&mut self, cap: String) { + #[cfg(feature = "server")] + sync!("removeCap", [cap.clone()]); + + self.caps.remove(&cap); + if let Some((i, _)) = self.caps_enabled.iter().find_position(|c| **c == cap) { + self.caps_enabled.remove(i); + } + } + + // TODO + pub fn add_irc_channel(&mut self, _name: String) {} + pub fn add_irc_user(&mut self, _hostmask: String) {} + + pub fn add_support(&mut self, key: String, value: String) { + #[cfg(feature = "server")] + sync!("addSupport", [key.clone(), value.clone()]); + + self.supports.insert(key, value); + } + + pub fn remove_support(&mut self, key: String) { + #[cfg(feature = "server")] + sync!("removeSupport", [key.clone()]); + + self.supports.remove(&key); + } + + pub fn emit_connection_error(&mut self, error: String) { + #[cfg(feature = "server")] + sync!("emitConnectionError", [error.clone()]); + + error!("{}", error) + } + + /// Rename the user object in the network object + /// TODO the actual nick change is done with a sepperate sync message against the IrcUser object? + pub fn irc_user_nick_changed(&mut self, before: String, after: String) { + #[cfg(feature = "server")] + sync!("ircUserNickChanged", [before.clone(), after.clone()]); + + if let Some(user) = self.irc_users.remove(&before) { + self.irc_users.insert(after, user); + } else { + warn!("irc user {} not found", before); + } + } +} + +impl Syncable for Network { + const CLASS: Class = Class::Network; +} + +#[cfg(feature = "client")] +impl crate::message::StatefulSyncableClient for Network { + fn sync_custom(&mut self, mut msg: crate::message::SyncMessage) + where + Self: Sized, + { + match msg.slot_name.as_str() { + "acknowledgeCap" => self.acknowledge_cap(get_param!(msg)), + "addCap" => self.add_cap(get_param!(msg), get_param!(msg)), + "addIrcChannel" => self.add_irc_channel(get_param!(msg)), + "addIrcUser" => self.add_irc_user(get_param!(msg)), + "addSupport" => self.add_support(get_param!(msg), get_param!(msg)), + "clearCaps" => self.clear_caps(), + "emitConnectionError" => self.emit_connection_error(get_param!(msg)), + "ircUserNickChanged" => self.irc_user_nick_changed(get_param!(msg), get_param!(msg)), + "removeCap" => self.remove_cap(get_param!(msg)), + "removeSupport" => self.remove_support(get_param!(msg)), + "setAutoIdentifyPassword" => self.network_info.set_auto_identify_password(get_param!(msg)), + "setAutoIdentifyService" => self.network_info.set_auto_identify_service(get_param!(msg)), + "setAutoReconnectInterval" => self.network_info.set_auto_reconnect_interval(get_param!(msg)), + "setAutoReconnectRetries" => self.network_info.set_auto_reconnect_retries(get_param!(msg)), + "setCodecForDecoding" => self.network_info.set_codec_for_decoding(get_param!(msg)), + "setCodecForEncoding" => self.network_info.set_codec_for_encoding(get_param!(msg)), + "setCodecForServer" => self.network_info.set_codec_for_server(get_param!(msg)), + "setConnected" => self.set_connected(get_param!(msg)), + "setConnectionState" => self.set_connection_state(get_param!(msg)), + "setCurrentServer" => self.set_current_server(get_param!(msg)), + "setIdentity" => self.network_info.set_identity_id(get_param!(msg)), + "setLatency" => self.set_latency(get_param!(msg)), + "setMessageRateBurstSize" => self.network_info.set_msg_rate_burst_size(get_param!(msg)), + "setMessageRateDelay" => self.network_info.set_msg_rate_message_delay(get_param!(msg)), + "setMyNick" => self.set_my_nick(get_param!(msg)), + "setNetworkName" => self.network_info.set_network_name(get_param!(msg)), + "setNetworkInfo" => self.set_network_info(NetworkInfo::from_network_map( + &mut VariantMap::try_from(msg.params.remove(0)).unwrap(), + )), + "setPerform" => self.network_info.set_perform(get_param!(msg)), + "setRejoinChannels" => self.network_info.set_rejoin_channels(get_param!(msg)), + "setSaslAccount" => self.network_info.set_sasl_account(get_param!(msg)), + "setSaslPassword" => self.network_info.set_sasl_password(get_param!(msg)), + // "setServerList" => self.network_info.set_server_list(get_param!(msg)), + "setActualServerList" => self.network_info.set_server_list({ + match msg.params.remove(0) { + Variant::VariantList(mut variants) => { + Vec::::from_network_map(&mut variants) + } + _ => { + error!("{}", ProtocolError::WrongVariant); + // TODO FIXME + Vec::new() + } + } + }), + "setUnlimitedMessageRate" => self.network_info.set_unlimited_message_rate(get_param!(msg)), + "setUnlimitedReconnectRetries" => { + self.network_info.set_unlimited_reconnect_retries(get_param!(msg)) + } + "setUseAutoIdentify" => self.network_info.set_use_auto_identify(get_param!(msg)), + "setUseAutoReconnect" => self.network_info.set_use_auto_reconnect(get_param!(msg)), + "setUseCustomMessageRate" => self.network_info.set_use_custom_message_rate(get_param!(msg)), + "setUseRandomServer" => self.network_info.set_use_random_server(get_param!(msg)), + "setUseSasl" => self.network_info.set_use_sasl(get_param!(msg)), + _ => (), + } + } +} + +#[cfg(feature = "server")] +impl crate::message::StatefulSyncableServer for Network { + fn sync_custom(&mut self, mut msg: crate::message::SyncMessage) + where + Self: Sized, + { + match msg.slot_name.as_str() { + "requestConnect" => self.connect(), + "requestDisconnect" => self.disconnect(), + "requestSetNetworkInfo" => self.set_network_info(NetworkInfo::from_network_map( + &mut VariantMap::try_from(msg.params.remove(0)).unwrap(), + )), + _ => (), + } + } } impl crate::message::signalproxy::NetworkList for Network { @@ -93,7 +290,7 @@ impl crate::message::signalproxy::NetworkList for Network { res.push(Variant::ByteArray(s!("isConnected"))); res.push(Variant::bool(self.is_connected)); res.push(Variant::ByteArray(s!("connectionState"))); - res.push(Variant::i32(self.connection_state.clone() as i32)); + res.push(Variant::i32(self.connection_state as i32)); res.push(Variant::ByteArray(s!("Supports"))); res.push(Variant::VariantMap( @@ -254,6 +451,143 @@ impl crate::message::signalproxy::NetworkList for Network { } } +impl crate::message::signalproxy::NetworkMap for Network { + type Item = VariantMap; + + fn to_network_map(&self) -> Self::Item { + let mut res = VariantMap::new(); + + res.insert("myNick".to_owned(), Variant::String(self.my_nick.clone())); + res.insert("latency".to_owned(), Variant::i32(self.latency)); + res.insert( + "currentServer".to_owned(), + Variant::String(self.current_server.clone()), + ); + res.insert("isConnected".to_owned(), Variant::bool(self.is_connected)); + res.insert( + "connectionState".to_owned(), + Variant::i32(self.connection_state as i32), + ); + + res.insert( + "Supports".to_owned(), + Variant::VariantMap( + self.supports + .iter() + .map(|(k, v)| (k.clone(), Variant::String(v.clone()))) + .collect(), + ), + ); + + res.insert( + "Caps".to_owned(), + Variant::VariantMap( + self.caps + .iter() + .map(|(k, v)| (k.clone(), Variant::String(v.clone()))) + .collect(), + ), + ); + + res.insert( + "CapsEnabled".to_owned(), + Variant::VariantList( + self.caps_enabled + .iter() + .map(|v| Variant::String(v.clone())) + .collect(), + ), + ); + + res.insert(s!("IrcUsersAndChannels"), { + let mut map = VariantMap::new(); + + map.insert( + s!("Users"), + Variant::VariantMap(self.irc_users.iter().fold(HashMap::new(), |mut res, (_, v)| { + res.extend(v.to_network_map()); + + res + })), + ); + + let channels = self.irc_channels.iter().fold(HashMap::new(), |mut res, (_, v)| { + res.extend(v.to_network_map()); + + res + }); + + map.insert(s!("Channels"), Variant::VariantMap(channels)); + + Variant::VariantMap(map) + }); + + res.extend(self.network_info.to_network_map()); + + return res; + } + + fn from_network_map(input: &mut Self::Item) -> Self { + let users_and_channels: VariantMap = + { input.get("IrcUsersAndChannels").unwrap().try_into().unwrap() }; + + return Self { + my_nick: input.get("myNick").unwrap().into(), + latency: input.get("latency").unwrap().try_into().unwrap(), + current_server: input.get("currentServer").unwrap().into(), + is_connected: input.get("isConnected").unwrap().try_into().unwrap(), + connection_state: ConnectionState::from_i32( + input.get("connectionState").unwrap().try_into().unwrap(), + ) + .unwrap(), + prefixes: Vec::new(), + prefix_modes: Vec::new(), + channel_modes: HashMap::with_capacity(4), + irc_users: { + match users_and_channels.get("Users") { + Some(users) => { + let users: Vec = Vec::::from_network_map( + &mut users.try_into().expect("failed to convert Users"), + ); + + users.into_iter().map(|user| (user.nick.clone(), user)).collect() + } + None => HashMap::new(), + } + }, + irc_channels: { + match users_and_channels.get("Channels") { + Some(channels) => { + let channels: Vec = + Vec::::from_network_map(&mut channels.try_into().unwrap()); + channels + .into_iter() + .map(|channel| (channel.name.clone(), channel)) + .collect() + } + None => HashMap::new(), + } + }, + supports: VariantMap::try_from(input.get("Supports").unwrap()) + .unwrap() + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect(), + caps: VariantMap::try_from(input.get("Caps").unwrap()) + .unwrap() + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect(), + caps_enabled: VariantList::try_from(input.get("CapsEnabled").unwrap()) + .unwrap() + .into_iter() + .map(|v| v.into()) + .collect(), + network_info: NetworkInfo::from_network_map(input), + }; + } +} + #[derive(Debug, Clone, PartialEq, NetworkMap)] pub struct NetworkServer { #[network(rename = "Host")] @@ -435,7 +769,7 @@ mod tests { } #[allow(dead_code)] -#[derive(Debug, Clone, PartialEq, FromPrimitive, ToPrimitive)] +#[derive(Debug, Clone, Copy, PartialEq, FromPrimitive, ToPrimitive)] #[repr(C)] pub enum ConnectionState { Disconnected = 0x00, @@ -452,6 +786,23 @@ impl Default for ConnectionState { } } +impl Into for ConnectionState { + fn into(self) -> Variant { + Variant::i32(self.to_i32().unwrap()) + } +} + +impl TryFrom for ConnectionState { + type Error = ProtocolError; + + fn try_from(value: Variant) -> Result { + match value { + Variant::i32(n) => Ok(ConnectionState::from_i32(n).unwrap()), + _ => Err(ProtocolError::WrongVariant), + } + } +} + #[allow(dead_code)] #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, FromPrimitive, ToPrimitive)] #[repr(C)] diff --git a/src/message/signalproxy/objects/networkinfo.rs b/src/message/signalproxy/objects/networkinfo.rs index d11b7c8..11a4d0f 100644 --- a/src/message/signalproxy/objects/networkinfo.rs +++ b/src/message/signalproxy/objects/networkinfo.rs @@ -1,14 +1,18 @@ -use crate::primitive::StringList; +use crate::{ + message::{Class, Syncable}, + primitive::StringList, +}; -use libquassel_derive::NetworkList; +use libquassel_derive::{NetworkList, NetworkMap, Setters}; use crate::message::objects::network::NetworkServer; -#[derive(Default, Debug, Clone, PartialEq, NetworkList)] +#[derive(Default, Debug, Clone, PartialEq, NetworkList, NetworkMap, Setters)] pub struct NetworkInfo { #[network(rename = "networkName")] pub network_name: String, + #[setter(skip)] #[network(rename = "ServerList", variant = "VariantList", network = "map")] pub server_list: Vec, #[network(rename = "perform")] @@ -34,7 +38,8 @@ pub struct NetworkInfo { // TODO add these type aliases or usertypes in variants // pub network_id: NetworkId, - // pub identity_id: IdentityId, + #[network(rename = "identityId")] + pub identity_id: i32, #[network(rename = "msgRateBurstSize")] pub msg_rate_burst_size: u32, #[network(rename = "msgRateMessageDelay")] @@ -65,6 +70,24 @@ pub struct NetworkInfo { // pub auto_away_active: bool, } +impl NetworkInfo { + pub fn set_server_list(&mut self, servers: Vec) { + #[cfg(feature = "server")] + { + use crate::message::NetworkMap; + use libquassel_derive::sync; + + sync!("setServerList", [Vec::::to_network_map(&servers)]); + } + + self.server_list = servers; + } +} + +impl Syncable for NetworkInfo { + const CLASS: Class = Class::Network; +} + #[cfg(test)] mod tests { use crate::primitive::{Variant, VariantList}; @@ -96,6 +119,8 @@ mod tests { Variant::ByteArray(s!("")), Variant::ByteArray(s!("codecForDecoding")), Variant::ByteArray(s!("")), + Variant::ByteArray(s!("identityId")), + Variant::i32(0), Variant::ByteArray(s!("msgRateBurstSize")), Variant::u32(5), Variant::ByteArray(s!("msgRateMessageDelay")), @@ -125,6 +150,7 @@ mod tests { fn get_runtime() -> NetworkInfo { NetworkInfo { + identity_id: 0, network_name: s!("snoonet"), server_list: vec![], perform: vec![s!("")], @@ -158,9 +184,6 @@ mod tests { #[test] fn networkinfo_from_network() { - assert_eq!( - NetworkInfo::from_network_list(&mut get_network()), - get_runtime() - ) + assert_eq!(NetworkInfo::from_network_list(&mut get_network()), get_runtime()) } } -- cgit v1.2.3