2 * Copyright (C) 2007 Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
3 * Copyright (C) 2006, 2007 OpenedHand Ltd.
5 * Author: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
6 * Jorn Baayen <jorn@openedhand.com>
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.
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.
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.
25 * SECTION:gupnp-service-introspection
26 * @short_description: Service introspection class.
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.
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.
41 * This class exposes internals of the UPnP protocol and should not need
42 * to be used for regular device or control point development.
46 #include <libsoup/soup.h>
49 #include "gupnp-service-introspection.h"
50 #include "gupnp-service-introspection-private.h"
52 #include "gvalue-util.h"
53 #include "gupnp-types.h"
54 #include "gupnp-types-private.h"
56 #define MAX_FIXED_14_4 99999999999999.9999
58 G_DEFINE_TYPE (GUPnPServiceIntrospection,
59 gupnp_service_introspection,
62 struct _GUPnPServiceIntrospectionPrivate {
66 /* For caching purposes */
68 GList *variable_names;
77 construct_introspection_info (GUPnPServiceIntrospection *introspection,
81 * gupnp_service_state_variable_info_free:
82 * @argument: A #GUPnPServiceStateVariableInfo
84 * Frees a #GUPnPServiceStateVariableInfo.
88 gupnp_service_state_variable_info_free
89 (GUPnPServiceStateVariableInfo *variable)
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);
99 g_list_free_full (variable->allowed_values, g_free);
101 g_slice_free (GUPnPServiceStateVariableInfo, variable);
105 gupnp_service_introspection_init (GUPnPServiceIntrospection *introspection)
107 introspection->priv =
108 G_TYPE_INSTANCE_GET_PRIVATE (introspection,
109 GUPNP_TYPE_SERVICE_INTROSPECTION,
110 GUPnPServiceIntrospectionPrivate);
114 gupnp_service_introspection_set_property (GObject *object,
119 GUPnPServiceIntrospection *introspection;
121 introspection = GUPNP_SERVICE_INTROSPECTION (object);
123 switch (property_id) {
125 /* Construct introspection data */
126 construct_introspection_info (introspection,
127 g_value_get_pointer (value));
130 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
137 * gupnp_service_action_arg_info_free:
138 * @argument: A #GUPnPServiceActionArgInfo
140 * Frees a #GUPnPServiceActionArgInfo.
144 gupnp_service_action_arg_info_free (GUPnPServiceActionArgInfo *argument)
146 g_free (argument->name);
147 g_free (argument->related_state_variable);
149 g_slice_free (GUPnPServiceActionArgInfo, argument);
153 * gupnp_service_action_info_free:
154 * @argument: A #GUPnPServiceActionInfo
156 * Frees a #GUPnPServiceActionInfo.
160 gupnp_service_action_info_free (GUPnPServiceActionInfo *action_info)
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);
171 gupnp_service_introspection_finalize (GObject *object)
173 GUPnPServiceIntrospection *introspection;
175 introspection = GUPNP_SERVICE_INTROSPECTION (object);
177 g_list_free_full (introspection->priv->variables,
178 (GDestroyNotify) gupnp_service_state_variable_info_free);
180 g_list_free_full (introspection->priv->actions,
181 (GDestroyNotify) gupnp_service_action_info_free);
183 /* Contents don't need to be freed, they were owned by priv->variables
185 if (introspection->priv->variable_names)
186 g_list_free (introspection->priv->variable_names);
188 /* Contents don't need to be freed, they were owned by priv->actions
190 if (introspection->priv->action_names)
191 g_list_free (introspection->priv->action_names);
195 gupnp_service_introspection_class_init (GUPnPServiceIntrospectionClass *klass)
197 GObjectClass *object_class;
199 object_class = G_OBJECT_CLASS (klass);
201 object_class->set_property = gupnp_service_introspection_set_property;
202 object_class->finalize = gupnp_service_introspection_finalize;
204 g_type_class_add_private (klass,
205 sizeof (GUPnPServiceIntrospectionPrivate));
208 * GUPnPServiceIntrospection:scpd:
210 * The scpd of the device description file.
212 g_object_class_install_property
215 g_param_spec_pointer ("scpd",
219 G_PARAM_CONSTRUCT_ONLY));
223 set_default_value (xmlNodePtr variable_node,
224 GUPnPServiceStateVariableInfo *variable)
226 xmlChar *default_str;
228 default_str = xml_util_get_child_element_content (variable_node,
231 gvalue_util_set_value_from_string (&variable->default_value,
232 (char *) default_str);
234 xmlFree (default_str);
239 set_string_value_limits (xmlNodePtr limit_node,
242 xmlNodePtr value_node;
244 for (value_node = limit_node->children;
246 value_node = value_node->next) {
247 xmlChar *allowed_value;
249 if (strcmp ("allowedValue", (char *) value_node->name) != 0)
252 allowed_value = xmlNodeGetContent (value_node);
256 *limits = g_list_append (*limits,
257 g_strdup ((char *) allowed_value));
258 xmlFree (allowed_value);
263 set_value_limit_by_name (xmlNodePtr limit_node,
265 const char *limit_name)
269 limit_str = xml_util_get_child_element_content (limit_node,
272 gvalue_util_set_value_from_string (limit, (char *) limit_str);
279 set_variable_limits (xmlNodePtr variable_node,
280 GUPnPServiceStateVariableInfo *variable)
282 xmlNodePtr limit_node;
284 if (variable->is_numeric) {
285 limit_node = xml_util_get_element (variable_node,
288 if (limit_node == NULL)
291 set_value_limit_by_name (limit_node,
292 &(variable->minimum),
294 set_value_limit_by_name (limit_node,
295 &(variable->maximum),
297 set_value_limit_by_name (limit_node,
300 } else if (variable->type == G_TYPE_STRING) {
301 limit_node = xml_util_get_element (variable_node,
304 if (limit_node == NULL)
307 set_string_value_limits (limit_node,
308 &(variable->allowed_values));
313 set_variable_type (GUPnPServiceStateVariableInfo *variable,
318 if (strcmp ("string", data_type) == 0) {
319 type = G_TYPE_STRING;
322 else if (strcmp ("char", data_type) == 0) {
326 else if (strcmp ("boolean", data_type) == 0) {
327 type = G_TYPE_BOOLEAN;
330 else if (strcmp ("i1", data_type) == 0) {
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;
341 else if (strcmp ("i2", data_type) == 0) {
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;
352 else if (strcmp ("i4", data_type) == 0 ||
353 strcmp ("int", data_type) == 0) {
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;
364 else if (strcmp ("ui1", data_type) == 0) {
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;
375 else if (strcmp ("ui2", data_type) == 0) {
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;
386 else if (strcmp ("ui4", data_type) == 0) {
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;
397 else if (strcmp ("r4", data_type) == 0) {
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;
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;
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;
431 /* TODO: "float", "int" */
434 type = gupnp_data_type_to_gtype (data_type);
437 if (type == G_TYPE_INVALID) {
438 g_warning ("Unknown type '%s' in the SCPD", data_type);
442 g_value_init (&variable->default_value, type);
443 variable->type = type;
450 * Creates a #GUPnPServiceStateVariableInfo, constructed from the stateVariable
451 * node @variable_node in the SCPD document
454 static GUPnPServiceStateVariableInfo *
455 get_state_variable (xmlNodePtr variable_node)
457 GUPnPServiceStateVariableInfo *variable;
458 xmlChar *send_events;
462 data_type = xml_util_get_child_element_content_glib (variable_node,
465 /* We can't report much about a state variable whose dataType
466 * is not specified so better not report anything at all */
470 variable = g_slice_new0 (GUPnPServiceStateVariableInfo);
472 success = set_variable_type (variable, data_type);
477 set_variable_limits (variable_node, variable);
478 set_default_value (variable_node, variable);
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,
489 if (strcmp ("yes", (char *) send_events) == 0)
490 variable->send_events = TRUE;
491 xmlFree (send_events);
499 * Creates a #GUPnPServiceActionArgInfo, constructed from the argument node
500 * @argument_node in the SCPD document
503 static GUPnPServiceActionArgInfo *
504 get_action_argument (xmlNodePtr argument_node)
506 GUPnPServiceActionArgInfo *argument;
507 char *name, *state_var;
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");
517 if (!name || !state_var || !direction) {
526 argument = g_slice_new0 (GUPnPServiceActionArgInfo);
528 argument->name = name;
529 argument->related_state_variable = state_var;
531 if (strcmp ("in", (char *) direction) == 0)
532 argument->direction = GUPNP_SERVICE_ACTION_ARG_DIRECTION_IN;
534 argument->direction = GUPNP_SERVICE_ACTION_ARG_DIRECTION_OUT;
537 if (xml_util_get_element (argument_node, "retval", NULL) != NULL)
538 argument->retval = TRUE;
545 * Creates a #GList of all the arguments (of type #GUPnPServiceActionArgInfo)
546 * from the action node @action_node in the SCPD document
550 get_action_arguments (xmlNodePtr action_node)
552 GList *argument_list = NULL;
553 xmlNodePtr arglist_node;
554 xmlNodePtr argument_node;
556 arglist_node = xml_util_get_element ((xmlNode *) action_node,
562 /* Iterate over all the arguments */
563 for (argument_node = arglist_node->children;
565 argument_node = argument_node->next) {
566 GUPnPServiceActionArgInfo *argument;
568 if (strcmp ("argument", (char *) argument_node->name) != 0)
571 argument = get_action_argument (argument_node);
573 argument_list = g_list_append (argument_list,
578 return argument_list;
583 * Creates a #GList of all the actions (of type #GUPnPServiceActionInfo) from
588 get_actions (xmlNode *list_element)
590 GList *actions = NULL;
591 xmlNodePtr action_node;
593 /* Iterate over all action elements */
594 for (action_node = list_element->children;
596 action_node = action_node->next) {
597 GUPnPServiceActionInfo *action_info;
601 if (strcmp ("action", (char *) action_node->name) != 0)
604 name = xml_util_get_child_element_content_glib (action_node,
609 arguments = get_action_arguments (action_node);
616 action_info = g_slice_new0 (GUPnPServiceActionInfo);
617 action_info->name = name;
618 action_info->arguments = arguments;
620 actions = g_list_append (actions, action_info);
628 * Creates a #GList of all the state variables (of type
629 * #GUPnPServiceStateVariableInfo) from the SCPD document.
633 get_state_variables (xmlNode *list_element)
635 GList *variables = NULL;
636 xmlNodePtr variable_node;
638 /* Iterate over all variable elements */
639 for (variable_node = list_element->children;
641 variable_node = variable_node->next) {
643 GUPnPServiceStateVariableInfo *variable;
645 if (strcmp ("stateVariable",
646 (char *) variable_node->name) != 0)
649 name = xml_util_get_child_element_content_glib (variable_node,
654 variable = get_state_variable (variable_node);
661 variable->name = name;
662 variables = g_list_append (variables, variable);
669 * Creates the #GHashTable's of action and state variable information
673 construct_introspection_info (GUPnPServiceIntrospection *introspection,
678 g_return_if_fail (scpd != NULL);
680 /* Get actionList element */
681 element = xml_util_get_element ((xmlNode *) scpd,
686 introspection->priv->actions = get_actions (element);
688 /* Get serviceStateTable element */
689 element = xml_util_get_element ((xmlNode *) scpd,
694 introspection->priv->variables = get_state_variables (element);
698 collect_action_names (gpointer data,
701 GList **action_names = (GList **) user_data;
702 GUPnPServiceActionInfo *action_info = (GUPnPServiceActionInfo *) data;
704 *action_names = g_list_append (*action_names, action_info->name);
708 collect_variable_names (gpointer data,
711 GList **variable_names = (GList **) user_data;
712 GUPnPServiceStateVariableInfo *variable =
713 (GUPnPServiceStateVariableInfo *) data;
715 *variable_names = g_list_append (*variable_names, variable->name);
719 * gupnp_service_introspection_new:
720 * @scpd: Pointer to the SCPD of the service to create a introspection for
722 * Create a new #GUPnPServiceIntrospection for the service created from the
723 * SCPD @scpd or %NULL.
725 * Return value: A new #GUPnPServiceIntrospection.
727 GUPnPServiceIntrospection *
728 gupnp_service_introspection_new (xmlDoc *scpd)
730 GUPnPServiceIntrospection *introspection;
732 g_return_val_if_fail (scpd != NULL, NULL);
734 introspection = g_object_new (GUPNP_TYPE_SERVICE_INTROSPECTION,
738 if (introspection->priv->actions == NULL &&
739 introspection->priv->variables == NULL) {
740 g_object_unref (introspection);
741 introspection = NULL;
744 return introspection;
748 * gupnp_service_introspection_list_action_names:
749 * @introspection: A #GUPnPServiceIntrospection
751 * Returns a GList of names of all the actions in this service.
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.
757 gupnp_service_introspection_list_action_names
758 (GUPnPServiceIntrospection *introspection)
760 if (introspection->priv->actions == NULL)
763 if (introspection->priv->action_names == NULL) {
764 g_list_foreach (introspection->priv->actions,
765 collect_action_names,
766 &introspection->priv->action_names);
769 return introspection->priv->action_names;
773 * gupnp_service_introspection_list_actions:
774 * @introspection: A #GUPnPServiceIntrospection
776 * Returns a #GList of all the actions (of type #GUPnPServiceActionInfo) in
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
784 gupnp_service_introspection_list_actions
785 (GUPnPServiceIntrospection *introspection)
787 return introspection->priv->actions;
791 * gupnp_service_introspection_list_state_variables:
792 * @introspection: A #GUPnPServiceIntrospection
794 * Returns a GList of all the state variables (of type
795 * #GUPnPServiceStateVariableInfo) in this service.
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
803 gupnp_service_introspection_list_state_variables
804 (GUPnPServiceIntrospection *introspection)
806 return introspection->priv->variables;
810 * gupnp_service_introspection_list_state_variable_names:
811 * @introspection: A #GUPnPServiceIntrospection
813 * Returns a #GList of names of all the state variables in this service.
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.
819 gupnp_service_introspection_list_state_variable_names
820 (GUPnPServiceIntrospection *introspection)
822 if (introspection->priv->variables == NULL)
825 if (introspection->priv->variable_names == NULL) {
826 g_list_foreach (introspection->priv->variables,
827 collect_variable_names,
828 &introspection->priv->variable_names);
831 return introspection->priv->variable_names;
835 state_variable_search_func (GUPnPServiceStateVariableInfo *variable,
836 const gchar *variable_name)
838 return strcmp (variable->name, variable_name);
842 * gupnp_service_introspection_get_state_variable:
843 * @introspection: A #GUPnPServiceIntrospection
844 * @variable_name: The name of the variable to retrieve
846 * Returns the state variable by the name @variable_name in this service.
848 * Return value: (transfer none): the state variable or %NULL. Do not modify or
851 const GUPnPServiceStateVariableInfo *
852 gupnp_service_introspection_get_state_variable
853 (GUPnPServiceIntrospection *introspection,
854 const gchar *variable_name)
856 GList *variable_node;
858 if (introspection->priv->variables == NULL)
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)
868 return (GUPnPServiceStateVariableInfo *) variable_node->data;
872 action_search_func (GUPnPServiceActionInfo *action,
873 const gchar *action_name)
875 return strcmp (action->name, action_name);
879 * gupnp_service_introspection_get_action:
880 * @introspection: A #GUPnPServiceIntrospection
881 * @action_name: The name of the action to retrieve
883 * Returns the action by the name @action_name in this service.
885 * Return value: (transfer none): the action or %NULL. Do not modify or free
888 const GUPnPServiceActionInfo *
889 gupnp_service_introspection_get_action
890 (GUPnPServiceIntrospection *introspection,
891 const gchar *action_name)
895 if (introspection->priv->variables == NULL)
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)
905 return (GUPnPServiceActionInfo *) action_node->data;