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