--- a/disco.c Sat Dec 26 05:28:52 2009 +0200
+++ b/disco.c Sat Jan 02 12:42:27 2010 +0200
@@ -26,19 +26,168 @@
#include "commands.h"
#include "logprint.h"
#include "utils.h"
+#include "hooks.h"
#include "xmpp.h"
#include "compl.h"
#include "xmpp_defines.h"
#include "screen.h"
#include "hbuf.h"
-static guint disco_cid = 0;
-static LmMessageHandler *disco_info_reply_handler = NULL;
-static LmMessageHandler *disco_items_reply_handler = NULL;
+#include "disco.h"
+
+//
+// private types
+//
+
+// lm message handler userdata
+typedef struct {
+ disco_info_handler_t handler;
+ gpointer data;
+ GDestroyNotify notify;
+} disco_info_reply_handler_t;
+
+// lm message handler userdata
+typedef struct {
+ disco_items_handler_t handler;
+ gpointer data;
+ GDestroyNotify notify;
+} disco_items_reply_handler_t;
+
+// user request disco handler userdata (common for info and items)
+typedef struct {
+ gchar *jid;
+ gchar *node;
+} disco_handler_t;
+
+//
+// globals
+//
+
+static guint disco_cid = 0;
+static GSList *reply_handlers = NULL;
+
+//
+// destroyers
+//
+
+static void disco_info_reply_handler_destroy_notify (gpointer data)
+{
+ disco_info_reply_handler_t *cb = data;
+ if (cb -> notify)
+ cb -> notify (cb -> data);
+ g_free (cb);
+ return;
+}
+
+static void disco_items_reply_handler_destroy_notify (gpointer data)
+{
+ disco_items_reply_handler_t *cb = data;
+ if (cb -> notify)
+ cb -> notify (cb -> data);
+ g_free (cb);
+ return;
+}
+
+static void disco_handler_destroy_notify (gpointer data)
+{
+ disco_handler_t *cb = data;
+ g_free (cb -> jid);
+ g_free (cb -> node);
+ g_free (cb);
+ return;
+}
+
+//
+// lm reply handlers
+//
+
+static LmHandlerResult disco_info_reply_handler (LmMessageHandler *handler, LmConnection *connection, LmMessage *message, gpointer udata)
+{
+ disco_info_reply_handler_t *cb = udata;
+
+ reply_handlers = g_slist_remove (reply_handlers, handler);
+
+ switch (lm_message_get_sub_type (message)) {
+ case LM_MESSAGE_SUB_TYPE_RESULT:
-static LmHandlerResult disco_handler (LmMessageHandler *handler, LmConnection *connection, LmMessage *message, gpointer udata)
+ {
+ LmMessageNode *node = lm_message_get_node (message);
+ const gchar *from = lm_message_node_get_attribute (node, "from");
+ GSList *identities = NULL;
+ GSList *features = NULL;
+
+ node = lm_message_node_get_child (node, "query");
+
+ // check xmlns
+ if (!node || g_strcmp0 (lm_message_node_get_attribute (node, "xmlns"), NS_DISCO_INFO))
+ break;
+
+ // parse request results
+ if (node->children)
+ for (node = node->children; node; node = node->next)
+ if (!strcasecmp (node->name, "identity")) {
+ disco_identity_t *identity = g_new (disco_identity_t, 1);
+
+ identity -> category = lm_message_node_get_attribute (node, "category");
+ identity -> type = lm_message_node_get_attribute (node, "type");
+ identity -> name = lm_message_node_get_attribute (node, "name");
+ identity -> reserved = NULL;
+
+ identities = g_slist_append (identities, identity);
+ } else if (!strcasecmp (node->name, "feature"))
+ features = g_slist_insert_sorted (features, (gpointer) lm_message_node_get_attribute (node, "var"), (GCompareFunc) g_strcmp0);
+
+ // call handler
+ cb -> handler (identities, features, cb -> data);
+
+ { // free resources
+ GSList *iel;
+
+ for (iel = identities; iel; iel = iel -> next)
+ g_free (iel -> data);
+
+ g_slist_free (identities);
+ g_slist_free (features);
+ }
+ }
+
+ break;
+
+ case LM_MESSAGE_SUB_TYPE_ERROR:
+
+ {
+ LmMessageNode *node = lm_message_get_node (message);
+ const gchar *from = lm_message_node_get_attribute (node, "from");
+ const gchar *type;
+ const gchar *reason;
+
+ node = lm_message_node_get_child (node, "error");
+ type = lm_message_node_get_attribute (node, "type");
+ if (node->children)
+ reason = node->children->name;
+ else
+ reason = "undefined";
+
+ // XXX: we need to inform user, but do we really need to print this on every possible error?
+ scr_LogPrint (LPRINT_LOGNORM, "disco: Service info discovery for %s failed: %s - %s", from, type, reason);
+
+ cb -> handler (NULL, NULL, cb -> data);
+ }
+
+ break;
+
+ default:
+ return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
+ }
+
+ return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+static LmHandlerResult disco_items_reply_handler (LmMessageHandler *handler, LmConnection *connection, LmMessage *message, gpointer udata)
{
- gboolean info_request = handler == disco_info_reply_handler ? TRUE : FALSE;
+ disco_items_reply_handler_t *cb = udata;
+
+ reply_handlers = g_slist_remove (reply_handlers, handler);
switch (lm_message_get_sub_type (message)) {
case LM_MESSAGE_SUB_TYPE_RESULT:
@@ -46,80 +195,38 @@
{
LmMessageNode *node = lm_message_get_node (message);
const gchar *from = lm_message_node_get_attribute (node, "from");
- GString *info;
+ GSList *items = NULL;
node = lm_message_node_get_child (node, "query");
// check xmlns
- if (!node || strcmp (lm_message_node_get_attribute (node, "xmlns"), info_request ? NS_DISCO_INFO : NS_DISCO_ITEMS))
+ if (!node || g_strcmp0 (lm_message_node_get_attribute (node, "xmlns"), NS_DISCO_ITEMS))
break;
- { // header for user message
- const gchar *rnode = lm_message_node_get_attribute (node, "node");
-
- // create user message string
- info = g_string_new (NULL);
- g_string_printf (info, "Service discovery %s results for %s", info_request ? "info" : "items", from);
- if (rnode)
- g_string_append_printf (info, " (%s):", rnode);
- else
- g_string_append (info, ":");
- }
+ // parse request results
+ if (node->children)
+ for (node = node->children; node; node = node->next)
+ if (!strcasecmp (node->name, "item")) {
+ disco_item_t *item = g_new (disco_item_t, 1);
- if (node->children) {
- // parse request results
- if (info_request) { // info
- GString *identities = g_string_new (NULL);
- GString *features = g_string_new (NULL);
-
- for (node = node->children; node; node = node->next) {
- if (!strcasecmp (node->name, "identity")) {
- const gchar *category = lm_message_node_get_attribute (node, "category");
- const gchar *type = lm_message_node_get_attribute (node, "type");
- const gchar *name = lm_message_node_get_attribute (node, "name");
-
- g_string_append_printf (identities, "\n [%s (%s)] %s", category ? category : "none", type ? type : "none", name ? name : "");
-
- } else if (!strcasecmp (node->name, "feature")) {
- const gchar *var = lm_message_node_get_attribute (node, "var");
-
- g_string_append_printf (features, "\n [%s]", var ? var : "none");
- }
- }
+ item -> name = lm_message_node_get_attribute (node, "name");
+ item -> jid = lm_message_node_get_attribute (node, "jid");
+ item -> node = lm_message_node_get_attribute (node, "node");
- if (identities->len)
- g_string_append_printf (info, "\n Identities:%s", identities->str);
- if (features->len)
- g_string_append_printf (info, "\n Features:%s", features->str);
-
- g_string_free (identities, TRUE);
- g_string_free (features, TRUE);
+ items = g_slist_append (items, item);
+ }
+
+ // call handler
+ cb -> handler (items, cb -> data);
- } else { // items
- for (node = node->children; node; node = node->next) {
- const gchar *name = lm_message_node_get_attribute (node, "name");
- const gchar *jid = lm_message_node_get_attribute (node, "jid");
- const gchar *inode = lm_message_node_get_attribute (node, "node");
-
- if (inode)
- g_string_append_printf (info, "\n [%s (%s)] %s", jid ? jid : "none", inode, name ? name : "");
- else
- g_string_append_printf (info, "\n [%s] %s", jid ? jid : "none", name ? name : "");
- }
- }
- } else
- g_string_append (info, "\n Empty result.");
-
- { // print to buddy's buffer
- gchar *jid = jidtodisp (from);
+ { // free resources
+ GSList *iel;
- // XXX check for message size? conference server lists may be huge...
- scr_WriteIncomingMessage (jid, info->str, 0, HBB_PREFIX_INFO, 0); // NO conversion from utf-8
+ for (iel = items; iel; iel = iel -> next)
+ g_free (iel -> data);
- g_free (jid);
+ g_slist_free (items);
}
-
- g_string_free (info, TRUE);
}
break;
@@ -139,27 +246,249 @@
else
reason = "undefined";
- { // print to buddy's buffer
- gchar *jid = jidtodisp (from);
- gchar *mesg = g_strdup_printf ("Service %s discovery for %s failed: %s - %s", info_request ? "info" : "items", from, type, reason);
+ // XXX: we need to inform user, but do we really need to print this on every possible error?
+ scr_LogPrint (LPRINT_LOGNORM, "disco: Service items discovery for %s failed: %s - %s", from, type, reason);
- scr_WriteIncomingMessage (jid, mesg, 0, HBB_PREFIX_INFO, 0);
-
- g_free (mesg);
- g_free (jid);
- }
+ cb -> handler (NULL, cb -> data);
}
break;
default:
return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
- break;
}
return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}
+//
+// disco requests sending
+//
+
+static void disco_features_cb (gconstpointer data, gpointer userdata)
+{
+ const gchar *feature = data;
+ GSList **features = userdata;
+ *features = g_slist_insert_sorted (*features, (gpointer) feature, (GCompareFunc) g_strcmp0);
+ return;
+}
+
+void disco_info_request (const gchar *jid, const gchar *dnode, disco_info_handler_t handler, gpointer userdata, GDestroyNotify notify)
+{
+ if (!handler) {
+ if (notify)
+ notify (userdata);
+ return;
+ }
+
+ if (0 && !dnode) { // FIXME: no way to get identity(ies) from caps
+ gchar *bjid = jidtodisp (jid);
+ GSList *buddy = roster_find (bjid, jidsearch, ROSTER_TYPE_USER | ROSTER_TYPE_ROOM | ROSTER_TYPE_AGENT);
+ const gchar *resource = strchr (jid, JID_RESOURCE_SEPARATOR);
+
+ g_free (bjid);
+
+ if (buddy) {
+ const gchar *hash = buddy_resource_getcaps (BUDDATA(buddy), resource); // ?? will it all work?
+ if (hash) { // cached result
+ GSList *identities = NULL;
+ GSList *features = NULL;
+ caps_foreach_feature (hash, disco_features_cb, &features);
+ handler (identities, features, userdata);
+ if (notify)
+ notify (userdata);
+ return;
+ }
+ }
+ }
+
+ { // send request
+ LmMessage *request;
+ LmMessageNode *node;
+ GError *error = NULL;
+
+ request = lm_message_new_with_sub_type (jid, LM_MESSAGE_TYPE_IQ, LM_MESSAGE_SUB_TYPE_GET);
+ node = lm_message_get_node (request);
+ node = lm_message_node_add_child (node, "query", NULL);
+ lm_message_node_set_attribute (node, "xmlns", NS_DISCO_INFO);
+ if (dnode)
+ lm_message_node_set_attribute (node, "node", dnode);
+
+ {
+ disco_info_reply_handler_t *cb = g_new (disco_info_reply_handler_t, 1);
+ LmMessageHandler *lhandler = lm_message_handler_new (disco_info_reply_handler, cb, disco_info_reply_handler_destroy_notify);
+
+ cb -> handler = handler;
+ cb -> data = userdata;
+ cb -> notify = notify;
+
+ reply_handlers = g_slist_append (reply_handlers, lhandler);
+
+ lm_connection_send_with_reply (lconnection, request, lhandler, &error);
+
+ if (error) {
+ scr_LogPrint (LPRINT_DEBUG, "disco: Error sending disco request: %s.", error -> message);
+ g_error_free (error);
+ }
+
+ lm_message_handler_unref (lhandler);
+ }
+
+ lm_message_unref (request);
+ }
+
+ return;
+}
+
+void disco_items_request (const gchar *jid, const gchar *dnode, disco_items_handler_t handler, gpointer userdata, GDestroyNotify notify)
+{
+ if (!handler) {
+ if (notify)
+ notify (userdata);
+ return;
+ }
+
+ { // send request
+ LmMessage *request;
+ LmMessageNode *node;
+ GError *error = NULL;
+
+ request = lm_message_new_with_sub_type (jid, LM_MESSAGE_TYPE_IQ, LM_MESSAGE_SUB_TYPE_GET);
+ node = lm_message_get_node (request);
+ node = lm_message_node_add_child (node, "query", NULL);
+ lm_message_node_set_attribute (node, "xmlns", NS_DISCO_ITEMS);
+ if (dnode)
+ lm_message_node_set_attribute (node, "node", dnode);
+
+ {
+ disco_items_reply_handler_t *cb = g_new (disco_items_reply_handler_t, 1);
+ LmMessageHandler *lhandler = lm_message_handler_new (disco_items_reply_handler, cb, disco_items_reply_handler_destroy_notify);
+
+ cb -> handler = handler;
+ cb -> data = userdata;
+ cb -> notify = notify;
+
+ reply_handlers = g_slist_append (reply_handlers, lhandler);
+
+ lm_connection_send_with_reply (lconnection, request, lhandler, &error);
+
+ if (error) {
+ scr_LogPrint (LPRINT_DEBUG, "disco: Error sending disco request: %s.", error -> message);
+ g_error_free (error);
+ }
+
+ lm_message_handler_unref (lhandler);
+ }
+
+ lm_message_unref (request);
+ }
+
+ return;
+}
+
+//
+// user requests handlers (print results)
+//
+
+static void disco_info_handler (GSList *identities, GSList *features, gpointer udata)
+{
+ disco_handler_t *cb = udata;
+ GString *info = g_string_new ("Service discovery info results for ");
+
+ g_string_append (info, cb -> jid);
+ if (cb -> node)
+ g_string_append_printf (info, " (%s):", cb -> node);
+ else
+ g_string_append_c (info, ':');
+
+ {
+ GString *tmp = g_string_new (NULL);
+ GSList *el;
+
+ // compose identities part
+ for (el = identities; el; el = el -> next) {
+ disco_identity_t *identity = el -> data;
+ g_string_append_printf (tmp, "\n [%s (%s)] %s",
+ identity -> category ? identity -> category : "none",
+ identity -> type ? identity -> type : "none",
+ identity -> name ? identity -> name : "");
+ }
+
+ if (tmp -> len) {
+ g_string_append_printf (info, "\n Identities:%s", tmp -> str);
+ g_string_truncate (info, 0);
+ }
+
+ // compose features part
+ for (el = features; el; el = el -> next) {
+ gchar *feature = el -> data;
+ g_string_append_printf (tmp, "\n [%s]", feature ? feature : "none");
+ }
+
+ if (tmp -> len)
+ g_string_append_printf (info, "\n Features:%s", tmp -> str);
+
+ g_string_free (tmp, TRUE);
+ }
+
+ { // print to buddy's buffer
+ gchar *bjid = jidtodisp (cb -> jid);
+
+ scr_WriteIncomingMessage (bjid, info -> str, 0, HBB_PREFIX_INFO, 0); // NO conversion from utf-8
+
+ g_free (bjid);
+ }
+
+ g_string_free (info, TRUE);
+
+ return;
+}
+
+static void disco_items_handler (GSList *items, gpointer udata)
+{
+ disco_handler_t *cb = udata;
+ GString *info = g_string_new ("Service discovery items results for ");
+
+ g_string_append (info, cb -> jid);
+ if (cb -> node)
+ g_string_append_printf (info, " (%s):", cb -> node);
+ else
+ g_string_append_c (info, ':');
+
+ {
+ GSList *el;
+
+ // add items info
+ for (el = items; el; el = el -> next) {
+ disco_item_t *item = el -> data;
+ if (item -> node)
+ g_string_append_printf (info, "\n [%s (%s)] %s",
+ item -> jid ? item -> jid : "none", item -> node,
+ item -> name ? item -> name : "");
+ else
+ g_string_append_printf (info, "\n [%s] %s",
+ item -> jid ? item -> jid : "none",
+ item -> name ? item -> name : "");
+ }
+ }
+
+ { // print to buddy's buffer
+ gchar *bjid = jidtodisp (cb -> jid);
+
+ scr_WriteIncomingMessage (bjid, info -> str, 0, HBB_PREFIX_INFO, 0); // NO conversion from utf-8
+
+ g_free (bjid);
+ }
+
+ g_string_free (info, TRUE);
+
+ return;
+}
+
+//
+// command
+//
+
static void do_disco (char *arg)
{
char **args = split_arg (arg, 3, 0);
@@ -173,10 +502,8 @@
scr_LogPrint (LPRINT_NORMAL, "Unknown subcomand.");
if (info != -1) {
- LmMessageHandler *handler;
- LmMessage *request;
- char *to = NULL;
- char *dnode = NULL;
+ char *to = NULL;
+ char *dnode = NULL;
if (args[0] && args[1]) {
char *p = args[1];
@@ -197,44 +524,58 @@
}
// XXX send to all resources/current resource?
- { // create message
- LmMessageNode *node;
+ {
+ disco_handler_t *cb = g_new (disco_handler_t, 1);
- request = lm_message_new_with_sub_type (to ? to : CURRENT_JID, LM_MESSAGE_TYPE_IQ, LM_MESSAGE_SUB_TYPE_GET);
- node = lm_message_get_node (request);
- node = lm_message_node_add_child (node, "query", NULL);
- lm_message_node_set_attribute (node, "xmlns", info ? NS_DISCO_INFO : NS_DISCO_ITEMS);
- if (dnode)
- lm_message_node_set_attribute (node, "node", dnode);
- }
-
- {
- GError *error = NULL;
+ cb -> jid = to ? to : g_strdup (CURRENT_JID);
+ cb -> node = dnode;
- lm_connection_send_with_reply (lconnection, request, info ? disco_info_reply_handler : disco_items_reply_handler, &error);
-
- if (error) {
- scr_LogPrint (LPRINT_DEBUG, "disco: Error sending disco request: %s.", error -> message);
- g_error_free (error);
- }
+ if (info)
+ disco_info_request (cb -> jid, cb -> node, disco_info_handler, cb, disco_handler_destroy_notify);
+ else
+ disco_items_request (cb -> jid, cb -> node, disco_items_handler, cb, disco_handler_destroy_notify);
}
-
- lm_message_unref (request);
- if (dnode)
- g_free (dnode);
- if (to)
- g_free (to);
}
free_arg_lst (args);
+ return;
+}
+
+//
+// module mechanics
+//
+
+static void disco_unregister_handlers (void)
+{
+ GSList *hel;
+
+ for (hel = reply_handlers; hel; hel = hel -> next) {
+ LmMessageHandler *handler = hel -> data;
+ lm_message_handler_invalidate (handler);
+ }
+
+ g_slist_free (reply_handlers);
+ reply_handlers = NULL;
+
+ return;
+}
+
+static void disco_hh (guint32 htype, hk_arg_t *args, gpointer ignore)
+{
+ hk_arg_t *arg;
+
+ for (arg = args; arg->name; ++arg)
+ if (!strcmp (arg->name, "hook")) {
+ if (!strcmp (arg->value, "hook-pre-disconnect"))
+ disco_unregister_handlers ();
+ return;
+ }
+
+ return;
}
const gchar *g_module_check_init(GModule *module)
{
- // create handlers
- disco_info_reply_handler = lm_message_handler_new (disco_handler, NULL, NULL);
- disco_items_reply_handler = lm_message_handler_new (disco_handler, NULL, NULL);
-
// completion
disco_cid = compl_new_category ();
if (disco_cid) {
@@ -242,6 +583,9 @@
compl_add_category_word (disco_cid, "items");
}
+ // hook handler
+ hk_add_handler (disco_hh, HOOK_INTERNAL, NULL);
+
// command
cmd_add ("disco", "", disco_cid, COMPL_JID, do_disco, NULL);
@@ -257,16 +601,13 @@
if (disco_cid)
compl_del_category (disco_cid);
+ // hook handler
+ hk_del_handler (disco_hh, NULL);
+
// unregister handlers
- if (disco_info_reply_handler) {
- lm_message_handler_invalidate (disco_info_reply_handler);
- lm_message_handler_unref (disco_info_reply_handler);
- }
+ disco_unregister_handlers ();
- if (disco_items_reply_handler) {
- lm_message_handler_invalidate (disco_items_reply_handler);
- lm_message_handler_unref (disco_items_reply_handler);
- }
+ return;
}
/* vim: se ts=4 sw=4: */