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>
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 "gupnp-acl.h"
41 #include "http-headers.h"
42 #include "gena-protocol.h"
44 #include "gvalue-util.h"
49 #include <uuid/uuid.h>
52 #define SUBSCRIPTION_TIMEOUT 300 /* DLNA (7.2.22.1) enforced */
54 G_DEFINE_TYPE (GUPnPService,
56 GUPNP_TYPE_SERVICE_INFO);
58 struct _GUPnPServicePrivate {
59 GUPnPRootDevice *root_device;
63 guint notify_available_id;
65 GHashTable *subscriptions;
67 GList *state_variables;
71 gboolean notify_frozen;
86 static guint signals[LAST_SIGNAL];
89 create_property_set (GQueue *queue);
92 notify_subscriber (gpointer key,
97 GUPnPService *service;
104 GSource *timeout_src;
106 GList *pending_messages; /* Pending SoupMessages from this
108 gboolean initial_state_sent;
113 subscription_data_can_delete (SubscriptionData *data) {
114 return data->initial_state_sent && data->to_delete;
118 send_initial_state (SubscriptionData *data);
121 gupnp_service_get_session (GUPnPService *service)
123 if (! service->priv->session) {
124 /* Create a dedicated session for this service to
125 * ensure that notifications are sent in the proper
126 * order. The session from GUPnPContext may use
127 * multiple connections.
129 service->priv->session = soup_session_async_new_with_options
130 (SOUP_SESSION_IDLE_TIMEOUT, 60,
131 SOUP_SESSION_ASYNC_CONTEXT,
132 g_main_context_get_thread_default (),
133 SOUP_SESSION_MAX_CONNS_PER_HOST, 1,
136 if (g_getenv ("GUPNP_DEBUG")) {
138 logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
139 soup_session_add_feature (
140 service->priv->session,
141 SOUP_SESSION_FEATURE (logger));
145 return service->priv->session;
149 subscription_data_free (SubscriptionData *data)
151 SoupSession *session;
153 session = gupnp_service_get_session (data->service);
155 /* Cancel pending messages */
156 while (data->pending_messages) {
159 msg = data->pending_messages->data;
161 soup_session_cancel_message (session,
163 SOUP_STATUS_CANCELLED);
165 data->pending_messages =
166 g_list_delete_link (data->pending_messages,
167 data->pending_messages);
170 /* Further cleanup */
171 g_list_free_full (data->callbacks, g_free);
175 if (data->timeout_src)
176 g_source_destroy (data->timeout_src);
178 g_slice_free (SubscriptionData, data);
187 notify_data_free (NotifyData *data)
189 g_free (data->variable);
190 g_value_unset (&data->value);
192 g_slice_free (NotifyData, data);
195 struct _GUPnPServiceAction {
196 volatile gint ref_count;
198 GUPnPContext *context;
203 gboolean accept_gzip;
208 GString *response_str;
210 guint argument_count;
214 gupnp_service_action_ref (GUPnPServiceAction *action)
216 g_return_val_if_fail (action, NULL);
217 g_return_val_if_fail (action->ref_count > 0, NULL);
219 g_atomic_int_inc (&action->ref_count);
225 gupnp_service_action_unref (GUPnPServiceAction *action)
227 g_return_if_fail (action);
228 g_return_if_fail (action->ref_count > 0);
230 if (g_atomic_int_dec_and_test (&action->ref_count)) {
231 g_free (action->name);
232 g_object_unref (action->msg);
233 g_object_unref (action->context);
234 g_object_unref (action->doc);
236 g_slice_free (GUPnPServiceAction, action);
241 * gupnp_service_action_get_type:
243 * Get the gtype for GUPnPServiceActon
245 * Return value: The gtype of GUPnPServiceAction
248 gupnp_service_action_get_type (void)
250 static GType our_type = 0;
253 our_type = g_boxed_type_register_static
254 ("GUPnPServiceAction",
255 (GBoxedCopyFunc) gupnp_service_action_ref,
256 (GBoxedFreeFunc) gupnp_service_action_unref);
262 finalize_action (GUPnPServiceAction *action)
266 /* Embed action->response_str in a SOAP document */
267 g_string_prepend (action->response_str,
268 "<?xml version=\"1.0\"?>"
269 "<s:Envelope xmlns:s="
270 "\"http://schemas.xmlsoap.org/soap/envelope/\" "
272 "\"http://schemas.xmlsoap.org/soap/encoding/\">"
275 if (action->msg->status_code != SOUP_STATUS_INTERNAL_SERVER_ERROR) {
276 g_string_append (action->response_str, "</u:");
277 g_string_append (action->response_str, action->name);
278 g_string_append (action->response_str, "Response>");
281 g_string_append (action->response_str,
285 soup_message_headers_replace (action->msg->response_headers,
287 "text/xml; charset=\"utf-8\"");
289 if (action->accept_gzip && action->response_str->len > 1024) {
290 http_response_set_body_gzip (action->msg,
291 action->response_str->str,
292 action->response_str->len);
293 g_string_free (action->response_str, TRUE);
295 soup_message_body_append (action->msg->response_body,
297 action->response_str->str,
298 action->response_str->len);
299 g_string_free (action->response_str, FALSE);
302 soup_message_headers_append (action->msg->response_headers, "Ext", "");
304 /* Server header on response */
305 soup_message_headers_append
306 (action->msg->response_headers,
308 gssdp_client_get_server_id
309 (GSSDP_CLIENT (action->context)));
311 /* Tell soup server that response is now ready */
312 server = gupnp_context_get_server (action->context);
313 soup_server_unpause_message (server, action->msg);
316 gupnp_service_action_unref (action);
320 * gupnp_service_action_get_name:
321 * @action: A #GUPnPServiceAction
323 * Get the name of @action.
325 * Return value: The name of @action
328 gupnp_service_action_get_name (GUPnPServiceAction *action)
330 g_return_val_if_fail (action != NULL, NULL);
336 * gupnp_service_action_get_locales:
337 * @action: A #GUPnPServiceAction
339 * Get an ordered (preferred first) #GList of locales preferred by
340 * the client. Free list and elements after use.
342 * Return value: (element-type utf8) (transfer full): A #GList of #char*
346 gupnp_service_action_get_locales (GUPnPServiceAction *action)
348 g_return_val_if_fail (action != NULL, NULL);
350 return http_request_get_accept_locales (action->msg);
354 * gupnp_service_action_get:
355 * @action: A #GUPnPServiceAction
356 * @...: tuples of argument name, argument type, and argument value
357 * location, terminated with %NULL.
359 * Retrieves the specified action arguments.
362 gupnp_service_action_get (GUPnPServiceAction *action,
367 g_return_if_fail (action != NULL);
369 va_start (var_args, action);
370 gupnp_service_action_get_valist (action, var_args);
375 * gupnp_service_action_get_valist:
376 * @action: A #GUPnPServiceAction
377 * @var_args: va_list of tuples of argument name, argument type, and argument
380 * See gupnp_service_action_get(); this version takes a va_list for
381 * use by language bindings.
384 gupnp_service_action_get_valist (GUPnPServiceAction *action,
387 const char *arg_name;
389 GValue value = {0, };
392 g_return_if_fail (action != NULL);
396 arg_name = va_arg (var_args, const char *);
398 arg_type = va_arg (var_args, GType);
399 g_value_init (&value, arg_type);
401 gupnp_service_action_get_value (action, arg_name, &value);
403 G_VALUE_LCOPY (&value, var_args, 0, ©_error);
405 g_value_unset (&value);
408 g_warning ("Error lcopying value: %s\n", copy_error);
413 arg_name = va_arg (var_args, const char *);
418 * gupnp_service_action_get_values:
419 * @action: A #GUPnPServiceAction
420 * @arg_names: (element-type utf8) : A #GList of argument names as string
421 * @arg_types: (element-type GType): A #GList of argument types as #GType
423 * A variant of #gupnp_service_action_get that uses #GList instead of varargs.
425 * Return value: (element-type GValue) (transfer full): The values as #GList of
426 * #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 retrieve '%s' argument of '%s' action",
500 * gupnp_service_action_get_gvalue: (rename-to gupnp_service_action_get_value)
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.
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.
540 gupnp_service_action_get_argument_count (GUPnPServiceAction *action)
542 return action->argument_count;
546 * gupnp_service_action_set:
547 * @action: A #GUPnPServiceAction
548 * @...: tuples of return value name, return value type, and
549 * actual return value, terminated with %NULL.
551 * Sets the specified action return values.
554 gupnp_service_action_set (GUPnPServiceAction *action,
559 g_return_if_fail (action != NULL);
561 va_start (var_args, action);
562 gupnp_service_action_set_valist (action, var_args);
567 * gupnp_service_action_set_valist:
568 * @action: A #GUPnPServiceAction
569 * @var_args: va_list of tuples of return value name, return value type, and
570 * actual return value.
572 * See gupnp_service_action_set(); this version takes a va_list for
573 * use by language bindings.
576 gupnp_service_action_set_valist (GUPnPServiceAction *action,
579 const char *arg_name;
581 GValue value = {0, };
584 g_return_if_fail (action != NULL);
586 collect_error = NULL;
588 arg_name = va_arg (var_args, const char *);
590 arg_type = va_arg (var_args, GType);
591 g_value_init (&value, arg_type);
593 G_VALUE_COLLECT (&value, var_args, G_VALUE_NOCOPY_CONTENTS,
595 if (!collect_error) {
596 gupnp_service_action_set_value (action,
599 g_value_unset (&value);
602 g_warning ("Error collecting value: %s\n",
605 g_free (collect_error);
608 arg_name = va_arg (var_args, const char *);
613 * gupnp_service_action_set_values:
614 * @action: A #GUPnPServiceAction
615 * @arg_names: (element-type utf8) (transfer none): A #GList of argument names
616 * @arg_values: (element-type GValue) (transfer none): The #GList of values (as
617 * #GValues) that line up with @arg_names.
619 * Sets the specified action return values.
624 gupnp_service_action_set_values (GUPnPServiceAction *action,
628 g_return_if_fail (action != NULL);
629 g_return_if_fail (arg_names != NULL);
630 g_return_if_fail (arg_values != NULL);
631 g_return_if_fail (g_list_length (arg_names) ==
632 g_list_length (arg_values));
634 if (action->msg->status_code == SOUP_STATUS_INTERNAL_SERVER_ERROR) {
635 g_warning ("Calling gupnp_service_action_set_value() after "
636 "having called gupnp_service_action_return_error() "
642 /* Append to response */
643 for (; arg_names; arg_names = arg_names->next) {
644 const char *arg_name;
647 arg_name = arg_names->data;
648 value = arg_values->data;
650 xml_util_start_element (action->response_str, arg_name);
651 gvalue_util_value_append_to_xml_string (value,
652 action->response_str);
653 xml_util_end_element (action->response_str, arg_name);
655 arg_values = arg_values->next;
660 * gupnp_service_action_set_value:
661 * @action: A #GUPnPServiceAction
662 * @argument: The name of the return value to retrieve
663 * @value: The #GValue to store the return value
665 * Sets the value of @argument to @value.
668 gupnp_service_action_set_value (GUPnPServiceAction *action,
669 const char *argument,
672 g_return_if_fail (action != NULL);
673 g_return_if_fail (argument != NULL);
674 g_return_if_fail (value != NULL);
676 if (action->msg->status_code == SOUP_STATUS_INTERNAL_SERVER_ERROR) {
677 g_warning ("Calling gupnp_service_action_set_value() after "
678 "having called gupnp_service_action_return_error() "
684 /* Append to response */
685 xml_util_start_element (action->response_str, argument);
686 gvalue_util_value_append_to_xml_string (value, action->response_str);
687 xml_util_end_element (action->response_str, argument);
691 * gupnp_service_action_return:
692 * @action: A #GUPnPServiceAction
694 * Return succesfully.
697 gupnp_service_action_return (GUPnPServiceAction *action)
699 g_return_if_fail (action != NULL);
701 soup_message_set_status (action->msg, SOUP_STATUS_OK);
703 finalize_action (action);
707 * gupnp_service_action_return_error:
708 * @action: A #GUPnPServiceAction
709 * @error_code: The error code
710 * @error_description: The error description, or %NULL if @error_code is
711 * one of #GUPNP_CONTROL_ERROR_INVALID_ACTION,
712 * #GUPNP_CONTROL_ERROR_INVALID_ARGS, #GUPNP_CONTROL_ERROR_OUT_OF_SYNC or
713 * #GUPNP_CONTROL_ERROR_ACTION_FAILED, in which case a description is
714 * provided automatically.
716 * Return @error_code.
719 gupnp_service_action_return_error (GUPnPServiceAction *action,
721 const char *error_description)
723 g_return_if_fail (action != NULL);
725 switch (error_code) {
726 case GUPNP_CONTROL_ERROR_INVALID_ACTION:
727 if (error_description == NULL)
728 error_description = "Invalid Action";
731 case GUPNP_CONTROL_ERROR_INVALID_ARGS:
732 if (error_description == NULL)
733 error_description = "Invalid Args";
736 case GUPNP_CONTROL_ERROR_OUT_OF_SYNC:
737 if (error_description == NULL)
738 error_description = "Out of Sync";
741 case GUPNP_CONTROL_ERROR_ACTION_FAILED:
742 if (error_description == NULL)
743 error_description = "Action Failed";
747 g_return_if_fail (error_description != NULL);
751 /* Replace response_str with a SOAP Fault */
752 g_string_erase (action->response_str, 0, -1);
754 xml_util_start_element (action->response_str, "s:Fault");
756 xml_util_start_element (action->response_str, "faultcode");
757 g_string_append (action->response_str, "s:Client");
758 xml_util_end_element (action->response_str, "faultcode");
760 xml_util_start_element (action->response_str, "faultstring");
761 g_string_append (action->response_str, "UPnPError");
762 xml_util_end_element (action->response_str, "faultstring");
764 xml_util_start_element (action->response_str, "detail");
766 xml_util_start_element (action->response_str,
768 "xmlns=\"urn:schemas-upnp-org:control-1-0\"");
770 xml_util_start_element (action->response_str, "errorCode");
771 g_string_append_printf (action->response_str, "%u", error_code);
772 xml_util_end_element (action->response_str, "errorCode");
774 xml_util_start_element (action->response_str, "errorDescription");
775 xml_util_add_content (action->response_str, error_description);
776 xml_util_end_element (action->response_str, "errorDescription");
778 xml_util_end_element (action->response_str, "UPnPError");
779 xml_util_end_element (action->response_str, "detail");
781 xml_util_end_element (action->response_str, "s:Fault");
783 soup_message_set_status (action->msg,
784 SOUP_STATUS_INTERNAL_SERVER_ERROR);
786 finalize_action (action);
790 * gupnp_service_action_get_message:
791 * @action: A #GUPnPServiceAction
793 * Get the #SoupMessage associated with @action. Mainly intended for
794 * applications to be able to read HTTP headers received from clients.
796 * Return value: (transfer full): #SoupMessage associated with @action. Unref
802 gupnp_service_action_get_message (GUPnPServiceAction *action)
804 return g_object_ref (action->msg);
808 gupnp_service_init (GUPnPService *service)
810 service->priv = G_TYPE_INSTANCE_GET_PRIVATE (service,
812 GUPnPServicePrivate);
814 service->priv->subscriptions =
815 g_hash_table_new_full (g_str_hash,
818 (GDestroyNotify) subscription_data_free);
820 service->priv->notify_queue = g_queue_new ();
823 /* Generate a new action response node for @action_name */
825 new_action_response_str (const char *action_name,
826 const char *service_type)
830 str = xml_util_new_string ();
832 g_string_append (str, "<u:");
833 g_string_append (str, action_name);
834 g_string_append (str, "Response xmlns:u=");
836 if (service_type != NULL) {
837 g_string_append (str, service_type);
838 g_string_append_c (str, '"');
840 g_warning ("No serviceType defined. Control may not work "
844 g_string_append_c (str, '>');
849 /* Handle QueryStateVariable action */
851 query_state_variable (GUPnPService *service,
852 GUPnPServiceAction *action)
856 /* Iterate requested variables */
857 for (node = action->node->children; node; node = node->next) {
861 if (strcmp ((char *) node->name, "varName") != 0)
865 var_name = xmlNodeGetContent (node);
867 gupnp_service_action_return_error (action,
875 g_signal_emit (service,
876 signals[QUERY_VARIABLE],
877 g_quark_from_string ((char *) var_name),
881 if (!G_IS_VALUE (&value)) {
882 gupnp_service_action_return_error (action,
891 /* Add variable to response */
892 gupnp_service_action_set_value (action,
897 g_value_unset (&value);
902 gupnp_service_action_return (action);
905 /* controlURL handler */
907 control_server_handler (SoupServer *server,
909 G_GNUC_UNUSED const char *server_path,
910 G_GNUC_UNUSED GHashTable *query,
911 G_GNUC_UNUSED SoupClientContext *soup_client,
914 GUPnPService *service;
915 GUPnPContext *context;
917 xmlNode *action_node, *node;
918 const char *soap_action;
919 const char *accept_encoding;
922 GUPnPServiceAction *action;
924 service = GUPNP_SERVICE (user_data);
926 if (msg->method != SOUP_METHOD_POST) {
927 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
932 if (msg->request_body->length == 0) {
933 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
938 /* DLNA 7.2.5.6: Always use HTTP 1.1 */
939 if (soup_message_get_http_version (msg) == SOUP_HTTP_1_0) {
940 soup_message_set_http_version (msg, SOUP_HTTP_1_1);
941 soup_message_headers_append (msg->response_headers,
946 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (service));
948 /* Get action name */
949 soap_action = soup_message_headers_get_one (msg->request_headers,
952 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
956 action_name = strchr (soap_action, '#');
958 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
963 /* This memory is libsoup-owned so we can do this */
967 /* This memory is libsoup-owned so we can do this */
968 end = strrchr (action_name, '"');
972 /* Parse action_node */
973 doc = xmlRecoverMemory (msg->request_body->data,
974 msg->request_body->length);
976 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
981 action_node = xml_util_get_element ((xmlNode *) doc,
987 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
992 /* Create action structure */
993 action = g_slice_new0 (GUPnPServiceAction);
995 action->ref_count = 1;
996 action->name = g_strdup (action_name);
997 action->msg = g_object_ref (msg);
998 action->doc = gupnp_xml_doc_new(doc);
999 action->node = action_node;
1000 action->response_str = new_action_response_str (action_name,
1002 action->context = g_object_ref (context);
1003 action->argument_count = 0;
1005 for (node = action->node->children; node; node = node->next)
1006 if (node->type == XML_ELEMENT_NODE)
1007 action->argument_count++;
1009 /* Get accepted encodings */
1010 accept_encoding = soup_message_headers_get_list (msg->request_headers,
1013 if (accept_encoding) {
1016 codings = soup_header_parse_quality_list (accept_encoding,
1019 g_slist_find_custom (codings, "gzip",
1020 (GCompareFunc) g_ascii_strcasecmp)) {
1021 action->accept_gzip = TRUE;
1024 soup_header_free_list (codings);
1027 /* Tell soup server that response is not ready yet */
1028 soup_server_pause_message (server, msg);
1030 /* QueryStateVariable? */
1031 if (strcmp (action_name, "QueryStateVariable") == 0)
1032 query_state_variable (service, action);
1034 GQuark action_name_quark;
1036 action_name_quark = g_quark_from_string (action_name);
1037 if (g_signal_has_handler_pending (service,
1038 signals[ACTION_INVOKED],
1041 /* Emit signal. Handler parses request and fills in
1043 g_signal_emit (service,
1044 signals[ACTION_INVOKED],
1048 /* No handlers attached. */
1049 gupnp_service_action_return_error (action,
1056 /* Generates a standard (re)subscription response */
1058 subscription_response (GUPnPService *service,
1063 GUPnPContext *context;
1064 GSSDPClient *client;
1067 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (service));
1068 client = GSSDP_CLIENT (context);
1070 /* Server header on response */
1071 soup_message_headers_append (msg->response_headers,
1073 gssdp_client_get_server_id (client));
1076 soup_message_headers_append (msg->response_headers,
1080 /* Timeout header */
1082 tmp = g_strdup_printf ("Second-%d", timeout);
1084 tmp = g_strdup ("infinite");
1086 soup_message_headers_append (msg->response_headers,
1092 soup_message_set_status (msg, SOUP_STATUS_OK);
1095 /* Generates a new SID */
1103 stat = UuidCreate (&uuid);
1104 if (stat == RPC_S_OK) {
1105 unsigned char* uuidStr = NULL;
1106 stat = UuidToString (&uuid, &uuidStr);
1107 if (stat == RPC_S_OK) {
1108 ret = g_strdup_printf ("uuid:%s", uuidStr);
1109 RpcStringFree (&uuidStr);
1119 uuid_unparse (id, out);
1121 return g_strdup_printf ("uuid:%s", out);
1125 /* Subscription expired */
1127 subscription_timeout (gpointer user_data)
1129 SubscriptionData *data;
1133 g_hash_table_remove (data->service->priv->subscriptions, data->sid);
1139 send_initial_state (SubscriptionData *data)
1145 /* Send initial event message */
1146 queue = g_queue_new ();
1148 for (l = data->service->priv->state_variables; l; l = l->next) {
1151 ndata = g_slice_new0 (NotifyData);
1153 g_signal_emit (data->service,
1154 signals[QUERY_VARIABLE],
1155 g_quark_from_string (l->data),
1159 if (!G_IS_VALUE (&ndata->value)) {
1160 g_slice_free (NotifyData, ndata);
1165 ndata->variable = g_strdup (l->data);
1167 g_queue_push_tail (queue, ndata);
1170 mem = create_property_set (queue);
1171 notify_subscriber (data->sid, data, mem);
1174 g_queue_free (queue);
1180 /* Subscription request */
1182 subscribe (GUPnPService *service,
1184 const char *callback)
1186 SubscriptionData *data;
1187 char *start, *end, *uri;
1189 data = g_slice_new0 (SubscriptionData);
1191 /* Parse callback list */
1192 start = (char *) callback;
1193 while ((start = strchr (start, '<'))) {
1195 if (!start || !*start)
1198 end = strchr (start, '>');
1202 if (strncmp (start, "http://", strlen ("http://")) == 0) {
1203 uri = g_strndup (start, end - start);
1204 data->callbacks = g_list_append (data->callbacks, uri);
1210 if (!data->callbacks) {
1211 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1213 g_slice_free (SubscriptionData, data);
1218 /* Add service and SID */
1219 data->service = service;
1220 data->sid = generate_sid ();
1223 data->timeout_src = g_timeout_source_new_seconds (SUBSCRIPTION_TIMEOUT);
1224 g_source_set_callback (data->timeout_src,
1225 subscription_timeout,
1229 g_source_attach (data->timeout_src,
1230 g_main_context_get_thread_default ());
1232 g_source_unref (data->timeout_src);
1235 g_hash_table_insert (service->priv->subscriptions,
1240 subscription_response (service, msg, data->sid, SUBSCRIPTION_TIMEOUT);
1242 send_initial_state (data);
1245 /* Resubscription request */
1247 resubscribe (GUPnPService *service,
1251 SubscriptionData *data;
1253 data = g_hash_table_lookup (service->priv->subscriptions, sid);
1255 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1260 /* Update timeout */
1261 if (data->timeout_src) {
1262 g_source_destroy (data->timeout_src);
1263 data->timeout_src = NULL;
1266 data->timeout_src = g_timeout_source_new_seconds (SUBSCRIPTION_TIMEOUT);
1267 g_source_set_callback (data->timeout_src,
1268 subscription_timeout,
1272 g_source_attach (data->timeout_src,
1273 g_main_context_get_thread_default ());
1275 g_source_unref (data->timeout_src);
1278 subscription_response (service, msg, sid, SUBSCRIPTION_TIMEOUT);
1281 /* Unsubscription request */
1283 unsubscribe (GUPnPService *service,
1287 SubscriptionData *data;
1289 data = g_hash_table_lookup (service->priv->subscriptions, sid);
1291 if (data->initial_state_sent)
1292 g_hash_table_remove (service->priv->subscriptions,
1295 data->to_delete = TRUE;
1296 soup_message_set_status (msg, SOUP_STATUS_OK);
1298 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1301 /* eventSubscriptionURL handler */
1303 subscription_server_handler (G_GNUC_UNUSED SoupServer *server,
1305 G_GNUC_UNUSED const char *server_path,
1306 G_GNUC_UNUSED GHashTable *query,
1307 G_GNUC_UNUSED SoupClientContext *soup_client,
1310 GUPnPService *service;
1311 GUPnPContext *context;
1313 const char *callback, *nt, *sid;
1315 service = GUPNP_SERVICE (user_data);
1317 callback = soup_message_headers_get_one (msg->request_headers,
1319 nt = soup_message_headers_get_one (msg->request_headers, "NT");
1320 sid = soup_message_headers_get_one (msg->request_headers, "SID");
1322 /* Choose appropriate handler */
1323 if (strcmp (msg->method, GENA_METHOD_SUBSCRIBE) == 0) {
1326 soup_message_set_status
1327 (msg, SOUP_STATUS_BAD_REQUEST);
1329 } else if (!nt || strcmp (nt, "upnp:event") != 0) {
1330 soup_message_set_status
1331 (msg, SOUP_STATUS_PRECONDITION_FAILED);
1334 subscribe (service, msg, callback);
1340 soup_message_set_status
1341 (msg, SOUP_STATUS_BAD_REQUEST);
1344 resubscribe (service, msg, sid);
1349 soup_message_set_status
1350 (msg, SOUP_STATUS_PRECONDITION_FAILED);
1354 } else if (strcmp (msg->method, GENA_METHOD_UNSUBSCRIBE) == 0) {
1356 if (nt || callback) {
1357 soup_message_set_status
1358 (msg, SOUP_STATUS_BAD_REQUEST);
1361 unsubscribe (service, msg, sid);
1366 soup_message_set_status
1367 (msg, SOUP_STATUS_PRECONDITION_FAILED);
1372 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
1378 got_introspection (GUPnPServiceInfo *info,
1379 GUPnPServiceIntrospection *introspection,
1380 const GError *error,
1381 G_GNUC_UNUSED gpointer user_data)
1383 GUPnPService *service = GUPNP_SERVICE (info);
1384 const GList *state_variables, *l;
1385 GHashTableIter iter;
1388 if (introspection) {
1390 gupnp_service_introspection_list_state_variables
1393 for (l = state_variables; l; l = l->next) {
1394 GUPnPServiceStateVariableInfo *variable;
1398 if (!variable->send_events)
1401 service->priv->state_variables =
1402 g_list_prepend (service->priv->state_variables,
1403 g_strdup (variable->name));
1406 g_object_unref (introspection);
1408 g_warning ("Failed to get SCPD: %s\n"
1409 "The initial event message will not be sent.",
1410 error ? error->message : "No error");
1412 g_hash_table_iter_init (&iter, service->priv->subscriptions);
1414 while (g_hash_table_iter_next (&iter, NULL, &data)) {
1415 send_initial_state ((SubscriptionData *) data);
1416 if (subscription_data_can_delete ((SubscriptionData *) data))
1417 g_hash_table_iter_remove (&iter);
1422 path_from_url (const char *url)
1427 uri = soup_uri_new (url);
1428 path = soup_uri_to_string (uri, TRUE);
1429 soup_uri_free (uri);
1435 gupnp_service_constructor (GType type,
1436 guint n_construct_params,
1437 GObjectConstructParam *construct_params)
1439 GObjectClass *object_class;
1441 GUPnPServiceInfo *info;
1442 GUPnPContext *context;
1443 AclServerHandler *handler;
1447 object_class = G_OBJECT_CLASS (gupnp_service_parent_class);
1450 object = object_class->constructor (type,
1454 info = GUPNP_SERVICE_INFO (object);
1456 /* Get introspection and save state variable names */
1457 gupnp_service_info_get_introspection_async (info,
1462 context = gupnp_service_info_get_context (info);
1464 /* Run listener on controlURL */
1465 url = gupnp_service_info_get_control_url (info);
1466 path = path_from_url (url);
1467 handler = acl_server_handler_new (GUPNP_SERVICE (object),
1469 control_server_handler,
1470 g_object_ref (object),
1472 _gupnp_context_add_server_handler_with_data (context, path, handler);
1476 /* Run listener on eventSubscriptionURL */
1477 url = gupnp_service_info_get_event_subscription_url (info);
1478 path = path_from_url (url);
1479 handler = acl_server_handler_new (GUPNP_SERVICE (object),
1481 subscription_server_handler,
1482 g_object_ref (object),
1484 _gupnp_context_add_server_handler_with_data (context, path, handler);
1491 /* Root device availability changed. */
1493 notify_available_cb (GObject *object,
1494 G_GNUC_UNUSED GParamSpec *pspec,
1497 GUPnPService *service;
1499 service = GUPNP_SERVICE (user_data);
1501 if (!gupnp_root_device_get_available (GUPNP_ROOT_DEVICE (object))) {
1502 /* Root device now unavailable: Purge subscriptions */
1503 g_hash_table_remove_all (service->priv->subscriptions);
1508 gupnp_service_set_property (GObject *object,
1510 const GValue *value,
1513 GUPnPService *service;
1515 service = GUPNP_SERVICE (object);
1517 switch (property_id) {
1518 case PROP_ROOT_DEVICE: {
1519 GUPnPRootDevice **dev;
1521 service->priv->root_device = g_value_get_object (value);
1522 dev = &(service->priv->root_device);
1524 g_object_add_weak_pointer
1525 (G_OBJECT (service->priv->root_device),
1528 service->priv->notify_available_id =
1529 g_signal_connect_object (service->priv->root_device,
1530 "notify::available",
1532 (notify_available_cb),
1539 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1545 gupnp_service_get_property (GObject *object,
1550 GUPnPService *service;
1552 service = GUPNP_SERVICE (object);
1554 switch (property_id) {
1555 case PROP_ROOT_DEVICE:
1556 g_value_set_object (value, service->priv->root_device);
1559 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1565 gupnp_service_dispose (GObject *object)
1567 GUPnPService *service;
1568 GObjectClass *object_class;
1569 GUPnPServiceInfo *info;
1570 GUPnPContext *context;
1574 service = GUPNP_SERVICE (object);
1577 info = GUPNP_SERVICE_INFO (service);
1578 context = gupnp_service_info_get_context (info);
1580 /* Remove listener on controlURL */
1581 url = gupnp_service_info_get_control_url (info);
1582 path = path_from_url (url);
1583 gupnp_context_remove_server_handler (context, path);
1587 /* Remove listener on eventSubscriptionURL */
1588 url = gupnp_service_info_get_event_subscription_url (info);
1589 path = path_from_url (url);
1590 gupnp_context_remove_server_handler (context, path);
1594 if (service->priv->root_device) {
1595 GUPnPRootDevice **dev = &(service->priv->root_device);
1597 if (g_signal_handler_is_connected
1598 (service->priv->root_device,
1599 service->priv->notify_available_id)) {
1600 g_signal_handler_disconnect
1601 (service->priv->root_device,
1602 service->priv->notify_available_id);
1605 g_object_remove_weak_pointer
1606 (G_OBJECT (service->priv->root_device),
1609 service->priv->root_device = NULL;
1612 /* Cancel pending messages */
1613 g_hash_table_remove_all (service->priv->subscriptions);
1616 object_class = G_OBJECT_CLASS (gupnp_service_parent_class);
1617 object_class->dispose (object);
1621 gupnp_service_finalize (GObject *object)
1623 GUPnPService *service;
1624 GObjectClass *object_class;
1627 service = GUPNP_SERVICE (object);
1629 /* Free subscription hash */
1630 g_hash_table_destroy (service->priv->subscriptions);
1632 /* Free state variable list */
1633 g_list_free_full (service->priv->state_variables, g_free);
1635 /* Free notify queue */
1636 while ((data = g_queue_pop_head (service->priv->notify_queue)))
1637 notify_data_free (data);
1639 g_queue_free (service->priv->notify_queue);
1641 if (service->priv->session) {
1642 g_object_unref (service->priv->session);
1643 service->priv->session = NULL;
1647 object_class = G_OBJECT_CLASS (gupnp_service_parent_class);
1648 object_class->finalize (object);
1652 gupnp_service_class_init (GUPnPServiceClass *klass)
1654 GObjectClass *object_class;
1656 object_class = G_OBJECT_CLASS (klass);
1658 object_class->set_property = gupnp_service_set_property;
1659 object_class->get_property = gupnp_service_get_property;
1660 object_class->constructor = gupnp_service_constructor;
1661 object_class->dispose = gupnp_service_dispose;
1662 object_class->finalize = gupnp_service_finalize;
1664 g_type_class_add_private (klass, sizeof (GUPnPServicePrivate));
1667 * GUPnPService:root-device:
1669 * The containing #GUPnPRootDevice.
1671 g_object_class_install_property
1674 g_param_spec_object ("root-device",
1676 "The GUPnPRootDevice",
1677 GUPNP_TYPE_ROOT_DEVICE,
1679 G_PARAM_CONSTRUCT_ONLY |
1680 G_PARAM_STATIC_NAME |
1681 G_PARAM_STATIC_NICK |
1682 G_PARAM_STATIC_BLURB));
1685 * GUPnPService::action-invoked:
1686 * @service: The #GUPnPService that received the signal
1687 * @action: The invoked #GUPnPServiceAction
1689 * Emitted whenever an action is invoked. Handler should process
1690 * @action and must call either gupnp_service_action_return() or
1691 * gupnp_service_action_return_error().
1693 signals[ACTION_INVOKED] =
1694 g_signal_new ("action-invoked",
1696 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
1697 G_STRUCT_OFFSET (GUPnPServiceClass,
1701 g_cclosure_marshal_VOID__BOXED,
1704 GUPNP_TYPE_SERVICE_ACTION);
1707 * GUPnPService::query-variable:
1708 * @service: The #GUPnPService that received the signal
1709 * @variable: The variable that is being queried
1710 * @value: (type GValue)(inout):The location of the #GValue of the variable
1712 * Emitted whenever @service needs to know the value of @variable.
1713 * Handler should fill @value with the value of @variable.
1715 signals[QUERY_VARIABLE] =
1716 g_signal_new ("query-variable",
1718 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
1719 G_STRUCT_OFFSET (GUPnPServiceClass,
1723 gupnp_marshal_VOID__STRING_POINTER,
1727 G_TYPE_POINTER /* Not G_TYPE_VALUE as this
1728 is an outward argument! */);
1731 * GUPnPService::notify-failed:
1732 * @service: The #GUPnPService that received the signal
1733 * @callback_url: (type GList)(element-type SoupURI):A #GList of callback URLs
1734 * @reason: (type GError): A pointer to a #GError describing why the notify failed
1736 * Emitted whenever notification of a client fails.
1738 signals[NOTIFY_FAILED] =
1739 g_signal_new ("notify-failed",
1742 G_STRUCT_OFFSET (GUPnPServiceClass,
1746 gupnp_marshal_VOID__POINTER_POINTER,
1754 * gupnp_service_notify:
1755 * @service: A #GUPnPService
1756 * @...: Tuples of variable name, variable type, and variable value,
1757 * terminated with %NULL.
1759 * Notifies listening clients that the properties listed in @Varargs
1760 * have changed to the specified values.
1763 gupnp_service_notify (GUPnPService *service,
1768 g_return_if_fail (GUPNP_IS_SERVICE (service));
1770 va_start (var_args, service);
1771 gupnp_service_notify_valist (service, var_args);
1776 * gupnp_service_notify_valist:
1777 * @service: A #GUPnPService
1778 * @var_args: A va_list of tuples of variable name, variable type, and variable
1779 * value, terminated with %NULL.
1781 * See gupnp_service_notify(); this version takes a va_list for
1782 * use by language bindings.
1785 gupnp_service_notify_valist (GUPnPService *service,
1788 const char *var_name;
1790 GValue value = {0, };
1791 char *collect_error;
1793 g_return_if_fail (GUPNP_IS_SERVICE (service));
1795 collect_error = NULL;
1797 var_name = va_arg (var_args, const char *);
1799 var_type = va_arg (var_args, GType);
1800 g_value_init (&value, var_type);
1802 G_VALUE_COLLECT (&value, var_args, G_VALUE_NOCOPY_CONTENTS,
1804 if (!collect_error) {
1805 gupnp_service_notify_value (service, var_name, &value);
1807 g_value_unset (&value);
1810 g_warning ("Error collecting value: %s\n",
1813 g_free (collect_error);
1816 var_name = va_arg (var_args, const char *);
1820 /* Received notify response. */
1822 notify_got_response (G_GNUC_UNUSED SoupSession *session,
1826 SubscriptionData *data;
1829 if (msg->status_code == SOUP_STATUS_CANCELLED)
1834 /* Remove from pending messages list */
1835 data->pending_messages = g_list_remove (data->pending_messages, msg);
1837 if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
1838 data->initial_state_sent = TRUE;
1840 /* Success: reset callbacks pointer */
1841 data->callbacks = g_list_first (data->callbacks);
1843 } else if (msg->status_code == SOUP_STATUS_PRECONDITION_FAILED) {
1844 /* Precondition failed: Cancel subscription */
1845 g_hash_table_remove (data->service->priv->subscriptions,
1849 /* Other failure: Try next callback or signal failure. */
1850 if (data->callbacks->next) {
1852 guint8 *property_set;
1855 /* Call next callback */
1856 data->callbacks = data->callbacks->next;
1858 /* Get property-set from old message */
1859 buffer = soup_message_body_flatten (msg->request_body);
1860 soup_buffer_get_data (buffer,
1861 (const guint8 **) &property_set,
1863 notify_subscriber (NULL, data, property_set);
1864 soup_buffer_free (buffer);
1866 /* Emit 'notify-failed' signal */
1869 error = g_error_new_literal
1870 (GUPNP_EVENTING_ERROR,
1871 GUPNP_EVENTING_ERROR_NOTIFY_FAILED,
1872 msg->reason_phrase);
1874 g_signal_emit (data->service,
1875 signals[NOTIFY_FAILED],
1880 g_error_free (error);
1882 /* Reset callbacks pointer */
1883 data->callbacks = g_list_first (data->callbacks);
1888 /* Send notification @user_data to subscriber @value */
1890 notify_subscriber (G_GNUC_UNUSED gpointer key,
1894 SubscriptionData *data;
1895 const char *property_set;
1898 SoupSession *session;
1901 property_set = user_data;
1903 /* Subscriber called unsubscribe */
1904 if (subscription_data_can_delete (data))
1907 /* Create message */
1908 msg = soup_message_new (GENA_METHOD_NOTIFY, data->callbacks->data);
1910 g_warning ("Invalid callback URL: %s",
1911 (char *) data->callbacks->data);
1916 soup_message_headers_append (msg->request_headers,
1920 soup_message_headers_append (msg->request_headers,
1924 soup_message_headers_append (msg->request_headers,
1928 tmp = g_strdup_printf ("%d", data->seq);
1929 soup_message_headers_append (msg->request_headers,
1934 /* Handle overflow */
1935 if (data->seq < G_MAXINT32)
1941 soup_message_set_request (msg,
1942 "text/xml; charset=\"utf-8\"",
1944 g_strdup (property_set),
1945 strlen (property_set));
1948 data->pending_messages = g_list_prepend (data->pending_messages, msg);
1949 soup_message_headers_append (msg->request_headers,
1950 "Connection", "close");
1952 session = gupnp_service_get_session (data->service);
1954 soup_session_queue_message (session,
1956 notify_got_response,
1960 /* Create a property set from @queue */
1962 create_property_set (GQueue *queue)
1967 /* Compose property set */
1968 str = xml_util_new_string ();
1970 g_string_append (str,
1971 "<?xml version=\"1.0\"?>"
1972 "<e:propertyset xmlns:e="
1973 "\"urn:schemas-upnp-org:event-1-0\">");
1976 while ((data = g_queue_pop_head (queue))) {
1977 xml_util_start_element (str, "e:property");
1978 xml_util_start_element (str, data->variable);
1979 gvalue_util_value_append_to_xml_string (&data->value, str);
1980 xml_util_end_element (str, data->variable);
1981 xml_util_end_element (str, "e:property");
1984 notify_data_free (data);
1987 g_string_append (str, "</e:propertyset>");
1989 /* Cleanup & return */
1990 return g_string_free (str, FALSE);
1993 /* Flush all queued notifications */
1995 flush_notifications (GUPnPService *service)
1999 /* Create property set */
2000 mem = create_property_set (service->priv->notify_queue);
2002 /* And send it off */
2003 g_hash_table_foreach (service->priv->subscriptions,
2012 * gupnp_service_notify_value:
2013 * @service: A #GUPnPService
2014 * @variable: The name of the variable to notify
2015 * @value: The value of the variable
2017 * Notifies listening clients that @variable has changed to @value.
2020 gupnp_service_notify_value (GUPnPService *service,
2021 const char *variable,
2022 const GValue *value)
2026 g_return_if_fail (GUPNP_IS_SERVICE (service));
2027 g_return_if_fail (variable != NULL);
2028 g_return_if_fail (G_IS_VALUE (value));
2031 data = g_slice_new0 (NotifyData);
2033 data->variable = g_strdup (variable);
2035 g_value_init (&data->value, G_VALUE_TYPE (value));
2036 g_value_copy (value, &data->value);
2038 g_queue_push_tail (service->priv->notify_queue, data);
2040 /* And flush, if not frozen */
2041 if (!service->priv->notify_frozen)
2042 flush_notifications (service);
2046 * gupnp_service_freeze_notify:
2047 * @service: A #GUPnPService
2049 * Causes new notifications to be queued up until gupnp_service_thaw_notify()
2053 gupnp_service_freeze_notify (GUPnPService *service)
2055 g_return_if_fail (GUPNP_IS_SERVICE (service));
2057 service->priv->notify_frozen = TRUE;
2061 * gupnp_service_thaw_notify:
2062 * @service: A #GUPnPService
2064 * Sends out any pending notifications, and stops queuing of new ones.
2067 gupnp_service_thaw_notify (GUPnPService *service)
2069 g_return_if_fail (GUPNP_IS_SERVICE (service));
2071 service->priv->notify_frozen = FALSE;
2073 if (g_queue_get_length (service->priv->notify_queue) == 0)
2074 return; /* Empty notify queue */
2076 flush_notifications (service);
2079 /* Convert a CamelCase string to a lowercase string with underscores */
2081 strip_camel_case (char *camel_str)
2086 /* Keep enough space for underscores */
2087 stripped = g_malloc (strlen (camel_str) * 2);
2089 for (i = 0, j = 0; i <= strlen (camel_str); i++) {
2090 /* Convert every upper case letter to lower case and unless
2091 * it's the first character, the last charachter, in the
2092 * middle of an abbreviation or there is already an underscore
2093 * before it, add an underscore before it */
2094 if (g_ascii_isupper (camel_str[i])) {
2096 camel_str[i + 1] != '\0' &&
2097 camel_str[i - 1] != '_' &&
2098 !g_ascii_isupper (camel_str[i - 1])) {
2099 stripped[j++] = '_';
2101 stripped[j++] = g_ascii_tolower (camel_str[i]);
2103 stripped[j++] = camel_str[i];
2110 find_callback_by_name (GModule *module,
2116 /* First try with 'on_' prefix */
2117 full_name = g_strjoin ("_",
2122 if (!g_module_symbol (module,
2124 (gpointer) &callback)) {
2127 /* Now try with '_cb' postfix */
2128 full_name = g_strjoin ("_",
2133 if (!g_module_symbol (module,
2135 (gpointer) &callback))
2144 /* Use the strings from @name_list as details to @signal_name, and connect
2145 * callbacks with names based on these same strings to @signal_name::string. */
2147 connect_names_to_signal_handlers (GUPnPService *service,
2149 const GList *name_list,
2150 const char *signal_name,
2151 const char *callback_prefix,
2154 const GList *name_node;
2156 for (name_node = name_list;
2158 name_node = name_node->next) {
2160 char *callback_name;
2161 char *signal_detail;
2163 signal_detail = (char *) name_node->data;
2164 callback_name = strip_camel_case (signal_detail);
2166 if (callback_prefix) {
2169 tmp = g_strjoin ("_",
2174 g_free (callback_name);
2175 callback_name = tmp;
2178 callback = find_callback_by_name (module, callback_name);
2179 g_free (callback_name);
2181 if (callback == NULL)
2184 signal_detail = g_strjoin ("::",
2189 g_signal_connect (service,
2194 g_free (signal_detail);
2199 * gupnp_service_signals_autoconnect:
2200 * @service: A #GUPnPService
2201 * @user_data: the data to pass to each of the callbacks
2202 * @error: return location for a #GError, or %NULL
2204 * A convenience function that attempts to connect all possible
2205 * #GUPnPService::action-invoked and #GUPnPService::query-variable signals to
2206 * appropriate callbacks for the service @service. It uses service introspection
2207 * and #GModule<!-- -->'s introspective features. It is very simillar to
2208 * gtk_builder_connect_signals() except that it attempts to guess the names of
2209 * the signal handlers on its own.
2211 * For this function to do its magic, the application must name the callback
2212 * functions for #GUPnPService::action-invoked signals by striping the CamelCase
2213 * off the action names and either prepend "on_" or append "_cb" to them. Same
2214 * goes for #GUPnPService::query-variable signals, except that "query_" should
2215 * be prepended to the variable name. For example, callback function for
2216 * <varname>GetSystemUpdateID</varname> action should be either named as
2217 * "get_system_update_id_cb" or "on_get_system_update_id" and callback function
2218 * for the query of "SystemUpdateID" state variable should be named
2219 * <function>query_system_update_id_cb</function> or
2220 * <function>on_query_system_update_id</function>.
2222 * <note>This function will not work correctly if #GModule is not supported
2223 * on the platform or introspection is not available for @service.</note>
2225 * <warning>This function can not and therefore does not guarantee that the
2226 * resulting signal connections will be correct as it depends heavily on a
2227 * particular naming schemes described above.</warning>
2230 gupnp_service_signals_autoconnect (GUPnPService *service,
2234 GUPnPServiceIntrospection *introspection;
2238 g_return_if_fail (GUPNP_IS_SERVICE (service));
2240 introspection = gupnp_service_info_get_introspection
2241 (GUPNP_SERVICE_INFO (service),
2246 /* Get a handle on the main executable -- use this to find symbols */
2247 module = g_module_open (NULL, 0);
2248 if (module == NULL) {
2249 g_error ("Failed to open module: %s", g_module_error ());
2251 g_object_unref (introspection);
2256 names = gupnp_service_introspection_list_action_names (introspection);
2257 connect_names_to_signal_handlers (service,
2264 names = gupnp_service_introspection_list_state_variable_names
2266 connect_names_to_signal_handlers (service,
2273 g_module_close (module);
2274 g_object_unref (introspection);