disco.c
author Myhailo Danylenko <isbear@ukrpost.net>
Mon, 02 Nov 2009 21:42:15 +0200
changeset 7 c9ae7744e2f7
parent 6 68d54d647b0f
child 8 859e715adfcd
permissions -rw-r--r--
Free command args

/*
 * disco.c         -- Service discovery requests
 *
 * Copyrigth (C) 2009      Myhailo Danylenko <isbear@ukrpost.net>
 *
 * This program 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 */

#include <glib.h>
#include <gmodule.h>
#include <loudmouth/loudmouth.h>
#include <strings.h>

#include "commands.h"
#include "logprint.h"
#include "utils.h"
#include "xmpp.h"
#include "compl.h"
#include "xmpp_defines.h"
#include "screen.h"
#include "hbuf.h"
#include "hooks.h"

static GSList *disco_handlers = NULL;
static guint   disco_cid      = 0;

static LmHandlerResult disco_handler (LmMessageHandler *handler, LmConnection *connection, LmMessage *message, gpointer udata)
{
	int info_request = (int) udata;

	disco_handlers = g_slist_remove (disco_handlers, handler);

	switch (lm_message_get_sub_type (message)) {
	case LM_MESSAGE_SUB_TYPE_RESULT:

		{
			LmMessageNode *node  = lm_message_get_node (message);
			const gchar   *from  = lm_message_node_get_attribute (node, "from");
			GString       *info;

			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))
				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, ":");
			}

			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");
						}
					}

					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);

				} 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);

				// 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

				g_free (jid);
			}

			g_string_free (info, TRUE);
		}

		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";

			{ // 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);

				scr_WriteIncomingMessage (jid, mesg, 0, HBB_PREFIX_INFO, 0);

				g_free (mesg);
				g_free (jid);
			}
		}

		break;

	default:
		return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
		break;
	}

	return LM_HANDLER_RESULT_REMOVE_MESSAGE;
}

static void do_disco (char *arg)
{
	char **args = split_arg (arg, 3, 0);
	int    info = -1;

	if (!args[0] || !strcmp (args[0], "info"))
		info = 1;
	else if (!strcmp (args[0], "items"))
		info = 0;
	else
		scr_LogPrint (LPRINT_NORMAL, "Unknown subcomand.");

	if (info != -1) {
		LmMessageHandler *handler;
		LmMessage        *request;
		char             *to      = NULL;
		char             *dnode   = NULL;
		
		if (args[0] && args[1]) {
			if (*args[1] == '.') {
				if (*(args[1] + 1) == JID_RESOURCE_SEPARATOR) { // allow "./resource" notation
					char *rest = to_utf8 (args[1] + 1);

					to = g_strdup_printf ("%s%s", CURRENT_JID, rest);
					g_free (rest);
				}
			} else
				to = to_utf8 (args[1]);

			if (args[2])
				dnode = to_utf8 (args[2]);
		}
			// XXX send to all resources/current resource?

		handler = lm_message_handler_new (disco_handler, (gpointer) info, NULL);

		{ // create message
			LmMessageNode *node;

			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);
		}

		lm_connection_send_with_reply (lconnection, request, handler, NULL);

		lm_message_handler_unref (handler);
		lm_message_unref (request);
		g_free (dnode);
		g_free (to);
	}

	free_arg_lst (args);
}

static void disco_free_handlers (void)
{
	GSList *hel;

	if (lconnection) { // XXX more checks?
		// let's hope, that after invalidation, lm will remove and free unreffed by us handler
		for (hel = disco_handlers; hel; hel = hel->next) {
			LmMessageHandler *handler = (LmMessageHandler *) hel->data;
			lm_message_handler_invalidate (handler);
		}
	}

	g_slist_free (disco_handlers);
	disco_handlers = NULL;
}

// release handlers before reconnect
static void disco_hh (guint32 hid, hk_arg_t *args, gpointer userdata)
{
	hk_arg_t *arg = args;

	for (arg = args; arg->name; arg++) {
		if (!strcmp (arg->name, "hook") && !strcmp (arg->value, "hook-pre-disconnect")) {
			disco_free_handlers ();
			return;
		}
	}
}

const gchar *g_module_check_init(GModule *module)
{
	disco_handlers = NULL;

	// completion
	disco_cid = compl_new_category ();
	if (disco_cid) {
		compl_add_category_word (disco_cid, "info");
		compl_add_category_word (disco_cid, "items");
	}

	// command
	cmd_add ("disco", "", disco_cid, COMPL_JID, do_disco, NULL);

	// register hook handler
	hk_add_handler (disco_hh, HOOK_INTERNAL, NULL);

	return NULL;
}

void g_module_unload(GModule *module)
{
	// release handlers
	disco_free_handlers ();

	// remove hook handler
	hk_del_handler (disco_hh, NULL);

	// command
	cmd_del ("disco");

	// completion
	if (disco_cid)
		compl_del_category (disco_cid);
}

/* vim: se ts=4: */