Submit version 0.20.1 of GUPnP (4186015)
[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 static gboolean
327 compare_service_types_versioned (const char *searched_service,
328                                  const char *current_service)
329 {
330         const char *searched_version_ptr, *current_version_ptr;
331         guint searched_version, current_version, searched_length;
332         guint current_length;
333
334         searched_version_ptr = strrchr (searched_service, ':');
335         if (searched_version_ptr == NULL)
336                 return FALSE;
337
338         current_version_ptr = strrchr (current_service, ':');
339         if (current_version_ptr == NULL)
340                 return FALSE;
341
342         searched_length = (searched_version_ptr - searched_service);
343         current_length = (current_version_ptr - current_service);
344
345         if (searched_length != current_length)
346                 return FALSE;
347
348         searched_version = (guint) atol (searched_version_ptr + 1);
349         if (searched_version == 0)
350                 return FALSE;
351
352         current_version = (guint) atol (current_version_ptr + 1);
353         if (current_version == 0)
354                 return FALSE;
355
356         if (current_version < searched_version)
357                 return FALSE;
358
359         return strncmp (searched_service,
360                         current_service,
361                         searched_length) == 0;
362 }
363
364 /* Search @element for matching services */
365 static void
366 process_service_list (xmlNode           *element,
367                       GUPnPControlPoint *control_point,
368                       GUPnPXMLDoc       *doc,
369                       const char        *udn,
370                       const char        *service_type,
371                       const char        *description_url,
372                       SoupURI           *url_base)
373 {
374         g_object_ref (control_point);
375
376         for (element = element->children; element; element = element->next) {
377                 xmlChar *prop;
378                 gboolean match;
379
380                 if (strcmp ((char *) element->name, "service") != 0)
381                         continue;
382
383                 /* See if this is a matching service */
384                 prop = xml_util_get_child_element_content (element,
385                                                            "serviceType");
386                 if (!prop)
387                         continue;
388
389                 match = compare_service_types_versioned (service_type,
390                                                          (char *) prop);
391                 xmlFree (prop);
392
393                 if (!match)
394                         continue;
395
396                 /* Match */
397
398                 create_and_report_service_proxy (control_point,
399                                                  doc,
400                                                  element,
401                                                  udn,
402                                                  service_type,
403                                                  description_url,
404                                                  url_base);
405         }
406
407         g_object_unref (control_point);
408 }
409
410 /* Recursively search @element for matching devices */
411 static void
412 process_device_list (xmlNode           *element,
413                      GUPnPControlPoint *control_point,
414                      GUPnPXMLDoc       *doc,
415                      const char        *udn,
416                      const char        *service_type,
417                      const char        *description_url,
418                      SoupURI           *url_base)
419 {
420         g_object_ref (control_point);
421
422         for (element = element->children; element; element = element->next) {
423                 xmlNode *children;
424                 xmlChar *prop;
425                 gboolean match;
426
427                 if (strcmp ((char *) element->name, "device") != 0)
428                         continue;
429
430                 /* Recurse into children */
431                 children = xml_util_get_element (element,
432                                                  "deviceList",
433                                                  NULL);
434
435                 if (children) {
436                         process_device_list (children,
437                                              control_point,
438                                              doc,
439                                              udn,
440                                              service_type,
441                                              description_url,
442                                              url_base);
443                 }
444
445                 /* See if this is a matching device */
446                 prop = xml_util_get_child_element_content (element, "UDN");
447                 if (!prop)
448                         continue;
449
450                 match = (strcmp ((char *) prop, udn) == 0);
451
452                 xmlFree (prop);
453
454                 if (!match)
455                         continue;
456
457                 /* Match */
458
459                 if (service_type) {
460                         /* Dive into serviceList */
461                         children = xml_util_get_element (element,
462                                                          "serviceList",
463                                                          NULL);
464
465                         if (children) {
466                                 process_service_list (children,
467                                                       control_point,
468                                                       doc,
469                                                       udn,
470                                                       service_type,
471                                                       description_url,
472                                                       url_base);
473                         }
474                 } else
475                         create_and_report_device_proxy (control_point,
476                                                         doc,
477                                                         element,
478                                                         udn,
479                                                         description_url,
480                                                         url_base);
481         }
482
483         g_object_unref (control_point);
484 }
485
486 /*
487  * Called when the description document is loaded.
488  */
489 static void
490 description_loaded (GUPnPControlPoint *control_point,
491                     GUPnPXMLDoc       *doc,
492                     const char        *udn,
493                     const char        *service_type,
494                     const char        *description_url)
495 {
496         xmlNode *element;
497         SoupURI *url_base;
498
499         /* Save the URL base, if any */
500         element = xml_util_get_element ((xmlNode *) doc->doc,
501                                         "root",
502                                         NULL);
503         if (element == NULL) {
504                 g_warning ("No 'root' element found in description document"
505                            " '%s'. Ignoring device '%s'\n",
506                            description_url,
507                            udn);
508
509                 return;
510         }
511
512         url_base = xml_util_get_child_element_content_uri (element,
513                                                            "URLBase",
514                                                            NULL);
515         if (!url_base)
516                 url_base = soup_uri_new (description_url);
517
518         /* Iterate matching devices */
519         process_device_list (element,
520                              control_point,
521                              doc,
522                              udn,
523                              service_type,
524                              description_url,
525                              url_base);
526
527         /* Cleanup */
528         soup_uri_free (url_base);
529 }
530
531 /*
532  * Description URL downloaded.
533  */
534 static void
535 got_description_url (SoupSession           *session,
536                      SoupMessage           *msg,
537                      GetDescriptionURLData *data)
538 {
539         GUPnPXMLDoc *doc;
540
541         if (msg->status_code == SOUP_STATUS_CANCELLED)
542                 return;
543
544         /* Now, make sure again this document is not already cached. If it is,
545          * we re-use the cached one. */
546         doc = g_hash_table_lookup (data->control_point->priv->doc_cache,
547                                    data->description_url);
548         if (doc) {
549                 /* Doc was cached */
550                 description_loaded (data->control_point,
551                                     doc,
552                                     data->udn,
553                                     data->service_type,
554                                     data->description_url);
555
556                 get_description_url_data_free (data);
557
558                 return;
559         }
560
561         /* Not cached */
562         if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
563                 xmlDoc *xml_doc;
564
565                 /* Parse response */
566                 xml_doc = xmlRecoverMemory (msg->response_body->data,
567                                             msg->response_body->length);
568                 if (xml_doc) {
569                         doc = gupnp_xml_doc_new (xml_doc);
570
571                         description_loaded (data->control_point,
572                                             doc,
573                                             data->udn,
574                                             data->service_type,
575                                             data->description_url);
576
577                         /* Insert into document cache */
578                         g_hash_table_insert
579                                           (data->control_point->priv->doc_cache,
580                                            g_strdup (data->description_url),
581                                            doc);
582
583                         /* Make sure the document is removed from the cache
584                          * once finalized. */
585                         g_object_weak_ref (G_OBJECT (doc),
586                                            doc_finalized,
587                                            data->control_point);
588
589                         /* If no proxy was created, make sure doc is freed. */
590                         g_object_unref (doc);
591                 } else
592                         g_warning ("Failed to parse %s", data->description_url);
593         } else
594                 g_warning ("Failed to GET %s: %s",
595                            data->description_url,
596                            msg->reason_phrase);
597
598         get_description_url_data_free (data);
599 }
600
601 /*
602  * Downloads and parses (or takes from cache) @description_url,
603  * creating:
604  *  - A #GUPnPDeviceProxy for the device specified by @udn if @service_type
605  *    is %NULL.
606  *  - A #GUPnPServiceProxy for the service of type @service_type from the device
607  *    specified by @udn if @service_type is not %NULL.
608  */
609 static void
610 load_description (GUPnPControlPoint *control_point,
611                   const char        *description_url,
612                   const char        *udn,
613                   const char        *service_type)
614 {
615         GUPnPXMLDoc *doc;
616
617         doc = g_hash_table_lookup (control_point->priv->doc_cache,
618                                    description_url);
619         if (doc) {
620                 /* Doc was cached */
621                 description_loaded (control_point,
622                                     doc,
623                                     udn,
624                                     service_type,
625                                     description_url);
626         } else {
627                 /* Asynchronously download doc */
628                 GUPnPContext *context;
629                 SoupSession *session;
630                 GetDescriptionURLData *data;
631
632                 context = gupnp_control_point_get_context (control_point);
633
634                 session = gupnp_context_get_session (context);
635
636                 data = g_slice_new (GetDescriptionURLData);
637
638                 data->message = soup_message_new (SOUP_METHOD_GET,
639                                                   description_url);
640                 if (data->message == NULL) {
641                         g_warning ("Invalid description URL: %s",
642                                    description_url);
643
644                         g_slice_free (GetDescriptionURLData, data);
645
646                         return;
647                 }
648
649                 http_request_set_accept_language (data->message);
650
651                 data->control_point   = control_point;
652
653                 data->udn             = g_strdup (udn);
654                 data->service_type    = g_strdup (service_type);
655                 data->description_url = g_strdup (description_url);
656
657                 control_point->priv->pending_gets =
658                         g_list_prepend (control_point->priv->pending_gets,
659                                         data);
660
661                 soup_session_queue_message (session,
662                                             data->message,
663                                             (SoupSessionCallback)
664                                                    got_description_url,
665                                             data);
666         }
667 }
668
669 static gboolean
670 parse_usn (const char *usn,
671            char      **udn,
672            char      **service_type)
673 {
674         gboolean ret;
675         char **bits;
676         guint count, i;
677
678         ret = FALSE;
679
680         *udn = *service_type = NULL;
681
682         /* Verify we have a valid USN */
683         if (strncmp (usn, "uuid:", strlen ("uuid:"))) {
684                 g_warning ("Invalid USN: %s", usn);
685
686                 return FALSE;
687         }
688
689         /* Parse USN */
690         bits = g_strsplit (usn, "::", -1);
691
692         /* Count elements */
693         count = g_strv_length (bits);
694
695         if (count == 1) {
696                 /* uuid:device-UUID */
697
698                 *udn = bits[0];
699
700                 ret = TRUE;
701
702         } else if (count == 2) {
703                 char **second_bits;
704                 guint n_second_bits;
705
706                 second_bits = g_strsplit (bits[1], ":", -1);
707                 n_second_bits = g_strv_length (second_bits);
708
709                 if (n_second_bits >= 2 &&
710                     !strcmp (second_bits[0], "upnp") &&
711                     !strcmp (second_bits[1], "rootdevice")) {
712                         /* uuid:device-UUID::upnp:rootdevice */
713
714                         *udn = bits[0];
715
716                         ret = TRUE;
717                 } else if (n_second_bits >= 3 &&
718                            !strcmp (second_bits[0], "urn")) {
719                         /* uuid:device-UIID::urn:domain-name:service/device:
720                          * type:v */
721
722                         if (!strcmp (second_bits[2], "device")) {
723                                 *udn = bits[0];
724
725                                 ret = TRUE;
726                         } else if (!strcmp (second_bits[2], "service")) {
727                                 *udn = bits[0];
728                                 *service_type = bits[1];
729
730                                 ret = TRUE;
731                         }
732                 }
733
734                 g_strfreev (second_bits);
735         }
736
737         if (*udn == NULL)
738                 g_warning ("Invalid USN: %s", usn);
739
740         for (i = 0; i < count; i++) {
741                 if ((bits[i] != *udn) &&
742                     (bits[i] != *service_type))
743                         g_free (bits[i]);
744         }
745
746         g_free (bits);
747
748         return ret;
749 }
750
751 static void
752 gupnp_control_point_resource_available (GSSDPResourceBrowser *resource_browser,
753                                         const char           *usn,
754                                         const GList          *locations)
755 {
756         GUPnPControlPoint *control_point;
757         char *udn, *service_type;
758
759         control_point = GUPNP_CONTROL_POINT (resource_browser);
760
761         /* Verify we have a location */
762         if (!locations) {
763                 g_warning ("No Location header for device with USN %s", usn);
764                 return;
765         }
766
767         /* Parse USN */
768         if (!parse_usn (usn, &udn, &service_type))
769                 return;
770
771         load_description (control_point,
772                           locations->data,
773                           udn,
774                           service_type);
775
776         g_free (udn);
777         g_free (service_type);
778 }
779
780 static void
781 gupnp_control_point_resource_unavailable
782                                 (GSSDPResourceBrowser *resource_browser,
783                                  const char           *usn)
784 {
785         GUPnPControlPoint *control_point;
786         char *udn, *service_type;
787
788         control_point = GUPNP_CONTROL_POINT (resource_browser);
789
790         /* Parse USN */
791         if (!parse_usn (usn, &udn, &service_type))
792                 return;
793
794         /* Find proxy */
795         if (service_type) {
796                 GList *l = find_service_node (control_point, udn, service_type);
797
798                 if (l) {
799                         GUPnPServiceProxy *proxy;
800
801                         /* Remove proxy */
802                         proxy = GUPNP_SERVICE_PROXY (l->data);
803
804                         control_point->priv->services =
805                                 g_list_delete_link
806                                         (control_point->priv->services, l);
807
808                         g_signal_emit (control_point,
809                                        signals[SERVICE_PROXY_UNAVAILABLE],
810                                        0,
811                                        proxy);
812
813                         g_object_unref (proxy);
814                 }
815         } else {
816                 GList *l = find_device_node (control_point, udn);
817
818                 if (l) {
819                         GUPnPDeviceProxy *proxy;
820
821                         /* Remove proxy */
822                         proxy = GUPNP_DEVICE_PROXY (l->data);
823
824                         control_point->priv->devices =
825                                  g_list_delete_link
826                                         (control_point->priv->devices, l);
827
828                         g_signal_emit (control_point,
829                                        signals[DEVICE_PROXY_UNAVAILABLE],
830                                        0,
831                                        proxy);
832
833                         g_object_unref (proxy);
834                 }
835         }
836
837         g_free (udn);
838         g_free (service_type);
839 }
840
841 static void
842 gupnp_control_point_set_property (GObject      *object,
843                                   guint         property_id,
844                                   const GValue *value,
845                                   GParamSpec   *pspec)
846 {
847         GUPnPControlPoint *control_point;
848
849         control_point = GUPNP_CONTROL_POINT (object);
850
851         switch (property_id) {
852         case PROP_RESOURCE_FACTORY:
853                 control_point->priv->factory = 
854                         GUPNP_RESOURCE_FACTORY (g_value_dup_object (value));
855                 break;
856         default:
857                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
858                 break;
859         }
860 }
861
862 static void
863 gupnp_control_point_get_property (GObject    *object,
864                                   guint       property_id,
865                                   GValue     *value,
866                                   GParamSpec *pspec)
867 {
868         GUPnPControlPoint *control_point;
869
870         control_point = GUPNP_CONTROL_POINT (object);
871
872         switch (property_id) {
873         case PROP_RESOURCE_FACTORY:
874                 g_value_set_object (value,
875                                     gupnp_control_point_get_resource_factory (control_point));
876                 break;
877         default:
878                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
879                 break;
880         }
881 }
882
883 static void
884 gupnp_control_point_class_init (GUPnPControlPointClass *klass)
885 {
886         GObjectClass *object_class;
887         GSSDPResourceBrowserClass *browser_class;
888
889         object_class = G_OBJECT_CLASS (klass);
890
891         object_class->set_property = gupnp_control_point_set_property;
892         object_class->get_property = gupnp_control_point_get_property;
893         object_class->dispose      = gupnp_control_point_dispose;
894         object_class->finalize     = gupnp_control_point_finalize;
895
896         browser_class = GSSDP_RESOURCE_BROWSER_CLASS (klass);
897
898         browser_class->resource_available =
899                 gupnp_control_point_resource_available;
900         browser_class->resource_unavailable =
901                 gupnp_control_point_resource_unavailable;
902
903         g_type_class_add_private (klass, sizeof (GUPnPControlPointPrivate));
904
905         /**
906          * GUPnPControlPoint:resource-factory:
907          *
908          * The resource factory to use. Set to NULL for default factory.
909          **/
910         g_object_class_install_property
911                 (object_class,
912                  PROP_RESOURCE_FACTORY,
913                  g_param_spec_object ("resource-factory",
914                                       "Resource Factory",
915                                       "The resource factory to use",
916                                       GUPNP_TYPE_RESOURCE_FACTORY,
917                                       G_PARAM_CONSTRUCT_ONLY |
918                                       G_PARAM_READWRITE |
919                                       G_PARAM_STATIC_NAME |
920                                       G_PARAM_STATIC_NICK |
921                                       G_PARAM_STATIC_BLURB));
922
923         /**
924          * GUPnPControlPoint::device-proxy-available:
925          * @control_point: The #GUPnPControlPoint that received the signal
926          * @proxy: The now available #GUPnPDeviceProxy
927          *
928          * The ::device-proxy-available signal is emitted whenever a new
929          * device has become available.
930          **/
931         signals[DEVICE_PROXY_AVAILABLE] =
932                 g_signal_new ("device-proxy-available",
933                               GUPNP_TYPE_CONTROL_POINT,
934                               G_SIGNAL_RUN_LAST,
935                               G_STRUCT_OFFSET (GUPnPControlPointClass,
936                                                device_proxy_available),
937                               NULL,
938                               NULL,
939                               g_cclosure_marshal_VOID__OBJECT,
940                               G_TYPE_NONE,
941                               1,
942                               GUPNP_TYPE_DEVICE_PROXY);
943
944         /**
945          * GUPnPControlPoint::device-proxy-unavailable:
946          * @control_point: The #GUPnPControlPoint that received the signal
947          * @proxy: The now unavailable #GUPnPDeviceProxy
948          *
949          * The ::device-proxy-unavailable signal is emitted whenever a
950          * device is not available any more.
951          **/
952         signals[DEVICE_PROXY_UNAVAILABLE] =
953                 g_signal_new ("device-proxy-unavailable",
954                               GUPNP_TYPE_CONTROL_POINT,
955                               G_SIGNAL_RUN_LAST,
956                               G_STRUCT_OFFSET (GUPnPControlPointClass,
957                                                device_proxy_unavailable),
958                               NULL,
959                               NULL,
960                               g_cclosure_marshal_VOID__OBJECT,
961                               G_TYPE_NONE,
962                               1,
963                               GUPNP_TYPE_DEVICE_PROXY);
964
965         /**
966          * GUPnPControlPoint::service-proxy-available:
967          * @control_point: The #GUPnPControlPoint that received the signal
968          * @proxy: The now available #GUPnPServiceProxy
969          *
970          * The ::service-proxy-available signal is emitted whenever a new
971          * service has become available.
972          **/
973         signals[SERVICE_PROXY_AVAILABLE] =
974                 g_signal_new ("service-proxy-available",
975                               GUPNP_TYPE_CONTROL_POINT,
976                               G_SIGNAL_RUN_LAST,
977                               G_STRUCT_OFFSET (GUPnPControlPointClass,
978                                                service_proxy_available),
979                               NULL,
980                               NULL,
981                               g_cclosure_marshal_VOID__OBJECT,
982                               G_TYPE_NONE,
983                               1,
984                               GUPNP_TYPE_SERVICE_PROXY);
985
986         /**
987          * GUPnPControlPoint::service-proxy-unavailable:
988          * @control_point: The #GUPnPControlPoint that received the signal
989          * @proxy: The now unavailable #GUPnPServiceProxy
990          *
991          * The ::service-proxy-unavailable signal is emitted whenever a
992          * service is not available any more.
993          **/
994         signals[SERVICE_PROXY_UNAVAILABLE] =
995                 g_signal_new ("service-proxy-unavailable",
996                               GUPNP_TYPE_CONTROL_POINT,
997                               G_SIGNAL_RUN_LAST,
998                               G_STRUCT_OFFSET (GUPnPControlPointClass,
999                                                service_proxy_unavailable),
1000                               NULL,
1001                               NULL,
1002                               g_cclosure_marshal_VOID__OBJECT,
1003                               G_TYPE_NONE,
1004                               1,
1005                               GUPNP_TYPE_SERVICE_PROXY);
1006 }
1007
1008 /**
1009  * gupnp_control_point_new:
1010  * @context: A #GUPnPContext
1011  * @target: The search target
1012  *
1013  * Create a new #GUPnPControlPoint with the specified @context and @target.
1014  *
1015  * @target should be a service or device name, such as
1016  * <literal>urn:schemas-upnp-org:service:WANIPConnection:1</literal> or
1017  * <literal>urn:schemas-upnp-org:device:MediaRenderer:1</literal>.
1018  *
1019  * Return value: A new #GUPnPControlPoint object.
1020  **/
1021 GUPnPControlPoint *
1022 gupnp_control_point_new (GUPnPContext *context,
1023                          const char   *target)
1024 {
1025         g_return_val_if_fail (GUPNP_IS_CONTEXT (context), NULL);
1026         g_return_val_if_fail (target, NULL);
1027
1028         return g_object_new (GUPNP_TYPE_CONTROL_POINT,
1029                              "client", context,
1030                              "target", target,
1031                              NULL);
1032 }
1033
1034 /**
1035  * gupnp_control_point_new_full:
1036  * @context: A #GUPnPContext
1037  * @factory: A #GUPnPResourceFactory
1038  * @target: The search target
1039  *
1040  * Create a new #GUPnPControlPoint with the specified @context, @factory and
1041  * @target.
1042  *
1043  * @target should be a service or device name, such as
1044  * <literal>urn:schemas-upnp-org:service:WANIPConnection:1</literal> or
1045  * <literal>urn:schemas-upnp-org:device:MediaRenderer:1</literal>.
1046  *
1047  * Return value: A new #GUPnPControlPoint object.
1048  **/
1049 GUPnPControlPoint *
1050 gupnp_control_point_new_full (GUPnPContext         *context,
1051                               GUPnPResourceFactory *factory,
1052                               const char           *target)
1053 {
1054         g_return_val_if_fail (GUPNP_IS_CONTEXT (context), NULL);
1055         g_return_val_if_fail (factory == NULL ||
1056                               GUPNP_IS_RESOURCE_FACTORY (factory), NULL);
1057         g_return_val_if_fail (target, NULL);
1058
1059         return g_object_new (GUPNP_TYPE_CONTROL_POINT,
1060                              "client", context,
1061                              "target", target,
1062                              "resource-factory", factory,
1063                              NULL);
1064 }
1065
1066 /**
1067  * gupnp_control_point_get_context:
1068  * @control_point: A #GUPnPControlPoint
1069  *
1070  * Get the #GUPnPControlPoint associated with @control_point.
1071  *
1072  * Returns: (transfer none): The #GUPnPContext.
1073  **/
1074 GUPnPContext *
1075 gupnp_control_point_get_context (GUPnPControlPoint *control_point)
1076 {
1077         GSSDPClient *client;
1078
1079         g_return_val_if_fail (GUPNP_IS_CONTROL_POINT (control_point), NULL);
1080
1081         client = gssdp_resource_browser_get_client
1082                                 (GSSDP_RESOURCE_BROWSER (control_point));
1083
1084         return GUPNP_CONTEXT (client);
1085 }
1086
1087 /**
1088  * gupnp_control_point_list_device_proxies:
1089  * @control_point: A #GUPnPControlPoint
1090  *
1091  * Get the #GList of discovered #GUPnPDeviceProxy objects. Do not free the list
1092  * nor its elements.
1093  *
1094  * Return value: (element-type GUPnP.DeviceProxy) (transfer none):  a #GList of
1095  * #GUPnPDeviceProxy objects.
1096  **/
1097 const GList *
1098 gupnp_control_point_list_device_proxies (GUPnPControlPoint *control_point)
1099 {
1100         g_return_val_if_fail (GUPNP_IS_CONTROL_POINT (control_point), NULL);
1101
1102         return (const GList *) control_point->priv->devices;
1103 }
1104
1105 /**
1106  * gupnp_control_point_list_service_proxies:
1107  * @control_point: A #GUPnPControlPoint
1108  *
1109  * Get the #GList of discovered #GUPnPServiceProxy objects. Do not free the
1110  * list nor its elements.
1111  *
1112  * Return value: (element-type GUPnP.ServiceProxy) (transfer none): a #GList
1113  * of #GUPnPServiceProxy objects.
1114  **/
1115 const GList *
1116 gupnp_control_point_list_service_proxies (GUPnPControlPoint *control_point)
1117 {
1118         g_return_val_if_fail (GUPNP_IS_CONTROL_POINT (control_point), NULL);
1119
1120         return (const GList *) control_point->priv->services;
1121 }
1122
1123 /**
1124  * gupnp_control_point_get_resource_factory:
1125  * @control_point: A #GUPnPControlPoint
1126  *
1127  * Get the #GUPnPResourceFactory used by the @control_point.
1128  *
1129  * Returns: (transfer none): A #GUPnPResourceFactory.
1130  **/
1131 GUPnPResourceFactory *
1132 gupnp_control_point_get_resource_factory (GUPnPControlPoint *control_point)
1133 {
1134         g_return_val_if_fail (GUPNP_IS_CONTROL_POINT (control_point), NULL);
1135
1136         if (control_point->priv->factory)
1137                   return control_point->priv->factory;
1138
1139         return gupnp_resource_factory_get_default ();
1140 }