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>
34 #include "gupnp-service.h"
35 #include "gupnp-root-device.h"
36 #include "gupnp-context-private.h"
37 #include "gupnp-marshal.h"
38 #include "gupnp-error.h"
39 #include "http-headers.h"
40 #include "gena-protocol.h"
42 #include "gvalue-util.h"
47 #include <uuid/uuid.h>
50 #define SUBSCRIPTION_TIMEOUT 300 /* DLNA (7.2.22.1) enforced */
52 G_DEFINE_TYPE (GUPnPService,
54 GUPNP_TYPE_SERVICE_INFO);
56 struct _GUPnPServicePrivate {
57 GUPnPRootDevice *root_device;
61 guint notify_available_id;
63 GHashTable *subscriptions;
65 GList *state_variables;
69 gboolean notify_frozen;
84 static guint signals[LAST_SIGNAL];
87 create_property_set (GQueue *queue);
90 notify_subscriber (gpointer key,
95 GUPnPService *service;
102 GSource *timeout_src;
104 GList *pending_messages; /* Pending SoupMessages from this
106 gboolean initial_state_sent;
111 subscription_data_can_delete (SubscriptionData *data) {
112 return data->initial_state_sent && data->to_delete;
116 send_initial_state (SubscriptionData *data);
119 gupnp_service_get_session (GUPnPService *service)
121 if (! service->priv->session) {
122 /* Create a dedicated session for this service to
123 * ensure that notifications are sent in the proper
124 * order. The session from GUPnPContext may use
125 * multiple connections.
127 service->priv->session = soup_session_async_new_with_options
128 (SOUP_SESSION_IDLE_TIMEOUT, 60,
129 SOUP_SESSION_ASYNC_CONTEXT,
130 g_main_context_get_thread_default (),
131 SOUP_SESSION_MAX_CONNS_PER_HOST, 1,
134 if (g_getenv ("GUPNP_DEBUG")) {
136 logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
137 soup_session_add_feature (
138 service->priv->session,
139 SOUP_SESSION_FEATURE (logger));
143 return service->priv->session;
147 subscription_data_free (SubscriptionData *data)
149 SoupSession *session;
151 session = gupnp_service_get_session (data->service);
153 /* Cancel pending messages */
154 while (data->pending_messages) {
157 msg = data->pending_messages->data;
159 soup_session_cancel_message (session,
161 SOUP_STATUS_CANCELLED);
163 data->pending_messages =
164 g_list_delete_link (data->pending_messages,
165 data->pending_messages);
168 /* Further cleanup */
169 while (data->callbacks) {
170 g_free (data->callbacks->data);
171 data->callbacks = g_list_delete_link (data->callbacks,
177 if (data->timeout_src)
178 g_source_destroy (data->timeout_src);
180 g_slice_free (SubscriptionData, data);
189 notify_data_free (NotifyData *data)
191 g_free (data->variable);
192 g_value_unset (&data->value);
194 g_slice_free (NotifyData, data);
197 struct _GUPnPServiceAction {
198 volatile gint ref_count;
200 GUPnPContext *context;
205 gboolean accept_gzip;
210 GString *response_str;
212 guint argument_count;
216 gupnp_service_action_ref (GUPnPServiceAction *action)
218 g_return_val_if_fail (action, NULL);
219 g_return_val_if_fail (action->ref_count > 0, NULL);
221 g_atomic_int_inc (&action->ref_count);
227 gupnp_service_action_unref (GUPnPServiceAction *action)
229 g_return_if_fail (action);
230 g_return_if_fail (action->ref_count > 0);
232 if (g_atomic_int_dec_and_test (&action->ref_count)) {
233 g_free (action->name);
234 g_object_unref (action->msg);
235 g_object_unref (action->context);
236 g_object_unref (action->doc);
238 g_slice_free (GUPnPServiceAction, action);
243 * gupnp_service_action_get_type:
245 * Get the gtype for GUPnPServiceActon
247 * Return value: The gtype of GUPnPServiceAction
250 gupnp_service_action_get_type (void)
252 static GType our_type = 0;
255 our_type = g_boxed_type_register_static
256 ("GUPnPServiceAction",
257 (GBoxedCopyFunc) gupnp_service_action_ref,
258 (GBoxedFreeFunc) gupnp_service_action_unref);
264 finalize_action (GUPnPServiceAction *action)
268 /* Embed action->response_str in a SOAP document */
269 g_string_prepend (action->response_str,
270 "<?xml version=\"1.0\"?>"
271 "<s:Envelope xmlns:s="
272 "\"http://schemas.xmlsoap.org/soap/envelope/\" "
274 "\"http://schemas.xmlsoap.org/soap/encoding/\">"
277 if (action->msg->status_code != SOUP_STATUS_INTERNAL_SERVER_ERROR) {
278 g_string_append (action->response_str, "</u:");
279 g_string_append (action->response_str, action->name);
280 g_string_append (action->response_str, "Response>");
283 g_string_append (action->response_str,
287 soup_message_headers_replace (action->msg->response_headers,
289 "text/xml; charset=\"utf-8\"");
291 if (action->accept_gzip && action->response_str->len > 1024) {
292 http_response_set_body_gzip (action->msg,
293 action->response_str->str,
294 action->response_str->len);
295 g_string_free (action->response_str, TRUE);
297 soup_message_body_append (action->msg->response_body,
299 action->response_str->str,
300 action->response_str->len);
301 g_string_free (action->response_str, FALSE);
304 soup_message_headers_append (action->msg->response_headers, "Ext", "");
306 /* Server header on response */
307 soup_message_headers_append
308 (action->msg->response_headers,
310 gssdp_client_get_server_id
311 (GSSDP_CLIENT (action->context)));
313 /* Tell soup server that response is now ready */
314 server = gupnp_context_get_server (action->context);
315 soup_server_unpause_message (server, action->msg);
318 gupnp_service_action_unref (action);
322 * gupnp_service_action_get_name:
323 * @action: A #GUPnPServiceAction
325 * Get the name of @action.
327 * Return value: The name of @action
330 gupnp_service_action_get_name (GUPnPServiceAction *action)
332 g_return_val_if_fail (action != NULL, NULL);
338 * gupnp_service_action_get_locales:
339 * @action: A #GUPnPServiceAction
341 * Get an ordered (preferred first) #GList of locales preferred by
342 * the client. Free list and elements after use.
344 * Return value: (element-type utf8) (transfer full): A #GList of #char*
348 gupnp_service_action_get_locales (GUPnPServiceAction *action)
350 g_return_val_if_fail (action != NULL, NULL);
352 return http_request_get_accept_locales (action->msg);
356 * gupnp_service_action_get:
357 * @action: A #GUPnPServiceAction
358 * @Varargs: tuples of argument name, argument type, and argument value
359 * location, terminated with %NULL.
361 * Retrieves the specified action arguments.
364 gupnp_service_action_get (GUPnPServiceAction *action,
369 g_return_if_fail (action != NULL);
371 va_start (var_args, action);
372 gupnp_service_action_get_valist (action, var_args);
377 * gupnp_service_action_get_valist:
378 * @action: A #GUPnPServiceAction
379 * @var_args: va_list of tuples of argument name, argument type, and argument
382 * See gupnp_service_action_get(); this version takes a va_list for
383 * use by language bindings.
386 gupnp_service_action_get_valist (GUPnPServiceAction *action,
389 const char *arg_name;
391 GValue value = {0, };
394 g_return_if_fail (action != NULL);
398 arg_name = va_arg (var_args, const char *);
400 arg_type = va_arg (var_args, GType);
401 g_value_init (&value, arg_type);
403 gupnp_service_action_get_value (action, arg_name, &value);
405 G_VALUE_LCOPY (&value, var_args, 0, ©_error);
407 g_value_unset (&value);
410 g_warning ("Error lcopying value: %s\n", copy_error);
415 arg_name = va_arg (var_args, const char *);
420 * gupnp_service_action_get_values:
421 * @action: A #GUPnPServiceAction
422 * @arg_names: (element-type utf8) : A #GList of argument names as string
423 * @arg_types: (element-type GType): A #GList of argument types as #GType
425 * A variant of #gupnp_service_action_get that uses #GList instead of varargs.
427 * Return value: (element-type GValue) (transfer full): The values as #GList of
428 * #GValue. #g_list_free the returned list and #g_value_unset and #g_slice_free
432 gupnp_service_action_get_values (GUPnPServiceAction *action,
439 g_return_val_if_fail (action != NULL, NULL);
443 for (i = 0; i < g_list_length (arg_names); i++) {
444 const char *arg_name;
448 arg_name = (const char *) g_list_nth_data (arg_names, i);
449 arg_type = (GType) g_list_nth_data (arg_types, i);
451 arg_value = g_slice_new0 (GValue);
452 g_value_init (arg_value, arg_type);
454 gupnp_service_action_get_value (action, arg_name, arg_value);
456 arg_values = g_list_append (arg_values, arg_value);
463 * gupnp_service_action_get_value: (skip)
464 * @action: A #GUPnPServiceAction
465 * @argument: The name of the argument to retrieve
466 * @value: (inout):The #GValue to store the value of the argument, initialized
467 * to the correct type.
469 * Retrieves the value of @argument into @value.
472 gupnp_service_action_get_value (GUPnPServiceAction *action,
473 const char *argument,
479 g_return_if_fail (action != NULL);
480 g_return_if_fail (argument != NULL);
481 g_return_if_fail (value != NULL);
484 for (node = action->node->children; node; node = node->next) {
485 if (strcmp ((char *) node->name, argument) != 0)
488 found = gvalue_util_set_value_from_xml_node (value, node);
494 g_warning ("Failed to retreive '%s' argument of '%s' action",
500 * gupnp_service_action_get_gvalue:
501 * @action: A #GUPnPServiceAction
502 * @argument: The name of the argument to retrieve
503 * @type: The type of argument to retrieve
505 * Retrieves the value of @argument into a GValue of type @type and returns it.
506 * The method exists only and only to satify PyGI, please use
507 * #gupnp_service_action_get_value and ignore this if possible.
509 * Return value: (transfer full): Value as #GValue associated with @action.
510 * #g_value_unset and #g_slice_free it after usage.
512 * Rename To: gupnp_service_action_get_value
515 gupnp_service_action_get_gvalue (GUPnPServiceAction *action,
516 const char *argument,
521 val = g_slice_new0 (GValue);
522 g_value_init (val, type);
524 gupnp_service_action_get_value (action, argument, val);
530 * gupnp_service_action_get_argument_count:
531 * @action: A #GUPnPServiceAction
533 * Get the number of IN arguments from the @action and return it.
535 * Return value: The number of IN arguments from the @action.
538 gupnp_service_action_get_argument_count (GUPnPServiceAction *action)
540 return action->argument_count;
544 * gupnp_service_action_set:
545 * @action: A #GUPnPServiceAction
546 * @Varargs: tuples of return value name, return value type, and
547 * actual return value, terminated with %NULL.
549 * Sets the specified action return values.
552 gupnp_service_action_set (GUPnPServiceAction *action,
557 g_return_if_fail (action != NULL);
559 va_start (var_args, action);
560 gupnp_service_action_set_valist (action, var_args);
565 * gupnp_service_action_set_valist:
566 * @action: A #GUPnPServiceAction
567 * @var_args: va_list of tuples of return value name, return value type, and
568 * actual return value.
570 * See gupnp_service_action_set(); this version takes a va_list for
571 * use by language bindings.
574 gupnp_service_action_set_valist (GUPnPServiceAction *action,
577 const char *arg_name;
579 GValue value = {0, };
582 g_return_if_fail (action != NULL);
584 collect_error = NULL;
586 arg_name = va_arg (var_args, const char *);
588 arg_type = va_arg (var_args, GType);
589 g_value_init (&value, arg_type);
591 G_VALUE_COLLECT (&value, var_args, G_VALUE_NOCOPY_CONTENTS,
593 if (!collect_error) {
594 gupnp_service_action_set_value (action,
597 g_value_unset (&value);
600 g_warning ("Error collecting value: %s\n",
603 g_free (collect_error);
606 arg_name = va_arg (var_args, const char *);
611 * gupnp_service_action_set_values:
612 * @action: A #GUPnPServiceAction
613 * @arg_names: (element-type utf8) (transfer none): A #GList of argument names
614 * @arg_values: (element-type GValue) (transfer none): The #GList of values (as
615 * #GValues) that line up with @arg_names.
617 * Sets the specified action return values.
620 gupnp_service_action_set_values (GUPnPServiceAction *action,
624 g_return_if_fail (action != NULL);
625 g_return_if_fail (arg_names != NULL);
626 g_return_if_fail (arg_values != NULL);
627 g_return_if_fail (g_list_length (arg_names) ==
628 g_list_length (arg_values));
630 if (action->msg->status_code == SOUP_STATUS_INTERNAL_SERVER_ERROR) {
631 g_warning ("Calling gupnp_service_action_set_value() after "
632 "having called gupnp_service_action_return_error() "
638 /* Append to response */
639 for (; arg_names; arg_names = arg_names->next) {
640 const char *arg_name;
643 arg_name = arg_names->data;
644 value = arg_values->data;
646 xml_util_start_element (action->response_str, arg_name);
647 gvalue_util_value_append_to_xml_string (value,
648 action->response_str);
649 xml_util_end_element (action->response_str, arg_name);
651 arg_values = arg_values->next;
656 * gupnp_service_action_set_value:
657 * @action: A #GUPnPServiceAction
658 * @argument: The name of the return value to retrieve
659 * @value: The #GValue to store the return value
661 * Sets the value of @argument to @value.
664 gupnp_service_action_set_value (GUPnPServiceAction *action,
665 const char *argument,
668 g_return_if_fail (action != NULL);
669 g_return_if_fail (argument != NULL);
670 g_return_if_fail (value != NULL);
672 if (action->msg->status_code == SOUP_STATUS_INTERNAL_SERVER_ERROR) {
673 g_warning ("Calling gupnp_service_action_set_value() after "
674 "having called gupnp_service_action_return_error() "
680 /* Append to response */
681 xml_util_start_element (action->response_str, argument);
682 gvalue_util_value_append_to_xml_string (value, action->response_str);
683 xml_util_end_element (action->response_str, argument);
687 * gupnp_service_action_return:
688 * @action: A #GUPnPServiceAction
690 * Return succesfully.
693 gupnp_service_action_return (GUPnPServiceAction *action)
695 g_return_if_fail (action != NULL);
697 soup_message_set_status (action->msg, SOUP_STATUS_OK);
699 finalize_action (action);
703 * gupnp_service_action_return_error:
704 * @action: A #GUPnPServiceAction
705 * @error_code: The error code
706 * @error_description: The error description, or %NULL if @error_code is
707 * one of #GUPNP_CONTROL_ERROR_INVALID_ACTION,
708 * #GUPNP_CONTROL_ERROR_INVALID_ARGS, #GUPNP_CONTROL_ERROR_OUT_OF_SYNC or
709 * #GUPNP_CONTROL_ERROR_ACTION_FAILED, in which case a description is
710 * provided automatically.
712 * Return @error_code.
715 gupnp_service_action_return_error (GUPnPServiceAction *action,
717 const char *error_description)
719 g_return_if_fail (action != NULL);
721 switch (error_code) {
722 case GUPNP_CONTROL_ERROR_INVALID_ACTION:
723 if (error_description == NULL)
724 error_description = "Invalid Action";
727 case GUPNP_CONTROL_ERROR_INVALID_ARGS:
728 if (error_description == NULL)
729 error_description = "Invalid Args";
732 case GUPNP_CONTROL_ERROR_OUT_OF_SYNC:
733 if (error_description == NULL)
734 error_description = "Out of Sync";
737 case GUPNP_CONTROL_ERROR_ACTION_FAILED:
738 if (error_description == NULL)
739 error_description = "Action Failed";
743 g_return_if_fail (error_description != NULL);
747 /* Replace response_str with a SOAP Fault */
748 g_string_erase (action->response_str, 0, -1);
750 xml_util_start_element (action->response_str, "s:Fault");
752 xml_util_start_element (action->response_str, "faultcode");
753 g_string_append (action->response_str, "s:Client");
754 xml_util_end_element (action->response_str, "faultcode");
756 xml_util_start_element (action->response_str, "faultstring");
757 g_string_append (action->response_str, "UPnPError");
758 xml_util_end_element (action->response_str, "faultstring");
760 xml_util_start_element (action->response_str, "detail");
762 xml_util_start_element (action->response_str,
764 "xmlns=\"urn:schemas-upnp-org:control-1-0\"");
766 xml_util_start_element (action->response_str, "errorCode");
767 g_string_append_printf (action->response_str, "%u", error_code);
768 xml_util_end_element (action->response_str, "errorCode");
770 xml_util_start_element (action->response_str, "errorDescription");
771 xml_util_add_content (action->response_str, error_description);
772 xml_util_end_element (action->response_str, "errorDescription");
774 xml_util_end_element (action->response_str, "UPnPError");
775 xml_util_end_element (action->response_str, "detail");
777 xml_util_end_element (action->response_str, "s:Fault");
779 soup_message_set_status (action->msg,
780 SOUP_STATUS_INTERNAL_SERVER_ERROR);
782 finalize_action (action);
786 * gupnp_service_action_get_message:
787 * @action: A #GUPnPServiceAction
789 * Get the #SoupMessage associated with @action. Mainly intended for
790 * applications to be able to read HTTP headers received from clients.
792 * Return value: (transfer full): #SoupMessage associated with @action. Unref
796 gupnp_service_action_get_message (GUPnPServiceAction *action)
798 return g_object_ref (action->msg);
802 gupnp_service_init (GUPnPService *service)
804 service->priv = G_TYPE_INSTANCE_GET_PRIVATE (service,
806 GUPnPServicePrivate);
808 service->priv->subscriptions =
809 g_hash_table_new_full (g_str_hash,
812 (GDestroyNotify) subscription_data_free);
814 service->priv->notify_queue = g_queue_new ();
817 /* Generate a new action response node for @action_name */
819 new_action_response_str (const char *action_name,
820 const char *service_type)
824 str = xml_util_new_string ();
826 g_string_append (str, "<u:");
827 g_string_append (str, action_name);
828 g_string_append (str, "Response xmlns:u=");
830 if (service_type != NULL) {
831 g_string_append (str, service_type);
832 g_string_append_c (str, '"');
834 g_warning ("No serviceType defined. Control may not work "
838 g_string_append_c (str, '>');
843 /* Handle QueryStateVariable action */
845 query_state_variable (GUPnPService *service,
846 GUPnPServiceAction *action)
850 /* Iterate requested variables */
851 for (node = action->node->children; node; node = node->next) {
855 if (strcmp ((char *) node->name, "varName") != 0)
859 var_name = xmlNodeGetContent (node);
861 gupnp_service_action_return_error (action,
869 g_signal_emit (service,
870 signals[QUERY_VARIABLE],
871 g_quark_from_string ((char *) var_name),
875 if (!G_IS_VALUE (&value)) {
876 gupnp_service_action_return_error (action,
885 /* Add variable to response */
886 gupnp_service_action_set_value (action,
891 g_value_unset (&value);
896 gupnp_service_action_return (action);
899 /* controlURL handler */
901 control_server_handler (SoupServer *server,
903 const char *server_path,
905 SoupClientContext *soup_client,
908 GUPnPService *service;
909 GUPnPContext *context;
911 xmlNode *action_node, *node;
912 const char *soap_action;
913 const char *accept_encoding;
916 GUPnPServiceAction *action;
918 service = GUPNP_SERVICE (user_data);
920 if (msg->method != SOUP_METHOD_POST) {
921 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
926 if (msg->request_body->length == 0) {
927 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
932 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (service));
934 /* Get action name */
935 soap_action = soup_message_headers_get_one (msg->request_headers,
938 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
942 action_name = strchr (soap_action, '#');
944 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
949 /* This memory is libsoup-owned so we can do this */
953 /* This memory is libsoup-owned so we can do this */
954 end = strrchr (action_name, '"');
958 /* Parse action_node */
959 doc = xmlRecoverMemory (msg->request_body->data,
960 msg->request_body->length);
962 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
967 action_node = xml_util_get_element ((xmlNode *) doc,
973 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
978 /* Create action structure */
979 action = g_slice_new0 (GUPnPServiceAction);
981 action->ref_count = 1;
982 action->name = g_strdup (action_name);
983 action->msg = g_object_ref (msg);
984 action->doc = gupnp_xml_doc_new(doc);
985 action->node = action_node;
986 action->response_str = new_action_response_str (action_name,
988 action->context = g_object_ref (context);
989 action->argument_count = 0;
991 for (node = action->node->children; node; node = node->next)
992 if (node->type == XML_ELEMENT_NODE)
993 action->argument_count++;
995 /* Get accepted encodings */
996 accept_encoding = soup_message_headers_get_list (msg->request_headers,
999 if (accept_encoding) {
1002 codings = soup_header_parse_quality_list (accept_encoding,
1005 g_slist_find_custom (codings, "gzip",
1006 (GCompareFunc) g_ascii_strcasecmp)) {
1007 action->accept_gzip = TRUE;
1010 soup_header_free_list (codings);
1013 /* Tell soup server that response is not ready yet */
1014 soup_server_pause_message (server, msg);
1016 /* QueryStateVariable? */
1017 if (strcmp (action_name, "QueryStateVariable") == 0)
1018 query_state_variable (service, action);
1020 GQuark action_name_quark;
1022 action_name_quark = g_quark_from_string (action_name);
1023 if (g_signal_has_handler_pending (service,
1024 signals[ACTION_INVOKED],
1027 /* Emit signal. Handler parses request and fills in
1029 g_signal_emit (service,
1030 signals[ACTION_INVOKED],
1034 /* No handlers attached. */
1035 gupnp_service_action_return_error (action,
1042 /* Generates a standard (re)subscription response */
1044 subscription_response (GUPnPService *service,
1049 GUPnPContext *context;
1050 GSSDPClient *client;
1053 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (service));
1054 client = GSSDP_CLIENT (context);
1056 /* Server header on response */
1057 soup_message_headers_append (msg->response_headers,
1059 gssdp_client_get_server_id (client));
1062 soup_message_headers_append (msg->response_headers,
1066 /* Timeout header */
1068 tmp = g_strdup_printf ("Second-%d", timeout);
1070 tmp = g_strdup ("infinite");
1072 soup_message_headers_append (msg->response_headers,
1078 soup_message_set_status (msg, SOUP_STATUS_OK);
1081 /* Generates a new SID */
1089 stat = UuidCreate (&uuid);
1090 if (stat == RPC_S_OK) {
1091 unsigned char* uuidStr = NULL;
1092 stat = UuidToString (&uuid, &uuidStr);
1093 if (stat == RPC_S_OK) {
1094 ret = g_strdup_printf ("uuid:%s", uuidStr);
1095 RpcStringFree (&uuidStr);
1105 uuid_unparse (id, out);
1107 return g_strdup_printf ("uuid:%s", out);
1111 /* Subscription expired */
1113 subscription_timeout (gpointer user_data)
1115 SubscriptionData *data;
1119 g_hash_table_remove (data->service->priv->subscriptions, data->sid);
1125 send_initial_state (SubscriptionData *data)
1131 /* Send initial event message */
1132 queue = g_queue_new ();
1134 for (l = data->service->priv->state_variables; l; l = l->next) {
1137 ndata = g_slice_new0 (NotifyData);
1139 g_signal_emit (data->service,
1140 signals[QUERY_VARIABLE],
1141 g_quark_from_string (l->data),
1145 if (!G_IS_VALUE (&ndata->value)) {
1146 g_slice_free (NotifyData, ndata);
1151 ndata->variable = g_strdup (l->data);
1153 g_queue_push_tail (queue, ndata);
1156 mem = create_property_set (queue);
1157 notify_subscriber (data->sid, data, mem);
1160 g_queue_free (queue);
1166 /* Subscription request */
1168 subscribe (GUPnPService *service,
1170 const char *callback)
1172 SubscriptionData *data;
1173 char *start, *end, *uri;
1175 data = g_slice_new0 (SubscriptionData);
1177 /* Parse callback list */
1178 start = (char *) callback;
1179 while ((start = strchr (start, '<'))) {
1181 if (!start || !*start)
1184 end = strchr (start, '>');
1188 if (strncmp (start, "http://", strlen ("http://")) == 0) {
1189 uri = g_strndup (start, end - start);
1190 data->callbacks = g_list_append (data->callbacks, uri);
1196 if (!data->callbacks) {
1197 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1199 g_slice_free (SubscriptionData, data);
1204 /* Add service and SID */
1205 data->service = service;
1206 data->sid = generate_sid ();
1209 data->timeout_src = g_timeout_source_new_seconds (SUBSCRIPTION_TIMEOUT);
1210 g_source_set_callback (data->timeout_src,
1211 subscription_timeout,
1215 g_source_attach (data->timeout_src,
1216 g_main_context_get_thread_default ());
1218 g_source_unref (data->timeout_src);
1221 g_hash_table_insert (service->priv->subscriptions,
1226 subscription_response (service, msg, data->sid, SUBSCRIPTION_TIMEOUT);
1228 send_initial_state (data);
1231 /* Resubscription request */
1233 resubscribe (GUPnPService *service,
1237 SubscriptionData *data;
1239 data = g_hash_table_lookup (service->priv->subscriptions, sid);
1241 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1246 /* Update timeout */
1247 if (data->timeout_src) {
1248 g_source_destroy (data->timeout_src);
1249 data->timeout_src = NULL;
1252 data->timeout_src = g_timeout_source_new_seconds (SUBSCRIPTION_TIMEOUT);
1253 g_source_set_callback (data->timeout_src,
1254 subscription_timeout,
1258 g_source_attach (data->timeout_src,
1259 g_main_context_get_thread_default ());
1261 g_source_unref (data->timeout_src);
1264 subscription_response (service, msg, sid, SUBSCRIPTION_TIMEOUT);
1267 /* Unsubscription request */
1269 unsubscribe (GUPnPService *service,
1273 SubscriptionData *data;
1275 data = g_hash_table_lookup (service->priv->subscriptions, sid);
1277 if (data->initial_state_sent)
1278 g_hash_table_remove (service->priv->subscriptions,
1281 data->to_delete = TRUE;
1282 soup_message_set_status (msg, SOUP_STATUS_OK);
1284 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1287 /* eventSubscriptionURL handler */
1289 subscription_server_handler (SoupServer *server,
1291 const char *server_path,
1293 SoupClientContext *soup_client,
1296 GUPnPService *service;
1297 const char *callback, *nt, *sid;
1299 service = GUPNP_SERVICE (user_data);
1301 callback = soup_message_headers_get_one (msg->request_headers,
1303 nt = soup_message_headers_get_one (msg->request_headers, "NT");
1304 sid = soup_message_headers_get_one (msg->request_headers, "SID");
1306 /* Choose appropriate handler */
1307 if (strcmp (msg->method, GENA_METHOD_SUBSCRIBE) == 0) {
1310 soup_message_set_status
1311 (msg, SOUP_STATUS_BAD_REQUEST);
1313 } else if (!nt || strcmp (nt, "upnp:event") != 0) {
1314 soup_message_set_status
1315 (msg, SOUP_STATUS_PRECONDITION_FAILED);
1318 subscribe (service, msg, callback);
1324 soup_message_set_status
1325 (msg, SOUP_STATUS_BAD_REQUEST);
1328 resubscribe (service, msg, sid);
1333 soup_message_set_status
1334 (msg, SOUP_STATUS_PRECONDITION_FAILED);
1338 } else if (strcmp (msg->method, GENA_METHOD_UNSUBSCRIBE) == 0) {
1340 if (nt || callback) {
1341 soup_message_set_status
1342 (msg, SOUP_STATUS_BAD_REQUEST);
1345 unsubscribe (service, msg, sid);
1350 soup_message_set_status
1351 (msg, SOUP_STATUS_PRECONDITION_FAILED);
1356 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
1362 got_introspection (GUPnPServiceInfo *info,
1363 GUPnPServiceIntrospection *introspection,
1364 const GError *error,
1367 GUPnPService *service = GUPNP_SERVICE (info);
1368 const GList *state_variables, *l;
1369 GHashTableIter iter;
1372 if (introspection) {
1374 gupnp_service_introspection_list_state_variables
1377 for (l = state_variables; l; l = l->next) {
1378 GUPnPServiceStateVariableInfo *variable;
1382 if (!variable->send_events)
1385 service->priv->state_variables =
1386 g_list_prepend (service->priv->state_variables,
1387 g_strdup (variable->name));
1390 g_object_unref (introspection);
1392 g_warning ("Failed to get SCPD: %s\n"
1393 "The initial event message will not be sent.",
1394 error ? error->message : "No error");
1396 g_hash_table_iter_init (&iter, service->priv->subscriptions);
1398 while (g_hash_table_iter_next (&iter, NULL, &data)) {
1399 send_initial_state ((SubscriptionData *) data);
1400 if (subscription_data_can_delete ((SubscriptionData *) data))
1401 g_hash_table_iter_remove (&iter);
1406 path_from_url (const char *url)
1411 uri = soup_uri_new (url);
1412 path = soup_uri_to_string (uri, TRUE);
1413 soup_uri_free (uri);
1419 gupnp_service_constructor (GType type,
1420 guint n_construct_params,
1421 GObjectConstructParam *construct_params)
1423 GObjectClass *object_class;
1425 GUPnPServiceInfo *info;
1426 GUPnPContext *context;
1431 object_class = G_OBJECT_CLASS (gupnp_service_parent_class);
1434 object = object_class->constructor (type,
1438 info = GUPNP_SERVICE_INFO (object);
1440 /* Get introspection and save state variable names */
1441 gupnp_service_info_get_introspection_async (info,
1446 context = gupnp_service_info_get_context (info);
1447 server = gupnp_context_get_server (context);
1449 /* Run listener on controlURL */
1450 url = gupnp_service_info_get_control_url (info);
1451 path = path_from_url (url);
1452 soup_server_add_handler (server, path,
1453 control_server_handler, object, NULL);
1457 /* Run listener on eventSubscriptionURL */
1458 url = gupnp_service_info_get_event_subscription_url (info);
1459 path = path_from_url (url);
1460 soup_server_add_handler (server, path,
1461 subscription_server_handler, object, NULL);
1468 /* Root device availability changed. */
1470 notify_available_cb (GObject *object,
1474 GUPnPService *service;
1476 service = GUPNP_SERVICE (user_data);
1478 if (!gupnp_root_device_get_available (GUPNP_ROOT_DEVICE (object))) {
1479 /* Root device now unavailable: Purge subscriptions */
1480 g_hash_table_remove_all (service->priv->subscriptions);
1485 gupnp_service_set_property (GObject *object,
1487 const GValue *value,
1490 GUPnPService *service;
1492 service = GUPNP_SERVICE (object);
1494 switch (property_id) {
1495 case PROP_ROOT_DEVICE: {
1496 GUPnPRootDevice **dev;
1498 service->priv->root_device = g_value_get_object (value);
1499 dev = &(service->priv->root_device);
1501 g_object_add_weak_pointer
1502 (G_OBJECT (service->priv->root_device),
1505 service->priv->notify_available_id =
1506 g_signal_connect_object (service->priv->root_device,
1507 "notify::available",
1509 (notify_available_cb),
1516 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1522 gupnp_service_get_property (GObject *object,
1527 GUPnPService *service;
1529 service = GUPNP_SERVICE (object);
1531 switch (property_id) {
1532 case PROP_ROOT_DEVICE:
1533 g_value_set_object (value, service->priv->root_device);
1536 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1542 gupnp_service_dispose (GObject *object)
1544 GUPnPService *service;
1545 GObjectClass *object_class;
1546 GUPnPServiceInfo *info;
1547 GUPnPContext *context;
1552 service = GUPNP_SERVICE (object);
1555 info = GUPNP_SERVICE_INFO (service);
1556 context = gupnp_service_info_get_context (info);
1557 server = gupnp_context_get_server (context);
1559 /* Remove listener on controlURL */
1560 url = gupnp_service_info_get_control_url (info);
1561 path = path_from_url (url);
1562 soup_server_remove_handler (server, path);
1566 /* Remove listener on eventSubscriptionURL */
1567 url = gupnp_service_info_get_event_subscription_url (info);
1568 path = path_from_url (url);
1569 soup_server_remove_handler (server, path);
1573 if (service->priv->root_device) {
1574 GUPnPRootDevice **dev = &(service->priv->root_device);
1576 if (g_signal_handler_is_connected
1577 (service->priv->root_device,
1578 service->priv->notify_available_id)) {
1579 g_signal_handler_disconnect
1580 (service->priv->root_device,
1581 service->priv->notify_available_id);
1584 g_object_remove_weak_pointer
1585 (G_OBJECT (service->priv->root_device),
1588 service->priv->root_device = NULL;
1591 /* Cancel pending messages */
1592 g_hash_table_remove_all (service->priv->subscriptions);
1595 object_class = G_OBJECT_CLASS (gupnp_service_parent_class);
1596 object_class->dispose (object);
1600 gupnp_service_finalize (GObject *object)
1602 GUPnPService *service;
1603 GObjectClass *object_class;
1606 service = GUPNP_SERVICE (object);
1608 /* Free subscription hash */
1609 g_hash_table_destroy (service->priv->subscriptions);
1611 /* Free state variable list */
1612 while (service->priv->state_variables) {
1613 g_free (service->priv->state_variables->data);
1614 service->priv->state_variables =
1615 g_list_delete_link (service->priv->state_variables,
1616 service->priv->state_variables);
1619 /* Free notify queue */
1620 while ((data = g_queue_pop_head (service->priv->notify_queue)))
1621 notify_data_free (data);
1623 g_queue_free (service->priv->notify_queue);
1625 if (service->priv->session) {
1626 g_object_unref (service->priv->session);
1627 service->priv->session = NULL;
1631 object_class = G_OBJECT_CLASS (gupnp_service_parent_class);
1632 object_class->finalize (object);
1636 gupnp_service_class_init (GUPnPServiceClass *klass)
1638 GObjectClass *object_class;
1640 object_class = G_OBJECT_CLASS (klass);
1642 object_class->set_property = gupnp_service_set_property;
1643 object_class->get_property = gupnp_service_get_property;
1644 object_class->constructor = gupnp_service_constructor;
1645 object_class->dispose = gupnp_service_dispose;
1646 object_class->finalize = gupnp_service_finalize;
1648 g_type_class_add_private (klass, sizeof (GUPnPServicePrivate));
1651 * GUPnPService:root-device:
1653 * The containing #GUPnPRootDevice.
1655 g_object_class_install_property
1658 g_param_spec_object ("root-device",
1660 "The GUPnPRootDevice",
1661 GUPNP_TYPE_ROOT_DEVICE,
1663 G_PARAM_CONSTRUCT_ONLY |
1664 G_PARAM_STATIC_NAME |
1665 G_PARAM_STATIC_NICK |
1666 G_PARAM_STATIC_BLURB));
1669 * GUPnPService::action-invoked:
1670 * @service: The #GUPnPService that received the signal
1671 * @action: The invoked #GUPnPServiceAction
1673 * Emitted whenever an action is invoked. Handler should process
1674 * @action and must call either gupnp_service_action_return() or
1675 * gupnp_service_action_return_error().
1677 signals[ACTION_INVOKED] =
1678 g_signal_new ("action-invoked",
1680 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
1681 G_STRUCT_OFFSET (GUPnPServiceClass,
1685 g_cclosure_marshal_VOID__BOXED,
1688 GUPNP_TYPE_SERVICE_ACTION);
1691 * GUPnPService::query-variable:
1692 * @service: The #GUPnPService that received the signal
1693 * @variable: The variable that is being queried
1694 * @value: (type GValue)(inout):The location of the #GValue of the variable
1696 * Emitted whenever @service needs to know the value of @variable.
1697 * Handler should fill @value with the value of @variable.
1699 signals[QUERY_VARIABLE] =
1700 g_signal_new ("query-variable",
1702 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
1703 G_STRUCT_OFFSET (GUPnPServiceClass,
1707 gupnp_marshal_VOID__STRING_POINTER,
1711 G_TYPE_POINTER /* Not G_TYPE_VALUE as this
1712 is an outward argument! */);
1715 * GUPnPService::notify-failed:
1716 * @service: The #GUPnPService that received the signal
1717 * @callback_url: (type GList)(element-type SoupURI):A #GList of callback URLs
1718 * @reason: (type GError): A pointer to a #GError describing why the notify failed
1720 * Emitted whenever notification of a client fails.
1722 signals[NOTIFY_FAILED] =
1723 g_signal_new ("notify-failed",
1726 G_STRUCT_OFFSET (GUPnPServiceClass,
1730 gupnp_marshal_VOID__POINTER_POINTER,
1738 * gupnp_service_notify:
1739 * @service: A #GUPnPService
1740 * @Varargs: Tuples of variable name, variable type, and variable value,
1741 * terminated with %NULL.
1743 * Notifies listening clients that the properties listed in @Varargs
1744 * have changed to the specified values.
1747 gupnp_service_notify (GUPnPService *service,
1752 g_return_if_fail (GUPNP_IS_SERVICE (service));
1754 va_start (var_args, service);
1755 gupnp_service_notify_valist (service, var_args);
1760 * gupnp_service_notify_valist:
1761 * @service: A #GUPnPService
1762 * @var_args: A va_list of tuples of variable name, variable type, and variable
1763 * value, terminated with %NULL.
1765 * See gupnp_service_notify(); this version takes a va_list for
1766 * use by language bindings.
1769 gupnp_service_notify_valist (GUPnPService *service,
1772 const char *var_name;
1774 GValue value = {0, };
1775 char *collect_error;
1777 g_return_if_fail (GUPNP_IS_SERVICE (service));
1779 collect_error = NULL;
1781 var_name = va_arg (var_args, const char *);
1783 var_type = va_arg (var_args, GType);
1784 g_value_init (&value, var_type);
1786 G_VALUE_COLLECT (&value, var_args, G_VALUE_NOCOPY_CONTENTS,
1788 if (!collect_error) {
1789 gupnp_service_notify_value (service, var_name, &value);
1791 g_value_unset (&value);
1794 g_warning ("Error collecting value: %s\n",
1797 g_free (collect_error);
1800 var_name = va_arg (var_args, const char *);
1804 /* Received notify response. */
1806 notify_got_response (SoupSession *session,
1810 SubscriptionData *data;
1813 if (msg->status_code == SOUP_STATUS_CANCELLED)
1818 /* Remove from pending messages list */
1819 data->pending_messages = g_list_remove (data->pending_messages, msg);
1821 if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
1822 data->initial_state_sent = TRUE;
1824 /* Success: reset callbacks pointer */
1825 data->callbacks = g_list_first (data->callbacks);
1827 } else if (msg->status_code == SOUP_STATUS_PRECONDITION_FAILED) {
1828 /* Precondition failed: Cancel subscription */
1829 g_hash_table_remove (data->service->priv->subscriptions,
1833 /* Other failure: Try next callback or signal failure. */
1834 if (data->callbacks->next) {
1836 SoupSession *service_session;
1838 /* Call next callback */
1839 data->callbacks = data->callbacks->next;
1841 uri = soup_uri_new (data->callbacks->data);
1842 soup_message_set_uri (msg, uri);
1843 soup_uri_free (uri);
1846 data->pending_messages =
1847 g_list_prepend (data->pending_messages, msg);
1849 service_session = gupnp_service_get_session (data->service);
1851 soup_session_requeue_message (service_session, msg);
1853 /* Emit 'notify-failed' signal */
1856 error = g_error_new_literal
1857 (GUPNP_EVENTING_ERROR,
1858 GUPNP_EVENTING_ERROR_NOTIFY_FAILED,
1859 msg->reason_phrase);
1861 g_signal_emit (data->service,
1862 signals[NOTIFY_FAILED],
1867 g_error_free (error);
1869 /* Reset callbacks pointer */
1870 data->callbacks = g_list_first (data->callbacks);
1875 /* Send notification @user_data to subscriber @value */
1877 notify_subscriber (gpointer key,
1881 SubscriptionData *data;
1882 const char *property_set;
1885 SoupSession *session;
1888 property_set = user_data;
1890 /* Subscriber called unsubscribe */
1891 if (subscription_data_can_delete (data))
1894 /* Create message */
1895 msg = soup_message_new (GENA_METHOD_NOTIFY, data->callbacks->data);
1897 g_warning ("Invalid callback URL: %s",
1898 (char *) data->callbacks->data);
1903 soup_message_headers_append (msg->request_headers,
1907 soup_message_headers_append (msg->request_headers,
1911 soup_message_headers_append (msg->request_headers,
1915 tmp = g_strdup_printf ("%d", data->seq);
1916 soup_message_headers_append (msg->request_headers,
1921 /* Handle overflow */
1922 if (data->seq < G_MAXINT32)
1928 soup_message_set_request (msg,
1929 "text/xml; charset=\"utf-8\"",
1931 g_strdup (property_set),
1932 strlen (property_set));
1935 data->pending_messages = g_list_prepend (data->pending_messages, msg);
1936 soup_message_headers_append (msg->request_headers,
1937 "Connection", "close");
1939 session = gupnp_service_get_session (data->service);
1941 soup_session_queue_message (session,
1943 notify_got_response,
1947 /* Create a property set from @queue */
1949 create_property_set (GQueue *queue)
1954 /* Compose property set */
1955 str = xml_util_new_string ();
1957 g_string_append (str,
1958 "<?xml version=\"1.0\"?>"
1959 "<e:propertyset xmlns:e="
1960 "\"urn:schemas-upnp-org:event-1-0\">");
1963 while ((data = g_queue_pop_head (queue))) {
1964 xml_util_start_element (str, "e:property");
1965 xml_util_start_element (str, data->variable);
1966 gvalue_util_value_append_to_xml_string (&data->value, str);
1967 xml_util_end_element (str, data->variable);
1968 xml_util_end_element (str, "e:property");
1971 notify_data_free (data);
1974 g_string_append (str, "</e:propertyset>");
1976 /* Cleanup & return */
1977 return g_string_free (str, FALSE);
1980 /* Flush all queued notifications */
1982 flush_notifications (GUPnPService *service)
1986 /* Create property set */
1987 mem = create_property_set (service->priv->notify_queue);
1989 /* And send it off */
1990 g_hash_table_foreach (service->priv->subscriptions,
1999 * gupnp_service_notify_value:
2000 * @service: A #GUPnPService
2001 * @variable: The name of the variable to notify
2002 * @value: The value of the variable
2004 * Notifies listening clients that @variable has changed to @value.
2007 gupnp_service_notify_value (GUPnPService *service,
2008 const char *variable,
2009 const GValue *value)
2013 g_return_if_fail (GUPNP_IS_SERVICE (service));
2014 g_return_if_fail (variable != NULL);
2015 g_return_if_fail (G_IS_VALUE (value));
2018 data = g_slice_new0 (NotifyData);
2020 data->variable = g_strdup (variable);
2022 g_value_init (&data->value, G_VALUE_TYPE (value));
2023 g_value_copy (value, &data->value);
2025 g_queue_push_tail (service->priv->notify_queue, data);
2027 /* And flush, if not frozen */
2028 if (!service->priv->notify_frozen)
2029 flush_notifications (service);
2033 * gupnp_service_freeze_notify:
2034 * @service: A #GUPnPService
2036 * Causes new notifications to be queued up until gupnp_service_thaw_notify()
2040 gupnp_service_freeze_notify (GUPnPService *service)
2042 g_return_if_fail (GUPNP_IS_SERVICE (service));
2044 service->priv->notify_frozen = TRUE;
2048 * gupnp_service_thaw_notify:
2049 * @service: A #GUPnPService
2051 * Sends out any pending notifications, and stops queuing of new ones.
2054 gupnp_service_thaw_notify (GUPnPService *service)
2056 g_return_if_fail (GUPNP_IS_SERVICE (service));
2058 service->priv->notify_frozen = FALSE;
2060 if (g_queue_get_length (service->priv->notify_queue) == 0)
2061 return; /* Empty notify queue */
2063 flush_notifications (service);
2066 /* Convert a CamelCase string to a lowercase string with underscores */
2068 strip_camel_case (char *camel_str)
2073 /* Keep enough space for underscores */
2074 stripped = g_malloc (strlen (camel_str) * 2);
2076 for (i = 0, j = 0; i <= strlen (camel_str); i++) {
2077 /* Convert every upper case letter to lower case and unless
2078 * it's the first character, the last charachter, in the
2079 * middle of an abbreviation or there is already an underscore
2080 * before it, add an underscore before it */
2081 if (g_ascii_isupper (camel_str[i])) {
2083 camel_str[i + 1] != '\0' &&
2084 camel_str[i - 1] != '_' &&
2085 !g_ascii_isupper (camel_str[i - 1])) {
2086 stripped[j++] = '_';
2088 stripped[j++] = g_ascii_tolower (camel_str[i]);
2090 stripped[j++] = camel_str[i];
2097 find_callback_by_name (GModule *module,
2103 /* First try with 'on_' prefix */
2104 full_name = g_strjoin ("_",
2109 if (!g_module_symbol (module,
2111 (gpointer) &callback)) {
2114 /* Now try with '_cb' postfix */
2115 full_name = g_strjoin ("_",
2120 if (!g_module_symbol (module,
2122 (gpointer) &callback))
2131 /* Use the strings from @name_list as details to @signal_name, and connect
2132 * callbacks with names based on these same strings to @signal_name::string. */
2134 connect_names_to_signal_handlers (GUPnPService *service,
2136 const GList *name_list,
2137 const char *signal_name,
2138 const char *callback_prefix,
2141 const GList *name_node;
2143 for (name_node = name_list;
2145 name_node = name_node->next) {
2147 char *callback_name;
2148 char *signal_detail;
2150 signal_detail = (char *) name_node->data;
2151 callback_name = strip_camel_case (signal_detail);
2153 if (callback_prefix) {
2156 tmp = g_strjoin ("_",
2161 g_free (callback_name);
2162 callback_name = tmp;
2165 callback = find_callback_by_name (module, callback_name);
2166 g_free (callback_name);
2168 if (callback == NULL)
2171 signal_detail = g_strjoin ("::",
2176 g_signal_connect (service,
2181 g_free (signal_detail);
2186 * gupnp_service_signals_autoconnect:
2187 * @service: A #GUPnPService
2188 * @user_data: the data to pass to each of the callbacks
2189 * @error: return location for a #GError, or %NULL
2191 * A convenience function that attempts to connect all possible
2192 * #GUPnPService::action-invoked and #GUPnPService::query-variable signals to
2193 * appropriate callbacks for the service @service. It uses service introspection
2194 * and GModule's introspective features. It is very simillar to
2195 * gtk_builder_connect_signals() except that it attempts to guess the names of
2196 * the signal handlers on its own.
2198 * For this function to do its magic, the application must name the callback
2199 * functions for #GUPnPService::action-invoked signals by striping the CamelCase
2200 * off the action names and either prepend "on_" or append "_cb" to them. Same
2201 * goes for #GUPnPService::query-variable signals, except that "query_" should
2202 * be prepended to the variable name. For example, callback function for
2203 * "GetSystemUpdateID" action should be either named as
2204 * "get_system_update_id_cb" or "on_get_system_update_id" and callback function
2205 * for the query of "SystemUpdateID" state variable should be named
2206 * "query_system_update_id_cb" or "on_query_system_update_id".
2208 * Note that this function will not work correctly if GModule is not supported
2209 * on the platform or introspection is not available for service @service.
2211 * WARNING: This function can not and therefore does not guarantee that the
2212 * resulting signal connections will be correct as it depends heavily on a
2213 * particular naming schemes described above.
2216 gupnp_service_signals_autoconnect (GUPnPService *service,
2220 GUPnPServiceIntrospection *introspection;
2224 g_return_if_fail (GUPNP_IS_SERVICE (service));
2226 introspection = gupnp_service_info_get_introspection
2227 (GUPNP_SERVICE_INFO (service),
2232 /* Get a handle on the main executable -- use this to find symbols */
2233 module = g_module_open (NULL, 0);
2234 if (module == NULL) {
2235 g_error ("Failed to open module: %s", g_module_error ());
2237 g_object_unref (introspection);
2242 names = gupnp_service_introspection_list_action_names (introspection);
2243 connect_names_to_signal_handlers (service,
2250 names = gupnp_service_introspection_list_state_variable_names
2252 connect_names_to_signal_handlers (service,
2259 g_module_close (module);
2260 g_object_unref (introspection);