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., 59 Temple Place - Suite 330,
21 * Boston, MA 02111-1307, 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);
98 g_list_foreach (variable->allowed_values,
101 g_list_free (variable->allowed_values);
103 g_slice_free (GUPnPServiceStateVariableInfo, variable);
107 gupnp_service_introspection_init (GUPnPServiceIntrospection *introspection)
109 introspection->priv =
110 G_TYPE_INSTANCE_GET_PRIVATE (introspection,
111 GUPNP_TYPE_SERVICE_INTROSPECTION,
112 GUPnPServiceIntrospectionPrivate);
116 gupnp_service_introspection_set_property (GObject *object,
121 GUPnPServiceIntrospection *introspection;
123 introspection = GUPNP_SERVICE_INTROSPECTION (object);
125 switch (property_id) {
127 /* Construct introspection data */
128 construct_introspection_info (introspection,
129 g_value_get_pointer (value));
132 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
139 * gupnp_service_action_arg_info_free:
140 * @argument: A #GUPnPServiceActionArgInfo
142 * Frees a #GUPnPServiceActionArgInfo.
146 gupnp_service_action_arg_info_free (GUPnPServiceActionArgInfo *argument)
148 g_free (argument->name);
149 g_free (argument->related_state_variable);
151 g_slice_free (GUPnPServiceActionArgInfo, argument);
155 * gupnp_service_action_info_free:
156 * @argument: A #GUPnPServiceActionInfo
158 * Frees a #GUPnPServiceActionInfo.
162 gupnp_service_action_info_free (GUPnPServiceActionInfo *action_info)
166 g_free (action_info->name);
168 for (iter = action_info->arguments; iter; iter = iter->next) {
169 gupnp_service_action_arg_info_free (
170 (GUPnPServiceActionArgInfo *) iter->data);
172 g_list_free (action_info->arguments);
173 g_slice_free (GUPnPServiceActionInfo, action_info);
177 gupnp_service_introspection_finalize (GObject *object)
179 GUPnPServiceIntrospection *introspection;
181 introspection = GUPNP_SERVICE_INTROSPECTION (object);
183 if (introspection->priv->variables) {
184 g_list_foreach (introspection->priv->variables,
185 (GFunc) gupnp_service_state_variable_info_free,
187 g_list_free (introspection->priv->variables);
190 if (introspection->priv->actions) {
191 g_list_foreach (introspection->priv->actions,
192 (GFunc) gupnp_service_action_info_free,
194 g_list_free (introspection->priv->actions);
197 if (introspection->priv->variable_names)
198 g_list_free (introspection->priv->variable_names);
200 if (introspection->priv->action_names)
201 g_list_free (introspection->priv->action_names);
205 gupnp_service_introspection_class_init (GUPnPServiceIntrospectionClass *klass)
207 GObjectClass *object_class;
209 object_class = G_OBJECT_CLASS (klass);
211 object_class->set_property = gupnp_service_introspection_set_property;
212 object_class->finalize = gupnp_service_introspection_finalize;
214 g_type_class_add_private (klass,
215 sizeof (GUPnPServiceIntrospectionPrivate));
218 * GUPnPServiceIntrospection:scpd:
220 * The scpd of the device description file.
222 g_object_class_install_property
225 g_param_spec_pointer ("scpd",
229 G_PARAM_CONSTRUCT_ONLY));
233 set_default_value (xmlNodePtr variable_node,
234 GUPnPServiceStateVariableInfo *variable)
236 xmlChar *default_str;
238 default_str = xml_util_get_child_element_content (variable_node,
241 gvalue_util_set_value_from_string (&variable->default_value,
242 (char *) default_str);
244 xmlFree (default_str);
249 set_string_value_limits (xmlNodePtr limit_node,
252 xmlNodePtr value_node;
254 for (value_node = limit_node->children;
256 value_node = value_node->next) {
257 xmlChar *allowed_value;
259 if (strcmp ("allowedValue", (char *) value_node->name) != 0)
262 allowed_value = xmlNodeGetContent (value_node);
266 *limits = g_list_append (*limits,
267 g_strdup ((char *) allowed_value));
268 xmlFree (allowed_value);
273 set_value_limit_by_name (xmlNodePtr limit_node,
275 const char *limit_name)
279 limit_str = xml_util_get_child_element_content (limit_node,
282 gvalue_util_set_value_from_string (limit, (char *) limit_str);
289 set_variable_limits (xmlNodePtr variable_node,
290 GUPnPServiceStateVariableInfo *variable)
292 xmlNodePtr limit_node;
294 if (variable->is_numeric) {
295 limit_node = xml_util_get_element (variable_node,
298 if (limit_node == NULL)
301 set_value_limit_by_name (limit_node,
302 &(variable->minimum),
304 set_value_limit_by_name (limit_node,
305 &(variable->maximum),
307 set_value_limit_by_name (limit_node,
310 } else if (variable->type == G_TYPE_STRING) {
311 limit_node = xml_util_get_element (variable_node,
314 if (limit_node == NULL)
317 set_string_value_limits (limit_node,
318 &(variable->allowed_values));
323 set_variable_type (GUPnPServiceStateVariableInfo *variable,
328 if (strcmp ("string", data_type) == 0) {
329 type = G_TYPE_STRING;
332 else if (strcmp ("char", data_type) == 0) {
336 else if (strcmp ("boolean", data_type) == 0) {
337 type = G_TYPE_BOOLEAN;
340 else if (strcmp ("i1", data_type) == 0) {
342 g_value_init (&variable->minimum, type);
343 g_value_set_int (&variable->minimum, G_MININT8);
344 g_value_init (&variable->maximum, type);
345 g_value_set_int (&variable->maximum, G_MAXINT8);
346 g_value_init (&variable->step, type);
347 g_value_set_int (&variable->step, 1);
348 variable->is_numeric = TRUE;
351 else if (strcmp ("i2", data_type) == 0) {
353 g_value_init (&variable->minimum, type);
354 g_value_set_int (&variable->minimum, G_MININT16);
355 g_value_init (&variable->maximum, type);
356 g_value_set_int (&variable->maximum, G_MAXINT16);
357 g_value_init (&variable->step, type);
358 g_value_set_int (&variable->step, 1);
359 variable->is_numeric = TRUE;
362 else if (strcmp ("i4", data_type) == 0 ||
363 strcmp ("int", data_type) == 0) {
365 g_value_init (&variable->minimum, type);
366 g_value_set_int (&variable->minimum, G_MININT32);
367 g_value_init (&variable->maximum, type);
368 g_value_set_int (&variable->maximum, G_MAXINT32);
369 g_value_init (&variable->step, type);
370 g_value_set_int (&variable->step, 1);
371 variable->is_numeric = TRUE;
374 else if (strcmp ("ui1", data_type) == 0) {
376 g_value_init (&variable->minimum, type);
377 g_value_set_uint (&variable->minimum, 0);
378 g_value_init (&variable->maximum, type);
379 g_value_set_uint (&variable->maximum, G_MAXUINT8);
380 g_value_init (&variable->step, type);
381 g_value_set_uint (&variable->step, 1);
382 variable->is_numeric = TRUE;
385 else if (strcmp ("ui2", data_type) == 0) {
387 g_value_init (&variable->minimum, type);
388 g_value_set_uint (&variable->minimum, 0);
389 g_value_init (&variable->maximum, type);
390 g_value_set_uint (&variable->maximum, G_MAXUINT16);
391 g_value_init (&variable->step, type);
392 g_value_set_uint (&variable->step, 1);
393 variable->is_numeric = TRUE;
396 else if (strcmp ("ui4", data_type) == 0) {
398 g_value_init (&variable->minimum, type);
399 g_value_set_uint (&variable->minimum, 0);
400 g_value_init (&variable->maximum, type);
401 g_value_set_uint (&variable->maximum, G_MAXUINT32);
402 g_value_init (&variable->step, type);
403 g_value_set_uint (&variable->step, 1);
404 variable->is_numeric = TRUE;
407 else if (strcmp ("r4", data_type) == 0) {
409 g_value_init (&variable->minimum, type);
410 g_value_set_float (&variable->minimum, -G_MAXFLOAT);
411 g_value_init (&variable->maximum, type);
412 g_value_set_float (&variable->maximum, G_MAXFLOAT);
413 g_value_init (&variable->step, type);
414 g_value_set_float (&variable->step, 1.0);
415 variable->is_numeric = TRUE;
418 else if (strcmp ("r8", data_type) == 0 ||
419 strcmp ("number", data_type) == 0) {
420 type = G_TYPE_DOUBLE;
421 g_value_init (&variable->minimum, type);
422 g_value_set_double (&variable->minimum, -G_MAXDOUBLE);
423 g_value_init (&variable->maximum, type);
424 g_value_set_double (&variable->maximum, G_MAXDOUBLE);
425 g_value_init (&variable->step, type);
426 g_value_set_double (&variable->step, 1.0);
427 variable->is_numeric = TRUE;
430 else if (strcmp ("fixed.14.4", data_type) == 0) {
431 /* Just how did this get into the UPnP specs? */
432 type = G_TYPE_DOUBLE;
433 g_value_init (&variable->minimum, type);
434 g_value_set_double (&variable->minimum, -MAX_FIXED_14_4);
435 g_value_init (&variable->maximum, type);
436 g_value_set_double (&variable->maximum, MAX_FIXED_14_4);
437 g_value_init (&variable->step, type);
438 g_value_set_double (&variable->step, 1.0);
439 variable->is_numeric = TRUE;
441 /* TODO: "float", "int" */
444 type = gupnp_data_type_to_gtype (data_type);
447 if (type == G_TYPE_INVALID) {
448 g_warning ("Unkown type '%s' in the SCPD", data_type);
452 g_value_init (&variable->default_value, type);
453 variable->type = type;
460 * Creates a #GUPnPServiceStateVariableInfo, constructed from the stateVariable
461 * node @variable_node in the SCPD document
464 static GUPnPServiceStateVariableInfo *
465 get_state_variable (xmlNodePtr variable_node)
467 GUPnPServiceStateVariableInfo *variable;
468 xmlChar *send_events;
472 data_type = xml_util_get_child_element_content_glib (variable_node,
475 /* We can't report much about a state variable whose dataType
476 * is not specified so better not report anything at all */
480 variable = g_slice_new0 (GUPnPServiceStateVariableInfo);
482 success = set_variable_type (variable, data_type);
487 set_variable_limits (variable_node, variable);
488 set_default_value (variable_node, variable);
490 send_events = xml_util_get_child_element_content
491 (variable_node, "sendEventsAttribute");
492 if (send_events == NULL) {
493 /* Some documents put it as attribute of the tag */
494 send_events = xml_util_get_attribute_contents (variable_node,
499 if (strcmp ("yes", (char *) send_events) == 0)
500 variable->send_events = TRUE;
501 xmlFree (send_events);
509 * Creates a #GUPnPServiceActionArgInfo, constructed from the argument node
510 * @argument_node in the SCPD document
513 static GUPnPServiceActionArgInfo *
514 get_action_argument (xmlNodePtr argument_node)
516 GUPnPServiceActionArgInfo *argument;
517 char *name, *state_var;
520 name = xml_util_get_child_element_content_glib
521 (argument_node, "name");
522 state_var = xml_util_get_child_element_content_glib
523 (argument_node, "relatedStateVariable");
524 direction = xml_util_get_child_element_content
525 (argument_node, "direction");
527 if (!name || !state_var || !direction) {
536 argument = g_slice_new0 (GUPnPServiceActionArgInfo);
538 argument->name = name;
539 argument->related_state_variable = state_var;
541 if (strcmp ("in", (char *) direction) == 0)
542 argument->direction = GUPNP_SERVICE_ACTION_ARG_DIRECTION_IN;
544 argument->direction = GUPNP_SERVICE_ACTION_ARG_DIRECTION_OUT;
547 if (xml_util_get_element (argument_node, "retval", NULL) != NULL)
548 argument->retval = TRUE;
555 * Creates a #GList of all the arguments (of type #GUPnPServiceActionArgInfo)
556 * from the action node @action_node in the SCPD document
560 get_action_arguments (xmlNodePtr action_node)
562 GList *argument_list = NULL;
563 xmlNodePtr arglist_node;
564 xmlNodePtr argument_node;
566 arglist_node = xml_util_get_element ((xmlNode *) action_node,
572 /* Iterate over all the arguments */
573 for (argument_node = arglist_node->children;
575 argument_node = argument_node->next) {
576 GUPnPServiceActionArgInfo *argument;
578 if (strcmp ("argument", (char *) argument_node->name) != 0)
581 argument = get_action_argument (argument_node);
583 argument_list = g_list_append (argument_list,
588 return argument_list;
593 * Creates a #GList of all the actions (of type #GUPnPServiceActionInfo) from
598 get_actions (xmlNode *list_element)
600 GList *actions = NULL;
601 xmlNodePtr action_node;
603 /* Iterate over all action elements */
604 for (action_node = list_element->children;
606 action_node = action_node->next) {
607 GUPnPServiceActionInfo *action_info;
611 if (strcmp ("action", (char *) action_node->name) != 0)
614 name = xml_util_get_child_element_content_glib (action_node,
619 arguments = get_action_arguments (action_node);
626 action_info = g_slice_new0 (GUPnPServiceActionInfo);
627 action_info->name = name;
628 action_info->arguments = arguments;
630 actions = g_list_append (actions, action_info);
638 * Creates a #GList of all the state variables (of type
639 * #GUPnPServiceStateVariableInfo) from the SCPD document.
643 get_state_variables (xmlNode *list_element)
645 GList *variables = NULL;
646 xmlNodePtr variable_node;
648 /* Iterate over all variable elements */
649 for (variable_node = list_element->children;
651 variable_node = variable_node->next) {
653 GUPnPServiceStateVariableInfo *variable;
655 if (strcmp ("stateVariable",
656 (char *) variable_node->name) != 0)
659 name = xml_util_get_child_element_content_glib (variable_node,
664 variable = get_state_variable (variable_node);
671 variable->name = name;
672 variables = g_list_append (variables, variable);
679 * Creates the #GHashTable's of action and state variable information
683 construct_introspection_info (GUPnPServiceIntrospection *introspection,
688 g_return_if_fail (scpd != NULL);
690 /* Get actionList element */
691 element = xml_util_get_element ((xmlNode *) scpd,
696 introspection->priv->actions = get_actions (element);
698 /* Get serviceStateTable element */
699 element = xml_util_get_element ((xmlNode *) scpd,
704 introspection->priv->variables = get_state_variables (element);
708 collect_action_names (gpointer data,
711 GList **action_names = (GList **) user_data;
712 GUPnPServiceActionInfo *action_info = (GUPnPServiceActionInfo *) data;
714 *action_names = g_list_append (*action_names, action_info->name);
718 collect_variable_names (gpointer data,
721 GList **variable_names = (GList **) user_data;
722 GUPnPServiceStateVariableInfo *variable =
723 (GUPnPServiceStateVariableInfo *) data;
725 *variable_names = g_list_append (*variable_names, variable->name);
729 * gupnp_service_introspection_new:
730 * @scpd: Pointer to the SCPD of the service to create a introspection for
732 * Create a new #GUPnPServiceIntrospection for the service created from the
733 * SCPD @scpd or %NULL.
735 * Return value: A new #GUPnPServiceIntrospection.
737 GUPnPServiceIntrospection *
738 gupnp_service_introspection_new (xmlDoc *scpd)
740 GUPnPServiceIntrospection *introspection;
742 g_return_val_if_fail (scpd != NULL, NULL);
744 introspection = g_object_new (GUPNP_TYPE_SERVICE_INTROSPECTION,
748 if (introspection->priv->actions == NULL &&
749 introspection->priv->variables == NULL) {
750 g_object_unref (introspection);
751 introspection = NULL;
754 return introspection;
758 * gupnp_service_introspection_list_action_names:
759 * @introspection: A #GUPnPServiceIntrospection
761 * Returns a GList of names of all the actions in this service.
763 * Return value: (transfer none) (element-type utf8) : A GList of names of all
764 * the actions or %NULL. Do not modify or free it or its contents.
767 gupnp_service_introspection_list_action_names
768 (GUPnPServiceIntrospection *introspection)
770 if (introspection->priv->actions == NULL)
773 if (introspection->priv->action_names == NULL) {
774 g_list_foreach (introspection->priv->actions,
775 collect_action_names,
776 &introspection->priv->action_names);
779 return introspection->priv->action_names;
783 * gupnp_service_introspection_list_actions:
784 * @introspection: A #GUPnPServiceIntrospection
786 * Returns a #GList of all the actions (of type #GUPnPServiceActionInfo) in
789 * Return value: (element-type GUPnP.ServiceActionInfo*) (transfer none): A
790 * #GList of all the actions or %NULL. Do not modify or free it or its
794 gupnp_service_introspection_list_actions
795 (GUPnPServiceIntrospection *introspection)
797 return introspection->priv->actions;
801 * gupnp_service_introspection_list_state_variables:
802 * @introspection: A #GUPnPServiceIntrospection
804 * Returns a GList of all the state variables (of type
805 * #GUPnPServiceStateVariableInfo) in this service.
807 * Return value: (element-type GUPnP.ServiceStateVariableInfo) (transfer none):
808 * A #GList of all the state variables or %NULL. Do not modify or free it or
813 gupnp_service_introspection_list_state_variables
814 (GUPnPServiceIntrospection *introspection)
816 return introspection->priv->variables;
820 * gupnp_service_introspection_list_state_variable_names:
821 * @introspection: A #GUPnPServiceIntrospection
823 * Returns a #GList of names of all the state variables in this service.
825 * Return value: (element-type utf8) (transfer none): A #GList of names of all
826 * the state variables or %NULL. Do not modify or free it or its contents.
829 gupnp_service_introspection_list_state_variable_names
830 (GUPnPServiceIntrospection *introspection)
832 if (introspection->priv->variables == NULL)
835 if (introspection->priv->variable_names == NULL) {
836 g_list_foreach (introspection->priv->variables,
837 collect_variable_names,
838 &introspection->priv->variable_names);
841 return introspection->priv->variable_names;
845 state_variable_search_func (GUPnPServiceStateVariableInfo *variable,
846 const gchar *variable_name)
848 return strcmp (variable->name, variable_name);
852 * gupnp_service_introspection_get_state_variable:
853 * @introspection: A #GUPnPServiceIntrospection
854 * @variable_name: The name of the variable to retreive
856 * Returns the state variable by the name @variable_name in this service.
858 * Return value: the state variable or %NULL. Do not modify or free it.
860 const GUPnPServiceStateVariableInfo *
861 gupnp_service_introspection_get_state_variable
862 (GUPnPServiceIntrospection *introspection,
863 const gchar *variable_name)
865 GList *variable_node;
867 if (introspection->priv->variables == NULL)
870 variable_node = g_list_find_custom (
871 introspection->priv->variables,
872 (gpointer) variable_name,
873 (GCompareFunc) state_variable_search_func);
874 if (variable_node == NULL)
877 return (GUPnPServiceStateVariableInfo *) variable_node->data;
881 action_search_func (GUPnPServiceActionInfo *action,
882 const gchar *action_name)
884 return strcmp (action->name, action_name);
888 * gupnp_service_introspection_get_action:
889 * @introspection: A #GUPnPServiceIntrospection
890 * @action_name: The name of the action to retreive
892 * Returns the action by the name @action_name in this service.
894 * Return value: (transfer none): the action or %NULL. Do not modify or free
897 const GUPnPServiceActionInfo *
898 gupnp_service_introspection_get_action
899 (GUPnPServiceIntrospection *introspection,
900 const gchar *action_name)
904 if (introspection->priv->variables == NULL)
907 action_node = g_list_find_custom (
908 introspection->priv->actions,
909 (gpointer) action_name,
910 (GCompareFunc) action_search_func);
911 if (action_node == NULL)
914 return (GUPnPServiceActionInfo *) action_node->data;