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 * @...: 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 retrieve '%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 * @...: 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 G_GNUC_UNUSED const char *server_path,
904 G_GNUC_UNUSED GHashTable *query,
905 G_GNUC_UNUSED 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 /* DLNA 7.2.5.6: Always use HTTP 1.1 */
933 if (soup_message_get_http_version (msg) == SOUP_HTTP_1_0) {
934 soup_message_set_http_version (msg, SOUP_HTTP_1_1);
935 soup_message_headers_append (msg->response_headers,
940 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (service));
942 /* Get action name */
943 soap_action = soup_message_headers_get_one (msg->request_headers,
946 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
950 action_name = strchr (soap_action, '#');
952 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
957 /* This memory is libsoup-owned so we can do this */
961 /* This memory is libsoup-owned so we can do this */
962 end = strrchr (action_name, '"');
966 /* Parse action_node */
967 doc = xmlRecoverMemory (msg->request_body->data,
968 msg->request_body->length);
970 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
975 action_node = xml_util_get_element ((xmlNode *) doc,
981 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
986 /* Create action structure */
987 action = g_slice_new0 (GUPnPServiceAction);
989 action->ref_count = 1;
990 action->name = g_strdup (action_name);
991 action->msg = g_object_ref (msg);
992 action->doc = gupnp_xml_doc_new(doc);
993 action->node = action_node;
994 action->response_str = new_action_response_str (action_name,
996 action->context = g_object_ref (context);
997 action->argument_count = 0;
999 for (node = action->node->children; node; node = node->next)
1000 if (node->type == XML_ELEMENT_NODE)
1001 action->argument_count++;
1003 /* Get accepted encodings */
1004 accept_encoding = soup_message_headers_get_list (msg->request_headers,
1007 if (accept_encoding) {
1010 codings = soup_header_parse_quality_list (accept_encoding,
1013 g_slist_find_custom (codings, "gzip",
1014 (GCompareFunc) g_ascii_strcasecmp)) {
1015 action->accept_gzip = TRUE;
1018 soup_header_free_list (codings);
1021 /* Tell soup server that response is not ready yet */
1022 soup_server_pause_message (server, msg);
1024 /* QueryStateVariable? */
1025 if (strcmp (action_name, "QueryStateVariable") == 0)
1026 query_state_variable (service, action);
1028 GQuark action_name_quark;
1030 action_name_quark = g_quark_from_string (action_name);
1031 if (g_signal_has_handler_pending (service,
1032 signals[ACTION_INVOKED],
1035 /* Emit signal. Handler parses request and fills in
1037 g_signal_emit (service,
1038 signals[ACTION_INVOKED],
1042 /* No handlers attached. */
1043 gupnp_service_action_return_error (action,
1050 /* Generates a standard (re)subscription response */
1052 subscription_response (GUPnPService *service,
1057 GUPnPContext *context;
1058 GSSDPClient *client;
1061 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (service));
1062 client = GSSDP_CLIENT (context);
1064 /* Server header on response */
1065 soup_message_headers_append (msg->response_headers,
1067 gssdp_client_get_server_id (client));
1070 soup_message_headers_append (msg->response_headers,
1074 /* Timeout header */
1076 tmp = g_strdup_printf ("Second-%d", timeout);
1078 tmp = g_strdup ("infinite");
1080 soup_message_headers_append (msg->response_headers,
1086 soup_message_set_status (msg, SOUP_STATUS_OK);
1089 /* Generates a new SID */
1097 stat = UuidCreate (&uuid);
1098 if (stat == RPC_S_OK) {
1099 unsigned char* uuidStr = NULL;
1100 stat = UuidToString (&uuid, &uuidStr);
1101 if (stat == RPC_S_OK) {
1102 ret = g_strdup_printf ("uuid:%s", uuidStr);
1103 RpcStringFree (&uuidStr);
1113 uuid_unparse (id, out);
1115 return g_strdup_printf ("uuid:%s", out);
1119 /* Subscription expired */
1121 subscription_timeout (gpointer user_data)
1123 SubscriptionData *data;
1127 g_hash_table_remove (data->service->priv->subscriptions, data->sid);
1133 send_initial_state (SubscriptionData *data)
1139 /* Send initial event message */
1140 queue = g_queue_new ();
1142 for (l = data->service->priv->state_variables; l; l = l->next) {
1145 ndata = g_slice_new0 (NotifyData);
1147 g_signal_emit (data->service,
1148 signals[QUERY_VARIABLE],
1149 g_quark_from_string (l->data),
1153 if (!G_IS_VALUE (&ndata->value)) {
1154 g_slice_free (NotifyData, ndata);
1159 ndata->variable = g_strdup (l->data);
1161 g_queue_push_tail (queue, ndata);
1164 mem = create_property_set (queue);
1165 notify_subscriber (data->sid, data, mem);
1168 g_queue_free (queue);
1174 /* Subscription request */
1176 subscribe (GUPnPService *service,
1178 const char *callback)
1180 SubscriptionData *data;
1181 char *start, *end, *uri;
1183 data = g_slice_new0 (SubscriptionData);
1185 /* Parse callback list */
1186 start = (char *) callback;
1187 while ((start = strchr (start, '<'))) {
1189 if (!start || !*start)
1192 end = strchr (start, '>');
1196 if (strncmp (start, "http://", strlen ("http://")) == 0) {
1197 uri = g_strndup (start, end - start);
1198 data->callbacks = g_list_append (data->callbacks, uri);
1204 if (!data->callbacks) {
1205 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1207 g_slice_free (SubscriptionData, data);
1212 /* Add service and SID */
1213 data->service = service;
1214 data->sid = generate_sid ();
1217 data->timeout_src = g_timeout_source_new_seconds (SUBSCRIPTION_TIMEOUT);
1218 g_source_set_callback (data->timeout_src,
1219 subscription_timeout,
1223 g_source_attach (data->timeout_src,
1224 g_main_context_get_thread_default ());
1226 g_source_unref (data->timeout_src);
1229 g_hash_table_insert (service->priv->subscriptions,
1234 subscription_response (service, msg, data->sid, SUBSCRIPTION_TIMEOUT);
1236 send_initial_state (data);
1239 /* Resubscription request */
1241 resubscribe (GUPnPService *service,
1245 SubscriptionData *data;
1247 data = g_hash_table_lookup (service->priv->subscriptions, sid);
1249 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1254 /* Update timeout */
1255 if (data->timeout_src) {
1256 g_source_destroy (data->timeout_src);
1257 data->timeout_src = NULL;
1260 data->timeout_src = g_timeout_source_new_seconds (SUBSCRIPTION_TIMEOUT);
1261 g_source_set_callback (data->timeout_src,
1262 subscription_timeout,
1266 g_source_attach (data->timeout_src,
1267 g_main_context_get_thread_default ());
1269 g_source_unref (data->timeout_src);
1272 subscription_response (service, msg, sid, SUBSCRIPTION_TIMEOUT);
1275 /* Unsubscription request */
1277 unsubscribe (GUPnPService *service,
1281 SubscriptionData *data;
1283 data = g_hash_table_lookup (service->priv->subscriptions, sid);
1285 if (data->initial_state_sent)
1286 g_hash_table_remove (service->priv->subscriptions,
1289 data->to_delete = TRUE;
1290 soup_message_set_status (msg, SOUP_STATUS_OK);
1292 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1295 /* eventSubscriptionURL handler */
1297 subscription_server_handler (G_GNUC_UNUSED SoupServer *server,
1299 G_GNUC_UNUSED const char *server_path,
1300 G_GNUC_UNUSED GHashTable *query,
1301 G_GNUC_UNUSED SoupClientContext *soup_client,
1304 GUPnPService *service;
1305 const char *callback, *nt, *sid;
1307 service = GUPNP_SERVICE (user_data);
1309 callback = soup_message_headers_get_one (msg->request_headers,
1311 nt = soup_message_headers_get_one (msg->request_headers, "NT");
1312 sid = soup_message_headers_get_one (msg->request_headers, "SID");
1314 /* Choose appropriate handler */
1315 if (strcmp (msg->method, GENA_METHOD_SUBSCRIBE) == 0) {
1318 soup_message_set_status
1319 (msg, SOUP_STATUS_BAD_REQUEST);
1321 } else if (!nt || strcmp (nt, "upnp:event") != 0) {
1322 soup_message_set_status
1323 (msg, SOUP_STATUS_PRECONDITION_FAILED);
1326 subscribe (service, msg, callback);
1332 soup_message_set_status
1333 (msg, SOUP_STATUS_BAD_REQUEST);
1336 resubscribe (service, msg, sid);
1341 soup_message_set_status
1342 (msg, SOUP_STATUS_PRECONDITION_FAILED);
1346 } else if (strcmp (msg->method, GENA_METHOD_UNSUBSCRIBE) == 0) {
1348 if (nt || callback) {
1349 soup_message_set_status
1350 (msg, SOUP_STATUS_BAD_REQUEST);
1353 unsubscribe (service, msg, sid);
1358 soup_message_set_status
1359 (msg, SOUP_STATUS_PRECONDITION_FAILED);
1364 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
1370 got_introspection (GUPnPServiceInfo *info,
1371 GUPnPServiceIntrospection *introspection,
1372 const GError *error,
1373 G_GNUC_UNUSED gpointer user_data)
1375 GUPnPService *service = GUPNP_SERVICE (info);
1376 const GList *state_variables, *l;
1377 GHashTableIter iter;
1380 if (introspection) {
1382 gupnp_service_introspection_list_state_variables
1385 for (l = state_variables; l; l = l->next) {
1386 GUPnPServiceStateVariableInfo *variable;
1390 if (!variable->send_events)
1393 service->priv->state_variables =
1394 g_list_prepend (service->priv->state_variables,
1395 g_strdup (variable->name));
1398 g_object_unref (introspection);
1400 g_warning ("Failed to get SCPD: %s\n"
1401 "The initial event message will not be sent.",
1402 error ? error->message : "No error");
1404 g_hash_table_iter_init (&iter, service->priv->subscriptions);
1406 while (g_hash_table_iter_next (&iter, NULL, &data)) {
1407 send_initial_state ((SubscriptionData *) data);
1408 if (subscription_data_can_delete ((SubscriptionData *) data))
1409 g_hash_table_iter_remove (&iter);
1414 path_from_url (const char *url)
1419 uri = soup_uri_new (url);
1420 path = soup_uri_to_string (uri, TRUE);
1421 soup_uri_free (uri);
1427 gupnp_service_constructor (GType type,
1428 guint n_construct_params,
1429 GObjectConstructParam *construct_params)
1431 GObjectClass *object_class;
1433 GUPnPServiceInfo *info;
1434 GUPnPContext *context;
1439 object_class = G_OBJECT_CLASS (gupnp_service_parent_class);
1442 object = object_class->constructor (type,
1446 info = GUPNP_SERVICE_INFO (object);
1448 /* Get introspection and save state variable names */
1449 gupnp_service_info_get_introspection_async (info,
1454 context = gupnp_service_info_get_context (info);
1455 server = gupnp_context_get_server (context);
1457 /* Run listener on controlURL */
1458 url = gupnp_service_info_get_control_url (info);
1459 path = path_from_url (url);
1460 soup_server_add_handler (server, path,
1461 control_server_handler, object, NULL);
1465 /* Run listener on eventSubscriptionURL */
1466 url = gupnp_service_info_get_event_subscription_url (info);
1467 path = path_from_url (url);
1468 soup_server_add_handler (server, path,
1469 subscription_server_handler, object, NULL);
1476 /* Root device availability changed. */
1478 notify_available_cb (GObject *object,
1479 G_GNUC_UNUSED GParamSpec *pspec,
1482 GUPnPService *service;
1484 service = GUPNP_SERVICE (user_data);
1486 if (!gupnp_root_device_get_available (GUPNP_ROOT_DEVICE (object))) {
1487 /* Root device now unavailable: Purge subscriptions */
1488 g_hash_table_remove_all (service->priv->subscriptions);
1493 gupnp_service_set_property (GObject *object,
1495 const GValue *value,
1498 GUPnPService *service;
1500 service = GUPNP_SERVICE (object);
1502 switch (property_id) {
1503 case PROP_ROOT_DEVICE: {
1504 GUPnPRootDevice **dev;
1506 service->priv->root_device = g_value_get_object (value);
1507 dev = &(service->priv->root_device);
1509 g_object_add_weak_pointer
1510 (G_OBJECT (service->priv->root_device),
1513 service->priv->notify_available_id =
1514 g_signal_connect_object (service->priv->root_device,
1515 "notify::available",
1517 (notify_available_cb),
1524 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1530 gupnp_service_get_property (GObject *object,
1535 GUPnPService *service;
1537 service = GUPNP_SERVICE (object);
1539 switch (property_id) {
1540 case PROP_ROOT_DEVICE:
1541 g_value_set_object (value, service->priv->root_device);
1544 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1550 gupnp_service_dispose (GObject *object)
1552 GUPnPService *service;
1553 GObjectClass *object_class;
1554 GUPnPServiceInfo *info;
1555 GUPnPContext *context;
1560 service = GUPNP_SERVICE (object);
1563 info = GUPNP_SERVICE_INFO (service);
1564 context = gupnp_service_info_get_context (info);
1565 server = gupnp_context_get_server (context);
1567 /* Remove listener on controlURL */
1568 url = gupnp_service_info_get_control_url (info);
1569 path = path_from_url (url);
1570 soup_server_remove_handler (server, path);
1574 /* Remove listener on eventSubscriptionURL */
1575 url = gupnp_service_info_get_event_subscription_url (info);
1576 path = path_from_url (url);
1577 soup_server_remove_handler (server, path);
1581 if (service->priv->root_device) {
1582 GUPnPRootDevice **dev = &(service->priv->root_device);
1584 if (g_signal_handler_is_connected
1585 (service->priv->root_device,
1586 service->priv->notify_available_id)) {
1587 g_signal_handler_disconnect
1588 (service->priv->root_device,
1589 service->priv->notify_available_id);
1592 g_object_remove_weak_pointer
1593 (G_OBJECT (service->priv->root_device),
1596 service->priv->root_device = NULL;
1599 /* Cancel pending messages */
1600 g_hash_table_remove_all (service->priv->subscriptions);
1603 object_class = G_OBJECT_CLASS (gupnp_service_parent_class);
1604 object_class->dispose (object);
1608 gupnp_service_finalize (GObject *object)
1610 GUPnPService *service;
1611 GObjectClass *object_class;
1614 service = GUPNP_SERVICE (object);
1616 /* Free subscription hash */
1617 g_hash_table_destroy (service->priv->subscriptions);
1619 /* Free state variable list */
1620 while (service->priv->state_variables) {
1621 g_free (service->priv->state_variables->data);
1622 service->priv->state_variables =
1623 g_list_delete_link (service->priv->state_variables,
1624 service->priv->state_variables);
1627 /* Free notify queue */
1628 while ((data = g_queue_pop_head (service->priv->notify_queue)))
1629 notify_data_free (data);
1631 g_queue_free (service->priv->notify_queue);
1633 if (service->priv->session) {
1634 g_object_unref (service->priv->session);
1635 service->priv->session = NULL;
1639 object_class = G_OBJECT_CLASS (gupnp_service_parent_class);
1640 object_class->finalize (object);
1644 gupnp_service_class_init (GUPnPServiceClass *klass)
1646 GObjectClass *object_class;
1648 object_class = G_OBJECT_CLASS (klass);
1650 object_class->set_property = gupnp_service_set_property;
1651 object_class->get_property = gupnp_service_get_property;
1652 object_class->constructor = gupnp_service_constructor;
1653 object_class->dispose = gupnp_service_dispose;
1654 object_class->finalize = gupnp_service_finalize;
1656 g_type_class_add_private (klass, sizeof (GUPnPServicePrivate));
1659 * GUPnPService:root-device:
1661 * The containing #GUPnPRootDevice.
1663 g_object_class_install_property
1666 g_param_spec_object ("root-device",
1668 "The GUPnPRootDevice",
1669 GUPNP_TYPE_ROOT_DEVICE,
1671 G_PARAM_CONSTRUCT_ONLY |
1672 G_PARAM_STATIC_NAME |
1673 G_PARAM_STATIC_NICK |
1674 G_PARAM_STATIC_BLURB));
1677 * GUPnPService::action-invoked:
1678 * @service: The #GUPnPService that received the signal
1679 * @action: The invoked #GUPnPServiceAction
1681 * Emitted whenever an action is invoked. Handler should process
1682 * @action and must call either gupnp_service_action_return() or
1683 * gupnp_service_action_return_error().
1685 signals[ACTION_INVOKED] =
1686 g_signal_new ("action-invoked",
1688 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
1689 G_STRUCT_OFFSET (GUPnPServiceClass,
1693 g_cclosure_marshal_VOID__BOXED,
1696 GUPNP_TYPE_SERVICE_ACTION);
1699 * GUPnPService::query-variable:
1700 * @service: The #GUPnPService that received the signal
1701 * @variable: The variable that is being queried
1702 * @value: (type GValue)(inout):The location of the #GValue of the variable
1704 * Emitted whenever @service needs to know the value of @variable.
1705 * Handler should fill @value with the value of @variable.
1707 signals[QUERY_VARIABLE] =
1708 g_signal_new ("query-variable",
1710 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
1711 G_STRUCT_OFFSET (GUPnPServiceClass,
1715 gupnp_marshal_VOID__STRING_POINTER,
1719 G_TYPE_POINTER /* Not G_TYPE_VALUE as this
1720 is an outward argument! */);
1723 * GUPnPService::notify-failed:
1724 * @service: The #GUPnPService that received the signal
1725 * @callback_url: (type GList)(element-type SoupURI):A #GList of callback URLs
1726 * @reason: (type GError): A pointer to a #GError describing why the notify failed
1728 * Emitted whenever notification of a client fails.
1730 signals[NOTIFY_FAILED] =
1731 g_signal_new ("notify-failed",
1734 G_STRUCT_OFFSET (GUPnPServiceClass,
1738 gupnp_marshal_VOID__POINTER_POINTER,
1746 * gupnp_service_notify:
1747 * @service: A #GUPnPService
1748 * @...: Tuples of variable name, variable type, and variable value,
1749 * terminated with %NULL.
1751 * Notifies listening clients that the properties listed in @Varargs
1752 * have changed to the specified values.
1755 gupnp_service_notify (GUPnPService *service,
1760 g_return_if_fail (GUPNP_IS_SERVICE (service));
1762 va_start (var_args, service);
1763 gupnp_service_notify_valist (service, var_args);
1768 * gupnp_service_notify_valist:
1769 * @service: A #GUPnPService
1770 * @var_args: A va_list of tuples of variable name, variable type, and variable
1771 * value, terminated with %NULL.
1773 * See gupnp_service_notify(); this version takes a va_list for
1774 * use by language bindings.
1777 gupnp_service_notify_valist (GUPnPService *service,
1780 const char *var_name;
1782 GValue value = {0, };
1783 char *collect_error;
1785 g_return_if_fail (GUPNP_IS_SERVICE (service));
1787 collect_error = NULL;
1789 var_name = va_arg (var_args, const char *);
1791 var_type = va_arg (var_args, GType);
1792 g_value_init (&value, var_type);
1794 G_VALUE_COLLECT (&value, var_args, G_VALUE_NOCOPY_CONTENTS,
1796 if (!collect_error) {
1797 gupnp_service_notify_value (service, var_name, &value);
1799 g_value_unset (&value);
1802 g_warning ("Error collecting value: %s\n",
1805 g_free (collect_error);
1808 var_name = va_arg (var_args, const char *);
1812 /* Received notify response. */
1814 notify_got_response (G_GNUC_UNUSED SoupSession *session,
1818 SubscriptionData *data;
1821 if (msg->status_code == SOUP_STATUS_CANCELLED)
1826 /* Remove from pending messages list */
1827 data->pending_messages = g_list_remove (data->pending_messages, msg);
1829 if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
1830 data->initial_state_sent = TRUE;
1832 /* Success: reset callbacks pointer */
1833 data->callbacks = g_list_first (data->callbacks);
1835 } else if (msg->status_code == SOUP_STATUS_PRECONDITION_FAILED) {
1836 /* Precondition failed: Cancel subscription */
1837 g_hash_table_remove (data->service->priv->subscriptions,
1841 /* Other failure: Try next callback or signal failure. */
1842 if (data->callbacks->next) {
1844 SoupSession *service_session;
1846 /* Call next callback */
1847 data->callbacks = data->callbacks->next;
1849 uri = soup_uri_new (data->callbacks->data);
1850 soup_message_set_uri (msg, uri);
1851 soup_uri_free (uri);
1854 data->pending_messages =
1855 g_list_prepend (data->pending_messages, msg);
1857 service_session = gupnp_service_get_session (data->service);
1859 soup_session_requeue_message (service_session, msg);
1861 /* Emit 'notify-failed' signal */
1864 error = g_error_new_literal
1865 (GUPNP_EVENTING_ERROR,
1866 GUPNP_EVENTING_ERROR_NOTIFY_FAILED,
1867 msg->reason_phrase);
1869 g_signal_emit (data->service,
1870 signals[NOTIFY_FAILED],
1875 g_error_free (error);
1877 /* Reset callbacks pointer */
1878 data->callbacks = g_list_first (data->callbacks);
1883 /* Send notification @user_data to subscriber @value */
1885 notify_subscriber (G_GNUC_UNUSED gpointer key,
1889 SubscriptionData *data;
1890 const char *property_set;
1893 SoupSession *session;
1896 property_set = user_data;
1898 /* Subscriber called unsubscribe */
1899 if (subscription_data_can_delete (data))
1902 /* Create message */
1903 msg = soup_message_new (GENA_METHOD_NOTIFY, data->callbacks->data);
1905 g_warning ("Invalid callback URL: %s",
1906 (char *) data->callbacks->data);
1911 soup_message_headers_append (msg->request_headers,
1915 soup_message_headers_append (msg->request_headers,
1919 soup_message_headers_append (msg->request_headers,
1923 tmp = g_strdup_printf ("%d", data->seq);
1924 soup_message_headers_append (msg->request_headers,
1929 /* Handle overflow */
1930 if (data->seq < G_MAXINT32)
1936 soup_message_set_request (msg,
1937 "text/xml; charset=\"utf-8\"",
1939 g_strdup (property_set),
1940 strlen (property_set));
1943 data->pending_messages = g_list_prepend (data->pending_messages, msg);
1944 soup_message_headers_append (msg->request_headers,
1945 "Connection", "close");
1947 session = gupnp_service_get_session (data->service);
1949 soup_session_queue_message (session,
1951 notify_got_response,
1955 /* Create a property set from @queue */
1957 create_property_set (GQueue *queue)
1962 /* Compose property set */
1963 str = xml_util_new_string ();
1965 g_string_append (str,
1966 "<?xml version=\"1.0\"?>"
1967 "<e:propertyset xmlns:e="
1968 "\"urn:schemas-upnp-org:event-1-0\">");
1971 while ((data = g_queue_pop_head (queue))) {
1972 xml_util_start_element (str, "e:property");
1973 xml_util_start_element (str, data->variable);
1974 gvalue_util_value_append_to_xml_string (&data->value, str);
1975 xml_util_end_element (str, data->variable);
1976 xml_util_end_element (str, "e:property");
1979 notify_data_free (data);
1982 g_string_append (str, "</e:propertyset>");
1984 /* Cleanup & return */
1985 return g_string_free (str, FALSE);
1988 /* Flush all queued notifications */
1990 flush_notifications (GUPnPService *service)
1994 /* Create property set */
1995 mem = create_property_set (service->priv->notify_queue);
1997 /* And send it off */
1998 g_hash_table_foreach (service->priv->subscriptions,
2007 * gupnp_service_notify_value:
2008 * @service: A #GUPnPService
2009 * @variable: The name of the variable to notify
2010 * @value: The value of the variable
2012 * Notifies listening clients that @variable has changed to @value.
2015 gupnp_service_notify_value (GUPnPService *service,
2016 const char *variable,
2017 const GValue *value)
2021 g_return_if_fail (GUPNP_IS_SERVICE (service));
2022 g_return_if_fail (variable != NULL);
2023 g_return_if_fail (G_IS_VALUE (value));
2026 data = g_slice_new0 (NotifyData);
2028 data->variable = g_strdup (variable);
2030 g_value_init (&data->value, G_VALUE_TYPE (value));
2031 g_value_copy (value, &data->value);
2033 g_queue_push_tail (service->priv->notify_queue, data);
2035 /* And flush, if not frozen */
2036 if (!service->priv->notify_frozen)
2037 flush_notifications (service);
2041 * gupnp_service_freeze_notify:
2042 * @service: A #GUPnPService
2044 * Causes new notifications to be queued up until gupnp_service_thaw_notify()
2048 gupnp_service_freeze_notify (GUPnPService *service)
2050 g_return_if_fail (GUPNP_IS_SERVICE (service));
2052 service->priv->notify_frozen = TRUE;
2056 * gupnp_service_thaw_notify:
2057 * @service: A #GUPnPService
2059 * Sends out any pending notifications, and stops queuing of new ones.
2062 gupnp_service_thaw_notify (GUPnPService *service)
2064 g_return_if_fail (GUPNP_IS_SERVICE (service));
2066 service->priv->notify_frozen = FALSE;
2068 if (g_queue_get_length (service->priv->notify_queue) == 0)
2069 return; /* Empty notify queue */
2071 flush_notifications (service);
2074 /* Convert a CamelCase string to a lowercase string with underscores */
2076 strip_camel_case (char *camel_str)
2081 /* Keep enough space for underscores */
2082 stripped = g_malloc (strlen (camel_str) * 2);
2084 for (i = 0, j = 0; i <= strlen (camel_str); i++) {
2085 /* Convert every upper case letter to lower case and unless
2086 * it's the first character, the last charachter, in the
2087 * middle of an abbreviation or there is already an underscore
2088 * before it, add an underscore before it */
2089 if (g_ascii_isupper (camel_str[i])) {
2091 camel_str[i + 1] != '\0' &&
2092 camel_str[i - 1] != '_' &&
2093 !g_ascii_isupper (camel_str[i - 1])) {
2094 stripped[j++] = '_';
2096 stripped[j++] = g_ascii_tolower (camel_str[i]);
2098 stripped[j++] = camel_str[i];
2105 find_callback_by_name (GModule *module,
2111 /* First try with 'on_' prefix */
2112 full_name = g_strjoin ("_",
2117 if (!g_module_symbol (module,
2119 (gpointer) &callback)) {
2122 /* Now try with '_cb' postfix */
2123 full_name = g_strjoin ("_",
2128 if (!g_module_symbol (module,
2130 (gpointer) &callback))
2139 /* Use the strings from @name_list as details to @signal_name, and connect
2140 * callbacks with names based on these same strings to @signal_name::string. */
2142 connect_names_to_signal_handlers (GUPnPService *service,
2144 const GList *name_list,
2145 const char *signal_name,
2146 const char *callback_prefix,
2149 const GList *name_node;
2151 for (name_node = name_list;
2153 name_node = name_node->next) {
2155 char *callback_name;
2156 char *signal_detail;
2158 signal_detail = (char *) name_node->data;
2159 callback_name = strip_camel_case (signal_detail);
2161 if (callback_prefix) {
2164 tmp = g_strjoin ("_",
2169 g_free (callback_name);
2170 callback_name = tmp;
2173 callback = find_callback_by_name (module, callback_name);
2174 g_free (callback_name);
2176 if (callback == NULL)
2179 signal_detail = g_strjoin ("::",
2184 g_signal_connect (service,
2189 g_free (signal_detail);
2194 * gupnp_service_signals_autoconnect:
2195 * @service: A #GUPnPService
2196 * @user_data: the data to pass to each of the callbacks
2197 * @error: return location for a #GError, or %NULL
2199 * A convenience function that attempts to connect all possible
2200 * #GUPnPService::action-invoked and #GUPnPService::query-variable signals to
2201 * appropriate callbacks for the service @service. It uses service introspection
2202 * and #GModule<!-- -->'s introspective features. It is very simillar to
2203 * gtk_builder_connect_signals() except that it attempts to guess the names of
2204 * the signal handlers on its own.
2206 * For this function to do its magic, the application must name the callback
2207 * functions for #GUPnPService::action-invoked signals by striping the CamelCase
2208 * off the action names and either prepend "on_" or append "_cb" to them. Same
2209 * goes for #GUPnPService::query-variable signals, except that "query_" should
2210 * be prepended to the variable name. For example, callback function for
2211 * <varname>GetSystemUpdateID</varname> action should be either named as
2212 * "get_system_update_id_cb" or "on_get_system_update_id" and callback function
2213 * for the query of "SystemUpdateID" state variable should be named
2214 * <function>query_system_update_id_cb</function> or
2215 * <function>on_query_system_update_id</function>.
2217 * <note>This function will not work correctly if #GModule is not supported
2218 * on the platform or introspection is not available for @service.</note>
2220 * <warning>This function can not and therefore does not guarantee that the
2221 * resulting signal connections will be correct as it depends heavily on a
2222 * particular naming schemes described above.</warning>
2225 gupnp_service_signals_autoconnect (GUPnPService *service,
2229 GUPnPServiceIntrospection *introspection;
2233 g_return_if_fail (GUPNP_IS_SERVICE (service));
2235 introspection = gupnp_service_info_get_introspection
2236 (GUPNP_SERVICE_INFO (service),
2241 /* Get a handle on the main executable -- use this to find symbols */
2242 module = g_module_open (NULL, 0);
2243 if (module == NULL) {
2244 g_error ("Failed to open module: %s", g_module_error ());
2246 g_object_unref (introspection);
2251 names = gupnp_service_introspection_list_action_names (introspection);
2252 connect_names_to_signal_handlers (service,
2259 names = gupnp_service_introspection_list_state_variable_names
2261 connect_names_to_signal_handlers (service,
2268 g_module_close (module);
2269 g_object_unref (introspection);