Imported Upstream version 0.20.12
[profile/ivi/GUPnP.git] / libgupnp / gupnp-service-introspection.c
1 /*
2  * Copyright (C) 2007 Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
3  * Copyright (C) 2006, 2007 OpenedHand Ltd.
4  *
5  * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
6  *         Jorn Baayen <jorn@openedhand.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23
24 /**
25  * SECTION:gupnp-service-introspection
26  * @short_description: Service introspection class.
27  *
28  * The #GUPnPServiceIntrospection class provides methods for service
29  * introspection based on information contained in its service description
30  * document (SCPD). There is no constructor provided for this class, please use
31  * #gupnp_service_info_get_introspection or
32  * #gupnp_service_info_get_introspection_async to create an
33  * #GUPnPServiceIntrospection object for a specific service.
34  *
35  * Note that all the introspection information is retreived from the service
36  * description document (SCPD) provided by the service and hence can not be
37  * guaranteed to be complete. A UPnP service is required to provide an SCPD but
38  * unfortunately, many services either do not provide this document or the
39  * document does not provide any or all of the introspection information.
40  *
41  * This class exposes internals of the UPnP protocol and should not need
42  * to be used for regular device or control point development.
43  *
44  **/
45
46 #include <libsoup/soup.h>
47 #include <string.h>
48
49 #include "gupnp-service-introspection.h"
50 #include "gupnp-service-introspection-private.h"
51 #include "xml-util.h"
52 #include "gvalue-util.h"
53 #include "gupnp-types.h"
54 #include "gupnp-types-private.h"
55
56 #define MAX_FIXED_14_4 99999999999999.9999
57
58 G_DEFINE_TYPE (GUPnPServiceIntrospection,
59                gupnp_service_introspection,
60                G_TYPE_OBJECT);
61
62 struct _GUPnPServiceIntrospectionPrivate {
63         GList *variables;
64         GList *actions;
65
66         /* For caching purposes */
67         GList *action_names;
68         GList *variable_names;
69 };
70
71 enum {
72         PROP_0,
73         PROP_SCPD
74 };
75
76 static void
77 construct_introspection_info (GUPnPServiceIntrospection *introspection,
78                               xmlDoc                    *scpd);
79
80 /**
81  * gupnp_service_state_variable_info_free:
82  * @argument: A #GUPnPServiceStateVariableInfo
83  *
84  * Frees a #GUPnPServiceStateVariableInfo.
85  *
86  **/
87 static void
88 gupnp_service_state_variable_info_free
89                                 (GUPnPServiceStateVariableInfo *variable)
90 {
91         g_free (variable->name);
92         g_value_unset (&variable->default_value);
93         if (variable->is_numeric) {
94                 g_value_unset (&variable->minimum);
95                 g_value_unset (&variable->maximum);
96                 g_value_unset (&variable->step);
97         }
98
99         g_list_free_full (variable->allowed_values, g_free);
100
101         g_slice_free (GUPnPServiceStateVariableInfo, variable);
102 }
103
104 static void
105 gupnp_service_introspection_init (GUPnPServiceIntrospection *introspection)
106 {
107         introspection->priv =
108                 G_TYPE_INSTANCE_GET_PRIVATE (introspection,
109                                              GUPNP_TYPE_SERVICE_INTROSPECTION,
110                                              GUPnPServiceIntrospectionPrivate);
111 }
112
113 static void
114 gupnp_service_introspection_set_property (GObject      *object,
115                                           guint        property_id,
116                                           const GValue *value,
117                                           GParamSpec   *pspec)
118 {
119         GUPnPServiceIntrospection *introspection;
120
121         introspection = GUPNP_SERVICE_INTROSPECTION (object);
122
123         switch (property_id) {
124         case PROP_SCPD:
125                 /* Construct introspection data */
126                 construct_introspection_info (introspection,
127                                               g_value_get_pointer (value));
128                 break;
129         default:
130                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
131                 break;
132         }
133 }
134
135
136 /**
137  * gupnp_service_action_arg_info_free:
138  * @argument: A #GUPnPServiceActionArgInfo
139  *
140  * Frees a #GUPnPServiceActionArgInfo.
141  *
142  **/
143 static void
144 gupnp_service_action_arg_info_free (GUPnPServiceActionArgInfo *argument)
145 {
146         g_free (argument->name);
147         g_free (argument->related_state_variable);
148
149         g_slice_free (GUPnPServiceActionArgInfo, argument);
150 }
151
152 /**
153  * gupnp_service_action_info_free:
154  * @argument: A #GUPnPServiceActionInfo
155  *
156  * Frees a #GUPnPServiceActionInfo.
157  *
158  **/
159 static void
160 gupnp_service_action_info_free (GUPnPServiceActionInfo *action_info)
161 {
162         GList *iter;
163
164         g_free (action_info->name);
165         g_list_free_full (action_info->arguments,
166                           (GDestroyNotify) gupnp_service_action_arg_info_free);
167         g_slice_free (GUPnPServiceActionInfo, action_info);
168 }
169
170 static void
171 gupnp_service_introspection_finalize (GObject *object)
172 {
173         GUPnPServiceIntrospection *introspection;
174
175         introspection = GUPNP_SERVICE_INTROSPECTION (object);
176
177         g_list_free_full (introspection->priv->variables,
178                           (GDestroyNotify) gupnp_service_state_variable_info_free);
179
180         g_list_free_full (introspection->priv->actions,
181                           (GDestroyNotify) gupnp_service_action_info_free);
182
183         /* Contents don't need to be freed, they were owned by priv->variables
184          */
185         if (introspection->priv->variable_names)
186                 g_list_free (introspection->priv->variable_names);
187
188         /* Contents don't need to be freed, they were owned by priv->actions
189          */
190         if (introspection->priv->action_names)
191                 g_list_free (introspection->priv->action_names);
192 }
193
194 static void
195 gupnp_service_introspection_class_init (GUPnPServiceIntrospectionClass *klass)
196 {
197         GObjectClass *object_class;
198
199         object_class = G_OBJECT_CLASS (klass);
200
201         object_class->set_property = gupnp_service_introspection_set_property;
202         object_class->finalize     = gupnp_service_introspection_finalize;
203
204         g_type_class_add_private (klass,
205                                   sizeof (GUPnPServiceIntrospectionPrivate));
206
207         /**
208          * GUPnPServiceIntrospection:scpd:
209          *
210          * The scpd of the device description file.
211          **/
212         g_object_class_install_property
213                 (object_class,
214                  PROP_SCPD,
215                  g_param_spec_pointer ("scpd",
216                                        "SCPD",
217                                        "Pointer to SCPD",
218                                        G_PARAM_WRITABLE |
219                                        G_PARAM_CONSTRUCT_ONLY));
220 }
221
222 static void
223 set_default_value (xmlNodePtr                     variable_node,
224                    GUPnPServiceStateVariableInfo *variable)
225 {
226         xmlChar *default_str;
227
228         default_str = xml_util_get_child_element_content (variable_node,
229                                                           "defaultValue");
230         if (default_str) {
231                 gvalue_util_set_value_from_string (&variable->default_value,
232                                                    (char *) default_str);
233
234                 xmlFree (default_str);
235         }
236 }
237
238 static void
239 set_string_value_limits (xmlNodePtr   limit_node,
240                          GList     **limits)
241 {
242         xmlNodePtr value_node;
243
244         for (value_node = limit_node->children;
245              value_node;
246              value_node = value_node->next) {
247                 xmlChar *allowed_value;
248
249                 if (strcmp ("allowedValue", (char *) value_node->name) != 0)
250                         continue;
251
252                 allowed_value = xmlNodeGetContent (value_node);
253                 if (!allowed_value)
254                         continue;
255
256                 *limits = g_list_append (*limits,
257                                           g_strdup ((char *) allowed_value));
258                 xmlFree (allowed_value);
259         }
260 }
261
262 static void
263 set_value_limit_by_name (xmlNodePtr  limit_node,
264                          GValue     *limit,
265                          const char *limit_name)
266 {
267         xmlChar *limit_str;
268
269         limit_str = xml_util_get_child_element_content (limit_node,
270                                                         limit_name);
271         if (limit_str) {
272                 gvalue_util_set_value_from_string (limit, (char *) limit_str);
273
274                 xmlFree (limit_str);
275         }
276 }
277
278 static void
279 set_variable_limits (xmlNodePtr                     variable_node,
280                      GUPnPServiceStateVariableInfo *variable)
281 {
282         xmlNodePtr limit_node;
283
284         if (variable->is_numeric) {
285                 limit_node = xml_util_get_element (variable_node,
286                                                    "allowedValueRange",
287                                                    NULL);
288                 if (limit_node == NULL)
289                         return;
290
291                 set_value_limit_by_name (limit_node,
292                                 &(variable->minimum),
293                                 "minimum");
294                 set_value_limit_by_name (limit_node,
295                                 &(variable->maximum),
296                                 "maximum");
297                 set_value_limit_by_name (limit_node,
298                                 &(variable->step),
299                                 "step");
300         } else if (variable->type == G_TYPE_STRING) {
301                 limit_node = xml_util_get_element (variable_node,
302                                                    "allowedValueList",
303                                                    NULL);
304                 if (limit_node == NULL)
305                         return;
306
307                 set_string_value_limits (limit_node,
308                                          &(variable->allowed_values));
309        }
310 }
311
312 static gboolean
313 set_variable_type (GUPnPServiceStateVariableInfo *variable,
314                    char                          *data_type)
315 {
316         GType type;
317
318         if (strcmp ("string", data_type) == 0) {
319                 type = G_TYPE_STRING;
320         }
321
322         else if (strcmp ("char", data_type) == 0) {
323                 type = G_TYPE_CHAR;
324         }
325
326         else if (strcmp ("boolean", data_type) == 0) {
327                 type = G_TYPE_BOOLEAN;
328         }
329
330         else if (strcmp ("i1", data_type) == 0) {
331                 type = G_TYPE_INT;
332                 g_value_init (&variable->minimum, type);
333                 g_value_set_int (&variable->minimum, G_MININT8);
334                 g_value_init (&variable->maximum, type);
335                 g_value_set_int (&variable->maximum, G_MAXINT8);
336                 g_value_init (&variable->step, type);
337                 g_value_set_int (&variable->step, 1);
338                 variable->is_numeric = TRUE;
339         }
340
341         else if (strcmp ("i2", data_type) == 0) {
342                 type = G_TYPE_INT;
343                 g_value_init (&variable->minimum, type);
344                 g_value_set_int (&variable->minimum, G_MININT16);
345                 g_value_init (&variable->maximum, type);
346                 g_value_set_int (&variable->maximum, G_MAXINT16);
347                 g_value_init (&variable->step, type);
348                 g_value_set_int (&variable->step, 1);
349                 variable->is_numeric = TRUE;
350         }
351
352         else if (strcmp ("i4", data_type) == 0 ||
353                  strcmp ("int", data_type) == 0) {
354                 type = G_TYPE_INT;
355                 g_value_init (&variable->minimum, type);
356                 g_value_set_int (&variable->minimum, G_MININT32);
357                 g_value_init (&variable->maximum, type);
358                 g_value_set_int (&variable->maximum, G_MAXINT32);
359                 g_value_init (&variable->step, type);
360                 g_value_set_int (&variable->step, 1);
361                 variable->is_numeric = TRUE;
362         }
363
364         else if (strcmp ("ui1", data_type) == 0) {
365                 type = G_TYPE_UINT;
366                 g_value_init (&variable->minimum, type);
367                 g_value_set_uint (&variable->minimum, 0);
368                 g_value_init (&variable->maximum, type);
369                 g_value_set_uint (&variable->maximum, G_MAXUINT8);
370                 g_value_init (&variable->step, type);
371                 g_value_set_uint (&variable->step, 1);
372                 variable->is_numeric = TRUE;
373         }
374
375         else if (strcmp ("ui2", data_type) == 0) {
376                 type = G_TYPE_UINT;
377                 g_value_init (&variable->minimum, type);
378                 g_value_set_uint (&variable->minimum, 0);
379                 g_value_init (&variable->maximum, type);
380                 g_value_set_uint (&variable->maximum, G_MAXUINT16);
381                 g_value_init (&variable->step, type);
382                 g_value_set_uint (&variable->step, 1);
383                 variable->is_numeric = TRUE;
384         }
385
386         else if (strcmp ("ui4", data_type) == 0) {
387                 type = G_TYPE_UINT;
388                 g_value_init (&variable->minimum, type);
389                 g_value_set_uint (&variable->minimum, 0);
390                 g_value_init (&variable->maximum, type);
391                 g_value_set_uint (&variable->maximum, G_MAXUINT32);
392                 g_value_init (&variable->step, type);
393                 g_value_set_uint (&variable->step, 1);
394                 variable->is_numeric = TRUE;
395         }
396
397         else if (strcmp ("r4", data_type) == 0) {
398                 type = G_TYPE_FLOAT;
399                 g_value_init (&variable->minimum, type);
400                 g_value_set_float (&variable->minimum, -G_MAXFLOAT);
401                 g_value_init (&variable->maximum, type);
402                 g_value_set_float (&variable->maximum, G_MAXFLOAT);
403                 g_value_init (&variable->step, type);
404                 g_value_set_float (&variable->step, 1.0);
405                 variable->is_numeric = TRUE;
406         }
407
408         else if (strcmp ("r8", data_type) == 0 ||
409                  strcmp ("number", data_type) == 0) {
410                 type = G_TYPE_DOUBLE;
411                 g_value_init (&variable->minimum, type);
412                 g_value_set_double (&variable->minimum,  -G_MAXDOUBLE);
413                 g_value_init (&variable->maximum, type);
414                 g_value_set_double (&variable->maximum, G_MAXDOUBLE);
415                 g_value_init (&variable->step, type);
416                 g_value_set_double (&variable->step, 1.0);
417                 variable->is_numeric = TRUE;
418         }
419
420         else if (strcmp ("fixed.14.4", data_type) == 0) {
421                 /* Just how did this get into the UPnP specs? */
422                 type = G_TYPE_DOUBLE;
423                 g_value_init (&variable->minimum, type);
424                 g_value_set_double (&variable->minimum,  -MAX_FIXED_14_4);
425                 g_value_init (&variable->maximum, type);
426                 g_value_set_double (&variable->maximum, MAX_FIXED_14_4);
427                 g_value_init (&variable->step, type);
428                 g_value_set_double (&variable->step, 1.0);
429                 variable->is_numeric = TRUE;
430         }
431         /* TODO: "float", "int" */
432
433         else {
434                 type = gupnp_data_type_to_gtype (data_type);
435         }
436
437         if (type == G_TYPE_INVALID) {
438                 g_warning ("Unknown type '%s' in the SCPD", data_type);
439                 return FALSE;
440         }
441
442         g_value_init (&variable->default_value, type);
443         variable->type = type;
444
445         return TRUE;
446 }
447
448 /*
449  *
450  * Creates a #GUPnPServiceStateVariableInfo, constructed from the stateVariable
451  * node @variable_node in the SCPD document
452  *
453  */
454 static GUPnPServiceStateVariableInfo *
455 get_state_variable (xmlNodePtr variable_node)
456 {
457         GUPnPServiceStateVariableInfo *variable;
458         xmlChar *send_events;
459         char *data_type;
460         gboolean success;
461
462         data_type = xml_util_get_child_element_content_glib (variable_node,
463                                                              "dataType");
464         if (!data_type) {
465                 /* We can't report much about a state variable whose dataType
466                  * is not specified so better not report anything at all */
467                 return NULL;
468         }
469
470         variable = g_slice_new0 (GUPnPServiceStateVariableInfo);
471
472         success = set_variable_type (variable, data_type);
473         g_free (data_type);
474         if (!success)
475                 return NULL;
476
477         set_variable_limits (variable_node, variable);
478         set_default_value (variable_node, variable);
479
480         send_events = xml_util_get_child_element_content
481                                        (variable_node, "sendEventsAttribute");
482         if (send_events == NULL) {
483                 /* Some documents put it as attribute of the tag */
484                 send_events = xml_util_get_attribute_contents (variable_node,
485                                                                "sendEvents");
486         }
487
488         if (send_events) {
489                 if (strcmp ("yes", (char *) send_events) == 0)
490                         variable->send_events = TRUE;
491                 xmlFree (send_events);
492         }
493
494         return variable;
495 }
496
497 /*
498  *
499  * Creates a #GUPnPServiceActionArgInfo, constructed from the argument node
500  * @argument_node in the SCPD document
501  *
502  */
503 static GUPnPServiceActionArgInfo *
504 get_action_argument (xmlNodePtr argument_node)
505 {
506         GUPnPServiceActionArgInfo *argument;
507         char *name, *state_var;
508         xmlChar *direction;
509
510         name      = xml_util_get_child_element_content_glib
511                                        (argument_node, "name");
512         state_var = xml_util_get_child_element_content_glib
513                                        (argument_node, "relatedStateVariable");
514         direction = xml_util_get_child_element_content
515                                        (argument_node, "direction");
516
517         if (!name || !state_var || !direction) {
518                 g_free (name);
519                 g_free (state_var);
520
521                 xmlFree (direction);
522
523                 return NULL;
524         }
525
526         argument = g_slice_new0 (GUPnPServiceActionArgInfo);
527
528         argument->name = name;
529         argument->related_state_variable = state_var;
530
531         if (strcmp ("in", (char *) direction) == 0)
532                 argument->direction = GUPNP_SERVICE_ACTION_ARG_DIRECTION_IN;
533         else
534                 argument->direction = GUPNP_SERVICE_ACTION_ARG_DIRECTION_OUT;
535         xmlFree (direction);
536
537         if (xml_util_get_element (argument_node, "retval", NULL) != NULL)
538                 argument->retval = TRUE;
539
540         return argument;
541 }
542
543 /*
544  *
545  * Creates a #GList of all the arguments (of type #GUPnPServiceActionArgInfo)
546  * from the action node @action_node in the SCPD document
547  *
548  */
549 static GList *
550 get_action_arguments (xmlNodePtr action_node)
551 {
552         GList *argument_list = NULL;
553         xmlNodePtr arglist_node;
554         xmlNodePtr argument_node;
555
556         arglist_node = xml_util_get_element ((xmlNode *) action_node,
557                                              "argumentList",
558                                              NULL);
559         if (!arglist_node)
560                 return NULL;
561
562         /* Iterate over all the arguments */
563         for (argument_node = arglist_node->children;
564              argument_node;
565              argument_node = argument_node->next) {
566                 GUPnPServiceActionArgInfo *argument;
567
568                 if (strcmp ("argument", (char *) argument_node->name) != 0)
569                         continue;
570
571                 argument = get_action_argument (argument_node);
572                 if (argument) {
573                         argument_list = g_list_append (argument_list,
574                                                         argument);
575                 }
576         }
577
578         return argument_list;
579 }
580
581 /*
582  *
583  * Creates a #GList of all the actions (of type #GUPnPServiceActionInfo) from
584  * the SCPD document.
585  *
586  */
587 static GList *
588 get_actions (xmlNode *list_element)
589 {
590         GList *actions = NULL;
591         xmlNodePtr action_node;
592
593         /* Iterate over all action elements */
594         for (action_node = list_element->children;
595              action_node;
596              action_node = action_node->next) {
597                 GUPnPServiceActionInfo *action_info;
598                 GList *arguments;
599                 char *name;
600
601                 if (strcmp ("action", (char *) action_node->name) != 0)
602                         continue;
603
604                 name = xml_util_get_child_element_content_glib (action_node,
605                                                                 "name");
606                 if (!name)
607                         continue;
608
609                 arguments = get_action_arguments (action_node);
610                 if (!arguments) {
611                         g_free (name);
612
613                         continue;
614                 }
615
616                 action_info = g_slice_new0 (GUPnPServiceActionInfo);
617                 action_info->name = name;
618                 action_info->arguments = arguments;
619
620                 actions = g_list_append (actions, action_info);
621         }
622
623         return actions;
624 }
625
626 /*
627  *
628  * Creates a #GList of all the state variables (of type
629  * #GUPnPServiceStateVariableInfo) from the SCPD document.
630  *
631  */
632 static GList *
633 get_state_variables (xmlNode *list_element)
634 {
635         GList *variables = NULL;
636         xmlNodePtr variable_node;
637
638         /* Iterate over all variable elements */
639         for (variable_node = list_element->children;
640              variable_node;
641              variable_node = variable_node->next) {
642                 char *name;
643                 GUPnPServiceStateVariableInfo *variable;
644
645                 if (strcmp ("stateVariable",
646                             (char *) variable_node->name) != 0)
647                         continue;
648
649                 name = xml_util_get_child_element_content_glib (variable_node,
650                                                                 "name");
651                 if (!name)
652                         continue;
653
654                 variable = get_state_variable (variable_node);
655                 if (!variable) {
656                         g_free (name);
657
658                         continue;
659                 }
660
661                 variable->name = name;
662                 variables = g_list_append (variables, variable);
663         }
664
665         return variables;
666 }
667
668 /*
669  * Creates the #GHashTable's of action and state variable information
670  *
671  * */
672 static void
673 construct_introspection_info (GUPnPServiceIntrospection *introspection,
674                               xmlDoc                    *scpd)
675 {
676         xmlNode *element;
677
678         g_return_if_fail (scpd != NULL);
679
680         /* Get actionList element */
681         element = xml_util_get_element ((xmlNode *) scpd,
682                                         "scpd",
683                                         "actionList",
684                                         NULL);
685         if (element)
686                 introspection->priv->actions = get_actions (element);
687
688         /* Get serviceStateTable element */
689         element = xml_util_get_element ((xmlNode *) scpd,
690                                         "scpd",
691                                         "serviceStateTable",
692                                         NULL);
693         if (element)
694                 introspection->priv->variables = get_state_variables (element);
695 }
696
697 static void
698 collect_action_names (gpointer data,
699                       gpointer user_data)
700 {
701         GList **action_names = (GList **) user_data;
702         GUPnPServiceActionInfo *action_info = (GUPnPServiceActionInfo *) data;
703
704         *action_names = g_list_append (*action_names, action_info->name);
705 }
706
707 static void
708 collect_variable_names (gpointer data,
709                         gpointer user_data)
710 {
711         GList **variable_names = (GList **) user_data;
712         GUPnPServiceStateVariableInfo *variable =
713                 (GUPnPServiceStateVariableInfo *) data;
714
715         *variable_names = g_list_append (*variable_names, variable->name);
716 }
717
718 /**
719  * gupnp_service_introspection_new:
720  * @scpd: Pointer to the SCPD of the service to create a introspection for
721  *
722  * Create a new #GUPnPServiceIntrospection for the service created from the
723  * SCPD @scpd or %NULL.
724  *
725  * Return value: A new #GUPnPServiceIntrospection.
726  **/
727 GUPnPServiceIntrospection *
728 gupnp_service_introspection_new (xmlDoc *scpd)
729 {
730         GUPnPServiceIntrospection *introspection;
731
732         g_return_val_if_fail (scpd != NULL, NULL);
733
734         introspection = g_object_new (GUPNP_TYPE_SERVICE_INTROSPECTION,
735                                       "scpd", scpd,
736                                       NULL);
737
738         if (introspection->priv->actions == NULL &&
739             introspection->priv->variables == NULL) {
740                 g_object_unref (introspection);
741                 introspection = NULL;
742         }
743
744         return introspection;
745 }
746
747 /**
748  * gupnp_service_introspection_list_action_names:
749  * @introspection: A #GUPnPServiceIntrospection
750  *
751  * Returns a GList of names of all the actions in this service.
752  *
753  * Return value: (transfer none) (element-type utf8) : A GList of names of all
754  * the actions or %NULL. Do not modify or free it or its contents.
755  **/
756 const GList *
757 gupnp_service_introspection_list_action_names
758                         (GUPnPServiceIntrospection *introspection)
759 {
760         if (introspection->priv->actions == NULL)
761                 return NULL;
762
763         if (introspection->priv->action_names == NULL) {
764                 g_list_foreach (introspection->priv->actions,
765                                 collect_action_names,
766                                 &introspection->priv->action_names);
767         }
768
769         return introspection->priv->action_names;
770 }
771
772 /**
773  * gupnp_service_introspection_list_actions:
774  * @introspection: A #GUPnPServiceIntrospection
775  *
776  * Returns a #GList of all the actions (of type #GUPnPServiceActionInfo) in
777  * this service.
778  *
779  * Return value: (element-type GUPnP.ServiceActionInfo) (transfer none): A
780  * #GList of all the actions or %NULL. Do not modify or free it or its
781  * contents.
782  **/
783 const GList *
784 gupnp_service_introspection_list_actions
785                         (GUPnPServiceIntrospection *introspection)
786 {
787         return introspection->priv->actions;
788 }
789
790 /**
791  * gupnp_service_introspection_list_state_variables:
792  * @introspection: A #GUPnPServiceIntrospection
793  *
794  * Returns a GList of all the state variables (of type
795  * #GUPnPServiceStateVariableInfo) in this service.
796  *
797  * Return value: (element-type GUPnP.ServiceStateVariableInfo) (transfer none):
798  * A #GList of all the state variables or %NULL. Do not modify or free it or
799  * its contents.
800  *
801  **/
802 const GList *
803 gupnp_service_introspection_list_state_variables
804                         (GUPnPServiceIntrospection *introspection)
805 {
806         return introspection->priv->variables;
807 }
808
809 /**
810  * gupnp_service_introspection_list_state_variable_names:
811  * @introspection: A #GUPnPServiceIntrospection
812  *
813  * Returns a #GList of names of all the state variables in this service.
814  *
815  * Return value: (element-type utf8) (transfer none): A #GList of names of all
816  * the state variables or %NULL. Do not modify or free it or its contents.
817  **/
818 const GList *
819 gupnp_service_introspection_list_state_variable_names
820                         (GUPnPServiceIntrospection *introspection)
821 {
822         if (introspection->priv->variables == NULL)
823                 return NULL;
824
825         if (introspection->priv->variable_names == NULL) {
826                 g_list_foreach (introspection->priv->variables,
827                                 collect_variable_names,
828                                 &introspection->priv->variable_names);
829         }
830
831         return introspection->priv->variable_names;
832 }
833
834 static gint
835 state_variable_search_func (GUPnPServiceStateVariableInfo *variable,
836                             const gchar                   *variable_name)
837 {
838         return strcmp (variable->name, variable_name);
839 }
840
841 /**
842  * gupnp_service_introspection_get_state_variable:
843  * @introspection: A #GUPnPServiceIntrospection
844  * @variable_name: The name of the variable to retrieve
845  *
846  * Returns the state variable by the name @variable_name in this service.
847  *
848  * Return value: (transfer none): the state variable or %NULL. Do not modify or
849  * free it.
850  **/
851 const GUPnPServiceStateVariableInfo *
852 gupnp_service_introspection_get_state_variable
853                         (GUPnPServiceIntrospection *introspection,
854                          const gchar               *variable_name)
855 {
856         GList *variable_node;
857
858         if (introspection->priv->variables == NULL)
859                 return NULL;
860
861         variable_node = g_list_find_custom (
862                                 introspection->priv->variables,
863                                 (gpointer) variable_name,
864                                 (GCompareFunc) state_variable_search_func);
865         if (variable_node == NULL)
866                 return NULL;
867
868         return (GUPnPServiceStateVariableInfo *) variable_node->data;
869 }
870
871 static gint
872 action_search_func (GUPnPServiceActionInfo *action,
873                     const gchar            *action_name)
874 {
875         return strcmp (action->name, action_name);
876 }
877
878 /**
879  * gupnp_service_introspection_get_action:
880  * @introspection: A #GUPnPServiceIntrospection
881  * @action_name: The name of the action to retrieve
882  *
883  * Returns the action by the name @action_name in this service.
884  *
885  * Return value: (transfer none): the action or %NULL. Do not modify or free
886  * it.
887  **/
888 const GUPnPServiceActionInfo *
889 gupnp_service_introspection_get_action
890                         (GUPnPServiceIntrospection *introspection,
891                          const gchar               *action_name)
892 {
893         GList *action_node;
894
895         if (introspection->priv->variables == NULL)
896                 return NULL;
897
898         action_node = g_list_find_custom (
899                                 introspection->priv->actions,
900                                 (gpointer) action_name,
901                                 (GCompareFunc) action_search_func);
902         if (action_node == NULL)
903                 return NULL;
904
905         return (GUPnPServiceActionInfo *) action_node->data;
906 }