Provide public interface to disco requests
authorMyhailo Danylenko <isbear@ukrpost.net>
Sat, 02 Jan 2010 12:42:27 +0200
changeset 16 e903da874e63
parent 15 2aa6a333b0af
child 17 2780d5e74400
Provide public interface to disco requests
CMakeLists.txt
TODO
disco.c
disco.h
--- a/CMakeLists.txt	Sat Dec 26 05:28:52 2009 +0200
+++ b/CMakeLists.txt	Sat Jan 02 12:42:27 2010 +0200
@@ -18,7 +18,7 @@
 project(disco C) 
 
 ## Target definitions
-add_library(disco MODULE disco.c) 
+add_library(disco MODULE disco.c)
 
 ## User settable options
 set(MCABBER_INCLUDE_DIR "${disco_SOURCE_DIR}/../include" 
@@ -29,7 +29,7 @@
 set(CPACK_PACKAGE_VERSION "0.0.1")
 set(CPACK_PACKAGE_VENDOR "IsBear")
 set(CPACK_PACKAGE_CONTACT "Myhailo Danylenko <isbear@ukrpost.net>")
-set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Mcabber example modularized disco implementation")
+set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Mcabber module for service discovery requests")
 set(CPACK_RESOURCE_FILE_LICENSE ${disco_SOURCE_DIR}/COPYING)
 set(CPACK_SOURCE_GENERATOR TBZ2)
 set(CPACK_GENERATOR DEB CACHE TEXT "Binary package generator, eg DEB, RPM, TGZ, NSIS...")
@@ -64,6 +64,7 @@
 ## Installation
 install(TARGETS disco DESTINATION lib/mcabber) 
 install(FILES disco.rc COPYING TODO README DESTINATION share/doc/${CPACK_PACKAGE_NAME})
+install(FILES disco.h DESTINATION include/mcabber)
 install(DIRECTORY help DESTINATION share/mcabber)
 
 ## The End ## vim: se ts=4: ##
--- a/TODO	Sat Dec 26 05:28:52 2009 +0200
+++ b/TODO	Sat Jan 02 12:42:27 2010 +0200
@@ -1,3 +1,3 @@
 
-Split large messages to avoid splitting in the middle of line?
+Enable use of caps cache by providing mcabber routine to get identity
 
--- 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: */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/disco.h	Sat Jan 02 12:42:27 2010 +0200
@@ -0,0 +1,48 @@
+
+/* Copyright 2009 Myhailo Danylenko
+ *
+ * Service discovery requests
+ *
+ * This file is part of mcabber-disco
+ *
+ * mcabber-disco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef MCABBER_DISCO_H
+#define MCABBER_DISCO_H
+
+#include <glib.h>
+
+typedef struct {
+	const gchar *category;
+	const gchar *type;
+	const gchar *reserved; // FIXME: look up in standard, what is fourth parameter
+	const gchar *name;
+} disco_identity_t;
+
+typedef const gchar *disco_feature_t;
+
+typedef struct {
+	const gchar *jid;
+	const gchar *name;
+	const gchar *node;
+} disco_item_t;
+
+typedef void (*disco_info_handler_t) (GSList *identities, GSList *features, gpointer userdata);
+typedef void (*disco_items_handler_t) (GSList *items, gpointer userdata);
+
+void disco_info_request  (const gchar *jid, const gchar *node, disco_info_handler_t  handler, gpointer userdata, GDestroyNotify notify);
+void disco_items_request (const gchar *jid, const gchar *node, disco_items_handler_t handler, gpointer userdata, GDestroyNotify notify);
+
+#endif
+