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., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
23 * SECTION:gupnp-service
24 * @short_description: Class for service implementations.
26 * #GUPnPService allows for handling incoming actions and state variable
27 * notification. #GUPnPService implements the #GUPnPServiceInfo interface.
30 #include <gobject/gvaluecollector.h>
32 #include <libsoup/soup-date.h>
33 #include <uuid/uuid.h>
35 #include "gupnp-service.h"
36 #include "gupnp-root-device.h"
37 #include "gupnp-context-private.h"
38 #include "gupnp-marshal.h"
39 #include "gupnp-error.h"
40 #include "http-headers.h"
41 #include "gena-protocol.h"
43 #include "gvalue-util.h"
45 #define SUBSCRIPTION_TIMEOUT 300 /* DLNA (7.2.22.1) enforced */
47 G_DEFINE_TYPE (GUPnPService,
49 GUPNP_TYPE_SERVICE_INFO);
51 struct _GUPnPServicePrivate {
52 GUPnPRootDevice *root_device;
56 guint notify_available_id;
58 GHashTable *subscriptions;
60 GList *state_variables;
64 gboolean notify_frozen;
79 static guint signals[LAST_SIGNAL];
82 create_property_set (GQueue *queue);
85 notify_subscriber (gpointer key,
90 GUPnPService *service;
99 GList *pending_messages; /* Pending SoupMessages from this
104 send_initial_state (SubscriptionData *data);
107 gupnp_service_get_session (GUPnPService *service)
109 if (! service->priv->session) {
110 GUPnPContext *context =
111 gupnp_service_info_get_context (GUPNP_SERVICE_INFO (service));
113 /* Create a dedicated session for this service to
114 * ensure that notifications are sent in the proper
115 * order. The session from GUPnPContext may use
116 * multiple connections.
118 service->priv->session = soup_session_async_new_with_options
119 (SOUP_SESSION_IDLE_TIMEOUT, 60,
120 SOUP_SESSION_ASYNC_CONTEXT,
121 gssdp_client_get_main_context (GSSDP_CLIENT (context)),
122 SOUP_SESSION_MAX_CONNS_PER_HOST, 1,
125 if (g_getenv ("GUPNP_DEBUG")) {
127 logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
128 soup_session_add_feature (
129 service->priv->session,
130 SOUP_SESSION_FEATURE (logger));
134 return service->priv->session;
138 subscription_data_free (SubscriptionData *data)
140 SoupSession *session;
142 session = gupnp_service_get_session (data->service);
144 /* Cancel pending messages */
145 while (data->pending_messages) {
148 msg = data->pending_messages->data;
150 soup_session_cancel_message (session,
152 SOUP_STATUS_CANCELLED);
154 data->pending_messages =
155 g_list_delete_link (data->pending_messages,
156 data->pending_messages);
159 /* Further cleanup */
160 while (data->callbacks) {
161 g_free (data->callbacks->data);
162 data->callbacks = g_list_delete_link (data->callbacks,
168 if (data->timeout_src)
169 g_source_destroy (data->timeout_src);
171 g_slice_free (SubscriptionData, data);
180 notify_data_free (NotifyData *data)
182 g_free (data->variable);
183 g_value_unset (&data->value);
185 g_slice_free (NotifyData, data);
188 struct _GUPnPServiceAction {
189 volatile gint ref_count;
191 GUPnPContext *context;
200 GString *response_str;
204 gupnp_service_action_ref (GUPnPServiceAction *action)
206 g_return_val_if_fail (action, NULL);
207 g_return_val_if_fail (action->ref_count > 0, NULL);
209 g_atomic_int_inc (&action->ref_count);
215 gupnp_service_action_unref (GUPnPServiceAction *action)
217 g_return_if_fail (action);
218 g_return_if_fail (action->ref_count > 0);
220 if (g_atomic_int_dec_and_test (&action->ref_count)) {
221 g_free (action->name);
222 g_object_unref (action->msg);
223 g_object_unref (action->context);
224 g_object_unref (action->doc);
226 g_slice_free (GUPnPServiceAction, action);
231 * gupnp_service_action_get_type:
233 * Get the gtype for GUPnPServiceActon
235 * Return value: The gtype of GUPnPServiceAction
238 gupnp_service_action_get_type (void)
240 static GType our_type = 0;
243 our_type = g_boxed_type_register_static
244 ("GUPnPServiceAction",
245 (GBoxedCopyFunc) gupnp_service_action_ref,
246 (GBoxedFreeFunc) gupnp_service_action_unref);
252 finalize_action (GUPnPServiceAction *action)
257 /* Embed action->response_str in a SOAP document */
258 g_string_prepend (action->response_str,
259 "<?xml version=\"1.0\"?>"
260 "<s:Envelope xmlns:s="
261 "\"http://schemas.xmlsoap.org/soap/envelope/\" "
263 "\"http://schemas.xmlsoap.org/soap/encoding/\">"
266 if (action->msg->status_code != SOUP_STATUS_INTERNAL_SERVER_ERROR) {
267 g_string_append (action->response_str, "</u:");
268 g_string_append (action->response_str, action->name);
269 g_string_append (action->response_str, "Response>");
272 g_string_append (action->response_str,
276 response_body = g_string_free (action->response_str, FALSE);
278 soup_message_set_response (action->msg,
279 "text/xml; charset=\"utf-8\"",
282 strlen (response_body));
284 /* Server header on response */
285 soup_message_headers_append
286 (action->msg->response_headers,
288 gssdp_client_get_server_id
289 (GSSDP_CLIENT (action->context)));
291 /* Tell soup server that response is now ready */
292 server = gupnp_context_get_server (action->context);
293 soup_server_unpause_message (server, action->msg);
296 gupnp_service_action_unref (action);
300 * gupnp_service_action_get_name:
301 * @action: A #GUPnPServiceAction
303 * Get the name of @action.
305 * Return value: The name of @action
308 gupnp_service_action_get_name (GUPnPServiceAction *action)
310 g_return_val_if_fail (action != NULL, NULL);
316 * gupnp_service_action_get_locales:
317 * @action: A #GUPnPServiceAction
319 * Get an ordered (preferred first) #GList of locales preferred by
320 * the client. Free list and elements after use.
322 * Return value: (element-type utf8) (transfer full): A #GList of #char*
326 gupnp_service_action_get_locales (GUPnPServiceAction *action)
328 g_return_val_if_fail (action != NULL, NULL);
330 return http_request_get_accept_locales (action->msg);
334 * gupnp_service_action_get:
335 * @action: A #GUPnPServiceAction
336 * @Varargs: tuples of argument name, argument type, and argument value
337 * location, terminated with %NULL.
339 * Retrieves the specified action arguments.
342 gupnp_service_action_get (GUPnPServiceAction *action,
347 g_return_if_fail (action != NULL);
349 va_start (var_args, action);
350 gupnp_service_action_get_valist (action, var_args);
355 * gupnp_service_action_get_valist:
356 * @action: A #GUPnPServiceAction
357 * @var_args: va_list of tuples of argument name, argument type, and argument
360 * See gupnp_service_action_get(); this version takes a va_list for
361 * use by language bindings.
364 gupnp_service_action_get_valist (GUPnPServiceAction *action,
367 const char *arg_name;
369 GValue value = {0, };
372 g_return_if_fail (action != NULL);
376 arg_name = va_arg (var_args, const char *);
378 arg_type = va_arg (var_args, GType);
379 g_value_init (&value, arg_type);
381 gupnp_service_action_get_value (action, arg_name, &value);
383 G_VALUE_LCOPY (&value, var_args, 0, ©_error);
385 g_value_unset (&value);
388 g_warning ("Error lcopying value: %s\n", copy_error);
393 arg_name = va_arg (var_args, const char *);
398 * gupnp_service_action_get_values:
399 * @action: A #GUPnPServiceAction
400 * @arg_names: (element-type utf8) : A #GList of argument names as string
401 * @arg_types: (element-type GType): A #GList of argument types as #GType
403 * A variant of #gupnp_service_action_get that uses #GList instead of varargs.
405 * Return value: (element-type GValue) (transfer full): The values as #GList of
406 * #GValue. #g_list_free the returned list and #g_value_unset and #g_slice_free
410 gupnp_service_action_get_values (GUPnPServiceAction *action,
417 g_return_val_if_fail (action != NULL, NULL);
421 for (i = 0; i < g_list_length (arg_names); i++) {
422 const char *arg_name;
426 arg_name = (const char *) g_list_nth_data (arg_names, i);
427 arg_type = (GType) g_list_nth_data (arg_types, i);
429 arg_value = g_slice_new0 (GValue);
430 g_value_init (arg_value, arg_type);
432 gupnp_service_action_get_value (action, arg_name, arg_value);
434 arg_values = g_list_append (arg_values, arg_value);
441 * gupnp_service_action_get_value: (skip)
442 * @action: A #GUPnPServiceAction
443 * @argument: The name of the argument to retrieve
444 * @value: The #GValue to store the value of the argument, initialized
445 * to the correct type.
447 * Retrieves the value of @argument into @value.
450 gupnp_service_action_get_value (GUPnPServiceAction *action,
451 const char *argument,
457 g_return_if_fail (action != NULL);
458 g_return_if_fail (argument != NULL);
459 g_return_if_fail (value != NULL);
462 for (node = action->node->children; node; node = node->next) {
463 if (strcmp ((char *) node->name, argument) != 0)
466 found = gvalue_util_set_value_from_xml_node (value, node);
472 g_warning ("Failed to retreive '%s' argument of '%s' action",
478 * gupnp_service_action_get_gvalue:
479 * @action: A #GUPnPServiceAction
480 * @argument: The name of the argument to retrieve
481 * @type: The type of argument to retrieve
483 * Rename To: gupnp_service_action_get_value
484 * Retrieves the value of @argument into a GValue of type @type and returns it.
485 * The method exists only and only to satify PyGI, please use
486 * #gupnp_service_action_get_value and ignore this if possible.
488 * Return value: (transfer full): Value as #GValue associated with @action.
489 * #g_value_unset and #g_slice_free it after usage.
492 gupnp_service_action_get_gvalue (GUPnPServiceAction *action,
493 const char *argument,
498 val = g_slice_new0 (GValue);
499 g_value_init (val, type);
501 gupnp_service_action_get_value (action, argument, val);
507 * gupnp_service_action_set:
508 * @action: A #GUPnPServiceAction
509 * @Varargs: tuples of return value name, return value type, and
510 * actual return value, terminated with %NULL.
512 * Sets the specified action return values.
515 gupnp_service_action_set (GUPnPServiceAction *action,
520 g_return_if_fail (action != NULL);
522 va_start (var_args, action);
523 gupnp_service_action_set_valist (action, var_args);
528 * gupnp_service_action_set_valist:
529 * @action: A #GUPnPServiceAction
530 * @var_args: va_list of tuples of return value name, return value type, and
531 * actual return value.
533 * See gupnp_service_action_set(); this version takes a va_list for
534 * use by language bindings.
537 gupnp_service_action_set_valist (GUPnPServiceAction *action,
540 const char *arg_name;
542 GValue value = {0, };
545 g_return_if_fail (action != NULL);
547 collect_error = NULL;
549 arg_name = va_arg (var_args, const char *);
551 arg_type = va_arg (var_args, GType);
552 g_value_init (&value, arg_type);
554 G_VALUE_COLLECT (&value, var_args, G_VALUE_NOCOPY_CONTENTS,
556 if (!collect_error) {
557 gupnp_service_action_set_value (action,
560 g_value_unset (&value);
563 g_warning ("Error collecting value: %s\n",
566 g_free (collect_error);
569 arg_name = va_arg (var_args, const char *);
574 * gupnp_service_action_set_values:
575 * @action: A #GUPnPServiceAction
576 * @arg_names: (element-type utf8) (transfer-none): A #GList of argument names
577 * @arg_values: (element-type GValue) (transfer-none): The #GList of values (as
578 * #GValues) that line up with @arg_names.
580 * Sets the specified action return values.
583 gupnp_service_action_set_values (GUPnPServiceAction *action,
587 g_return_if_fail (action != NULL);
588 g_return_if_fail (arg_names != NULL);
589 g_return_if_fail (arg_values != NULL);
590 g_return_if_fail (g_list_length (arg_names) ==
591 g_list_length (arg_values));
593 if (action->msg->status_code == SOUP_STATUS_INTERNAL_SERVER_ERROR) {
594 g_warning ("Calling gupnp_service_action_set_value() after "
595 "having called gupnp_service_action_return_error() "
601 /* Append to response */
602 for (; arg_names; arg_names = arg_names->next) {
603 const char *arg_name;
606 arg_name = arg_names->data;
607 value = arg_values->data;
609 xml_util_start_element (action->response_str, arg_name);
610 gvalue_util_value_append_to_xml_string (value,
611 action->response_str);
612 xml_util_end_element (action->response_str, arg_name);
614 arg_values = arg_values->next;
619 * gupnp_service_action_set_value:
620 * @action: A #GUPnPServiceAction
621 * @argument: The name of the return value to retrieve
622 * @value: The #GValue to store the return value
624 * Sets the value of @argument to @value.
627 gupnp_service_action_set_value (GUPnPServiceAction *action,
628 const char *argument,
631 g_return_if_fail (action != NULL);
632 g_return_if_fail (argument != NULL);
633 g_return_if_fail (value != NULL);
635 if (action->msg->status_code == SOUP_STATUS_INTERNAL_SERVER_ERROR) {
636 g_warning ("Calling gupnp_service_action_set_value() after "
637 "having called gupnp_service_action_return_error() "
643 /* Append to response */
644 xml_util_start_element (action->response_str, argument);
645 gvalue_util_value_append_to_xml_string (value, action->response_str);
646 xml_util_end_element (action->response_str, argument);
650 * gupnp_service_action_return:
651 * @action: A #GUPnPServiceAction
653 * Return succesfully.
656 gupnp_service_action_return (GUPnPServiceAction *action)
658 g_return_if_fail (action != NULL);
660 soup_message_set_status (action->msg, SOUP_STATUS_OK);
662 finalize_action (action);
666 * gupnp_service_action_return_error:
667 * @action: A #GUPnPServiceAction
668 * @error_code: The error code
669 * @error_description: The error description, or %NULL if @error_code is
670 * one of #GUPNP_CONTROL_ERROR_INVALID_ACTION,
671 * #GUPNP_CONTROL_ERROR_INVALID_ARGS, #GUPNP_CONTROL_ERROR_OUT_OF_SYNC or
672 * #GUPNP_CONTROL_ERROR_ACTION_FAILED, in which case a description is
673 * provided automatically.
675 * Return @error_code.
678 gupnp_service_action_return_error (GUPnPServiceAction *action,
680 const char *error_description)
682 g_return_if_fail (action != NULL);
684 switch (error_code) {
685 case GUPNP_CONTROL_ERROR_INVALID_ACTION:
686 if (error_description == NULL)
687 error_description = "Invalid Action";
690 case GUPNP_CONTROL_ERROR_INVALID_ARGS:
691 if (error_description == NULL)
692 error_description = "Invalid Args";
695 case GUPNP_CONTROL_ERROR_OUT_OF_SYNC:
696 if (error_description == NULL)
697 error_description = "Out of Sync";
700 case GUPNP_CONTROL_ERROR_ACTION_FAILED:
701 if (error_description == NULL)
702 error_description = "Action Failed";
706 g_return_if_fail (error_description != NULL);
710 /* Replace response_str with a SOAP Fault */
711 g_string_erase (action->response_str, 0, -1);
713 xml_util_start_element (action->response_str, "s:Fault");
715 xml_util_start_element (action->response_str, "faultcode");
716 g_string_append (action->response_str, "s:Client");
717 xml_util_end_element (action->response_str, "faultcode");
719 xml_util_start_element (action->response_str, "faultstring");
720 g_string_append (action->response_str, "UPnPError");
721 xml_util_end_element (action->response_str, "faultstring");
723 xml_util_start_element (action->response_str, "detail");
725 xml_util_start_element (action->response_str,
727 "xmlns=\"urn:schemas-upnp-org:control-1-0\"");
729 xml_util_start_element (action->response_str, "errorCode");
730 g_string_append_printf (action->response_str, "%u", error_code);
731 xml_util_end_element (action->response_str, "errorCode");
733 xml_util_start_element (action->response_str, "errorDescription");
734 xml_util_add_content (action->response_str, error_description);
735 xml_util_end_element (action->response_str, "errorDescription");
737 xml_util_end_element (action->response_str, "UPnPError");
738 xml_util_end_element (action->response_str, "detail");
740 xml_util_end_element (action->response_str, "s:Fault");
742 soup_message_set_status (action->msg,
743 SOUP_STATUS_INTERNAL_SERVER_ERROR);
745 finalize_action (action);
749 * gupnp_service_action_get_message:
750 * @action: A #GUPnPServiceAction
752 * Get the #SoupMessage associated with @action. Mainly intended for
753 * applications to be able to read HTTP headers received from clients.
755 * Return value: (transfer full): #SoupMessage associated with @action. Unref
759 gupnp_service_action_get_message (GUPnPServiceAction *action)
761 return g_object_ref (action->msg);
765 gupnp_service_init (GUPnPService *service)
767 service->priv = G_TYPE_INSTANCE_GET_PRIVATE (service,
769 GUPnPServicePrivate);
771 service->priv->subscriptions =
772 g_hash_table_new_full (g_str_hash,
775 (GDestroyNotify) subscription_data_free);
777 service->priv->notify_queue = g_queue_new ();
780 /* Generate a new action response node for @action_name */
782 new_action_response_str (const char *action_name,
783 const char *service_type)
787 str = xml_util_new_string ();
789 g_string_append (str, "<u:");
790 g_string_append (str, action_name);
791 g_string_append (str, "Response xmlns:u=");
793 if (service_type != NULL) {
794 g_string_append (str, service_type);
795 g_string_append_c (str, '"');
797 g_warning ("No serviceType defined. Control may not work "
801 g_string_append_c (str, '>');
806 /* Handle QueryStateVariable action */
808 query_state_variable (GUPnPService *service,
809 GUPnPServiceAction *action)
813 /* Iterate requested variables */
814 for (node = action->node->children; node; node = node->next) {
818 if (strcmp ((char *) node->name, "varName") != 0)
822 var_name = xmlNodeGetContent (node);
824 gupnp_service_action_return_error (action,
832 g_signal_emit (service,
833 signals[QUERY_VARIABLE],
834 g_quark_from_string ((char *) var_name),
838 if (!G_IS_VALUE (&value)) {
839 gupnp_service_action_return_error (action,
848 /* Add variable to response */
849 gupnp_service_action_set_value (action,
854 g_value_unset (&value);
859 gupnp_service_action_return (action);
862 /* controlURL handler */
864 control_server_handler (SoupServer *server,
866 const char *server_path,
868 SoupClientContext *soup_client,
871 GUPnPService *service;
872 GUPnPContext *context;
874 xmlNode *action_node;
875 const char *soap_action;
878 GUPnPServiceAction *action;
880 service = GUPNP_SERVICE (user_data);
882 if (msg->method != SOUP_METHOD_POST) {
883 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
888 if (msg->request_body->length == 0) {
889 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
894 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (service));
896 /* Get action name */
897 soap_action = soup_message_headers_get_one (msg->request_headers,
900 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
904 action_name = strchr (soap_action, '#');
906 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
911 /* This memory is libsoup-owned so we can do this */
915 /* This memory is libsoup-owned so we can do this */
916 end = strrchr (action_name, '"');
920 /* Parse action_node */
921 doc = xmlRecoverMemory (msg->request_body->data,
922 msg->request_body->length);
924 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
929 action_node = xml_util_get_element ((xmlNode *) doc,
935 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
940 /* Create action structure */
941 action = g_slice_new (GUPnPServiceAction);
943 action->ref_count = 1;
944 action->name = g_strdup (action_name);
945 action->msg = g_object_ref (msg);
946 action->doc = gupnp_xml_doc_new(doc);
947 action->node = action_node;
948 action->response_str = new_action_response_str (action_name,
950 action->context = g_object_ref (context);
952 /* Tell soup server that response is not ready yet */
953 soup_server_pause_message (server, msg);
955 /* QueryStateVariable? */
956 if (strcmp (action_name, "QueryStateVariable") == 0)
957 query_state_variable (service, action);
959 GQuark action_name_quark;
961 action_name_quark = g_quark_from_string (action_name);
962 if (g_signal_has_handler_pending (service,
963 signals[ACTION_INVOKED],
966 /* Emit signal. Handler parses request and fills in
968 g_signal_emit (service,
969 signals[ACTION_INVOKED],
973 /* No handlers attached. */
974 gupnp_service_action_return_error (action,
981 /* Generates a standard (re)subscription response */
983 subscription_response (GUPnPService *service,
988 GUPnPContext *context;
992 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (service));
993 client = GSSDP_CLIENT (context);
995 /* Server header on response */
996 soup_message_headers_append (msg->response_headers,
998 gssdp_client_get_server_id (client));
1001 soup_message_headers_append (msg->response_headers,
1005 /* Timeout header */
1007 tmp = g_strdup_printf ("Second-%d", timeout);
1009 tmp = g_strdup ("infinite");
1011 soup_message_headers_append (msg->response_headers,
1017 soup_message_set_status (msg, SOUP_STATUS_OK);
1020 /* Generates a new SID */
1028 uuid_unparse (id, out);
1030 return g_strdup_printf ("uuid:%s", out);
1033 /* Subscription expired */
1035 subscription_timeout (gpointer user_data)
1037 SubscriptionData *data;
1041 g_hash_table_remove (data->service->priv->subscriptions, data->sid);
1047 send_initial_state (SubscriptionData *data)
1053 /* Send initial event message */
1054 queue = g_queue_new ();
1056 for (l = data->service->priv->state_variables; l; l = l->next) {
1059 ndata = g_slice_new0 (NotifyData);
1061 g_signal_emit (data->service,
1062 signals[QUERY_VARIABLE],
1063 g_quark_from_string (l->data),
1067 if (!G_IS_VALUE (&ndata->value)) {
1068 g_slice_free (NotifyData, ndata);
1073 ndata->variable = g_strdup (l->data);
1075 g_queue_push_tail (queue, ndata);
1078 mem = create_property_set (queue);
1079 notify_subscriber (data->sid, data, mem);
1082 g_queue_free (queue);
1088 /* Subscription request */
1090 subscribe (GUPnPService *service,
1092 const char *callback)
1094 SubscriptionData *data;
1095 char *start, *end, *uri;
1096 GUPnPContext *context;
1097 GMainContext *main_context;
1099 data = g_slice_new0 (SubscriptionData);
1101 /* Parse callback list */
1102 start = (char *) callback;
1103 while ((start = strchr (start, '<'))) {
1105 if (!start || !*start)
1108 end = strchr (start, '>');
1112 if (strncmp (start, "http://", strlen ("http://")) == 0) {
1113 uri = g_strndup (start, end - start);
1114 data->callbacks = g_list_append (data->callbacks, uri);
1120 if (!data->callbacks) {
1121 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1123 g_slice_free (SubscriptionData, data);
1128 /* Add service and SID */
1129 data->service = service;
1130 data->sid = generate_sid ();
1133 data->timeout_src = g_timeout_source_new_seconds (SUBSCRIPTION_TIMEOUT);
1134 g_source_set_callback (data->timeout_src,
1135 subscription_timeout,
1139 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (service));
1140 main_context = gssdp_client_get_main_context (GSSDP_CLIENT (context));
1141 g_source_attach (data->timeout_src, main_context);
1143 g_source_unref (data->timeout_src);
1146 g_hash_table_insert (service->priv->subscriptions,
1151 subscription_response (service, msg, data->sid, SUBSCRIPTION_TIMEOUT);
1153 send_initial_state (data);
1156 /* Resubscription request */
1158 resubscribe (GUPnPService *service,
1162 SubscriptionData *data;
1163 GUPnPContext *context;
1164 GMainContext *main_context;
1166 data = g_hash_table_lookup (service->priv->subscriptions, sid);
1168 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1173 /* Update timeout */
1174 if (data->timeout_src) {
1175 g_source_destroy (data->timeout_src);
1176 data->timeout_src = NULL;
1179 data->timeout_src = g_timeout_source_new_seconds (SUBSCRIPTION_TIMEOUT);
1180 g_source_set_callback (data->timeout_src,
1181 subscription_timeout,
1185 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (service));
1186 main_context = gssdp_client_get_main_context (GSSDP_CLIENT (context));
1187 g_source_attach (data->timeout_src, main_context);
1189 g_source_unref (data->timeout_src);
1192 subscription_response (service, msg, sid, SUBSCRIPTION_TIMEOUT);
1195 /* Unsubscription request */
1197 unsubscribe (GUPnPService *service,
1201 if (g_hash_table_remove (service->priv->subscriptions, sid))
1202 soup_message_set_status (msg, SOUP_STATUS_OK);
1204 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1207 /* eventSubscriptionURL handler */
1209 subscription_server_handler (SoupServer *server,
1211 const char *server_path,
1213 SoupClientContext *soup_client,
1216 GUPnPService *service;
1217 const char *callback, *nt, *sid;
1219 service = GUPNP_SERVICE (user_data);
1221 callback = soup_message_headers_get_one (msg->request_headers,
1223 nt = soup_message_headers_get_one (msg->request_headers, "NT");
1224 sid = soup_message_headers_get_one (msg->request_headers, "SID");
1226 /* Choose appropriate handler */
1227 if (strcmp (msg->method, GENA_METHOD_SUBSCRIBE) == 0) {
1230 soup_message_set_status
1231 (msg, SOUP_STATUS_BAD_REQUEST);
1233 } else if (!nt || strcmp (nt, "upnp:event") != 0) {
1234 soup_message_set_status
1235 (msg, SOUP_STATUS_PRECONDITION_FAILED);
1238 subscribe (service, msg, callback);
1244 soup_message_set_status
1245 (msg, SOUP_STATUS_BAD_REQUEST);
1248 resubscribe (service, msg, sid);
1253 soup_message_set_status
1254 (msg, SOUP_STATUS_BAD_REQUEST);
1258 } else if (strcmp (msg->method, GENA_METHOD_UNSUBSCRIBE) == 0) {
1260 if (nt || callback) {
1261 soup_message_set_status
1262 (msg, SOUP_STATUS_BAD_REQUEST);
1265 unsubscribe (service, msg, sid);
1270 soup_message_set_status
1271 (msg, SOUP_STATUS_PRECONDITION_FAILED);
1276 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
1282 got_introspection (GUPnPServiceInfo *info,
1283 GUPnPServiceIntrospection *introspection,
1284 const GError *error,
1287 GUPnPService *service;
1288 const GList *state_variables, *l;
1289 GHashTableIter iter;
1292 service = GUPNP_SERVICE (user_data);
1294 if (introspection) {
1296 gupnp_service_introspection_list_state_variables
1299 for (l = state_variables; l; l = l->next) {
1300 GUPnPServiceStateVariableInfo *variable;
1304 if (!variable->send_events)
1307 service->priv->state_variables =
1308 g_list_prepend (service->priv->state_variables,
1309 g_strdup (variable->name));
1312 g_object_unref (introspection);
1314 g_warning ("Failed to get SCPD: %s\n"
1315 "The initial event message will not be sent.",
1316 error ? error->message : "No error");
1318 g_hash_table_iter_init (&iter, service->priv->subscriptions);
1320 while (g_hash_table_iter_next (&iter, NULL, &data))
1321 send_initial_state ((SubscriptionData *) data);
1323 g_object_unref (service);
1327 path_from_url (const char *url)
1332 uri = soup_uri_new (url);
1333 path = soup_uri_to_string (uri, TRUE);
1334 soup_uri_free (uri);
1340 gupnp_service_constructor (GType type,
1341 guint n_construct_params,
1342 GObjectConstructParam *construct_params)
1344 GObjectClass *object_class;
1346 GUPnPServiceInfo *info;
1347 GUPnPContext *context;
1352 object_class = G_OBJECT_CLASS (gupnp_service_parent_class);
1355 object = object_class->constructor (type,
1359 info = GUPNP_SERVICE_INFO (object);
1361 /* Get introspection and save state variable names */
1362 gupnp_service_info_get_introspection_async (info,
1365 g_object_ref (object);
1368 context = gupnp_service_info_get_context (info);
1369 server = gupnp_context_get_server (context);
1371 /* Run listener on controlURL */
1372 url = gupnp_service_info_get_control_url (info);
1373 path = path_from_url (url);
1374 soup_server_add_handler (server, path,
1375 control_server_handler, object, NULL);
1379 /* Run listener on eventSubscriptionURL */
1380 url = gupnp_service_info_get_event_subscription_url (info);
1381 path = path_from_url (url);
1382 soup_server_add_handler (server, path,
1383 subscription_server_handler, object, NULL);
1390 /* Root device availability changed. */
1392 notify_available_cb (GObject *object,
1396 GUPnPService *service;
1398 service = GUPNP_SERVICE (user_data);
1400 if (!gupnp_root_device_get_available (GUPNP_ROOT_DEVICE (object))) {
1401 /* Root device now unavailable: Purge subscriptions */
1402 g_hash_table_remove_all (service->priv->subscriptions);
1407 gupnp_service_set_property (GObject *object,
1409 const GValue *value,
1412 GUPnPService *service;
1414 service = GUPNP_SERVICE (object);
1416 switch (property_id) {
1417 case PROP_ROOT_DEVICE: {
1418 GUPnPRootDevice **dev;
1420 service->priv->root_device = g_value_get_object (value);
1421 dev = &(service->priv->root_device);
1423 g_object_add_weak_pointer
1424 (G_OBJECT (service->priv->root_device),
1427 service->priv->notify_available_id =
1428 g_signal_connect_object (service->priv->root_device,
1429 "notify::available",
1431 (notify_available_cb),
1438 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1444 gupnp_service_get_property (GObject *object,
1449 GUPnPService *service;
1451 service = GUPNP_SERVICE (object);
1453 switch (property_id) {
1454 case PROP_ROOT_DEVICE:
1455 g_value_set_object (value, service->priv->root_device);
1458 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
1464 gupnp_service_dispose (GObject *object)
1466 GUPnPService *service;
1467 GObjectClass *object_class;
1468 GUPnPServiceInfo *info;
1469 GUPnPContext *context;
1474 service = GUPNP_SERVICE (object);
1477 info = GUPNP_SERVICE_INFO (service);
1478 context = gupnp_service_info_get_context (info);
1479 server = gupnp_context_get_server (context);
1481 /* Remove listener on controlURL */
1482 url = gupnp_service_info_get_control_url (info);
1483 path = path_from_url (url);
1484 soup_server_remove_handler (server, path);
1488 /* Remove listener on eventSubscriptionURL */
1489 url = gupnp_service_info_get_event_subscription_url (info);
1490 path = path_from_url (url);
1491 soup_server_remove_handler (server, path);
1495 if (service->priv->root_device) {
1496 GUPnPRootDevice **dev = &(service->priv->root_device);
1498 if (g_signal_handler_is_connected
1499 (service->priv->root_device,
1500 service->priv->notify_available_id)) {
1501 g_signal_handler_disconnect
1502 (service->priv->root_device,
1503 service->priv->notify_available_id);
1506 g_object_remove_weak_pointer
1507 (G_OBJECT (service->priv->root_device),
1510 service->priv->root_device = NULL;
1513 /* Cancel pending messages */
1514 g_hash_table_remove_all (service->priv->subscriptions);
1517 object_class = G_OBJECT_CLASS (gupnp_service_parent_class);
1518 object_class->dispose (object);
1522 gupnp_service_finalize (GObject *object)
1524 GUPnPService *service;
1525 GObjectClass *object_class;
1528 service = GUPNP_SERVICE (object);
1530 /* Free subscription hash */
1531 g_hash_table_destroy (service->priv->subscriptions);
1533 /* Free state variable list */
1534 while (service->priv->state_variables) {
1535 g_free (service->priv->state_variables->data);
1536 service->priv->state_variables =
1537 g_list_delete_link (service->priv->state_variables,
1538 service->priv->state_variables);
1541 /* Free notify queue */
1542 while ((data = g_queue_pop_head (service->priv->notify_queue)))
1543 notify_data_free (data);
1545 g_queue_free (service->priv->notify_queue);
1547 if (service->priv->session) {
1548 g_object_unref (service->priv->session);
1549 service->priv->session = NULL;
1553 object_class = G_OBJECT_CLASS (gupnp_service_parent_class);
1554 object_class->finalize (object);
1558 gupnp_service_class_init (GUPnPServiceClass *klass)
1560 GObjectClass *object_class;
1562 object_class = G_OBJECT_CLASS (klass);
1564 object_class->set_property = gupnp_service_set_property;
1565 object_class->get_property = gupnp_service_get_property;
1566 object_class->constructor = gupnp_service_constructor;
1567 object_class->dispose = gupnp_service_dispose;
1568 object_class->finalize = gupnp_service_finalize;
1570 g_type_class_add_private (klass, sizeof (GUPnPServicePrivate));
1573 * GUPnPService:root-device:
1575 * The containing #GUPnPRootDevice.
1577 g_object_class_install_property
1580 g_param_spec_object ("root-device",
1582 "The GUPnPRootDevice",
1583 GUPNP_TYPE_ROOT_DEVICE,
1585 G_PARAM_CONSTRUCT_ONLY |
1586 G_PARAM_STATIC_NAME |
1587 G_PARAM_STATIC_NICK |
1588 G_PARAM_STATIC_BLURB));
1591 * GUPnPService::action-invoked:
1592 * @service: The #GUPnPService that received the signal
1593 * @action: The invoked #GUPnPAction
1595 * Emitted whenever an action is invoked. Handler should process
1596 * @action and must call either gupnp_service_action_return() or
1597 * gupnp_service_action_return_error().
1599 signals[ACTION_INVOKED] =
1600 g_signal_new ("action-invoked",
1602 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
1603 G_STRUCT_OFFSET (GUPnPServiceClass,
1607 g_cclosure_marshal_VOID__BOXED,
1610 GUPNP_TYPE_SERVICE_ACTION);
1613 * GUPnPService::query-variable:
1614 * @service: The #GUPnPService that received the signal
1615 * @variable: The variable that is being queried
1616 * @value: The location of the #GValue of the variable
1618 * Emitted whenever @service needs to know the value of @variable.
1619 * Handler should fill @value with the value of @variable.
1621 signals[QUERY_VARIABLE] =
1622 g_signal_new ("query-variable",
1624 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
1625 G_STRUCT_OFFSET (GUPnPServiceClass,
1629 gupnp_marshal_VOID__STRING_POINTER,
1633 G_TYPE_POINTER /* Not G_TYPE_VALUE as this
1634 is an outward argument! */);
1637 * GUPnPService::notify-failed:
1638 * @service: The #GUPnPService that received the signal
1639 * @callback_url: The callback URL
1640 * @reason: A pointer to a #GError describing why the notify failed
1642 * Emitted whenever notification of a client fails.
1644 signals[NOTIFY_FAILED] =
1645 g_signal_new ("notify-failed",
1648 G_STRUCT_OFFSET (GUPnPServiceClass,
1652 gupnp_marshal_VOID__POINTER_POINTER,
1660 * gupnp_service_notify:
1661 * @service: A #GUPnPService
1662 * @Varargs: Tuples of variable name, variable type, and variable value,
1663 * terminated with %NULL.
1665 * Notifies listening clients that the properties listed in @Varargs
1666 * have changed to the specified values.
1669 gupnp_service_notify (GUPnPService *service,
1674 g_return_if_fail (GUPNP_IS_SERVICE (service));
1676 va_start (var_args, service);
1677 gupnp_service_notify_valist (service, var_args);
1682 * gupnp_service_notify_valist:
1683 * @service: A #GUPnPService
1684 * @var_args: A va_list of tuples of variable name, variable type, and variable
1685 * value, terminated with %NULL.
1687 * See gupnp_service_notify(); this version takes a va_list for
1688 * use by language bindings.
1691 gupnp_service_notify_valist (GUPnPService *service,
1694 const char *var_name;
1696 GValue value = {0, };
1697 char *collect_error;
1699 g_return_if_fail (GUPNP_IS_SERVICE (service));
1701 collect_error = NULL;
1703 var_name = va_arg (var_args, const char *);
1705 var_type = va_arg (var_args, GType);
1706 g_value_init (&value, var_type);
1708 G_VALUE_COLLECT (&value, var_args, G_VALUE_NOCOPY_CONTENTS,
1710 if (!collect_error) {
1711 gupnp_service_notify_value (service, var_name, &value);
1713 g_value_unset (&value);
1716 g_warning ("Error collecting value: %s\n",
1719 g_free (collect_error);
1722 var_name = va_arg (var_args, const char *);
1726 /* Received notify response. */
1728 notify_got_response (SoupSession *session,
1732 SubscriptionData *data;
1735 if (msg->status_code == SOUP_STATUS_CANCELLED)
1740 /* Remove from pending messages list */
1741 data->pending_messages = g_list_remove (data->pending_messages, msg);
1743 if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
1744 /* Success: reset callbacks pointer */
1745 data->callbacks = g_list_first (data->callbacks);
1747 } else if (msg->status_code == SOUP_STATUS_PRECONDITION_FAILED) {
1748 /* Precondition failed: Cancel subscription */
1749 g_hash_table_remove (data->service->priv->subscriptions,
1753 /* Other failure: Try next callback or signal failure. */
1754 if (data->callbacks->next) {
1756 SoupSession *service_session;
1758 /* Call next callback */
1759 data->callbacks = data->callbacks->next;
1761 uri = soup_uri_new (data->callbacks->data);
1762 soup_message_set_uri (msg, uri);
1763 soup_uri_free (uri);
1766 data->pending_messages =
1767 g_list_prepend (data->pending_messages, msg);
1769 service_session = gupnp_service_get_session (data->service);
1771 soup_session_requeue_message (service_session, msg);
1773 /* Emit 'notify-failed' signal */
1776 error = g_error_new_literal
1777 (GUPNP_EVENTING_ERROR,
1778 GUPNP_EVENTING_ERROR_NOTIFY_FAILED,
1779 msg->reason_phrase);
1781 g_signal_emit (data->service,
1782 signals[NOTIFY_FAILED],
1787 g_error_free (error);
1789 /* Reset callbacks pointer */
1790 data->callbacks = g_list_first (data->callbacks);
1795 /* Send notification @user_data to subscriber @value */
1797 notify_subscriber (gpointer key,
1801 SubscriptionData *data;
1802 const char *property_set;
1805 SoupSession *session;
1808 property_set = user_data;
1810 /* Create message */
1811 msg = soup_message_new (GENA_METHOD_NOTIFY, data->callbacks->data);
1813 g_warning ("Invalid callback URL: %s",
1814 (char *) data->callbacks->data);
1819 soup_message_headers_append (msg->request_headers,
1823 soup_message_headers_append (msg->request_headers,
1827 soup_message_headers_append (msg->request_headers,
1831 tmp = g_strdup_printf ("%d", data->seq);
1832 soup_message_headers_append (msg->request_headers,
1837 /* Handle overflow */
1838 if (data->seq < G_MAXINT32)
1844 soup_message_set_request (msg,
1845 "text/xml; charset=utf-8",
1847 g_strdup (property_set),
1848 strlen (property_set));
1851 data->pending_messages = g_list_prepend (data->pending_messages, msg);
1853 session = gupnp_service_get_session (data->service);
1855 soup_session_queue_message (session,
1857 notify_got_response,
1861 /* Create a property set from @queue */
1863 create_property_set (GQueue *queue)
1868 /* Compose property set */
1869 str = xml_util_new_string ();
1871 g_string_append (str,
1872 "<?xml version=\"1.0\"?>"
1873 "<e:propertyset xmlns:e="
1874 "\"urn:schemas-upnp-org:event-1-0\">");
1877 while ((data = g_queue_pop_head (queue))) {
1878 xml_util_start_element (str, "e:property");
1879 xml_util_start_element (str, data->variable);
1880 gvalue_util_value_append_to_xml_string (&data->value, str);
1881 xml_util_end_element (str, data->variable);
1882 xml_util_end_element (str, "e:property");
1885 notify_data_free (data);
1888 g_string_append (str, "</e:propertyset>");
1890 /* Cleanup & return */
1891 return g_string_free (str, FALSE);
1894 /* Flush all queued notifications */
1896 flush_notifications (GUPnPService *service)
1900 /* Create property set */
1901 mem = create_property_set (service->priv->notify_queue);
1903 /* And send it off */
1904 g_hash_table_foreach (service->priv->subscriptions,
1913 * gupnp_service_notify_value:
1914 * @service: A #GUPnPService
1915 * @variable: The name of the variable to notify
1916 * @value: The value of the variable
1918 * Notifies listening clients that @variable has changed to @value.
1921 gupnp_service_notify_value (GUPnPService *service,
1922 const char *variable,
1923 const GValue *value)
1927 g_return_if_fail (GUPNP_IS_SERVICE (service));
1928 g_return_if_fail (variable != NULL);
1929 g_return_if_fail (G_IS_VALUE (value));
1932 data = g_slice_new0 (NotifyData);
1934 data->variable = g_strdup (variable);
1936 g_value_init (&data->value, G_VALUE_TYPE (value));
1937 g_value_copy (value, &data->value);
1939 g_queue_push_tail (service->priv->notify_queue, data);
1941 /* And flush, if not frozen */
1942 if (!service->priv->notify_frozen)
1943 flush_notifications (service);
1947 * gupnp_service_freeze_notify:
1948 * @service: A #GUPnPService
1950 * Causes new notifications to be queued up until gupnp_service_thaw_notify()
1954 gupnp_service_freeze_notify (GUPnPService *service)
1956 g_return_if_fail (GUPNP_IS_SERVICE (service));
1958 service->priv->notify_frozen = TRUE;
1962 * gupnp_service_thaw_notify:
1963 * @service: A #GUPnPService
1965 * Sends out any pending notifications, and stops queuing of new ones.
1968 gupnp_service_thaw_notify (GUPnPService *service)
1970 g_return_if_fail (GUPNP_IS_SERVICE (service));
1972 service->priv->notify_frozen = FALSE;
1974 if (g_queue_get_length (service->priv->notify_queue) == 0)
1975 return; /* Empty notify queue */
1977 flush_notifications (service);
1980 /* Convert a CamelCase string to a lowercase string with underscores */
1982 strip_camel_case (char *camel_str)
1987 /* Keep enough space for underscores */
1988 stripped = g_malloc (strlen (camel_str) * 2);
1990 for (i = 0, j = 0; i <= strlen (camel_str); i++) {
1991 /* Convert every upper case letter to lower case and unless
1992 * it's the first character, the last charachter, in the
1993 * middle of an abbreviation or there is already an underscore
1994 * before it, add an underscore before it */
1995 if (g_ascii_isupper (camel_str[i])) {
1997 camel_str[i + 1] != '\0' &&
1998 camel_str[i - 1] != '_' &&
1999 !g_ascii_isupper (camel_str[i - 1])) {
2000 stripped[j++] = '_';
2002 stripped[j++] = g_ascii_tolower (camel_str[i]);
2004 stripped[j++] = camel_str[i];
2011 find_callback_by_name (GModule *module,
2017 /* First try with 'on_' prefix */
2018 full_name = g_strjoin ("_",
2023 if (!g_module_symbol (module,
2025 (gpointer) &callback)) {
2028 /* Now try with '_cb' postfix */
2029 full_name = g_strjoin ("_",
2034 g_module_symbol (module,
2036 (gpointer) &callback);
2044 /* Use the strings from @name_list as details to @signal_name, and connect
2045 * callbacks with names based on these same strings to @signal_name::string. */
2047 connect_names_to_signal_handlers (GUPnPService *service,
2049 const GList *name_list,
2050 const char *signal_name,
2051 const char *callback_prefix,
2054 const GList *name_node;
2056 for (name_node = name_list;
2058 name_node = name_node->next) {
2060 char *callback_name;
2061 char *signal_detail;
2063 signal_detail = (char *) name_node->data;
2064 callback_name = strip_camel_case (signal_detail);
2066 if (callback_prefix) {
2069 tmp = g_strjoin ("_",
2074 g_free (callback_name);
2075 callback_name = tmp;
2078 callback = find_callback_by_name (module, callback_name);
2079 g_free (callback_name);
2081 if (callback == NULL)
2084 signal_detail = g_strjoin ("::",
2089 g_signal_connect (service,
2094 g_free (signal_detail);
2099 * gupnp_service_signals_autoconnect:
2100 * @service: A #GUPnPService
2101 * @user_data: the data to pass to each of the callbacks
2102 * @error: return location for a #GError, or %NULL
2104 * A convenience function that attempts to connect all possible
2105 * #GUPnPService::action-invoked and #GUPnPService::query-variable signals to
2106 * appropriate callbacks for the service @service. It uses service introspection
2107 * and GModule's introspective features. It is very simillar to
2108 * glade_xml_signal_autoconnect() except that it attempts to guess the names of
2109 * the signal handlers on its own.
2111 * For this function to do its magic, the application must name the callback
2112 * functions for #GUPnPService::action-invoked signals by striping the CamelCase
2113 * off the action names and either prepend "on_" or append "_cb" to them. Same
2114 * goes for #GUPnPService::query-variable signals, except that "query_" should
2115 * be prepended to the variable name. For example, callback function for
2116 * "GetSystemUpdateID" action should be either named as
2117 * "get_system_update_id_cb" or "on_get_system_update_id" and callback function
2118 * for the query of "SystemUpdateID" state variable should be named
2119 * "query_system_update_id_cb" or "on_query_system_update_id".
2121 * Note that this function will not work correctly if GModule is not supported
2122 * on the platform or introspection is not available for service @service.
2124 * WARNING: This function can not and therefore does not guarantee that the
2125 * resulting signal connections will be correct as it depends heavily on a
2126 * particular naming schemes described above.
2129 gupnp_service_signals_autoconnect (GUPnPService *service,
2133 GUPnPServiceIntrospection *introspection;
2137 g_return_if_fail (GUPNP_IS_SERVICE (service));
2139 introspection = gupnp_service_info_get_introspection
2140 (GUPNP_SERVICE_INFO (service),
2145 /* Get a handle on the main executable -- use this to find symbols */
2146 module = g_module_open (NULL, 0);
2147 if (module == NULL) {
2148 g_error ("Failed to open module: %s", g_module_error ());
2150 g_object_unref (introspection);
2155 names = gupnp_service_introspection_list_action_names (introspection);
2156 connect_names_to_signal_handlers (service,
2163 names = gupnp_service_introspection_list_state_variable_names
2165 connect_names_to_signal_handlers (service,
2172 g_module_close (module);
2173 g_object_unref (introspection);