2 * Copyright (C) 2007, 2008 OpenedHand Ltd.
4 * Author: Jorn Baayen <jorn@openedhand.com>
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.
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.
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.
23 * SECTION:gupnp-service
24 * @short_description: Class for service implementations.
26 * #GUPnPService allows for handling incoming actions and state variable
27 * notification. #GUPnPService implements the #GUPnPServiceInfo interface.
30 #include <gobject/gvaluecollector.h>
32 #include <libsoup/soup-date.h>
33 #include <uuid/uuid.h>
35 #include "gupnp-service.h"
36 #include "gupnp-root-device.h"
37 #include "gupnp-context-private.h"
38 #include "gupnp-marshal.h"
39 #include "gupnp-error.h"
40 #include "http-headers.h"
41 #include "gena-protocol.h"
43 #include "gvalue-util.h"
45 #define SUBSCRIPTION_TIMEOUT 300 /* DLNA (7.2.22.1) enforced */
47 G_DEFINE_TYPE (GUPnPService,
49 GUPNP_TYPE_SERVICE_INFO);
51 struct _GUPnPServicePrivate {
52 GUPnPRootDevice *root_device;
56 guint notify_available_id;
58 GHashTable *subscriptions;
60 GList *state_variables;
64 gboolean notify_frozen;
79 static guint signals[LAST_SIGNAL];
82 create_property_set (GQueue *queue);
85 notify_subscriber (gpointer key,
90 GUPnPService *service;
99 GList *pending_messages; /* Pending SoupMessages from this
101 gboolean initial_state_sent;
106 subscription_data_can_delete (SubscriptionData *data) {
107 return data->initial_state_sent && data->to_delete;
111 send_initial_state (SubscriptionData *data);
114 gupnp_service_get_session (GUPnPService *service)
116 if (! service->priv->session) {
117 /* Create a dedicated session for this service to
118 * ensure that notifications are sent in the proper
119 * order. The session from GUPnPContext may use
120 * multiple connections.
122 service->priv->session = soup_session_async_new_with_options
123 (SOUP_SESSION_IDLE_TIMEOUT, 60,
124 SOUP_SESSION_ASYNC_CONTEXT,
125 g_main_context_get_thread_default (),
126 SOUP_SESSION_MAX_CONNS_PER_HOST, 1,
129 if (g_getenv ("GUPNP_DEBUG")) {
131 logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
132 soup_session_add_feature (
133 service->priv->session,
134 SOUP_SESSION_FEATURE (logger));
138 return service->priv->session;
142 subscription_data_free (SubscriptionData *data)
144 SoupSession *session;
146 session = gupnp_service_get_session (data->service);
148 /* Cancel pending messages */
149 while (data->pending_messages) {
152 msg = data->pending_messages->data;
154 soup_session_cancel_message (session,
156 SOUP_STATUS_CANCELLED);
158 data->pending_messages =
159 g_list_delete_link (data->pending_messages,
160 data->pending_messages);
163 /* Further cleanup */
164 while (data->callbacks) {
165 g_free (data->callbacks->data);
166 data->callbacks = g_list_delete_link (data->callbacks,
172 if (data->timeout_src)
173 g_source_destroy (data->timeout_src);
175 g_slice_free (SubscriptionData, data);
184 notify_data_free (NotifyData *data)
186 g_free (data->variable);
187 g_value_unset (&data->value);
189 g_slice_free (NotifyData, data);
192 struct _GUPnPServiceAction {
193 volatile gint ref_count;
195 GUPnPContext *context;
200 gboolean accept_gzip;
205 GString *response_str;
207 guint argument_count;
211 gupnp_service_action_ref (GUPnPServiceAction *action)
213 g_return_val_if_fail (action, NULL);
214 g_return_val_if_fail (action->ref_count > 0, NULL);
216 g_atomic_int_inc (&action->ref_count);
222 gupnp_service_action_unref (GUPnPServiceAction *action)
224 g_return_if_fail (action);
225 g_return_if_fail (action->ref_count > 0);
227 if (g_atomic_int_dec_and_test (&action->ref_count)) {
228 g_free (action->name);
229 g_object_unref (action->msg);
230 g_object_unref (action->context);
231 g_object_unref (action->doc);
233 g_slice_free (GUPnPServiceAction, action);
238 * gupnp_service_action_get_type:
240 * Get the gtype for GUPnPServiceActon
242 * Return value: The gtype of GUPnPServiceAction
245 gupnp_service_action_get_type (void)
247 static GType our_type = 0;
250 our_type = g_boxed_type_register_static
251 ("GUPnPServiceAction",
252 (GBoxedCopyFunc) gupnp_service_action_ref,
253 (GBoxedFreeFunc) gupnp_service_action_unref);
259 finalize_action (GUPnPServiceAction *action)
263 /* Embed action->response_str in a SOAP document */
264 g_string_prepend (action->response_str,
265 "<?xml version=\"1.0\"?>"
266 "<s:Envelope xmlns:s="
267 "\"http://schemas.xmlsoap.org/soap/envelope/\" "
269 "\"http://schemas.xmlsoap.org/soap/encoding/\">"
272 if (action->msg->status_code != SOUP_STATUS_INTERNAL_SERVER_ERROR) {
273 g_string_append (action->response_str, "</u:");
274 g_string_append (action->response_str, action->name);
275 g_string_append (action->response_str, "Response>");
278 g_string_append (action->response_str,
282 soup_message_headers_replace (action->msg->response_headers,
284 "text/xml; charset=\"utf-8\"");
286 if (action->accept_gzip && action->response_str->len > 1024) {
287 http_response_set_body_gzip (action->msg,
288 action->response_str->str,
289 action->response_str->len);
290 g_string_free (action->response_str, TRUE);
292 soup_message_body_append (action->msg->response_body,
294 action->response_str->str,
295 action->response_str->len);
296 g_string_free (action->response_str, FALSE);
299 soup_message_headers_append (action->msg->response_headers, "Ext", "");
301 /* Server header on response */
302 soup_message_headers_append
303 (action->msg->response_headers,
305 gssdp_client_get_server_id
306 (GSSDP_CLIENT (action->context)));
308 /* Tell soup server that response is now ready */
309 server = gupnp_context_get_server (action->context);
310 soup_server_unpause_message (server, action->msg);
313 gupnp_service_action_unref (action);
317 * gupnp_service_action_get_name:
318 * @action: A #GUPnPServiceAction
320 * Get the name of @action.
322 * Return value: The name of @action
325 gupnp_service_action_get_name (GUPnPServiceAction *action)
327 g_return_val_if_fail (action != NULL, NULL);
333 * gupnp_service_action_get_locales:
334 * @action: A #GUPnPServiceAction
336 * Get an ordered (preferred first) #GList of locales preferred by
337 * the client. Free list and elements after use.
339 * Return value: (element-type utf8) (transfer full): A #GList of #char*
343 gupnp_service_action_get_locales (GUPnPServiceAction *action)
345 g_return_val_if_fail (action != NULL, NULL);
347 return http_request_get_accept_locales (action->msg);
351 * gupnp_service_action_get:
352 * @action: A #GUPnPServiceAction
353 * @Varargs: tuples of argument name, argument type, and argument value
354 * location, terminated with %NULL.
356 * Retrieves the specified action arguments.
359 gupnp_service_action_get (GUPnPServiceAction *action,
364 g_return_if_fail (action != NULL);
366 va_start (var_args, action);
367 gupnp_service_action_get_valist (action, var_args);
372 * gupnp_service_action_get_valist:
373 * @action: A #GUPnPServiceAction
374 * @var_args: va_list of tuples of argument name, argument type, and argument
377 * See gupnp_service_action_get(); this version takes a va_list for
378 * use by language bindings.
381 gupnp_service_action_get_valist (GUPnPServiceAction *action,
384 const char *arg_name;
386 GValue value = {0, };
389 g_return_if_fail (action != NULL);
393 arg_name = va_arg (var_args, const char *);
395 arg_type = va_arg (var_args, GType);
396 g_value_init (&value, arg_type);
398 gupnp_service_action_get_value (action, arg_name, &value);
400 G_VALUE_LCOPY (&value, var_args, 0, ©_error);
402 g_value_unset (&value);
405 g_warning ("Error lcopying value: %s\n", copy_error);
410 arg_name = va_arg (var_args, const char *);
415 * gupnp_service_action_get_values:
416 * @action: A #GUPnPServiceAction
417 * @arg_names: (element-type utf8) : A #GList of argument names as string
418 * @arg_types: (element-type GType): A #GList of argument types as #GType
420 * A variant of #gupnp_service_action_get that uses #GList instead of varargs.
422 * Return value: (element-type GValue) (transfer full): The values as #GList of
423 * #GValue. #g_list_free the returned list and #g_value_unset and #g_slice_free
427 gupnp_service_action_get_values (GUPnPServiceAction *action,
434 g_return_val_if_fail (action != NULL, NULL);
438 for (i = 0; i < g_list_length (arg_names); i++) {
439 const char *arg_name;
443 arg_name = (const char *) g_list_nth_data (arg_names, i);
444 arg_type = (GType) g_list_nth_data (arg_types, i);
446 arg_value = g_slice_new0 (GValue);
447 g_value_init (arg_value, arg_type);
449 gupnp_service_action_get_value (action, arg_name, arg_value);
451 arg_values = g_list_append (arg_values, arg_value);
458 * gupnp_service_action_get_value: (skip)
459 * @action: A #GUPnPServiceAction
460 * @argument: The name of the argument to retrieve
461 * @value: The #GValue to store the value of the argument, initialized
462 * to the correct type.
464 * Retrieves the value of @argument into @value.
467 gupnp_service_action_get_value (GUPnPServiceAction *action,
468 const char *argument,
474 g_return_if_fail (action != NULL);
475 g_return_if_fail (argument != NULL);
476 g_return_if_fail (value != NULL);
479 for (node = action->node->children; node; node = node->next) {
480 if (strcmp ((char *) node->name, argument) != 0)
483 found = gvalue_util_set_value_from_xml_node (value, node);
489 g_warning ("Failed to retreive '%s' argument of '%s' action",
495 * gupnp_service_action_get_gvalue:
496 * @action: A #GUPnPServiceAction
497 * @argument: The name of the argument to retrieve
498 * @type: The type of argument to retrieve
500 * Retrieves the value of @argument into a GValue of type @type and returns it.
501 * The method exists only and only to satify PyGI, please use
502 * #gupnp_service_action_get_value and ignore this if possible.
504 * Return value: (transfer full): Value as #GValue associated with @action.
505 * #g_value_unset and #g_slice_free it after usage.
507 * Rename To: gupnp_service_action_get_value
510 gupnp_service_action_get_gvalue (GUPnPServiceAction *action,
511 const char *argument,
516 val = g_slice_new0 (GValue);
517 g_value_init (val, type);
519 gupnp_service_action_get_value (action, argument, val);
525 * gupnp_service_action_get_argument_count:
526 * @action: A #GUPnPServiceAction
528 * Get the number of IN arguments from the @action and return it.
530 * Return value: The number of IN arguments from the @action.
533 gupnp_service_action_get_argument_count (GUPnPServiceAction *action)
535 return action->argument_count;
539 * gupnp_service_action_set:
540 * @action: A #GUPnPServiceAction
541 * @Varargs: tuples of return value name, return value type, and
542 * actual return value, terminated with %NULL.
544 * Sets the specified action return values.
547 gupnp_service_action_set (GUPnPServiceAction *action,
552 g_return_if_fail (action != NULL);
554 va_start (var_args, action);
555 gupnp_service_action_set_valist (action, var_args);
560 * gupnp_service_action_set_valist:
561 * @action: A #GUPnPServiceAction
562 * @var_args: va_list of tuples of return value name, return value type, and
563 * actual return value.
565 * See gupnp_service_action_set(); this version takes a va_list for
566 * use by language bindings.
569 gupnp_service_action_set_valist (GUPnPServiceAction *action,
572 const char *arg_name;
574 GValue value = {0, };
577 g_return_if_fail (action != NULL);
579 collect_error = NULL;
581 arg_name = va_arg (var_args, const char *);
583 arg_type = va_arg (var_args, GType);
584 g_value_init (&value, arg_type);
586 G_VALUE_COLLECT (&value, var_args, G_VALUE_NOCOPY_CONTENTS,
588 if (!collect_error) {
589 gupnp_service_action_set_value (action,
592 g_value_unset (&value);
595 g_warning ("Error collecting value: %s\n",
598 g_free (collect_error);
601 arg_name = va_arg (var_args, const char *);
606 * gupnp_service_action_set_values:
607 * @action: A #GUPnPServiceAction
608 * @arg_names: (element-type utf8) (transfer none): A #GList of argument names
609 * @arg_values: (element-type GValue) (transfer none): The #GList of values (as
610 * #GValues) that line up with @arg_names.
612 * Sets the specified action return values.
615 gupnp_service_action_set_values (GUPnPServiceAction *action,
619 g_return_if_fail (action != NULL);
620 g_return_if_fail (arg_names != NULL);
621 g_return_if_fail (arg_values != NULL);
622 g_return_if_fail (g_list_length (arg_names) ==
623 g_list_length (arg_values));
625 if (action->msg->status_code == SOUP_STATUS_INTERNAL_SERVER_ERROR) {
626 g_warning ("Calling gupnp_service_action_set_value() after "
627 "having called gupnp_service_action_return_error() "
633 /* Append to response */
634 for (; arg_names; arg_names = arg_names->next) {
635 const char *arg_name;
638 arg_name = arg_names->data;
639 value = arg_values->data;
641 xml_util_start_element (action->response_str, arg_name);
642 gvalue_util_value_append_to_xml_string (value,
643 action->response_str);
644 xml_util_end_element (action->response_str, arg_name);
646 arg_values = arg_values->next;
651 * gupnp_service_action_set_value:
652 * @action: A #GUPnPServiceAction
653 * @argument: The name of the return value to retrieve
654 * @value: The #GValue to store the return value
656 * Sets the value of @argument to @value.
659 gupnp_service_action_set_value (GUPnPServiceAction *action,
660 const char *argument,
663 g_return_if_fail (action != NULL);
664 g_return_if_fail (argument != NULL);
665 g_return_if_fail (value != NULL);
667 if (action->msg->status_code == SOUP_STATUS_INTERNAL_SERVER_ERROR) {
668 g_warning ("Calling gupnp_service_action_set_value() after "
669 "having called gupnp_service_action_return_error() "
675 /* Append to response */
676 xml_util_start_element (action->response_str, argument);
677 gvalue_util_value_append_to_xml_string (value, action->response_str);
678 xml_util_end_element (action->response_str, argument);
682 * gupnp_service_action_return:
683 * @action: A #GUPnPServiceAction
685 * Return succesfully.
688 gupnp_service_action_return (GUPnPServiceAction *action)
690 g_return_if_fail (action != NULL);
692 soup_message_set_status (action->msg, SOUP_STATUS_OK);
694 finalize_action (action);
698 * gupnp_service_action_return_error:
699 * @action: A #GUPnPServiceAction
700 * @error_code: The error code
701 * @error_description: The error description, or %NULL if @error_code is
702 * one of #GUPNP_CONTROL_ERROR_INVALID_ACTION,
703 * #GUPNP_CONTROL_ERROR_INVALID_ARGS, #GUPNP_CONTROL_ERROR_OUT_OF_SYNC or
704 * #GUPNP_CONTROL_ERROR_ACTION_FAILED, in which case a description is
705 * provided automatically.
707 * Return @error_code.
710 gupnp_service_action_return_error (GUPnPServiceAction *action,
712 const char *error_description)
714 g_return_if_fail (action != NULL);
716 switch (error_code) {
717 case GUPNP_CONTROL_ERROR_INVALID_ACTION:
718 if (error_description == NULL)
719 error_description = "Invalid Action";
722 case GUPNP_CONTROL_ERROR_INVALID_ARGS:
723 if (error_description == NULL)
724 error_description = "Invalid Args";
727 case GUPNP_CONTROL_ERROR_OUT_OF_SYNC:
728 if (error_description == NULL)
729 error_description = "Out of Sync";
732 case GUPNP_CONTROL_ERROR_ACTION_FAILED:
733 if (error_description == NULL)
734 error_description = "Action Failed";
738 g_return_if_fail (error_description != NULL);
742 /* Replace response_str with a SOAP Fault */
743 g_string_erase (action->response_str, 0, -1);
745 xml_util_start_element (action->response_str, "s:Fault");
747 xml_util_start_element (action->response_str, "faultcode");
748 g_string_append (action->response_str, "s:Client");
749 xml_util_end_element (action->response_str, "faultcode");
751 xml_util_start_element (action->response_str, "faultstring");
752 g_string_append (action->response_str, "UPnPError");
753 xml_util_end_element (action->response_str, "faultstring");
755 xml_util_start_element (action->response_str, "detail");
757 xml_util_start_element (action->response_str,
759 "xmlns=\"urn:schemas-upnp-org:control-1-0\"");
761 xml_util_start_element (action->response_str, "errorCode");
762 g_string_append_printf (action->response_str, "%u", error_code);
763 xml_util_end_element (action->response_str, "errorCode");
765 xml_util_start_element (action->response_str, "errorDescription");
766 xml_util_add_content (action->response_str, error_description);
767 xml_util_end_element (action->response_str, "errorDescription");
769 xml_util_end_element (action->response_str, "UPnPError");
770 xml_util_end_element (action->response_str, "detail");
772 xml_util_end_element (action->response_str, "s:Fault");
774 soup_message_set_status (action->msg,
775 SOUP_STATUS_INTERNAL_SERVER_ERROR);
777 finalize_action (action);
781 * gupnp_service_action_get_message:
782 * @action: A #GUPnPServiceAction
784 * Get the #SoupMessage associated with @action. Mainly intended for
785 * applications to be able to read HTTP headers received from clients.
787 * Return value: (transfer full): #SoupMessage associated with @action. Unref
791 gupnp_service_action_get_message (GUPnPServiceAction *action)
793 return g_object_ref (action->msg);
797 gupnp_service_init (GUPnPService *service)
799 service->priv = G_TYPE_INSTANCE_GET_PRIVATE (service,
801 GUPnPServicePrivate);
803 service->priv->subscriptions =
804 g_hash_table_new_full (g_str_hash,
807 (GDestroyNotify) subscription_data_free);
809 service->priv->notify_queue = g_queue_new ();
812 /* Generate a new action response node for @action_name */
814 new_action_response_str (const char *action_name,
815 const char *service_type)
819 str = xml_util_new_string ();
821 g_string_append (str, "<u:");
822 g_string_append (str, action_name);
823 g_string_append (str, "Response xmlns:u=");
825 if (service_type != NULL) {
826 g_string_append (str, service_type);
827 g_string_append_c (str, '"');
829 g_warning ("No serviceType defined. Control may not work "
833 g_string_append_c (str, '>');
838 /* Handle QueryStateVariable action */
840 query_state_variable (GUPnPService *service,
841 GUPnPServiceAction *action)
845 /* Iterate requested variables */
846 for (node = action->node->children; node; node = node->next) {
850 if (strcmp ((char *) node->name, "varName") != 0)
854 var_name = xmlNodeGetContent (node);
856 gupnp_service_action_return_error (action,
864 g_signal_emit (service,
865 signals[QUERY_VARIABLE],
866 g_quark_from_string ((char *) var_name),
870 if (!G_IS_VALUE (&value)) {
871 gupnp_service_action_return_error (action,
880 /* Add variable to response */
881 gupnp_service_action_set_value (action,
886 g_value_unset (&value);
891 gupnp_service_action_return (action);
894 /* controlURL handler */
896 control_server_handler (SoupServer *server,
898 const char *server_path,
900 SoupClientContext *soup_client,
903 GUPnPService *service;
904 GUPnPContext *context;
906 xmlNode *action_node, *node;
907 const char *soap_action;
908 const char *accept_encoding;
911 GUPnPServiceAction *action;
913 service = GUPNP_SERVICE (user_data);
915 if (msg->method != SOUP_METHOD_POST) {
916 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
921 if (msg->request_body->length == 0) {
922 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
927 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (service));
929 /* Get action name */
930 soap_action = soup_message_headers_get_one (msg->request_headers,
933 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
937 action_name = strchr (soap_action, '#');
939 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
944 /* This memory is libsoup-owned so we can do this */
948 /* This memory is libsoup-owned so we can do this */
949 end = strrchr (action_name, '"');
953 /* Parse action_node */
954 doc = xmlRecoverMemory (msg->request_body->data,
955 msg->request_body->length);
957 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
962 action_node = xml_util_get_element ((xmlNode *) doc,
968 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
973 /* Create action structure */
974 action = g_slice_new0 (GUPnPServiceAction);
976 action->ref_count = 1;
977 action->name = g_strdup (action_name);
978 action->msg = g_object_ref (msg);
979 action->doc = gupnp_xml_doc_new(doc);
980 action->node = action_node;
981 action->response_str = new_action_response_str (action_name,
983 action->context = g_object_ref (context);
984 action->argument_count = 0;
986 for (node = action->node->children; node; node = node->next)
987 if (node->type == XML_ELEMENT_NODE)
988 action->argument_count++;
990 /* Get accepted encodings */
991 accept_encoding = soup_message_headers_get_list (msg->request_headers,
994 if (accept_encoding) {
997 codings = soup_header_parse_quality_list (accept_encoding,
1000 g_slist_find_custom (codings, "gzip",
1001 (GCompareFunc) g_ascii_strcasecmp)) {
1002 action->accept_gzip = TRUE;
1005 soup_header_free_list (codings);
1008 /* Tell soup server that response is not ready yet */
1009 soup_server_pause_message (server, msg);
1011 /* QueryStateVariable? */
1012 if (strcmp (action_name, "QueryStateVariable") == 0)
1013 query_state_variable (service, action);
1015 GQuark action_name_quark;
1017 action_name_quark = g_quark_from_string (action_name);
1018 if (g_signal_has_handler_pending (service,
1019 signals[ACTION_INVOKED],
1022 /* Emit signal. Handler parses request and fills in
1024 g_signal_emit (service,
1025 signals[ACTION_INVOKED],
1029 /* No handlers attached. */
1030 gupnp_service_action_return_error (action,
1037 /* Generates a standard (re)subscription response */
1039 subscription_response (GUPnPService *service,
1044 GUPnPContext *context;
1045 GSSDPClient *client;
1048 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (service));
1049 client = GSSDP_CLIENT (context);
1051 /* Server header on response */
1052 soup_message_headers_append (msg->response_headers,
1054 gssdp_client_get_server_id (client));
1057 soup_message_headers_append (msg->response_headers,
1061 /* Timeout header */
1063 tmp = g_strdup_printf ("Second-%d", timeout);
1065 tmp = g_strdup ("infinite");
1067 soup_message_headers_append (msg->response_headers,
1073 soup_message_set_status (msg, SOUP_STATUS_OK);
1076 /* Generates a new SID */
1084 uuid_unparse (id, out);
1086 return g_strdup_printf ("uuid:%s", out);
1089 /* Subscription expired */
1091 subscription_timeout (gpointer user_data)
1093 SubscriptionData *data;
1097 g_hash_table_remove (data->service->priv->subscriptions, data->sid);
1103 send_initial_state (SubscriptionData *data)
1109 /* Send initial event message */
1110 queue = g_queue_new ();
1112 for (l = data->service->priv->state_variables; l; l = l->next) {
1115 ndata = g_slice_new0 (NotifyData);
1117 g_signal_emit (data->service,
1118 signals[QUERY_VARIABLE],
1119 g_quark_from_string (l->data),
1123 if (!G_IS_VALUE (&ndata->value)) {
1124 g_slice_free (NotifyData, ndata);
1129 ndata->variable = g_strdup (l->data);
1131 g_queue_push_tail (queue, ndata);
1134 mem = create_property_set (queue);
1135 notify_subscriber (data->sid, data, mem);
1138 g_queue_free (queue);
1144 /* Subscription request */
1146 subscribe (GUPnPService *service,
1148 const char *callback)
1150 SubscriptionData *data;
1151 char *start, *end, *uri;
1153 data = g_slice_new0 (SubscriptionData);
1155 /* Parse callback list */
1156 start = (char *) callback;
1157 while ((start = strchr (start, '<'))) {
1159 if (!start || !*start)
1162 end = strchr (start, '>');
1166 if (strncmp (start, "http://", strlen ("http://")) == 0) {
1167 uri = g_strndup (start, end - start);
1168 data->callbacks = g_list_append (data->callbacks, uri);
1174 if (!data->callbacks) {
1175 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1177 g_slice_free (SubscriptionData, data);
1182 /* Add service and SID */
1183 data->service = service;
1184 data->sid = generate_sid ();
1187 data->timeout_src = g_timeout_source_new_seconds (SUBSCRIPTION_TIMEOUT);
1188 g_source_set_callback (data->timeout_src,
1189 subscription_timeout,
1193 g_source_attach (data->timeout_src,
1194 g_main_context_get_thread_default ());
1196 g_source_unref (data->timeout_src);
1199 g_hash_table_insert (service->priv->subscriptions,
1204 subscription_response (service, msg, data->sid, SUBSCRIPTION_TIMEOUT);
1206 send_initial_state (data);
1209 /* Resubscription request */
1211 resubscribe (GUPnPService *service,
1215 SubscriptionData *data;
1217 data = g_hash_table_lookup (service->priv->subscriptions, sid);
1219 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1224 /* Update timeout */
1225 if (data->timeout_src) {
1226 g_source_destroy (data->timeout_src);
1227 data->timeout_src = NULL;
1230 data->timeout_src = g_timeout_source_new_seconds (SUBSCRIPTION_TIMEOUT);
1231 g_source_set_callback (data->timeout_src,
1232 subscription_timeout,
1236 g_source_attach (data->timeout_src,
1237 g_main_context_get_thread_default ());
1239 g_source_unref (data->timeout_src);
1242 subscription_response (service, msg, sid, SUBSCRIPTION_TIMEOUT);
1245 /* Unsubscription request */
1247 unsubscribe (GUPnPService *service,
1251 SubscriptionData *data;
1253 data = g_hash_table_lookup (service->priv->subscriptions, sid);
1255 if (data->initial_state_sent)
1256 g_hash_table_remove (service->priv->subscriptions,
1259 data->to_delete = TRUE;
1260 soup_message_set_status (msg, SOUP_STATUS_OK);
1262 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1265 /* eventSubscriptionURL handler */
1267 subscription_server_handler (SoupServer *server,
1269 const char *server_path,
1271 SoupClientContext *soup_client,
1274 GUPnPService *service;
1275 const char *callback, *nt, *sid;
1277 service = GUPNP_SERVICE (user_data);
1279 callback = soup_message_headers_get_one (msg->request_headers,
1281 nt = soup_message_headers_get_one (msg->request_headers, "NT");
1282 sid = soup_message_headers_get_one (msg->request_headers, "SID");
1284 /* Choose appropriate handler */
1285 if (strcmp (msg->method, GENA_METHOD_SUBSCRIBE) == 0) {
1288 soup_message_set_status
1289 (msg, SOUP_STATUS_BAD_REQUEST);
1291 } else if (!nt || strcmp (nt, "upnp:event") != 0) {
1292 soup_message_set_status
1293 (msg, SOUP_STATUS_PRECONDITION_FAILED);
1296 subscribe (service, msg, callback);
1302 soup_message_set_status
1303 (msg, SOUP_STATUS_BAD_REQUEST);
1306 resubscribe (service, msg, sid);
1311 soup_message_set_status
1312 (msg, SOUP_STATUS_PRECONDITION_FAILED);
1316 } else if (strcmp (msg->method, GENA_METHOD_UNSUBSCRIBE) == 0) {
1318 if (nt || callback) {
1319 soup_message_set_status
1320 (msg, SOUP_STATUS_BAD_REQUEST);
1323 unsubscribe (service, msg, sid);
1328 soup_message_set_status
1329 (msg, SOUP_STATUS_PRECONDITION_FAILED);
1334 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
1340 got_introspection (GUPnPServiceInfo *info,
1341 GUPnPServiceIntrospection *introspection,
1342 const GError *error,
1345 GUPnPService *service = GUPNP_SERVICE (info);
1346 const GList *state_variables, *l;
1347 GHashTableIter iter;
1350 if (introspection) {
1352 gupnp_service_introspection_list_state_variables
1355 for (l = state_variables; l; l = l->next) {
1356 GUPnPServiceStateVariableInfo *variable;
1360 if (!variable->send_events)
1363 service->priv->state_variables =
1364 g_list_prepend (service->priv->state_variables,
1365 g_strdup (variable->name));
1368 g_object_unref (introspection);
1370 g_warning ("Failed to get SCPD: %s\n"
1371 "The initial event message will not be sent.",
1372 error ? error->message : "No error");
1374 g_hash_table_iter_init (&iter, service->priv->subscriptions);
1376 while (g_hash_table_iter_next (&iter, NULL, &data)) {
1377 send_initial_state ((SubscriptionData *) data);
1378 if (subscription_data_can_delete ((SubscriptionData *) data))
1379 g_hash_table_iter_remove (&iter);
1384 path_from_url (const char *url)
1389 uri = soup_uri_new (url);
1390 path = soup_uri_to_string (uri, TRUE);
1391 soup_uri_free (uri);
1397 gupnp_service_constructor (GType type,
1398 guint n_construct_params,
1399 GObjectConstructParam *construct_params)
1401 GObjectClass *object_class;
1403 GUPnPServiceInfo *info;
1404 GUPnPContext *context;
1409 object_class = G_OBJECT_CLASS (gupnp_service_parent_class);
1412 object = object_class->constructor (type,
1416 info = GUPNP_SERVICE_INFO (object);
1418 /* Get introspection and save state variable names */
1419 gupnp_service_info_get_introspection_async (info,
1424 context = gupnp_service_info_get_context (info);
1425 server = gupnp_context_get_server (context);
1427 /* Run listener on controlURL */
1428 url = gupnp_service_info_get_control_url (info);
1429 path = path_from_url (url);
1430 soup_server_add_handler (server, path,
1431 control_server_handler, object, NULL);
1435 /* Run listener on eventSubscriptionURL */
1436 url = gupnp_service_info_get_event_subscription_url (info);
1437 path = path_from_url (url);
1438 soup_server_add_handler (server, path,
1439 subscription_server_handler, object, NULL);
1446 /* Root device availability changed. */
1448 notify_available_cb (GObject *object,
1452 GUPnPService *service;
1454 service = GUPNP_SERVICE (user_data);
1456 if (!gupnp_root_device_get_available (GUPNP_ROOT_DEVICE (object))) {
1457 /* Root device now unavailable: Purge subscriptions */
1458 g_hash_table_remove_all (service->priv->subscriptions);
1463 gupnp_service_set_property (GObject *object,
1465 const GValue *value,
1468 GUPnPService *service;
1470 service = GUPNP_SERVICE (object);
1472 switch (property_id) {
1473 case PROP_ROOT_DEVICE: {
1474 GUPnPRootDevice **dev;
1476 service->priv->root_device = g_value_get_object (value);
1477 dev = &(service->priv->root_device);
1479 g_object_add_weak_pointer
1480 (G_OBJECT (service->priv->root_device),
1483 service->priv->notify_available_id =
1484 g_signal_connect_object (service->priv->root_device,
1485 "notify::available",
1487 (notify_available_cb),
1494 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1500 gupnp_service_get_property (GObject *object,
1505 GUPnPService *service;
1507 service = GUPNP_SERVICE (object);
1509 switch (property_id) {
1510 case PROP_ROOT_DEVICE:
1511 g_value_set_object (value, service->priv->root_device);
1514 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1520 gupnp_service_dispose (GObject *object)
1522 GUPnPService *service;
1523 GObjectClass *object_class;
1524 GUPnPServiceInfo *info;
1525 GUPnPContext *context;
1530 service = GUPNP_SERVICE (object);
1533 info = GUPNP_SERVICE_INFO (service);
1534 context = gupnp_service_info_get_context (info);
1535 server = gupnp_context_get_server (context);
1537 /* Remove listener on controlURL */
1538 url = gupnp_service_info_get_control_url (info);
1539 path = path_from_url (url);
1540 soup_server_remove_handler (server, path);
1544 /* Remove listener on eventSubscriptionURL */
1545 url = gupnp_service_info_get_event_subscription_url (info);
1546 path = path_from_url (url);
1547 soup_server_remove_handler (server, path);
1551 if (service->priv->root_device) {
1552 GUPnPRootDevice **dev = &(service->priv->root_device);
1554 if (g_signal_handler_is_connected
1555 (service->priv->root_device,
1556 service->priv->notify_available_id)) {
1557 g_signal_handler_disconnect
1558 (service->priv->root_device,
1559 service->priv->notify_available_id);
1562 g_object_remove_weak_pointer
1563 (G_OBJECT (service->priv->root_device),
1566 service->priv->root_device = NULL;
1569 /* Cancel pending messages */
1570 g_hash_table_remove_all (service->priv->subscriptions);
1573 object_class = G_OBJECT_CLASS (gupnp_service_parent_class);
1574 object_class->dispose (object);
1578 gupnp_service_finalize (GObject *object)
1580 GUPnPService *service;
1581 GObjectClass *object_class;
1584 service = GUPNP_SERVICE (object);
1586 /* Free subscription hash */
1587 g_hash_table_destroy (service->priv->subscriptions);
1589 /* Free state variable list */
1590 while (service->priv->state_variables) {
1591 g_free (service->priv->state_variables->data);
1592 service->priv->state_variables =
1593 g_list_delete_link (service->priv->state_variables,
1594 service->priv->state_variables);
1597 /* Free notify queue */
1598 while ((data = g_queue_pop_head (service->priv->notify_queue)))
1599 notify_data_free (data);
1601 g_queue_free (service->priv->notify_queue);
1603 if (service->priv->session) {
1604 g_object_unref (service->priv->session);
1605 service->priv->session = NULL;
1609 object_class = G_OBJECT_CLASS (gupnp_service_parent_class);
1610 object_class->finalize (object);
1614 gupnp_service_class_init (GUPnPServiceClass *klass)
1616 GObjectClass *object_class;
1618 object_class = G_OBJECT_CLASS (klass);
1620 object_class->set_property = gupnp_service_set_property;
1621 object_class->get_property = gupnp_service_get_property;
1622 object_class->constructor = gupnp_service_constructor;
1623 object_class->dispose = gupnp_service_dispose;
1624 object_class->finalize = gupnp_service_finalize;
1626 g_type_class_add_private (klass, sizeof (GUPnPServicePrivate));
1629 * GUPnPService:root-device:
1631 * The containing #GUPnPRootDevice.
1633 g_object_class_install_property
1636 g_param_spec_object ("root-device",
1638 "The GUPnPRootDevice",
1639 GUPNP_TYPE_ROOT_DEVICE,
1641 G_PARAM_CONSTRUCT_ONLY |
1642 G_PARAM_STATIC_NAME |
1643 G_PARAM_STATIC_NICK |
1644 G_PARAM_STATIC_BLURB));
1647 * GUPnPService::action-invoked:
1648 * @service: The #GUPnPService that received the signal
1649 * @action: The invoked #GUPnPAction
1651 * Emitted whenever an action is invoked. Handler should process
1652 * @action and must call either gupnp_service_action_return() or
1653 * gupnp_service_action_return_error().
1655 signals[ACTION_INVOKED] =
1656 g_signal_new ("action-invoked",
1658 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
1659 G_STRUCT_OFFSET (GUPnPServiceClass,
1663 g_cclosure_marshal_VOID__BOXED,
1666 GUPNP_TYPE_SERVICE_ACTION);
1669 * GUPnPService::query-variable:
1670 * @service: The #GUPnPService that received the signal
1671 * @variable: The variable that is being queried
1672 * @value: The location of the #GValue of the variable
1674 * Emitted whenever @service needs to know the value of @variable.
1675 * Handler should fill @value with the value of @variable.
1677 signals[QUERY_VARIABLE] =
1678 g_signal_new ("query-variable",
1680 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
1681 G_STRUCT_OFFSET (GUPnPServiceClass,
1685 gupnp_marshal_VOID__STRING_POINTER,
1689 G_TYPE_POINTER /* Not G_TYPE_VALUE as this
1690 is an outward argument! */);
1693 * GUPnPService::notify-failed:
1694 * @service: The #GUPnPService that received the signal
1695 * @callback_url: The callback URL
1696 * @reason: A pointer to a #GError describing why the notify failed
1698 * Emitted whenever notification of a client fails.
1700 signals[NOTIFY_FAILED] =
1701 g_signal_new ("notify-failed",
1704 G_STRUCT_OFFSET (GUPnPServiceClass,
1708 gupnp_marshal_VOID__POINTER_POINTER,
1716 * gupnp_service_notify:
1717 * @service: A #GUPnPService
1718 * @Varargs: Tuples of variable name, variable type, and variable value,
1719 * terminated with %NULL.
1721 * Notifies listening clients that the properties listed in @Varargs
1722 * have changed to the specified values.
1725 gupnp_service_notify (GUPnPService *service,
1730 g_return_if_fail (GUPNP_IS_SERVICE (service));
1732 va_start (var_args, service);
1733 gupnp_service_notify_valist (service, var_args);
1738 * gupnp_service_notify_valist:
1739 * @service: A #GUPnPService
1740 * @var_args: A va_list of tuples of variable name, variable type, and variable
1741 * value, terminated with %NULL.
1743 * See gupnp_service_notify(); this version takes a va_list for
1744 * use by language bindings.
1747 gupnp_service_notify_valist (GUPnPService *service,
1750 const char *var_name;
1752 GValue value = {0, };
1753 char *collect_error;
1755 g_return_if_fail (GUPNP_IS_SERVICE (service));
1757 collect_error = NULL;
1759 var_name = va_arg (var_args, const char *);
1761 var_type = va_arg (var_args, GType);
1762 g_value_init (&value, var_type);
1764 G_VALUE_COLLECT (&value, var_args, G_VALUE_NOCOPY_CONTENTS,
1766 if (!collect_error) {
1767 gupnp_service_notify_value (service, var_name, &value);
1769 g_value_unset (&value);
1772 g_warning ("Error collecting value: %s\n",
1775 g_free (collect_error);
1778 var_name = va_arg (var_args, const char *);
1782 /* Received notify response. */
1784 notify_got_response (SoupSession *session,
1788 SubscriptionData *data;
1791 if (msg->status_code == SOUP_STATUS_CANCELLED)
1796 /* Remove from pending messages list */
1797 data->pending_messages = g_list_remove (data->pending_messages, msg);
1799 if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
1800 data->initial_state_sent = TRUE;
1802 /* Success: reset callbacks pointer */
1803 data->callbacks = g_list_first (data->callbacks);
1805 } else if (msg->status_code == SOUP_STATUS_PRECONDITION_FAILED) {
1806 /* Precondition failed: Cancel subscription */
1807 g_hash_table_remove (data->service->priv->subscriptions,
1811 /* Other failure: Try next callback or signal failure. */
1812 if (data->callbacks->next) {
1814 SoupSession *service_session;
1816 /* Call next callback */
1817 data->callbacks = data->callbacks->next;
1819 uri = soup_uri_new (data->callbacks->data);
1820 soup_message_set_uri (msg, uri);
1821 soup_uri_free (uri);
1824 data->pending_messages =
1825 g_list_prepend (data->pending_messages, msg);
1827 service_session = gupnp_service_get_session (data->service);
1829 soup_session_requeue_message (service_session, msg);
1831 /* Emit 'notify-failed' signal */
1834 error = g_error_new_literal
1835 (GUPNP_EVENTING_ERROR,
1836 GUPNP_EVENTING_ERROR_NOTIFY_FAILED,
1837 msg->reason_phrase);
1839 g_signal_emit (data->service,
1840 signals[NOTIFY_FAILED],
1845 g_error_free (error);
1847 /* Reset callbacks pointer */
1848 data->callbacks = g_list_first (data->callbacks);
1853 /* Send notification @user_data to subscriber @value */
1855 notify_subscriber (gpointer key,
1859 SubscriptionData *data;
1860 const char *property_set;
1863 SoupSession *session;
1866 property_set = user_data;
1868 /* Subscriber called unsubscribe */
1869 if (subscription_data_can_delete (data))
1872 /* Create message */
1873 msg = soup_message_new (GENA_METHOD_NOTIFY, data->callbacks->data);
1875 g_warning ("Invalid callback URL: %s",
1876 (char *) data->callbacks->data);
1881 soup_message_headers_append (msg->request_headers,
1885 soup_message_headers_append (msg->request_headers,
1889 soup_message_headers_append (msg->request_headers,
1893 tmp = g_strdup_printf ("%d", data->seq);
1894 soup_message_headers_append (msg->request_headers,
1899 /* Handle overflow */
1900 if (data->seq < G_MAXINT32)
1906 soup_message_set_request (msg,
1907 "text/xml; charset=\"utf-8\"",
1909 g_strdup (property_set),
1910 strlen (property_set));
1913 data->pending_messages = g_list_prepend (data->pending_messages, msg);
1915 session = gupnp_service_get_session (data->service);
1917 soup_session_queue_message (session,
1919 notify_got_response,
1923 /* Create a property set from @queue */
1925 create_property_set (GQueue *queue)
1930 /* Compose property set */
1931 str = xml_util_new_string ();
1933 g_string_append (str,
1934 "<?xml version=\"1.0\"?>"
1935 "<e:propertyset xmlns:e="
1936 "\"urn:schemas-upnp-org:event-1-0\">");
1939 while ((data = g_queue_pop_head (queue))) {
1940 xml_util_start_element (str, "e:property");
1941 xml_util_start_element (str, data->variable);
1942 gvalue_util_value_append_to_xml_string (&data->value, str);
1943 xml_util_end_element (str, data->variable);
1944 xml_util_end_element (str, "e:property");
1947 notify_data_free (data);
1950 g_string_append (str, "</e:propertyset>");
1952 /* Cleanup & return */
1953 return g_string_free (str, FALSE);
1956 /* Flush all queued notifications */
1958 flush_notifications (GUPnPService *service)
1962 /* Create property set */
1963 mem = create_property_set (service->priv->notify_queue);
1965 /* And send it off */
1966 g_hash_table_foreach (service->priv->subscriptions,
1975 * gupnp_service_notify_value:
1976 * @service: A #GUPnPService
1977 * @variable: The name of the variable to notify
1978 * @value: The value of the variable
1980 * Notifies listening clients that @variable has changed to @value.
1983 gupnp_service_notify_value (GUPnPService *service,
1984 const char *variable,
1985 const GValue *value)
1989 g_return_if_fail (GUPNP_IS_SERVICE (service));
1990 g_return_if_fail (variable != NULL);
1991 g_return_if_fail (G_IS_VALUE (value));
1994 data = g_slice_new0 (NotifyData);
1996 data->variable = g_strdup (variable);
1998 g_value_init (&data->value, G_VALUE_TYPE (value));
1999 g_value_copy (value, &data->value);
2001 g_queue_push_tail (service->priv->notify_queue, data);
2003 /* And flush, if not frozen */
2004 if (!service->priv->notify_frozen)
2005 flush_notifications (service);
2009 * gupnp_service_freeze_notify:
2010 * @service: A #GUPnPService
2012 * Causes new notifications to be queued up until gupnp_service_thaw_notify()
2016 gupnp_service_freeze_notify (GUPnPService *service)
2018 g_return_if_fail (GUPNP_IS_SERVICE (service));
2020 service->priv->notify_frozen = TRUE;
2024 * gupnp_service_thaw_notify:
2025 * @service: A #GUPnPService
2027 * Sends out any pending notifications, and stops queuing of new ones.
2030 gupnp_service_thaw_notify (GUPnPService *service)
2032 g_return_if_fail (GUPNP_IS_SERVICE (service));
2034 service->priv->notify_frozen = FALSE;
2036 if (g_queue_get_length (service->priv->notify_queue) == 0)
2037 return; /* Empty notify queue */
2039 flush_notifications (service);
2042 /* Convert a CamelCase string to a lowercase string with underscores */
2044 strip_camel_case (char *camel_str)
2049 /* Keep enough space for underscores */
2050 stripped = g_malloc (strlen (camel_str) * 2);
2052 for (i = 0, j = 0; i <= strlen (camel_str); i++) {
2053 /* Convert every upper case letter to lower case and unless
2054 * it's the first character, the last charachter, in the
2055 * middle of an abbreviation or there is already an underscore
2056 * before it, add an underscore before it */
2057 if (g_ascii_isupper (camel_str[i])) {
2059 camel_str[i + 1] != '\0' &&
2060 camel_str[i - 1] != '_' &&
2061 !g_ascii_isupper (camel_str[i - 1])) {
2062 stripped[j++] = '_';
2064 stripped[j++] = g_ascii_tolower (camel_str[i]);
2066 stripped[j++] = camel_str[i];
2073 find_callback_by_name (GModule *module,
2079 /* First try with 'on_' prefix */
2080 full_name = g_strjoin ("_",
2085 if (!g_module_symbol (module,
2087 (gpointer) &callback)) {
2090 /* Now try with '_cb' postfix */
2091 full_name = g_strjoin ("_",
2096 if (!g_module_symbol (module,
2098 (gpointer) &callback))
2107 /* Use the strings from @name_list as details to @signal_name, and connect
2108 * callbacks with names based on these same strings to @signal_name::string. */
2110 connect_names_to_signal_handlers (GUPnPService *service,
2112 const GList *name_list,
2113 const char *signal_name,
2114 const char *callback_prefix,
2117 const GList *name_node;
2119 for (name_node = name_list;
2121 name_node = name_node->next) {
2123 char *callback_name;
2124 char *signal_detail;
2126 signal_detail = (char *) name_node->data;
2127 callback_name = strip_camel_case (signal_detail);
2129 if (callback_prefix) {
2132 tmp = g_strjoin ("_",
2137 g_free (callback_name);
2138 callback_name = tmp;
2141 callback = find_callback_by_name (module, callback_name);
2142 g_free (callback_name);
2144 if (callback == NULL)
2147 signal_detail = g_strjoin ("::",
2152 g_signal_connect (service,
2157 g_free (signal_detail);
2162 * gupnp_service_signals_autoconnect:
2163 * @service: A #GUPnPService
2164 * @user_data: the data to pass to each of the callbacks
2165 * @error: return location for a #GError, or %NULL
2167 * A convenience function that attempts to connect all possible
2168 * #GUPnPService::action-invoked and #GUPnPService::query-variable signals to
2169 * appropriate callbacks for the service @service. It uses service introspection
2170 * and GModule's introspective features. It is very simillar to
2171 * glade_xml_signal_autoconnect() except that it attempts to guess the names of
2172 * the signal handlers on its own.
2174 * For this function to do its magic, the application must name the callback
2175 * functions for #GUPnPService::action-invoked signals by striping the CamelCase
2176 * off the action names and either prepend "on_" or append "_cb" to them. Same
2177 * goes for #GUPnPService::query-variable signals, except that "query_" should
2178 * be prepended to the variable name. For example, callback function for
2179 * "GetSystemUpdateID" action should be either named as
2180 * "get_system_update_id_cb" or "on_get_system_update_id" and callback function
2181 * for the query of "SystemUpdateID" state variable should be named
2182 * "query_system_update_id_cb" or "on_query_system_update_id".
2184 * Note that this function will not work correctly if GModule is not supported
2185 * on the platform or introspection is not available for service @service.
2187 * WARNING: This function can not and therefore does not guarantee that the
2188 * resulting signal connections will be correct as it depends heavily on a
2189 * particular naming schemes described above.
2192 gupnp_service_signals_autoconnect (GUPnPService *service,
2196 GUPnPServiceIntrospection *introspection;
2200 g_return_if_fail (GUPNP_IS_SERVICE (service));
2202 introspection = gupnp_service_info_get_introspection
2203 (GUPNP_SERVICE_INFO (service),
2208 /* Get a handle on the main executable -- use this to find symbols */
2209 module = g_module_open (NULL, 0);
2210 if (module == NULL) {
2211 g_error ("Failed to open module: %s", g_module_error ());
2213 g_object_unref (introspection);
2218 names = gupnp_service_introspection_list_action_names (introspection);
2219 connect_names_to_signal_handlers (service,
2226 names = gupnp_service_introspection_list_state_variable_names
2228 connect_names_to_signal_handlers (service,
2235 g_module_close (module);
2236 g_object_unref (introspection);