Notify user when cancelling requests
Call handler with empty results, if destroying disco request object
not by user request (eg. on reconnect or module unloading)
/* Copyright 2009,2010 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/>. */
#include <glib.h>
#include <loudmouth/loudmouth.h>
#include <string.h>
#include <mcabber/commands.h>
#include <mcabber/logprint.h>
#include <mcabber/utils.h>
#include <mcabber/hooks.h>
#include <mcabber/xmpp.h>
#include <mcabber/compl.h>
#include <mcabber/xmpp_defines.h>
#include <mcabber/screen.h>
#include <mcabber/hbuf.h>
#include <mcabber/modules.h>
#include <mcabber/caps.h>
#include "disco.h"
#include "config.h"
//
// module description
//
void disco_init (void);
void disco_uninit (void);
#define DESCRIPTION ( "Service discovery requests\nProvides command /disco" )
module_info_t info_disco = {
.branch = MCABBER_BRANCH,
.api = MCABBER_API_VERSION,
.version = PROJECT_VERSION,
.description = DESCRIPTION,
.requires = NULL,
.init = disco_init,
.uninit = disco_uninit,
.next = NULL,
};
//
// private types
//
#define DISCO_INFO_REQUEST ( 0 )
#define DISCO_ITEMS_REQUEST ( 1 )
// lm message handler userdata
typedef struct {
LmMessageHandler * reply_handler;
gpointer data;
GDestroyNotify notify;
guint type;
union {
disco_info_handler_t info;
disco_items_handler_t items;
} handler;
} disco_request_t;
// user request disco handler userdata (common for info and items)
typedef struct {
gchar *jid;
gchar *node;
} disco_handler_t;
//
// globals
//
#ifdef MCABBER_API_HAVE_CMD_ID
static gpointer disco_cmid = NULL;
#endif
static guint disco_cid = 0;
static guint disco_hid = 0;
static GSList * disco_requests = NULL;
//
// destroyers
//
static void disco_request_free ( disco_request_t * cb )
{
disco_requests = g_slist_remove ( disco_requests, cb );
if ( cb -> reply_handler ) {
lm_message_handler_invalidate ( cb -> reply_handler );
#ifdef HAVE_LM_CONNECTION_UNREGISTER_REPLY_HANDLER
if ( lconnection )
lm_connection_unregister_reply_handler ( lconnection, cb -> reply_handler );
#endif
}
if ( cb -> notify )
cb -> notify ( cb -> data );
g_slice_free ( disco_request_t, cb );
return;
}
static void disco_handler_destroy_notify ( gpointer data )
{
disco_handler_t * cb = data;
g_free ( cb -> jid );
g_free ( cb -> node );
g_slice_free ( disco_handler_t, cb );
return;
}
//
// lm reply handlers
//
static LmHandlerResult disco_reply_handler (LmMessageHandler *handler, LmConnection *connection, LmMessage *message, gpointer udata)
{
disco_request_t * cb = udata;
LmHandlerResult result = LM_HANDLER_RESULT_REMOVE_MESSAGE;
switch (lm_message_get_sub_type (message)) {
case LM_MESSAGE_SUB_TYPE_RESULT:
{
LmMessageNode *node = lm_message_get_node (message);
node = lm_message_node_get_child (node, "query");
if ( cb -> type == DISCO_INFO_REQUEST ) {
GSList * identities = NULL;
GSList * features = NULL;
// 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_slice_new (disco_identity_t);
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.info (identities, features, cb -> data);
{ // free resources
GSList *iel;
for (iel = identities; iel; iel = iel -> next)
g_slice_free (disco_identity_t, iel -> data);
g_slist_free (identities);
g_slist_free (features);
}
} else { // items request
GSList * items = NULL;
// check xmlns
if (!node || g_strcmp0 (lm_message_node_get_attribute (node, "xmlns"), NS_DISCO_ITEMS))
break;
// parse request results
if (node->children)
for (node = node->children; node; node = node->next)
if (!strcasecmp (node->name, "item")) {
disco_item_t *item = g_slice_new (disco_item_t);
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");
items = g_slist_append (items, item);
}
// call handler
cb -> handler.items (items, cb -> data);
{ // free resources
GSList *iel;
for (iel = items; iel; iel = iel -> next)
g_slice_free (disco_item_t, iel -> data);
g_slist_free (items);
}
}
}
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?
if ( cb -> type == DISCO_INFO_REQUEST ) {
scr_log_print (LPRINT_LOGNORM, "disco: Service info discovery for %s failed: %s - %s", from, type, reason);
cb -> handler.info (NULL, NULL, cb -> data);
} else {
scr_log_print (LPRINT_LOGNORM, "disco: Service items discovery for %s failed: %s - %s", from, type, reason);
cb -> handler.items (NULL, cb -> data);
}
}
break;
default:
result = LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
break;
}
disco_request_free ( cb );
return result;
}
//
// disco requests sending
//
#if 0
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;
}
#endif
gpointer disco_info_request (const gchar *jid, const gchar *dnode, disco_info_handler_t handler, gpointer userdata, GDestroyNotify notify)
{
if (!handler || !xmpp_is_online ()) {
if ( handler )
handler ( NULL, NULL, userdata ); // XXX
if ( notify )
notify ( userdata );
return NULL;
}
#if 0
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 NULL;
}
}
}
#endif
{ // send request
LmMessage * request;
LmMessageNode * node;
LmMessageHandler * lhandler;
GError * error = NULL;
disco_request_t * cb = g_slice_new ( disco_request_t );
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);
lhandler = lm_message_handler_new (disco_reply_handler, cb, NULL);
cb -> reply_handler = lhandler;
cb -> type = DISCO_INFO_REQUEST;
cb -> handler.info = handler;
cb -> data = userdata;
cb -> notify = notify;
disco_requests = g_slist_append ( disco_requests, cb );
lm_connection_send_with_reply (lconnection, request, lhandler, &error);
if (error) {
scr_log_print (LPRINT_DEBUG, "disco: Error sending disco request: %s.", error -> message);
g_error_free (error);
handler ( NULL, NULL, userdata ); // XXX
disco_request_free ( cb );
cb = NULL;
}
lm_message_handler_unref (lhandler);
lm_message_unref (request);
return cb;
}
}
gpointer disco_items_request (const gchar *jid, const gchar *dnode, disco_items_handler_t handler, gpointer userdata, GDestroyNotify notify)
{
if (!handler || !xmpp_is_online ()) {
if ( handler )
handler ( NULL, userdata ); // XXX
if ( notify )
notify ( userdata );
return NULL;
}
{ // send request
LmMessage * request;
LmMessageNode * node;
LmMessageHandler * lhandler;
GError * error = NULL;
disco_request_t * cb = g_slice_new (disco_request_t);
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);
lhandler = lm_message_handler_new (disco_reply_handler, cb, NULL);
cb -> reply_handler = lhandler;
cb -> type = DISCO_ITEMS_REQUEST;
cb -> handler.items = handler;
cb -> data = userdata;
cb -> notify = notify;
disco_requests = g_slist_append ( disco_requests, cb );
lm_connection_send_with_reply (lconnection, request, lhandler, &error);
if (error) {
scr_log_print (LPRINT_DEBUG, "disco: Error sending disco request: %s.", error -> message);
g_error_free (error);
handler ( NULL, userdata );
disco_request_free ( cb );
cb = NULL;
}
lm_message_handler_unref (lhandler);
lm_message_unref (request);
return cb;
}
}
void disco_cancel_request ( gpointer id )
{
if ( g_slist_find ( disco_requests, id ) ) {
disco_request_t * cb = id;
disco_request_free ( cb );
}
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 (tmp, 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_write_incoming_message (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_write_incoming_message (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);
int info = -1;
if (!args[0] || !strcmp (args[0], "info"))
info = 1;
else if (!strcmp (args[0], "items"))
info = 0;
else
scr_log_print (LPRINT_NORMAL, "Unknown subcomand.");
if (info != -1) {
char *to = NULL;
char *dnode = NULL;
if (args[0] && args[1]) {
char *p = args[1];
if (*p == '.') {
if (*(p + 1) == JID_RESOURCE_SEPARATOR) {
char *rest = to_utf8 (p + 1);
to = g_strdup_printf ("%s%s", CURRENT_JID, rest);
g_free (rest);
} else if (*(p + 1))
to = to_utf8 (p);
} else
to = to_utf8 (p);
if (args[2])
dnode = to_utf8 (args[2]);
}
if (!to) {
// Use currently selected item, if possible
if (current_buddy) {
gpointer bud = BUDDATA(current_buddy);
if (bud) {
guint type = buddy_gettype (bud);
if (type != ROSTER_TYPE_GROUP && type != ROSTER_TYPE_SPECIAL)
to = g_strdup (buddy_getjid (bud)); // XXX memleak!
}
}
}
if (!to) {
scr_log_print (LPRINT_NORMAL, "Invalid disco target.");
free_arg_lst (args);
return;
}
// XXX send to all resources/current resource?
{
disco_handler_t *cb = g_slice_new (disco_handler_t);
cb -> jid = to;
cb -> node = dnode;
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);
}
}
free_arg_lst (args);
return;
}
//
// module mechanics
//
static void disco_cancel_requests (void)
{
GSList * rel = disco_requests;
while ( rel ) {
disco_request_t * cb = rel -> data;
rel = rel -> next;
if ( cb -> type == DISCO_INFO_REQUEST )
cb -> handler.info ( NULL, NULL, cb -> data );
else
cb -> handler.items ( NULL, cb -> data );
disco_request_free ( cb );
}
return;
}
static guint disco_hh (const gchar *htype, hk_arg_t *args, gpointer ignore)
{
disco_cancel_requests ();
return HOOK_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
}
void disco_init (void)
{
// completion
#ifdef MCABBER_API_HAVE_COMPL_FLAGS
disco_cid = compl_new_category (COMPL_FLAGS_SORT);
#else
disco_cid = compl_new_category ();
#endif
if (disco_cid) {
compl_add_category_word (disco_cid, "info");
compl_add_category_word (disco_cid, "items");
}
// hook handler
disco_hid = hk_add_handler (disco_hh, HOOK_PRE_DISCONNECT, G_PRIORITY_DEFAULT, NULL);
// command
#ifndef MCABBER_API_HAVE_CMD_ID
cmd_add ("disco", "", disco_cid, COMPL_JID, do_disco, NULL);
#else
disco_cmid = cmd_add ("disco", "", disco_cid, COMPL_JID, do_disco, NULL);
#endif
return;
}
void disco_uninit (void)
{
// command
#ifndef MCABBER_API_HAVE_CMD_ID
cmd_del ("disco");
#else
if (disco_cmid)
cmd_del (disco_cmid);
#endif
// completion
if (disco_cid)
compl_del_category (disco_cid);
// hook handler
hk_del_handler (HOOK_PRE_DISCONNECT, disco_hid);
// unregister handlers
disco_cancel_requests ();
return;
}
/* vim: se ts=4 sw=4: */