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