use std::{collections::HashSet, fmt::Display}; use jid::JID; use rusqlite::{ ToSql, types::{FromSql, ToSqlOutput, Value}, }; pub struct ContactUpdate { pub name: Option, pub groups: HashSet, } #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "reactive_stores", derive(reactive_stores::Store))] pub struct Contact { // jid is the id used to reference everything, but not the primary key pub user_jid: JID, pub subscription: Subscription, /// client user defined name pub name: Option, // TODO: avatar, nickname /// nickname picked by contact // nickname: Option, #[cfg_attr(feature = "reactive_stores", store(key: String = |group| group.clone()))] pub groups: HashSet, } #[derive(Debug, Clone, PartialEq, Eq)] /// Contact subscription state. pub enum Subscription { /// No subscriptions. None, /// Pending outgoing subscription request. PendingOut, /// Pending incoming subscription request. PendingIn, /// Pending incoming & pending outgoing subscription requests. PendingInPendingOut, /// Subscribed to. OnlyOut, /// Subscription from. OnlyIn, /// Subscribed to & pending incoming subscription request. OutPendingIn, /// Subscription from & pending outgoing subscription request. InPendingOut, /// Buddy (subscriptions both ways). Buddy, // TODO: perhaps don't need, just emit event to remove contact // Remove, } impl ToSql for Subscription { fn to_sql(&self) -> rusqlite::Result> { Ok(match self { Subscription::None => ToSqlOutput::Owned(Value::Text("none".to_string())), Subscription::PendingOut => ToSqlOutput::Owned(Value::Text("pending-out".to_string())), Subscription::PendingIn => ToSqlOutput::Owned(Value::Text("pending-in".to_string())), Subscription::PendingInPendingOut => { ToSqlOutput::Owned(Value::Text("pending-in-pending-out".to_string())) } Subscription::OnlyOut => ToSqlOutput::Owned(Value::Text("only-out".to_string())), Subscription::OnlyIn => ToSqlOutput::Owned(Value::Text("only-in".to_string())), Subscription::OutPendingIn => { ToSqlOutput::Owned(Value::Text("out-pending-in".to_string())) } Subscription::InPendingOut => { ToSqlOutput::Owned(Value::Text("in-pending-out".to_string())) } Subscription::Buddy => ToSqlOutput::Owned(Value::Text("buddy".to_string())), }) } } impl FromSql for Subscription { fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { Ok(match value.as_str()? { "none" => Self::None, "pending-out" => Self::PendingOut, "pending-in" => Self::PendingIn, "pending-in-pending-out" => Self::PendingInPendingOut, "only-out" => Self::OnlyOut, "only-in" => Self::OnlyIn, "out-pending-in" => Self::OutPendingIn, "in-pending-out" => Self::InPendingOut, "buddy" => Self::Buddy, // TODO: don't have these lol value => panic!("unexpected subscription `{value}`"), }) } } impl Display for Subscription { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Subscription::None => write!(f, "No Subscriptions"), Subscription::PendingOut => write!(f, "Pending Outgoing Subscription Request"), Subscription::PendingIn => write!(f, "Pending Incoming Subscription Request"), Subscription::PendingInPendingOut => write!( f, "Pending Incoming & Pending Outgoing Subscription Requests" ), Subscription::OnlyOut => write!(f, "Subscribed To"), Subscription::OnlyIn => write!(f, "Subscription From"), Subscription::OutPendingIn => { write!(f, "Subscribed To & Pending Incoming Subscription Request") } Subscription::InPendingOut => write!( f, "Subscription From & Pending Outgoing Subscription Request" ), Subscription::Buddy => write!(f, "Buddy (Subscriptions Both Ways)"), } } } // none // > // >> // < // << // >< // >>< // ><< // >><< impl From for Contact { fn from(value: stanza::roster::Item) -> Self { let subscription = match value.ask { true => match value.subscription { Some(s) => match s { stanza::roster::Subscription::Both => Subscription::Buddy, stanza::roster::Subscription::From => Subscription::InPendingOut, stanza::roster::Subscription::None => Subscription::PendingOut, stanza::roster::Subscription::Remove => Subscription::PendingOut, stanza::roster::Subscription::To => Subscription::OnlyOut, }, None => Subscription::PendingOut, }, false => match value.subscription { Some(s) => match s { stanza::roster::Subscription::Both => Subscription::Buddy, stanza::roster::Subscription::From => Subscription::OnlyIn, stanza::roster::Subscription::None => Subscription::None, stanza::roster::Subscription::Remove => Subscription::None, stanza::roster::Subscription::To => Subscription::OnlyOut, }, None => Subscription::None, }, }; Contact { user_jid: value.jid, subscription, name: value.name, groups: HashSet::from_iter(value.groups.into_iter().filter_map(|group| group.0)), } } }