8e780421a816266ca0f3af54e64e18c6cfd38e33
[profile/ivi/GUPnP.git] / libgupnp / gupnp-control-point.c
1 /*
2  * Copyright (C) 2006, 2007, 2008 OpenedHand Ltd.
3  *
4  * Author: Jorn Baayen <jorn@openedhand.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21
22 /**
23  * SECTION:gupnp-control-point
24  * @short_description: Class for resource discovery.
25  *
26  * #GUPnPControlPoint handles device and service discovery. After creating
27  * a control point and activating it using gssdp_resource_browser_set_active(),
28  * the ::device-proxy-available, ::service-proxy-available,
29  * ::device-proxy-unavailable and ::service-proxy-unavailable signals will
30  * be emitted whenever the availability of a device or service matching
31  * the specified discovery target changes.
32  */
33
34 #include <string.h>
35
36 #include "gupnp-control-point.h"
37 #include "gupnp-context-private.h"
38 #include "gupnp-resource-factory-private.h"
39 #include "http-headers.h"
40 #include "xml-util.h"
41
42 G_DEFINE_TYPE (GUPnPControlPoint,
43                gupnp_control_point,
44                GSSDP_TYPE_RESOURCE_BROWSER);
45
46 struct _GUPnPControlPointPrivate {
47         GUPnPResourceFactory *factory;
48
49         GList *devices;
50         GList *services;
51
52         GHashTable *doc_cache;
53
54         GList *pending_gets;
55 };
56
57 enum {
58         PROP_0,
59         PROP_RESOURCE_FACTORY,
60 };
61
62 enum {
63         DEVICE_PROXY_AVAILABLE,
64         DEVICE_PROXY_UNAVAILABLE,
65         SERVICE_PROXY_AVAILABLE,
66         SERVICE_PROXY_UNAVAILABLE,
67         SIGNAL_LAST
68 };
69
70 static guint signals[SIGNAL_LAST];
71
72 typedef struct {
73         GUPnPControlPoint *control_point;
74
75         char *udn;
76         char *service_type;
77         char *description_url;
78
79         SoupMessage *message;
80 } GetDescriptionURLData;
81
82 static void
83 get_description_url_data_free (GetDescriptionURLData *data)
84 {
85         data->control_point->priv->pending_gets =
86                 g_list_remove (data->control_point->priv->pending_gets, data);
87
88         g_free (data->udn);
89         g_free (data->service_type);
90         g_free (data->description_url);
91
92         g_slice_free (GetDescriptionURLData, data);
93 }
94
95 static void
96 gupnp_control_point_init (GUPnPControlPoint *control_point)
97 {
98         control_point->priv =
99                 G_TYPE_INSTANCE_GET_PRIVATE (control_point,
100                                              GUPNP_TYPE_CONTROL_POINT,
101                                              GUPnPControlPointPrivate);
102
103         control_point->priv->doc_cache =
104                 g_hash_table_new_full (g_str_hash,
105                                        g_str_equal,
106                                        g_free,
107                                        NULL);
108 }
109
110 /* Return TRUE if value == user_data */
111 static gboolean
112 find_doc (gpointer key,
113           gpointer value,
114           gpointer user_data)
115 {
116         return (value == user_data);
117 }
118
119 /* xmlDoc wrapper finalized */
120 static void
121 doc_finalized (gpointer user_data,
122                GObject *where_the_object_was)
123 {
124         GUPnPControlPoint *control_point;
125
126         control_point = GUPNP_CONTROL_POINT (user_data);
127
128         g_hash_table_foreach_remove (control_point->priv->doc_cache,
129                                      find_doc,
130                                      where_the_object_was);
131 }
132
133 /* Release weak reference on xmlDoc wrapper */
134 static void
135 weak_unref_doc (gpointer key,
136                 gpointer value,
137                 gpointer user_data)
138 {
139         g_object_weak_unref (G_OBJECT (value), doc_finalized, user_data);
140 }
141
142 static void
143 gupnp_control_point_dispose (GObject *object)
144 {
145         GUPnPControlPoint *control_point;
146         GSSDPResourceBrowser *browser;
147         GObjectClass *object_class;
148
149         control_point = GUPNP_CONTROL_POINT (object);
150         browser = GSSDP_RESOURCE_BROWSER (control_point);
151
152         gssdp_resource_browser_set_active (browser, FALSE);
153
154         if (control_point->priv->factory) {
155                 g_object_unref (control_point->priv->factory);
156                 control_point->priv->factory = NULL;
157         }
158
159         /* Cancel any pending description file GETs */
160         while (control_point->priv->pending_gets) {
161                 GetDescriptionURLData *data;
162                 GUPnPContext *context;
163                 SoupSession *session;
164
165                 data = control_point->priv->pending_gets->data;
166
167                 context = gupnp_control_point_get_context (control_point);
168                 session = gupnp_context_get_session (context);
169
170                 soup_session_cancel_message (session,
171                                              data->message,
172                                              SOUP_STATUS_CANCELLED);
173
174                 get_description_url_data_free (data);
175         }
176
177         /* Release weak references on remaining cached documents */
178         g_hash_table_foreach (control_point->priv->doc_cache,
179                               weak_unref_doc,
180                               control_point);
181
182         /* Call super */
183         object_class = G_OBJECT_CLASS (gupnp_control_point_parent_class);
184         object_class->dispose (object);
185 }
186
187 static void
188 gupnp_control_point_finalize (GObject *object)
189 {
190         GUPnPControlPoint *control_point;
191         GObjectClass *object_class;
192
193         control_point = GUPNP_CONTROL_POINT (object);
194
195         g_hash_table_destroy (control_point->priv->doc_cache);
196
197         /* Call super */
198         object_class = G_OBJECT_CLASS (gupnp_control_point_parent_class);
199         object_class->finalize (object);
200 }
201
202 static GList *
203 find_service_node (GUPnPControlPoint *control_point,
204                    const char        *udn,
205                    const char        *service_type)
206 {
207         GList *l;
208
209         l = control_point->priv->services;
210
211         while (l) {
212                 GUPnPServiceInfo *info;
213
214                 info = GUPNP_SERVICE_INFO (l->data);
215
216                 if ((strcmp (gupnp_service_info_get_udn (info), udn) == 0) &&
217                     (strcmp (gupnp_service_info_get_service_type (info),
218                              service_type) == 0))
219                         break;
220
221                 l = l->next;
222         }
223
224         return l;
225 }
226
227 static GList *
228 find_device_node (GUPnPControlPoint *control_point,
229                   const char        *udn)
230 {
231         GList *l;
232
233         l = control_point->priv->devices;
234
235         while (l) {
236                 GUPnPDeviceInfo *info;
237
238                 info = GUPNP_DEVICE_INFO (l->data);
239
240                 if (strcmp (udn, gupnp_device_info_get_udn (info)) == 0)
241                         break;
242
243                 l = l->next;
244         }
245
246         return l;
247 }
248
249 static void
250 create_and_report_service_proxy (GUPnPControlPoint  *control_point,
251                                  GUPnPXMLDoc        *doc,
252                                  xmlNode            *element,
253                                  const char         *udn,
254                                  const char         *service_type,
255                                  const char         *description_url,
256                                  SoupURI            *url_base)
257 {
258         GUPnPServiceProxy *proxy;
259         GUPnPResourceFactory *factory;
260         GUPnPContext *context;
261
262         if (find_service_node (control_point, udn, service_type) != NULL)
263                 /* We already have a proxy for this service */
264                 return;
265
266         factory = gupnp_control_point_get_resource_factory (control_point);
267         context = gupnp_control_point_get_context (control_point);
268
269         /* Create proxy */
270         proxy = gupnp_resource_factory_create_service_proxy (factory,
271                                                              context,
272                                                              doc,
273                                                              element,
274                                                              udn,
275                                                              service_type,
276                                                              description_url,
277                                                              url_base);
278
279         control_point->priv->services =
280                                 g_list_prepend (control_point->priv->services,
281                                                 proxy);
282
283         g_signal_emit (control_point,
284                        signals[SERVICE_PROXY_AVAILABLE],
285                        0,
286                        proxy);
287 }
288
289 static void
290 create_and_report_device_proxy (GUPnPControlPoint  *control_point,
291                                 GUPnPXMLDoc        *doc,
292                                 xmlNode            *element,
293                                 const char         *udn,
294                                 const char         *description_url,
295                                 SoupURI            *url_base)
296 {
297         GUPnPDeviceProxy *proxy;
298         GUPnPResourceFactory *factory;
299         GUPnPContext *context;
300
301         if (find_device_node (control_point, udn) != NULL)
302                 /* We already have a proxy for this device */
303                 return;
304
305         factory = gupnp_control_point_get_resource_factory (control_point);
306         context = gupnp_control_point_get_context (control_point);
307
308         proxy = gupnp_resource_factory_create_device_proxy (factory,
309                                                             context,
310                                                             doc,
311                                                             element,
312                                                             udn,
313                                                             description_url,
314                                                             url_base);
315
316         control_point->priv->devices = g_list_prepend
317                                                 (control_point->priv->devices,
318                                                  proxy);
319
320         g_signal_emit (control_point,
321                        signals[DEVICE_PROXY_AVAILABLE],
322                        0,
323                        proxy);
324 }
325
326 /* Search @element for matching services */
327 static void
328 process_service_list (xmlNode           *element,
329                       GUPnPControlPoint *control_point,
330                       GUPnPXMLDoc       *doc,
331                       const char        *udn,
332                       const char        *service_type,
333                       const char        *description_url,
334                       SoupURI           *url_base)
335 {
336         g_object_ref (control_point);
337
338         for (element = element->children; element; element = element->next) {
339                 xmlChar *prop;
340                 gboolean match;
341
342                 if (strcmp ((char *) element->name, "service") != 0)
343                         continue;
344
345                 /* See if this is a matching service */
346                 prop = xml_util_get_child_element_content (element,
347                                                            "serviceType");
348                 if (!prop)
349                         continue;
350
351                 match = (strcmp ((char *) prop, service_type) == 0);
352
353                 xmlFree (prop);
354
355                 if (!match)
356                         continue;
357
358                 /* Match */
359
360                 create_and_report_service_proxy (control_point,
361                                                  doc,
362                                                  element,
363                                                  udn,
364                                                  service_type,
365                                                  description_url,
366                                                  url_base);
367         }
368
369         g_object_unref (control_point);
370 }
371
372 /* Recursively search @element for matching devices */
373 static void
374 process_device_list (xmlNode           *element,
375                      GUPnPControlPoint *control_point,
376                      GUPnPXMLDoc       *doc,
377                      const char        *udn,
378                      const char        *service_type,
379                      const char        *description_url,
380                      SoupURI           *url_base)
381 {
382         g_object_ref (control_point);
383
384         for (element = element->children; element; element = element->next) {
385                 xmlNode *children;
386                 xmlChar *prop;
387                 gboolean match;
388
389                 if (strcmp ((char *) element->name, "device") != 0)
390                         continue;
391
392                 /* Recurse into children */
393                 children = xml_util_get_element (element,
394                                                  "deviceList",
395                                                  NULL);
396
397                 if (children) {
398                         process_device_list (children,
399                                              control_point,
400                                              doc,
401                                              udn,
402                                              service_type,
403                                              description_url,
404                                              url_base);
405                 }
406
407                 /* See if this is a matching device */
408                 prop = xml_util_get_child_element_content (element, "UDN");
409                 if (!prop)
410                         continue;
411
412                 match = (strcmp ((char *) prop, udn) == 0);
413
414                 xmlFree (prop);
415
416                 if (!match)
417                         continue;
418
419                 /* Match */
420
421                 if (service_type) {
422                         /* Dive into serviceList */
423                         children = xml_util_get_element (element,
424                                                          "serviceList",
425                                                          NULL);
426
427                         if (children) {
428                                 process_service_list (children,
429                                                       control_point,
430                                                       doc,
431                                                       udn,
432                                                       service_type,
433                                                       description_url,
434                                                       url_base);
435                         }
436                 } else
437                         create_and_report_device_proxy (control_point,
438                                                         doc,
439                                                         element,
440                                                         udn,
441                                                         description_url,
442                                                         url_base);
443         }
444
445         g_object_unref (control_point);
446 }
447
448 /*
449  * Called when the description document is loaded.
450  */
451 static void
452 description_loaded (GUPnPControlPoint *control_point,
453                     GUPnPXMLDoc       *doc,
454                     const char        *udn,
455                     const char        *service_type,
456                     const char        *description_url)
457 {
458         xmlNode *element;
459         SoupURI *url_base;
460
461         /* Save the URL base, if any */
462         element = xml_util_get_element ((xmlNode *) doc->doc,
463                                         "root",
464                                         NULL);
465         if (element == NULL) {
466                 g_warning ("No 'root' element found in description document"
467                            " '%s'. Ignoring device '%s'\n",
468                            description_url,
469                            udn);
470
471                 return;
472         }
473
474         url_base = xml_util_get_child_element_content_uri (element,
475                                                            "URLBase",
476                                                            NULL);
477         if (!url_base)
478                 url_base = soup_uri_new (description_url);
479
480         /* Iterate matching devices */
481         process_device_list (element,
482                              control_point,
483                              doc,
484                              udn,
485                              service_type,
486                              description_url,
487                              url_base);
488
489         /* Cleanup */
490         soup_uri_free (url_base);
491 }
492
493 /*
494  * Description URL downloaded.
495  */
496 static void
497 got_description_url (SoupSession           *session,
498                      SoupMessage           *msg,
499                      GetDescriptionURLData *data)
500 {
501         GUPnPXMLDoc *doc;
502
503         if (msg->status_code == SOUP_STATUS_CANCELLED)
504                 return;
505
506         /* Now, make sure again this document is not already cached. If it is,
507          * we re-use the cached one. */
508         doc = g_hash_table_lookup (data->control_point->priv->doc_cache,
509                                    data->description_url);
510         if (doc) {
511                 /* Doc was cached */
512                 description_loaded (data->control_point,
513                                     doc,
514                                     data->udn,
515                                     data->service_type,
516                                     data->description_url);
517
518                 get_description_url_data_free (data);
519
520                 return;
521         }
522
523         /* Not cached */
524         if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
525                 xmlDoc *xml_doc;
526
527                 /* Parse response */
528                 xml_doc = xmlRecoverMemory (msg->response_body->data,
529                                             msg->response_body->length);
530                 if (xml_doc) {
531                         doc = gupnp_xml_doc_new (xml_doc);
532
533                         description_loaded (data->control_point,
534                                             doc,
535                                             data->udn,
536                                             data->service_type,
537                                             data->description_url);
538
539                         /* Insert into document cache */
540                         g_hash_table_insert
541                                           (data->control_point->priv->doc_cache,
542                                            g_strdup (data->description_url),
543                                            doc);
544
545                         /* Make sure the document is removed from the cache
546                          * once finalized. */
547                         g_object_weak_ref (G_OBJECT (doc),
548                                            doc_finalized,
549                                            data->control_point);
550
551                         /* If no proxy was created, make sure doc is freed. */
552                         g_object_unref (doc);
553                 } else
554                         g_warning ("Failed to parse %s", data->description_url);
555         } else
556                 g_warning ("Failed to GET %s: %s",
557                            data->description_url,
558                            msg->reason_phrase);
559
560         get_description_url_data_free (data);
561 }
562
563 /*
564  * Downloads and parses (or takes from cache) @description_url,
565  * creating:
566  *  - A #GUPnPDeviceProxy for the device specified by @udn if @service_type
567  *    is %NULL.
568  *  - A #GUPnPServiceProxy for the service of type @service_type from the device
569  *    specified by @udn if @service_type is not %NULL.
570  */
571 static void
572 load_description (GUPnPControlPoint *control_point,
573                   const char        *description_url,
574                   const char        *udn,
575                   const char        *service_type)
576 {
577         GUPnPXMLDoc *doc;
578
579         doc = g_hash_table_lookup (control_point->priv->doc_cache,
580                                    description_url);
581         if (doc) {
582                 /* Doc was cached */
583                 description_loaded (control_point,
584                                     doc,
585                                     udn,
586                                     service_type,
587                                     description_url);
588         } else {
589                 /* Asynchronously download doc */
590                 GUPnPContext *context;
591                 SoupSession *session;
592                 GetDescriptionURLData *data;
593
594                 context = gupnp_control_point_get_context (control_point);
595
596                 session = gupnp_context_get_session (context);
597
598                 data = g_slice_new (GetDescriptionURLData);
599
600                 data->message = soup_message_new (SOUP_METHOD_GET,
601                                                   description_url);
602                 if (data->message == NULL) {
603                         g_warning ("Invalid description URL: %s",
604                                    description_url);
605
606                         g_slice_free (GetDescriptionURLData, data);
607
608                         return;
609                 }
610
611                 http_request_set_accept_language (data->message);
612
613                 data->control_point   = control_point;
614
615                 data->udn             = g_strdup (udn);
616                 data->service_type    = g_strdup (service_type);
617                 data->description_url = g_strdup (description_url);
618
619                 control_point->priv->pending_gets =
620                         g_list_prepend (control_point->priv->pending_gets,
621                                         data);
622
623                 soup_session_queue_message (session,
624                                             data->message,
625                                             (SoupSessionCallback)
626                                                    got_description_url,
627                                             data);
628         }
629 }
630
631 static gboolean
632 parse_usn (const char *usn,
633            char      **udn,
634            char      **service_type)
635 {
636         gboolean ret;
637         char **bits;
638         guint count, i;
639
640         ret = FALSE;
641
642         *udn = *service_type = NULL;
643
644         /* Verify we have a valid USN */
645         if (strncmp (usn, "uuid:", strlen ("uuid:"))) {
646                 g_warning ("Invalid USN: %s", usn);
647
648                 return FALSE;
649         }
650
651         /* Parse USN */
652         bits = g_strsplit (usn, "::", -1);
653
654         /* Count elements */
655         count = g_strv_length (bits);
656
657         if (count == 1) {
658                 /* uuid:device-UUID */
659
660                 *udn = bits[0];
661
662                 ret = TRUE;
663
664         } else if (count == 2) {
665                 char **second_bits;
666                 guint n_second_bits;
667
668                 second_bits = g_strsplit (bits[1], ":", -1);
669                 n_second_bits = g_strv_length (second_bits);
670
671                 if (n_second_bits >= 2 &&
672                     !strcmp (second_bits[0], "upnp") &&
673                     !strcmp (second_bits[1], "rootdevice")) {
674                         /* uuid:device-UUID::upnp:rootdevice */
675
676                         *udn = bits[0];
677
678                         ret = TRUE;
679                 } else if (n_second_bits >= 3 &&
680                            !strcmp (second_bits[0], "urn")) {
681                         /* uuid:device-UIID::urn:domain-name:service/device:
682                          * type:v */
683
684                         if (!strcmp (second_bits[2], "device")) {
685                                 *udn = bits[0];
686
687                                 ret = TRUE;
688                         } else if (!strcmp (second_bits[2], "service")) {
689                                 *udn = bits[0];
690                                 *service_type = bits[1];
691
692                                 ret = TRUE;
693                         }
694                 }
695
696                 g_strfreev (second_bits);
697         }
698
699         if (*udn == NULL)
700                 g_warning ("Invalid USN: %s", usn);
701
702         for (i = 0; i < count; i++) {
703                 if ((bits[i] != *udn) &&
704                     (bits[i] != *service_type))
705                         g_free (bits[i]);
706         }
707
708         g_free (bits);
709
710         return ret;
711 }
712
713 static void
714 gupnp_control_point_resource_available (GSSDPResourceBrowser *resource_browser,
715                                         const char           *usn,
716                                         const GList          *locations)
717 {
718         GUPnPControlPoint *control_point;
719         char *udn, *service_type;
720
721         control_point = GUPNP_CONTROL_POINT (resource_browser);
722
723         /* Verify we have a location */
724         if (!locations) {
725                 g_warning ("No Location header for device with USN %s", usn);
726                 return;
727         }
728
729         /* Parse USN */
730         if (!parse_usn (usn, &udn, &service_type))
731                 return;
732
733         load_description (control_point,
734                           locations->data,
735                           udn,
736                           service_type);
737
738         g_free (udn);
739         g_free (service_type);
740 }
741
742 static void
743 gupnp_control_point_resource_unavailable
744                                 (GSSDPResourceBrowser *resource_browser,
745                                  const char           *usn)
746 {
747         GUPnPControlPoint *control_point;
748         char *udn, *service_type;
749
750         control_point = GUPNP_CONTROL_POINT (resource_browser);
751
752         /* Parse USN */
753         if (!parse_usn (usn, &udn, &service_type))
754                 return;
755
756         /* Find proxy */
757         if (service_type) {
758                 GList *l = find_service_node (control_point, udn, service_type);
759
760                 if (l) {
761                         GUPnPServiceProxy *proxy;
762
763                         /* Remove proxy */
764                         proxy = GUPNP_SERVICE_PROXY (l->data);
765
766                         control_point->priv->services =
767                                 g_list_delete_link
768                                         (control_point->priv->services, l);
769
770                         g_signal_emit (control_point,
771                                        signals[SERVICE_PROXY_UNAVAILABLE],
772                                        0,
773                                        proxy);
774
775                         g_object_unref (proxy);
776                 }
777         } else {
778                 GList *l = find_device_node (control_point, udn);
779
780                 if (l) {
781                         GUPnPDeviceProxy *proxy;
782
783                         /* Remove proxy */
784                         proxy = GUPNP_DEVICE_PROXY (l->data);
785
786                         control_point->priv->devices =
787                                  g_list_delete_link
788                                         (control_point->priv->devices, l);
789
790                         g_signal_emit (control_point,
791                                        signals[DEVICE_PROXY_UNAVAILABLE],
792                                        0,
793                                        proxy);
794
795                         g_object_unref (proxy);
796                 }
797         }
798
799         g_free (udn);
800         g_free (service_type);
801 }
802
803 static void
804 gupnp_control_point_set_property (GObject      *object,
805                                   guint         property_id,
806                                   const GValue *value,
807                                   GParamSpec   *pspec)
808 {
809         GUPnPControlPoint *control_point;
810
811         control_point = GUPNP_CONTROL_POINT (object);
812
813         switch (property_id) {
814         case PROP_RESOURCE_FACTORY:
815                 control_point->priv->factory = 
816                         GUPNP_RESOURCE_FACTORY (g_value_dup_object (value));
817                 break;
818         default:
819                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
820                 break;
821         }
822 }
823
824 static void
825 gupnp_control_point_get_property (GObject    *object,
826                                   guint       property_id,
827                                   GValue     *value,
828                                   GParamSpec *pspec)
829 {
830         GUPnPControlPoint *control_point;
831
832         control_point = GUPNP_CONTROL_POINT (object);
833
834         switch (property_id) {
835         case PROP_RESOURCE_FACTORY:
836                 g_value_set_object (value,
837                                     gupnp_control_point_get_resource_factory (control_point));
838                 break;
839         default:
840                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
841                 break;
842         }
843 }
844
845 static void
846 gupnp_control_point_class_init (GUPnPControlPointClass *klass)
847 {
848         GObjectClass *object_class;
849         GSSDPResourceBrowserClass *browser_class;
850
851         object_class = G_OBJECT_CLASS (klass);
852
853         object_class->set_property = gupnp_control_point_set_property;
854         object_class->get_property = gupnp_control_point_get_property;
855         object_class->dispose      = gupnp_control_point_dispose;
856         object_class->finalize     = gupnp_control_point_finalize;
857
858         browser_class = GSSDP_RESOURCE_BROWSER_CLASS (klass);
859
860         browser_class->resource_available =
861                 gupnp_control_point_resource_available;
862         browser_class->resource_unavailable =
863                 gupnp_control_point_resource_unavailable;
864
865         g_type_class_add_private (klass, sizeof (GUPnPControlPointPrivate));
866
867         /**
868          * GUPnPControlPoint:resource-factory:
869          *
870          * The resource factory to use. Set to NULL for default factory.
871          **/
872         g_object_class_install_property
873                 (object_class,
874                  PROP_RESOURCE_FACTORY,
875                  g_param_spec_object ("resource-factory",
876                                       "Resource Factory",
877                                       "The resource factory to use",
878                                       GUPNP_TYPE_RESOURCE_FACTORY,
879                                       G_PARAM_CONSTRUCT_ONLY |
880                                       G_PARAM_READWRITE |
881                                       G_PARAM_STATIC_NAME |
882                                       G_PARAM_STATIC_NICK |
883                                       G_PARAM_STATIC_BLURB));
884
885         /**
886          * GUPnPControlPoint::device-proxy-available:
887          * @control_point: The #GUPnPControlPoint that received the signal
888          * @proxy: The now available #GUPnPDeviceProxy
889          *
890          * The ::device-proxy-available signal is emitted whenever a new
891          * device has become available.
892          **/
893         signals[DEVICE_PROXY_AVAILABLE] =
894                 g_signal_new ("device-proxy-available",
895                               GUPNP_TYPE_CONTROL_POINT,
896                               G_SIGNAL_RUN_LAST,
897                               G_STRUCT_OFFSET (GUPnPControlPointClass,
898                                                device_proxy_available),
899                               NULL,
900                               NULL,
901                               g_cclosure_marshal_VOID__OBJECT,
902                               G_TYPE_NONE,
903                               1,
904                               GUPNP_TYPE_DEVICE_PROXY);
905
906         /**
907          * GUPnPControlPoint::device-proxy-unavailable:
908          * @control_point: The #GUPnPControlPoint that received the signal
909          * @proxy: The now unavailable #GUPnPDeviceProxy
910          *
911          * The ::device-proxy-unavailable signal is emitted whenever a
912          * device is not available any more.
913          **/
914         signals[DEVICE_PROXY_UNAVAILABLE] =
915                 g_signal_new ("device-proxy-unavailable",
916                               GUPNP_TYPE_CONTROL_POINT,
917                               G_SIGNAL_RUN_LAST,
918                               G_STRUCT_OFFSET (GUPnPControlPointClass,
919                                                device_proxy_unavailable),
920                               NULL,
921                               NULL,
922                               g_cclosure_marshal_VOID__OBJECT,
923                               G_TYPE_NONE,
924                               1,
925                               GUPNP_TYPE_DEVICE_PROXY);
926
927         /**
928          * GUPnPControlPoint::service-proxy-available:
929          * @control_point: The #GUPnPControlPoint that received the signal
930          * @proxy: The now available #GUPnPServiceProxy
931          *
932          * The ::service-proxy-available signal is emitted whenever a new
933          * service has become available.
934          **/
935         signals[SERVICE_PROXY_AVAILABLE] =
936                 g_signal_new ("service-proxy-available",
937                               GUPNP_TYPE_CONTROL_POINT,
938                               G_SIGNAL_RUN_LAST,
939                               G_STRUCT_OFFSET (GUPnPControlPointClass,
940                                                service_proxy_available),
941                               NULL,
942                               NULL,
943                               g_cclosure_marshal_VOID__OBJECT,
944                               G_TYPE_NONE,
945                               1,
946                               GUPNP_TYPE_SERVICE_PROXY);
947
948         /**
949          * GUPnPControlPoint::service-proxy-unavailable:
950          * @control_point: The #GUPnPControlPoint that received the signal
951          * @proxy: The now unavailable #GUPnPServiceProxy
952          *
953          * The ::service-proxy-unavailable signal is emitted whenever a
954          * service is not available any more.
955          **/
956         signals[SERVICE_PROXY_UNAVAILABLE] =
957                 g_signal_new ("service-proxy-unavailable",
958                               GUPNP_TYPE_CONTROL_POINT,
959                               G_SIGNAL_RUN_LAST,
960                               G_STRUCT_OFFSET (GUPnPControlPointClass,
961                                                service_proxy_unavailable),
962                               NULL,
963                               NULL,
964                               g_cclosure_marshal_VOID__OBJECT,
965                               G_TYPE_NONE,
966                               1,
967                               GUPNP_TYPE_SERVICE_PROXY);
968 }
969
970 /**
971  * gupnp_control_point_new:
972  * @context: A #GUPnPContext
973  * @target: The search target
974  *
975  * Create a new #GUPnPControlPoint with the specified @context and @target.
976  *
977  * @target should be a service or device name, such as
978  * <literal>urn:schemas-upnp-org:service:WANIPConnection:1</literal> or
979  * <literal>urn:schemas-upnp-org:device:MediaRenderer:1</literal>.
980  *
981  * Return value: A new #GUPnPControlPoint object.
982  **/
983 GUPnPControlPoint *
984 gupnp_control_point_new (GUPnPContext *context,
985                          const char   *target)
986 {
987         g_return_val_if_fail (GUPNP_IS_CONTEXT (context), NULL);
988         g_return_val_if_fail (target, NULL);
989
990         return g_object_new (GUPNP_TYPE_CONTROL_POINT,
991                              "client", context,
992                              "target", target,
993                              NULL);
994 }
995
996 /**
997  * gupnp_control_point_new_full:
998  * @context: A #GUPnPContext
999  * @factory: A #GUPnPResourceFactory
1000  * @target: The search target
1001  *
1002  * Create a new #GUPnPControlPoint with the specified @context, @factory and
1003  * @target.
1004  *
1005  * @target should be a service or device name, such as
1006  * <literal>urn:schemas-upnp-org:service:WANIPConnection:1</literal> or
1007  * <literal>urn:schemas-upnp-org:device:MediaRenderer:1</literal>.
1008  *
1009  * Return value: A new #GUPnPControlPoint object.
1010  **/
1011 GUPnPControlPoint *
1012 gupnp_control_point_new_full (GUPnPContext         *context,
1013                               GUPnPResourceFactory *factory,
1014                               const char           *target)
1015 {
1016         g_return_val_if_fail (GUPNP_IS_CONTEXT (context), NULL);
1017         g_return_val_if_fail (factory == NULL ||
1018                               GUPNP_IS_RESOURCE_FACTORY (factory), NULL);
1019         g_return_val_if_fail (target, NULL);
1020
1021         return g_object_new (GUPNP_TYPE_CONTROL_POINT,
1022                              "client", context,
1023                              "target", target,
1024                              "resource-factory", factory,
1025                              NULL);
1026 }
1027
1028 /**
1029  * gupnp_control_point_get_context:
1030  * @control_point: A #GUPnPControlPoint
1031  *
1032  * Get the #GUPnPControlPoint associated with @control_point.
1033  *
1034  * Returns: (transfer none): The #GUPnPContext.
1035  **/
1036 GUPnPContext *
1037 gupnp_control_point_get_context (GUPnPControlPoint *control_point)
1038 {
1039         GSSDPClient *client;
1040
1041         g_return_val_if_fail (GUPNP_IS_CONTROL_POINT (control_point), NULL);
1042
1043         client = gssdp_resource_browser_get_client
1044                                 (GSSDP_RESOURCE_BROWSER (control_point));
1045
1046         return GUPNP_CONTEXT (client);
1047 }
1048
1049 /**
1050  * gupnp_control_point_list_device_proxies:
1051  * @control_point: A #GUPnPControlPoint
1052  *
1053  * Get the #GList of discovered #GUPnPDeviceProxy objects. Do not free the list
1054  * nor its elements.
1055  *
1056  * Return value: (element-type GUPnP.DeviceProxy) (transfer none):  a #GList of
1057  * #GUPnPDeviceProxy objects.
1058  **/
1059 const GList *
1060 gupnp_control_point_list_device_proxies (GUPnPControlPoint *control_point)
1061 {
1062         g_return_val_if_fail (GUPNP_IS_CONTROL_POINT (control_point), NULL);
1063
1064         return (const GList *) control_point->priv->devices;
1065 }
1066
1067 /**
1068  * gupnp_control_point_list_service_proxies:
1069  * @control_point: A #GUPnPControlPoint
1070  *
1071  * Get the #GList of discovered #GUPnPServiceProxy objects. Do not free the
1072  * list nor its elements.
1073  *
1074  * Return value: (element-type GUPnP.ServiceProxy) (transfer none): a #GList
1075  * of #GUPnPServiceProxy objects.
1076  **/
1077 const GList *
1078 gupnp_control_point_list_service_proxies (GUPnPControlPoint *control_point)
1079 {
1080         g_return_val_if_fail (GUPNP_IS_CONTROL_POINT (control_point), NULL);
1081
1082         return (const GList *) control_point->priv->services;
1083 }
1084
1085 /**
1086  * gupnp_control_point_get_resource_factory:
1087  * @control_point: A #GUPnPControlPoint
1088  *
1089  * Get the #GUPnPResourceFactory used by the @control_point.
1090  *
1091  * Returns: (transfer none): A #GUPnPResourceFactory.
1092  **/
1093 GUPnPResourceFactory *
1094 gupnp_control_point_get_resource_factory (GUPnPControlPoint *control_point)
1095 {
1096         g_return_val_if_fail (GUPNP_IS_CONTROL_POINT (control_point), NULL);
1097
1098         if (control_point->priv->factory)
1099                   return control_point->priv->factory;
1100
1101         return gupnp_resource_factory_get_default ();
1102 }