telepathygabble/src/disco.c
changeset 10 59927b2d3b75
parent 0 d0f3a028347a
equal deleted inserted replaced
0:d0f3a028347a 10:59927b2d3b75
     1 /*
       
     2  * gabble-media-stream-glue.h
       
     3  *
       
     4  * Copyright (C) 2006 Collabora Ltd.
       
     5  * 
       
     6  *
       
     7  * This library is free software; you can redistribute it and/or
       
     8  * modify it under the terms of the GNU Lesser General Public
       
     9  * License as published by the Free Software Foundation; either
       
    10  * version 2.1 of the License, or (at your option) any later version.
       
    11  *
       
    12  * This library is distributed in the hope that it will be useful,
       
    13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
       
    15  * Lesser General Public License for more details.
       
    16  *
       
    17  * You should have received a copy of the GNU Lesser General Public
       
    18  * License along with this library; if not, write to the Free Software
       
    19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
       
    20  *
       
    21 
       
    22  */
       
    23 
       
    24 
       
    25 
       
    26 #include <dbus/dbus-glib.h>
       
    27 #include <dbus/dbus-glib-lowlevel.h>
       
    28 #include <stdlib.h>
       
    29 #include <string.h>
       
    30 #include "loudmouth/loudmouth.h"
       
    31 
       
    32 #include "debug.h"
       
    33 #include "disco.h"
       
    34 #include "gabble-connection.h"
       
    35 #include "gabble-error.h"
       
    36 #include "namespaces.h"
       
    37 #include "telepathy-helpers.h"
       
    38 #include "util.h"
       
    39 #include "search-keys-info.h"
       
    40 
       
    41 
       
    42 #ifdef EMULATOR
       
    43 #include "libgabble_wsd_solution.h"
       
    44 
       
    45 	GET_STATIC_VAR_FROM_TLS(quark,gabble_disco,GQuark)
       
    46 	#define quark (*GET_WSD_VAR_NAME(quark,gabble_disco,s)())
       
    47 	
       
    48 	GET_STATIC_VAR_FROM_TLS(gabble_disco_parent_class,gabble_disco,gpointer)
       
    49 	#define gabble_disco_parent_class (*GET_WSD_VAR_NAME(gabble_disco_parent_class,gabble_disco,s)())
       
    50 	
       
    51 	GET_STATIC_VAR_FROM_TLS(g_define_type_id,gabble_disco,GType)
       
    52 	#define g_define_type_id (*GET_WSD_VAR_NAME(g_define_type_id,gabble_disco,s)())
       
    53 	
       
    54 	
       
    55 	
       
    56 static void gabble_disco_init (GabbleDisco *self); 
       
    57 static void gabble_disco_class_init (GabbleDiscoClass *klass); 
       
    58 static void gabble_disco_class_intern_init (gpointer klass) 
       
    59 { 
       
    60 gabble_disco_parent_class = g_type_class_peek_parent (klass); 
       
    61 gabble_disco_class_init ((GabbleDiscoClass*) klass); 
       
    62 } 
       
    63 EXPORT_C GType gabble_disco_get_type (void) 
       
    64 { 
       
    65 
       
    66 if ((g_define_type_id == 0)) 
       
    67 { 
       
    68 static const GTypeInfo g_define_type_info = 
       
    69 	{ sizeof (GabbleDiscoClass), (GBaseInitFunc) ((void *)0), (GBaseFinalizeFunc) ((void *)0), (GClassInitFunc) gabble_disco_class_intern_init, (GClassFinalizeFunc) ((void *)0), ((void *)0), sizeof (GabbleDisco), 0, (GInstanceInitFunc) gabble_disco_init, ((void *)0) }; g_define_type_id = g_type_register_static ( ((GType) ((20) << (2))), g_intern_static_string ("GabbleDisco"), &g_define_type_info, (GTypeFlags) 0); { {} ; } } return g_define_type_id; 
       
    70 	} ;
       
    71 
       
    72 
       
    73 #endif
       
    74 
       
    75 
       
    76 #define DBUS_API_SUBJECT_TO_CHANGE
       
    77 #define DEBUG_FLAG GABBLE_DEBUG_DISCO
       
    78 #define DEFAULT_REQUEST_TIMEOUT 20000
       
    79 #define DISCO_PIPELINE_SIZE 10
       
    80 
       
    81 
       
    82 /* Properties */
       
    83 enum
       
    84 {
       
    85   PROP_CONNECTION = 1,
       
    86   LAST_PROPERTY
       
    87 };
       
    88 
       
    89 #ifndef EMULATOR
       
    90 G_DEFINE_TYPE(GabbleDisco, gabble_disco, G_TYPE_OBJECT);
       
    91 #endif
       
    92 
       
    93 typedef struct _GabbleDiscoPrivate GabbleDiscoPrivate;
       
    94 struct _GabbleDiscoPrivate
       
    95 {
       
    96   GabbleConnection *connection;
       
    97   GSList *service_cache;
       
    98   GList *requests;
       
    99   gboolean dispose_has_run;
       
   100 };
       
   101 
       
   102 struct _GabbleDiscoRequest
       
   103 {
       
   104   GabbleDisco *disco;
       
   105   guint timer_id;
       
   106 
       
   107   GabbleDiscoType type;
       
   108   gchar *jid;
       
   109   gchar *node;
       
   110   GabbleDiscoCb callback;
       
   111   gpointer user_data;
       
   112   GObject *bound_object;
       
   113 };
       
   114 
       
   115 GQuark
       
   116 gabble_disco_error_quark (void)
       
   117 {
       
   118 
       
   119 #ifndef EMULATOR
       
   120   static GQuark quark = 0;
       
   121 #endif
       
   122 
       
   123   if (!quark)
       
   124     quark = g_quark_from_static_string ("gabble-disco-error");
       
   125   return quark;
       
   126 }
       
   127 
       
   128 #define GABBLE_DISCO_GET_PRIVATE(o)     ((GabbleDiscoPrivate*)((o)->priv));
       
   129 
       
   130 
       
   131 
       
   132 
       
   133 
       
   134 
       
   135 static void
       
   136 gabble_disco_init (GabbleDisco *obj)
       
   137 {
       
   138   GabbleDiscoPrivate *priv =
       
   139      G_TYPE_INSTANCE_GET_PRIVATE (obj, GABBLE_TYPE_DISCO, GabbleDiscoPrivate);
       
   140   obj->priv = priv;
       
   141 }
       
   142 
       
   143 
       
   144 
       
   145 
       
   146 static GObject *gabble_disco_constructor (GType type, guint n_props,
       
   147     GObjectConstructParam *props);
       
   148 static void gabble_disco_set_property (GObject *object, guint property_id,
       
   149     const GValue *value, GParamSpec *pspec);
       
   150 static void gabble_disco_get_property (GObject *object, guint property_id,
       
   151     GValue *value, GParamSpec *pspec);
       
   152 static void gabble_disco_dispose (GObject *object);
       
   153 static void gabble_disco_finalize (GObject *object);
       
   154 
       
   155 static void
       
   156 gabble_disco_class_init (GabbleDiscoClass *gabble_disco_class)
       
   157 {
       
   158   GObjectClass *object_class = G_OBJECT_CLASS (gabble_disco_class);
       
   159   GParamSpec *param_spec;  
       
   160   
       
   161   g_type_class_add_private (gabble_disco_class, sizeof (GabbleDiscoPrivate));
       
   162 
       
   163   object_class->constructor = gabble_disco_constructor;
       
   164 
       
   165   object_class->get_property = gabble_disco_get_property;
       
   166   object_class->set_property = gabble_disco_set_property;
       
   167 
       
   168   object_class->dispose = gabble_disco_dispose;
       
   169   object_class->finalize = gabble_disco_finalize;
       
   170 
       
   171   param_spec = g_param_spec_object ("connection", "GabbleConnection object",
       
   172                                     "Gabble connection object that owns this "
       
   173                                     "XMPP Discovery object.",
       
   174                                     GABBLE_TYPE_CONNECTION,
       
   175                                     G_PARAM_CONSTRUCT_ONLY |
       
   176                                     G_PARAM_READWRITE |
       
   177                                     G_PARAM_STATIC_NICK |
       
   178                                     G_PARAM_STATIC_BLURB);
       
   179   g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
       
   180 }
       
   181 
       
   182 static void
       
   183 gabble_disco_get_property (GObject    *object,
       
   184                                 guint       property_id,
       
   185                                 GValue     *value,
       
   186                                 GParamSpec *pspec)
       
   187 {
       
   188   GabbleDisco *chan = GABBLE_DISCO (object);
       
   189   GabbleDiscoPrivate *priv = GABBLE_DISCO_GET_PRIVATE (chan);
       
   190 
       
   191   switch (property_id) {
       
   192     case PROP_CONNECTION:
       
   193       g_value_set_object (value, priv->connection);
       
   194       break;
       
   195     default:
       
   196       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
       
   197       break;
       
   198   }
       
   199 }
       
   200 
       
   201 static void
       
   202 gabble_disco_set_property (GObject     *object,
       
   203                            guint        property_id,
       
   204                            const GValue *value,
       
   205                            GParamSpec   *pspec)
       
   206 {
       
   207   GabbleDisco *chan = GABBLE_DISCO (object);
       
   208   GabbleDiscoPrivate *priv = GABBLE_DISCO_GET_PRIVATE (chan);
       
   209 
       
   210   switch (property_id) {
       
   211     case PROP_CONNECTION:
       
   212       priv->connection = g_value_get_object (value);
       
   213       break;
       
   214     default:
       
   215       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
       
   216       break;
       
   217   }
       
   218 }
       
   219 
       
   220 static void gabble_disco_conn_status_changed_cb (GabbleConnection *conn,
       
   221     TpConnectionStatus status, TpConnectionStatusReason reason, gpointer data);
       
   222 
       
   223 static GObject *
       
   224 gabble_disco_constructor (GType type, guint n_props,
       
   225                           GObjectConstructParam *props)
       
   226 {
       
   227   GObject *obj;
       
   228   GabbleDisco *disco;
       
   229   GabbleDiscoPrivate *priv;
       
   230 
       
   231   obj = G_OBJECT_CLASS (gabble_disco_parent_class)-> constructor (type,
       
   232       n_props, props);
       
   233   disco = GABBLE_DISCO (obj);
       
   234   priv = GABBLE_DISCO_GET_PRIVATE (disco);
       
   235 
       
   236   g_signal_connect (priv->connection, "status-changed",
       
   237       G_CALLBACK (gabble_disco_conn_status_changed_cb), disco);
       
   238 
       
   239   return obj;
       
   240 }
       
   241 
       
   242 static void cancel_request (GabbleDiscoRequest *request);
       
   243 
       
   244 void
       
   245 gabble_disco_dispose (GObject *object)
       
   246 {
       
   247   GSList *l;
       
   248   DBusGProxy *bus_proxy;
       
   249 
       
   250   GabbleDisco *self = GABBLE_DISCO (object);
       
   251   GabbleDiscoPrivate *priv = GABBLE_DISCO_GET_PRIVATE (self);
       
   252   bus_proxy = tp_get_bus_proxy ();
       
   253 
       
   254   if (priv->dispose_has_run)
       
   255     return;
       
   256 
       
   257   priv->dispose_has_run = TRUE;
       
   258 
       
   259   gabble_debug (DEBUG_FLAG, "dispose called");
       
   260 
       
   261   /* cancel request removes the element from the list after cancelling */
       
   262   while (priv->requests)
       
   263     cancel_request (priv->requests->data);
       
   264 
       
   265   for (l = priv->service_cache; l; l = g_slist_next (l))
       
   266     {
       
   267       GabbleDiscoItem *item = (GabbleDiscoItem *) l->data;
       
   268       g_free ((char *) item->jid);
       
   269       g_free ((char *) item->name);
       
   270       g_free ((char *) item->type);
       
   271       g_free ((char *) item->category);
       
   272       g_hash_table_destroy (item->features);
       
   273       g_free (item);
       
   274     }
       
   275 
       
   276   g_slist_free (priv->service_cache);
       
   277   priv->service_cache = NULL;
       
   278 
       
   279   if (G_OBJECT_CLASS (gabble_disco_parent_class)->dispose)
       
   280     G_OBJECT_CLASS (gabble_disco_parent_class)->dispose (object);
       
   281 }
       
   282 
       
   283 void
       
   284 gabble_disco_finalize (GObject *object)
       
   285 {
       
   286   gabble_debug (DEBUG_FLAG, "called with %p", object);
       
   287 
       
   288   G_OBJECT_CLASS (gabble_disco_parent_class)->finalize (object);
       
   289 }
       
   290 
       
   291 /**
       
   292  * gabble_disco_new:
       
   293  * @conn: The #GabbleConnection to use for service discovery
       
   294  *
       
   295  * Creates an object to use for Jabber service discovery (DISCO)
       
   296  * There should be one of these per connection
       
   297  */
       
   298 GabbleDisco *
       
   299 gabble_disco_new (GabbleConnection *conn)
       
   300 {
       
   301   GabbleDisco *disco;
       
   302 
       
   303   g_return_val_if_fail (GABBLE_IS_CONNECTION (conn), NULL);
       
   304 
       
   305   disco = GABBLE_DISCO (g_object_new (GABBLE_TYPE_DISCO,
       
   306         "connection", conn,
       
   307         NULL));
       
   308 
       
   309   return disco;
       
   310 }
       
   311 
       
   312 
       
   313 static void notify_delete_request (gpointer data, GObject *obj);
       
   314 
       
   315 static void
       
   316 delete_request (GabbleDiscoRequest *request)
       
   317 {
       
   318   GabbleDisco *disco = request->disco;
       
   319   GabbleDiscoPrivate *priv;
       
   320 
       
   321   g_assert (NULL != request);
       
   322     /* fix: Application Crashed: Main */
       
   323   if ( disco == NULL )
       
   324       {
       
   325       g_debug ("%s: accesing after dereferenced connection", G_STRFUNC);
       
   326       return;
       
   327       }
       
   328   g_assert (GABBLE_IS_DISCO (disco));
       
   329 
       
   330   priv = GABBLE_DISCO_GET_PRIVATE (disco);
       
   331 
       
   332   g_assert (NULL != g_list_find (priv->requests, request));
       
   333 
       
   334   priv->requests = g_list_remove (priv->requests, request);
       
   335 
       
   336   if (NULL != request->bound_object)
       
   337     {
       
   338       g_object_weak_unref (request->bound_object, notify_delete_request, request);
       
   339     }
       
   340 
       
   341   if (0 != request->timer_id)
       
   342     {
       
   343       g_source_remove (request->timer_id);
       
   344     }
       
   345 
       
   346   g_free (request->jid);
       
   347   g_free (request);
       
   348 }
       
   349 
       
   350 static gboolean
       
   351 timeout_request (gpointer data)
       
   352 {
       
   353   GabbleDiscoRequest *request = (GabbleDiscoRequest*) data;
       
   354   GError *err;
       
   355   g_return_val_if_fail (data != NULL, FALSE);
       
   356 
       
   357   err = g_error_new (GABBLE_DISCO_ERROR, GABBLE_DISCO_ERROR_TIMEOUT,
       
   358       "Request for %s on %s timed out",
       
   359       (request->type == GABBLE_DISCO_TYPE_INFO)?"info":"items",
       
   360       request->jid);
       
   361   (request->callback)(request->disco, request, request->jid, request->node,
       
   362                       NULL, err, request->user_data);
       
   363   g_error_free (err);
       
   364 
       
   365   request->timer_id = 0;
       
   366   delete_request (request);
       
   367   return FALSE;
       
   368 }
       
   369 
       
   370 static void
       
   371 cancel_request (GabbleDiscoRequest *request)
       
   372 {
       
   373   GError *err;
       
   374 
       
   375   g_assert (request != NULL);
       
   376 
       
   377   err = g_error_new (GABBLE_DISCO_ERROR, GABBLE_DISCO_ERROR_CANCELLED,
       
   378       "Request for %s on %s cancelled",
       
   379       (request->type == GABBLE_DISCO_TYPE_INFO)?"info":"items",
       
   380       request->jid);
       
   381   (request->callback)(request->disco, request, request->jid, request->node,
       
   382                       NULL, err, request->user_data);
       
   383   g_error_free (err);
       
   384 
       
   385   delete_request (request);
       
   386 }
       
   387 
       
   388 static LmHandlerResult
       
   389 request_reply_cb (GabbleConnection *conn, LmMessage *sent_msg,
       
   390                   LmMessage *reply_msg, GObject *object, gpointer user_data)
       
   391 {
       
   392   LmMessageNode *query_node;
       
   393   GError *err = NULL;
       
   394 
       
   395   GabbleDiscoRequest *request = (GabbleDiscoRequest*) user_data;
       
   396   GabbleDisco *disco = GABBLE_DISCO (object);
       
   397   GabbleDiscoPrivate *priv = GABBLE_DISCO_GET_PRIVATE (disco);
       
   398 
       
   399   g_assert (request);
       
   400 
       
   401   if (!g_list_find (priv->requests, request))
       
   402     return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS;
       
   403 
       
   404   query_node = lm_message_node_get_child (reply_msg->node, "query");
       
   405 
       
   406   if (lm_message_get_sub_type (reply_msg) == LM_MESSAGE_SUB_TYPE_ERROR)
       
   407     {
       
   408       LmMessageNode *error_node;
       
   409 
       
   410       error_node = lm_message_node_get_child (reply_msg->node, "error");
       
   411       if (error_node)
       
   412         {
       
   413           err = gabble_xmpp_error_to_g_error (
       
   414               gabble_xmpp_error_from_node (error_node));
       
   415         }
       
   416 
       
   417       if (err == NULL)
       
   418         {
       
   419           err = g_error_new (GABBLE_DISCO_ERROR,
       
   420                              GABBLE_DISCO_ERROR_UNKNOWN,
       
   421                              "an unknown error occurred");
       
   422         }
       
   423     }
       
   424   else if (NULL == query_node)
       
   425     {
       
   426       err = g_error_new (GABBLE_DISCO_ERROR, GABBLE_DISCO_ERROR_UNKNOWN,
       
   427           "disco response contained no <query> node");
       
   428     }
       
   429 
       
   430   request->callback (request->disco, request, request->jid, request->node,
       
   431                      query_node, err, request->user_data);
       
   432   delete_request (request);
       
   433 
       
   434   if (err)
       
   435     g_error_free (err);
       
   436 
       
   437   return LM_HANDLER_RESULT_REMOVE_MESSAGE;
       
   438 }
       
   439 
       
   440 static void
       
   441 notify_delete_request (gpointer data, GObject *obj)
       
   442 {
       
   443   GabbleDiscoRequest *request = (GabbleDiscoRequest *) data;
       
   444   request->bound_object = NULL;
       
   445   delete_request (request);
       
   446 }
       
   447 
       
   448 /**
       
   449  * gabble_disco_request:
       
   450  * @self: #GabbleDisco object to use for request
       
   451  * @type: type of request
       
   452  * @jid: Jabber ID to request on
       
   453  * @node: node to request on @jid, or NULL
       
   454  * @callback: #GabbleDiscoCb to call on request fullfilment
       
   455  * @object: GObject to bind request to. the callback will not be
       
   456  *          called if this object has been unrefed. NULL if not needed
       
   457  * @error: #GError to return a telepathy error in if unable to make
       
   458  *         request, NULL if unneeded.
       
   459  *
       
   460  * Make a disco request on the given jid with the default timeout.
       
   461  */
       
   462 GabbleDiscoRequest *
       
   463 gabble_disco_request (GabbleDisco *self, GabbleDiscoType type,
       
   464                       const gchar *jid, const char *node,
       
   465                       GabbleDiscoCb callback, gpointer user_data,
       
   466                       GObject *object, GError **error)
       
   467 {
       
   468   return gabble_disco_request_with_timeout (self, type, jid, node,
       
   469                                             DEFAULT_REQUEST_TIMEOUT,
       
   470                                             callback, user_data,
       
   471                                             object, error);
       
   472 }
       
   473 
       
   474 /**
       
   475  * gabble_disco_request_with_timeout:
       
   476  * @self: #GabbleDisco object to use for request
       
   477  * @type: type of request
       
   478  * @jid: Jabber ID to request on
       
   479  * @node: node to request on @jid, or NULL
       
   480  * @timeout: the time until the request fails, in milliseconds (1/1000ths of a second)
       
   481  * @callback: #GabbleDiscoCb to call on request fullfilment
       
   482  * @object: GObject to bind request to. the callback will not be
       
   483  *          called if this object has been unrefed. NULL if not needed
       
   484  * @error: #GError to return a telepathy error in if unable to make
       
   485  *         request, NULL if unneeded.
       
   486  *
       
   487  * Make a disco request on the given jid, which will fail unless a reply
       
   488  * is received within the given timeout interval.
       
   489  */
       
   490 GabbleDiscoRequest *
       
   491 gabble_disco_request_with_timeout (GabbleDisco *self, GabbleDiscoType type,
       
   492                                    const gchar *jid, const char *node,
       
   493                                    guint timeout, GabbleDiscoCb callback,
       
   494                                    gpointer user_data, GObject *object,
       
   495                                    GError **error)
       
   496 {
       
   497   const gchar *xmlns;
       
   498 
       
   499   LmMessage *msg;
       
   500   LmMessageNode *lm_node;
       
   501   GabbleDiscoRequest *request;
       
   502   GabbleDiscoPrivate *priv = GABBLE_DISCO_GET_PRIVATE (self);
       
   503 
       
   504 
       
   505   request = g_new0 (GabbleDiscoRequest, 1);
       
   506   request->disco = self;
       
   507   request->type = type;
       
   508   request->jid = g_strdup (jid);
       
   509   if (node)
       
   510     request->node = g_strdup (node);
       
   511   request->callback = callback;
       
   512   request->user_data = user_data;
       
   513   request->bound_object = object;
       
   514 
       
   515   if (NULL != object)
       
   516     g_object_weak_ref (object, notify_delete_request, request);
       
   517 
       
   518   gabble_debug (DEBUG_FLAG, "Creating disco request %p for %s",
       
   519            request, request->jid);
       
   520 
       
   521   priv->requests = g_list_prepend (priv->requests, request);
       
   522   msg = lm_message_new_with_sub_type (jid, LM_MESSAGE_TYPE_IQ,
       
   523                                            LM_MESSAGE_SUB_TYPE_GET);
       
   524   lm_node = lm_message_node_add_child (msg->node, "query", NULL);
       
   525 
       
   526   switch (type) {
       
   527     case GABBLE_DISCO_TYPE_INFO:
       
   528       xmlns = NS_DISCO_INFO;
       
   529       break;
       
   530     case GABBLE_DISCO_TYPE_ITEMS:
       
   531       xmlns = NS_DISCO_ITEMS;
       
   532       break;
       
   533     default:
       
   534       g_assert_not_reached ();
       
   535       return NULL;
       
   536   }
       
   537 
       
   538   lm_message_node_set_attribute (lm_node, "xmlns", xmlns);
       
   539 
       
   540   if (node)
       
   541     {
       
   542       lm_message_node_set_attribute (lm_node, "node", node);
       
   543     }
       
   544 
       
   545   if (! _gabble_connection_send_with_reply (priv->connection, msg,
       
   546         request_reply_cb, G_OBJECT(self), request, error))
       
   547     {
       
   548       delete_request (request);
       
   549       lm_message_unref (msg);
       
   550       return NULL;
       
   551     }
       
   552   else
       
   553     {
       
   554       request->timer_id =
       
   555           g_timeout_add (timeout, timeout_request, request);
       
   556       lm_message_unref (msg);
       
   557       return request;
       
   558     }
       
   559 }
       
   560 
       
   561 void
       
   562 gabble_disco_cancel_request (GabbleDisco *disco, GabbleDiscoRequest *request)
       
   563 {
       
   564   GabbleDiscoPrivate *priv;
       
   565 
       
   566   g_return_if_fail (GABBLE_IS_DISCO (disco));
       
   567   g_return_if_fail (NULL != request);
       
   568 
       
   569   priv = GABBLE_DISCO_GET_PRIVATE (disco);
       
   570 
       
   571   g_return_if_fail (NULL != g_list_find (priv->requests, request));
       
   572 
       
   573   cancel_request (request);
       
   574 }
       
   575 
       
   576 /* Disco pipeline */
       
   577 
       
   578 
       
   579 typedef struct _GabbleDiscoPipeline GabbleDiscoPipeline;
       
   580 struct _GabbleDiscoPipeline {
       
   581     GabbleDisco *disco;
       
   582     gpointer user_data;
       
   583     GabbleDiscoPipelineCb callback;
       
   584     GabbleDiscoEndCb end_callback;
       
   585     GPtrArray *disco_pipeline;
       
   586     GHashTable *remaining_items;
       
   587     GabbleDiscoRequest *list_request;
       
   588     gboolean running;
       
   589 };
       
   590 
       
   591 static void
       
   592 gabble_disco_fill_pipeline (GabbleDisco *disco, GabbleDiscoPipeline *pipeline);
       
   593 
       
   594 static void
       
   595 item_info_cb (GabbleDisco *disco,
       
   596               GabbleDiscoRequest *request,
       
   597               const gchar *jid,
       
   598               const gchar *node,
       
   599               LmMessageNode *result,
       
   600               GError *error,
       
   601               gpointer user_data)
       
   602 {
       
   603   LmMessageNode *identity, *feature, *field, *value_node;
       
   604   const char *category, *type, *var, *name, *value;
       
   605   GHashTable *keys;
       
   606   GabbleDiscoItem item;
       
   607 
       
   608   GabbleDiscoPipeline *pipeline = (GabbleDiscoPipeline *) user_data;
       
   609   GabbleDiscoPrivate *priv = GABBLE_DISCO_GET_PRIVATE (disco);
       
   610 
       
   611   g_ptr_array_remove_fast (pipeline->disco_pipeline, request);
       
   612 
       
   613   if (error)
       
   614     {
       
   615       gabble_debug (DEBUG_FLAG, "got error %s", error->message);
       
   616       goto done;
       
   617     }
       
   618 
       
   619   identity = lm_message_node_get_child (result, "identity");
       
   620   if (NULL == identity)
       
   621     goto done;
       
   622 
       
   623   name = lm_message_node_get_attribute (identity, "name");
       
   624   if (NULL == name)
       
   625     goto done;
       
   626 
       
   627   category = lm_message_node_get_attribute (identity, "category");
       
   628   if (NULL == category)
       
   629     goto done;
       
   630 
       
   631   type = lm_message_node_get_attribute (identity, "type");
       
   632   if (NULL == type)
       
   633     goto done;
       
   634 
       
   635   gabble_debug (DEBUG_FLAG, "got item identity, jid=%s, name=%s, category=%s, type=%s",
       
   636       jid, name, category, type);
       
   637 
       
   638   keys = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
       
   639 
       
   640   for (feature = result->children; feature; feature = feature->next)
       
   641     {
       
   642       if (0 == strcmp (feature->name, "feature"))
       
   643         {
       
   644           var = lm_message_node_get_attribute (feature, "var");
       
   645           if (0 == strcmp (var, NS_SEARCH))
       
   646 	          {
       
   647 	          priv->connection->features |= GABBLE_CONNECTION_FEATURES_SEARCH;
       
   648 	          get_search_keys_info(priv->connection,jid);	
       
   649 	          }
       
   650           if (var)
       
   651             g_hash_table_insert (keys, g_strdup (var), NULL);
       
   652         }
       
   653       else if (0 == strcmp (feature->name, "x"))
       
   654         {
       
   655           if (lm_message_node_has_namespace (feature, NS_X_DATA, NULL))
       
   656             {
       
   657               for (field = feature->children;
       
   658                    field; field = field->next)
       
   659                 {
       
   660                   if (0 != strcmp (field->name, "field"))
       
   661                     continue;
       
   662 
       
   663                   var = lm_message_node_get_attribute (field, "var");
       
   664                   if (NULL == var)
       
   665                     continue;
       
   666 
       
   667                   value_node = lm_message_node_get_child (field, "value");
       
   668                   if (NULL == value_node)
       
   669                     continue;
       
   670 
       
   671                   value = lm_message_node_get_value (value_node);
       
   672                   if (NULL == value)
       
   673                     continue;
       
   674 
       
   675                   g_hash_table_insert (keys, g_strdup (var), g_strdup (value));
       
   676                 }
       
   677             }
       
   678         }
       
   679     }
       
   680 
       
   681   item.jid = jid;
       
   682   item.name = name;
       
   683   item.category = category;
       
   684   item.type = type;
       
   685   item.features = keys;
       
   686 
       
   687   pipeline->callback (pipeline, &item, pipeline->user_data);
       
   688   g_hash_table_destroy (keys);
       
   689 
       
   690 done:
       
   691   gabble_disco_fill_pipeline (disco, pipeline);
       
   692 
       
   693   return;
       
   694 }
       
   695 
       
   696 
       
   697 static gboolean
       
   698 return_true (gpointer key, gpointer value, gpointer data)
       
   699 {
       
   700   return TRUE;
       
   701 }
       
   702 
       
   703 static void
       
   704 gabble_disco_fill_pipeline (GabbleDisco *disco, GabbleDiscoPipeline *pipeline)
       
   705 {
       
   706   if (!pipeline->running)
       
   707     {
       
   708       gabble_debug (DEBUG_FLAG, "pipeline not running, not refilling");
       
   709     }
       
   710   else
       
   711     {
       
   712       /* send disco requests for the JIDs in the remaining_items hash table
       
   713        * until there are DISCO_PIPELINE_SIZE requests in progress */
       
   714       while (pipeline->disco_pipeline->len < DISCO_PIPELINE_SIZE)
       
   715         {
       
   716           gchar *jid;
       
   717           GabbleDiscoRequest *request;
       
   718 
       
   719           jid = (gchar *) g_hash_table_find (pipeline->remaining_items,
       
   720               return_true, NULL);
       
   721           if (NULL == jid)
       
   722             break;
       
   723 
       
   724           request = gabble_disco_request (disco,
       
   725               GABBLE_DISCO_TYPE_INFO, jid, NULL, item_info_cb, pipeline,
       
   726               G_OBJECT(disco), NULL);
       
   727 
       
   728           g_ptr_array_add (pipeline->disco_pipeline, request);
       
   729 
       
   730           /* frees jid */
       
   731           g_hash_table_remove (pipeline->remaining_items, jid);
       
   732         }
       
   733 
       
   734       if (0 == pipeline->disco_pipeline->len)
       
   735         {
       
   736           /* signal that the pipeline has finished */
       
   737           pipeline->running = FALSE;
       
   738           pipeline->end_callback (pipeline, pipeline->user_data);
       
   739         }
       
   740     }
       
   741 }
       
   742 
       
   743 
       
   744 static void
       
   745 disco_items_cb (GabbleDisco *disco,
       
   746           GabbleDiscoRequest *request,
       
   747           const gchar *jid,
       
   748           const gchar *node,
       
   749           LmMessageNode *result,
       
   750           GError *error,
       
   751           gpointer user_data)
       
   752 {
       
   753   LmMessageNode *iter;
       
   754   const char *item_jid;
       
   755   gpointer key, value;
       
   756   GabbleDiscoPipeline *pipeline = (GabbleDiscoPipeline *) user_data;
       
   757 
       
   758   pipeline->list_request = NULL;
       
   759 
       
   760   if (error)
       
   761     {
       
   762       gabble_debug (DEBUG_FLAG, "Got error on items request: %s", error->message);
       
   763       goto out;
       
   764     }
       
   765 
       
   766   iter = result->children;
       
   767 
       
   768   for (; iter; iter = iter->next)
       
   769     {
       
   770       if (0 != strcmp (iter->name, "item"))
       
   771         continue;
       
   772 
       
   773       item_jid = lm_message_node_get_attribute (iter, "jid");
       
   774 
       
   775       if (NULL != item_jid &&
       
   776           !g_hash_table_lookup_extended (pipeline->remaining_items, item_jid, &key, &value))
       
   777         {
       
   778           gchar *tmp = g_strdup (item_jid);
       
   779           gabble_debug (DEBUG_FLAG, "discovered service item: %s", tmp);
       
   780           g_hash_table_insert (pipeline->remaining_items, tmp, tmp);
       
   781         }
       
   782     }
       
   783 
       
   784 out:
       
   785   gabble_disco_fill_pipeline (disco, pipeline);
       
   786 }
       
   787 
       
   788 /**
       
   789  * gabble_disco_pipeline_init:
       
   790  * @disco: disco object to use in the pipeline
       
   791  * @callback: GFunc to call on request fullfilment
       
   792  * @user_data: the usual
       
   793  *
       
   794  * Prepares the pipeline for making the ITEM request on the server and
       
   795  * subsequent INFO elements on returned items.
       
   796  *
       
   797  * GabbleDiscoPipeline is opaque structure for the user.
       
   798  */
       
   799 gpointer gabble_disco_pipeline_init (GabbleDisco *disco,
       
   800                                      GabbleDiscoPipelineCb callback,
       
   801                                      GabbleDiscoEndCb end_callback,
       
   802                                      gpointer user_data)
       
   803 {
       
   804   GabbleDiscoPipeline *pipeline = g_new (GabbleDiscoPipeline, 1);
       
   805   pipeline->user_data = user_data;
       
   806   pipeline->callback = callback;
       
   807   pipeline->end_callback = end_callback;
       
   808   pipeline->disco_pipeline = g_ptr_array_sized_new (DISCO_PIPELINE_SIZE);
       
   809   pipeline->remaining_items = g_hash_table_new_full (g_str_hash, g_str_equal,
       
   810       g_free, NULL);
       
   811   pipeline->running = TRUE;
       
   812   pipeline->disco = disco;
       
   813 
       
   814   return pipeline;
       
   815 }
       
   816 
       
   817 /**
       
   818  * gabble_disco_pipeline_run:
       
   819  * @self: reference to the pipeline structure
       
   820  * @server: server to query
       
   821  *
       
   822  * Makes ITEMS request on the server, and afterwards queries for INFO
       
   823  * on each item. INFO queries are pipelined. The item properties are stored
       
   824  * in hash table parameter to the callback function. The user is responsible
       
   825  * for destroying the hash table after it's done with.
       
   826  *
       
   827  * Upon returning all the results, the end_callback is called with
       
   828  * reference to the pipeline.
       
   829  */
       
   830 void
       
   831 gabble_disco_pipeline_run (gpointer self, const char *server)
       
   832 {
       
   833   GabbleDiscoPipeline *pipeline = (GabbleDiscoPipeline *) self;
       
   834 
       
   835   pipeline->running = TRUE;
       
   836 
       
   837   pipeline->list_request = gabble_disco_request (pipeline->disco,
       
   838       GABBLE_DISCO_TYPE_ITEMS, server, NULL, disco_items_cb, pipeline,
       
   839       G_OBJECT (pipeline->disco), NULL);
       
   840 }
       
   841 
       
   842 
       
   843 /**
       
   844  * gabble_disco_pipeline_cancel:
       
   845  * @pipeline: pipeline to cancel
       
   846  *
       
   847  * Flushes the pipeline (cancels all pending disco requests) and
       
   848  * destroys it.
       
   849  */
       
   850 void
       
   851 gabble_disco_pipeline_destroy (gpointer self)
       
   852 {
       
   853   GabbleDiscoPipeline *pipeline = (GabbleDiscoPipeline *) self;
       
   854 
       
   855   pipeline->running = FALSE;
       
   856 
       
   857   if (pipeline->list_request != NULL)
       
   858     {
       
   859       gabble_disco_cancel_request (pipeline->disco, pipeline->list_request);
       
   860       pipeline->list_request = NULL;
       
   861     }
       
   862 
       
   863   /* iterate using a while loop otherwise we're modifying
       
   864    * the array as we iterate it, and miss things! */
       
   865   while (pipeline->disco_pipeline->len > 0)
       
   866     {
       
   867       GabbleDiscoRequest *request =
       
   868         g_ptr_array_index (pipeline->disco_pipeline, 0);
       
   869       gabble_disco_cancel_request (pipeline->disco, request);
       
   870     }
       
   871 
       
   872   g_hash_table_destroy (pipeline->remaining_items);
       
   873   g_ptr_array_free (pipeline->disco_pipeline, TRUE);
       
   874   g_free (pipeline);
       
   875 }
       
   876 
       
   877 
       
   878 static void
       
   879 service_feature_copy_one (gpointer k, gpointer v, gpointer user_data)
       
   880 {
       
   881   char *key = (char *) k;
       
   882   char *value = (char *) v;
       
   883 
       
   884   GHashTable *target = (GHashTable *) user_data;
       
   885   g_hash_table_insert (target, g_strdup (key), g_strdup (value));
       
   886 }
       
   887 
       
   888 /* Service discovery */
       
   889 static void
       
   890 services_cb (gpointer pipeline, GabbleDiscoItem *item, gpointer user_data)
       
   891 {
       
   892   GabbleDiscoItem *my_item = g_new0 (GabbleDiscoItem, 1);
       
   893 
       
   894   GabbleDisco *disco = GABBLE_DISCO (user_data);
       
   895   GabbleDiscoPrivate *priv = GABBLE_DISCO_GET_PRIVATE (disco);
       
   896 
       
   897   my_item->jid = g_strdup (item->jid);
       
   898   my_item->name = g_strdup (item->name);
       
   899   my_item->type = g_strdup (item->type);
       
   900   my_item->category = g_strdup (item->category);
       
   901 
       
   902   my_item->features = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
       
   903   g_hash_table_foreach  (item->features, service_feature_copy_one, my_item->features);
       
   904 
       
   905   priv->service_cache = g_slist_prepend (priv->service_cache, my_item);
       
   906 }
       
   907 
       
   908 static void
       
   909 end_cb (gpointer pipeline, gpointer user_data)
       
   910 {
       
   911   GabbleDisco *disco = GABBLE_DISCO (user_data);
       
   912   GabbleDiscoPrivate *priv = GABBLE_DISCO_GET_PRIVATE (disco);
       
   913 
       
   914   gabble_disco_pipeline_destroy (pipeline);
       
   915   priv->service_cache = g_slist_reverse (priv->service_cache);
       
   916 
       
   917   /* FIXME - service discovery done - signal that somehow */
       
   918 }
       
   919 
       
   920 static void
       
   921 gabble_disco_conn_status_changed_cb (GabbleConnection *conn,
       
   922                                      TpConnectionStatus status,
       
   923                                      TpConnectionStatusReason reason,
       
   924                                      gpointer data)
       
   925 {
       
   926   GabbleDisco *disco = GABBLE_DISCO (data);
       
   927   GabbleDiscoPrivate *priv = GABBLE_DISCO_GET_PRIVATE (disco);
       
   928 
       
   929   if (status == TP_CONN_STATUS_CONNECTED)
       
   930     {
       
   931       char *server;
       
   932       gpointer pipeline = gabble_disco_pipeline_init (disco, services_cb,
       
   933           end_cb, disco);
       
   934 
       
   935       g_object_get (priv->connection, "stream-server", &server, NULL);
       
   936 
       
   937       g_assert (server != NULL);
       
   938 
       
   939       gabble_debug (DEBUG_FLAG, "connected, initiating service discovery on %s", server);
       
   940       gabble_disco_pipeline_run (pipeline, server);
       
   941 
       
   942       g_free (server);
       
   943     }
       
   944 }
       
   945 
       
   946 const GabbleDiscoItem *
       
   947 gabble_disco_service_find (GabbleDisco *disco,
       
   948                            const char *type,
       
   949                            const char *category,
       
   950                            const char *feature)
       
   951 {
       
   952   GabbleDiscoPrivate *priv;
       
   953   GSList *l;
       
   954 
       
   955   g_assert (GABBLE_IS_DISCO (disco));
       
   956   priv = GABBLE_DISCO_GET_PRIVATE (disco);
       
   957 
       
   958   for (l = priv->service_cache; l; l = g_slist_next (l))
       
   959     {
       
   960       GabbleDiscoItem *item = (GabbleDiscoItem *) l->data;
       
   961       gboolean selected = TRUE;
       
   962 
       
   963       if (type != NULL && g_strdiff (type, item->type))
       
   964         selected = FALSE;
       
   965 
       
   966       if (category != NULL && g_strdiff (category, item->category))
       
   967         selected = FALSE;
       
   968 
       
   969       if (feature != NULL)
       
   970         {
       
   971           gpointer k, v;
       
   972           if (!g_hash_table_lookup_extended (item->features, feature, &k, &v))
       
   973             selected = FALSE;
       
   974         }
       
   975 
       
   976       if (selected)
       
   977         return item;
       
   978     }
       
   979 
       
   980   return NULL;
       
   981 }