add a plugin to query and cache caps

Emmanuel Gil Peyrot created

Change summary

examples/client.rs  |   2 
src/plugins/caps.rs | 140 +++++++++++++++++++++++++++++++++++++++++++++++
src/plugins/mod.rs  |   1 
3 files changed, 143 insertions(+)

Detailed changes

examples/client.rs 🔗

@@ -8,6 +8,7 @@ use xmpp::plugins::unhandled_iq::UnhandledIqPlugin;
 use xmpp::plugins::messaging::{MessagingPlugin, MessageEvent};
 use xmpp::plugins::presence::{PresencePlugin, Type, Show};
 use xmpp::plugins::disco::DiscoPlugin;
+use xmpp::plugins::caps::CapsPlugin;
 use xmpp::plugins::ibb::IbbPlugin;
 use xmpp::plugins::ping::PingPlugin;
 use xmpp::event::{Priority, Propagation};
@@ -29,6 +30,7 @@ fn main() {
     client.register_plugin(MessagingPlugin::new());
     client.register_plugin(PresencePlugin::new());
     client.register_plugin(DiscoPlugin::new("client", "bot", "en", "xmpp-rs"));
+    client.register_plugin(CapsPlugin::new());
     client.register_plugin(IbbPlugin::new());
     client.register_plugin(PingPlugin::new());
     client.plugin::<PingPlugin>().init();

src/plugins/caps.rs 🔗

@@ -0,0 +1,140 @@
+use std::collections::HashMap;
+use std::convert::TryFrom;
+use std::sync::{Mutex, Arc};
+
+use plugin::PluginProxy;
+use event::{Event, Priority, Propagation};
+use jid::Jid;
+use base64;
+
+use plugins::stanza::{Presence, Iq};
+use plugins::disco::DiscoInfoResult;
+use xmpp_parsers::presence::Type as PresenceType;
+use xmpp_parsers::iq::IqType;
+use xmpp_parsers::disco::Disco;
+use xmpp_parsers::caps::Caps;
+
+#[derive(Debug)]
+pub struct DiscoInfoRequest {
+    pub from: Jid,
+    pub id: String,
+    pub node: Option<String>,
+}
+
+impl Event for DiscoInfoRequest {}
+
+pub struct CapsPlugin {
+    proxy: PluginProxy,
+    pending: Arc<Mutex<HashMap<Jid, (String, String)>>>,
+    cache: Arc<Mutex<HashMap<(Jid, String), Disco>>>,
+}
+
+impl CapsPlugin {
+    pub fn new() -> CapsPlugin {
+        CapsPlugin {
+            proxy: PluginProxy::new(),
+            pending: Arc::new(Mutex::new(HashMap::new())),
+            cache: Arc::new(Mutex::new(HashMap::new())),
+        }
+    }
+
+    fn handle_presence(&self, presence: &Presence) -> Propagation {
+        let presence = presence.clone();
+        match presence.type_ {
+            PresenceType::None => for payload in presence.payloads {
+                let caps = match Caps::try_from(payload) {
+                    Ok(caps) => caps,
+                    Err(_) => continue,
+                };
+                let recipient = presence.from.unwrap();
+                let node = format!("{}#{}", caps.node, base64::encode(&caps.hash.hash));
+                {
+                    let cache = self.cache.lock().unwrap();
+                    if cache.contains_key(&(recipient.clone(), node.clone())) {
+                        break;
+                    }
+                }
+                let id = self.proxy.gen_id();
+                {
+                    let mut pending = self.pending.lock().unwrap();
+                    pending.insert(recipient.clone(), (id.clone(), node.clone()));
+                }
+                let disco = Disco {
+                    node: Some(node),
+                    identities: vec!(),
+                    features: vec!(),
+                    extensions: vec!(),
+                };
+                self.proxy.send(Iq {
+                    to: Some(recipient),
+                    from: None,
+                    id: Some(id),
+                    payload: IqType::Get(disco.into()),
+                }.into());
+                break;
+            },
+            PresenceType::Unavailable
+          | PresenceType::Error => {
+                let recipient = presence.from.unwrap();
+                let mut pending = self.pending.lock().unwrap();
+                let previous = pending.remove(&recipient);
+                if previous.is_none() {
+                    // This wasn’t one of our requests.
+                    return Propagation::Continue;
+                }
+                // TODO: maybe add a negative cache?
+            },
+            _ => (),
+        }
+        Propagation::Continue
+    }
+
+    fn handle_result(&self, result: &DiscoInfoResult) -> Propagation {
+        let from = result.from.clone();
+        let mut pending = self.pending.lock().unwrap();
+        let previous = pending.remove(&from.clone());
+        if let Some((id, node)) = previous {
+            if id != result.id {
+                return Propagation::Continue;
+            }
+            if Some(node.clone()) != result.disco.node {
+                // TODO: make that a debug log.
+                println!("Wrong node in result!");
+                return Propagation::Continue;
+            }
+            {
+                let mut cache = self.cache.lock().unwrap();
+                cache.insert((from, node), result.disco.clone());
+            }
+        } else {
+            // TODO: make that a debug log.
+            println!("No such request from us.");
+            return Propagation::Continue;
+        }
+        Propagation::Stop
+    }
+
+    // This is only for errors.
+    // TODO: also do the same thing for timeouts.
+    fn handle_iq(&self, iq: &Iq) -> Propagation {
+        let iq = iq.clone();
+        if let IqType::Error(_) = iq.payload {
+            let from = iq.from.unwrap();
+            let mut pending = self.pending.lock().unwrap();
+            let previous = pending.remove(&from.clone());
+            if previous.is_none() {
+                // This wasn’t one of our requests.
+                return Propagation::Continue;
+            }
+            // TODO: maybe add a negative cache?
+            return Propagation::Stop;
+        }
+        Propagation::Continue
+    }
+}
+
+impl_plugin!(CapsPlugin, proxy, [
+    (Presence, Priority::Default) => handle_presence,
+    (Iq, Priority::Default) => handle_iq,
+    (DiscoInfoResult, Priority::Default) => handle_result,
+]);

src/plugins/mod.rs 🔗

@@ -2,6 +2,7 @@ pub mod messaging;
 pub mod presence;
 pub mod roster;
 pub mod disco;
+pub mod caps;
 pub mod ping;
 pub mod ibb;
 pub mod stanza;