2 * Copyright (C) 2006, 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-proxy
24 * @short_description: Proxy class for remote services.
26 * #GUPnPServiceProxy sends commands to a remote UPnP service and handles
27 * incoming event notifications. #GUPnPServiceProxy implements the
28 * #GUPnPServiceInfo interface.
31 #include <libsoup/soup.h>
32 #include <gobject/gvaluecollector.h>
37 #include "gupnp-service-proxy.h"
38 #include "gupnp-context-private.h"
39 #include "gupnp-error.h"
40 #include "gupnp-error-private.h"
41 #include "gupnp-types.h"
43 #include "gena-protocol.h"
44 #include "http-headers.h"
45 #include "gvalue-util.h"
47 G_DEFINE_TYPE (GUPnPServiceProxy,
49 GUPNP_TYPE_SERVICE_INFO);
51 struct _GUPnPServiceProxyPrivate {
54 GList *pending_actions;
56 char *path; /* Path to this proxy */
58 char *sid; /* Subscription ID */
59 GSource *subscription_timeout_src;
61 guint32 seq; /* Event sequence number */
63 GHashTable *notify_hash;
65 GList *pending_messages; /* Pending SoupMessages from this proxy */
67 GList *pending_notifies; /* Pending notifications to be sent (xmlDoc) */
68 GSource *notify_idle_src; /* Idle handler src of notification emiter */
81 static guint signals[LAST_SIGNAL];
83 struct _GUPnPServiceProxyAction {
84 GUPnPServiceProxy *proxy;
89 GUPnPServiceProxyActionCallback callback;
92 GError *error; /* If non-NULL, description of error that
93 occurred when preparing message */
103 GUPnPServiceProxyNotifyCallback callback;
115 subscribe_got_response (SoupSession *session,
117 GUPnPServiceProxy *proxy);
119 subscribe (GUPnPServiceProxy *proxy);
121 unsubscribe (GUPnPServiceProxy *proxy);
124 gupnp_service_proxy_action_free (GUPnPServiceProxyAction *action)
126 action->proxy->priv->pending_actions =
127 g_list_remove (action->proxy->priv->pending_actions, action);
129 if (action->msg != NULL)
130 g_object_unref (action->msg);
132 g_slice_free (GUPnPServiceProxyAction, action);
136 notify_data_free (NotifyData *data)
138 g_list_free (data->callbacks);
140 g_slice_free (NotifyData, data);
143 /* Steals doc reference */
144 static EmitNotifyData *
145 emit_notify_data_new (const char *sid,
149 EmitNotifyData *data;
151 data = g_slice_new (EmitNotifyData);
153 data->sid = g_strdup (sid);
161 emit_notify_data_free (EmitNotifyData *data)
164 xmlFreeDoc (data->doc);
166 g_slice_free (EmitNotifyData, data);
170 gupnp_service_proxy_init (GUPnPServiceProxy *proxy)
172 static int proxy_counter = 0;
174 proxy->priv = G_TYPE_INSTANCE_GET_PRIVATE (proxy,
175 GUPNP_TYPE_SERVICE_PROXY,
176 GUPnPServiceProxyPrivate);
178 /* Generate unique path */
179 proxy->priv->path = g_strdup_printf ("/ServiceProxy%d", proxy_counter);
182 /* Set up notify hash */
183 proxy->priv->notify_hash =
184 g_hash_table_new_full (g_str_hash,
187 (GDestroyNotify) notify_data_free);
191 gupnp_service_proxy_set_property (GObject *object,
196 GUPnPServiceProxy *proxy;
198 proxy = GUPNP_SERVICE_PROXY (object);
200 switch (property_id) {
201 case PROP_SUBSCRIBED:
202 gupnp_service_proxy_set_subscribed
203 (proxy, g_value_get_boolean (value));
206 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
212 gupnp_service_proxy_get_property (GObject *object,
217 GUPnPServiceProxy *proxy;
219 proxy = GUPNP_SERVICE_PROXY (object);
221 switch (property_id) {
222 case PROP_SUBSCRIBED:
223 g_value_set_boolean (value,
224 proxy->priv->subscribed);
227 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
233 gupnp_service_proxy_dispose (GObject *object)
235 GUPnPServiceProxy *proxy;
236 GObjectClass *object_class;
237 GUPnPContext *context;
238 SoupSession *session;
240 proxy = GUPNP_SERVICE_PROXY (object);
243 if (proxy->priv->subscribed) {
246 proxy->priv->subscribed = FALSE;
249 /* Cancel pending actions */
250 while (proxy->priv->pending_actions) {
251 GUPnPServiceProxyAction *action;
253 action = proxy->priv->pending_actions->data;
255 gupnp_service_proxy_cancel_action (proxy, action);
258 /* Cancel pending messages */
259 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
261 session = gupnp_context_get_session (context);
263 session = NULL; /* Not the first time dispose is called. */
265 while (proxy->priv->pending_messages) {
268 msg = proxy->priv->pending_messages->data;
270 soup_session_cancel_message (session,
272 SOUP_STATUS_CANCELLED);
274 proxy->priv->pending_messages =
275 g_list_delete_link (proxy->priv->pending_messages,
276 proxy->priv->pending_messages);
279 /* Cancel pending notifications */
280 if (proxy->priv->notify_idle_src) {
281 g_source_destroy (proxy->priv->notify_idle_src);
282 proxy->priv->notify_idle_src = NULL;
285 while (proxy->priv->pending_notifies) {
286 emit_notify_data_free (proxy->priv->pending_notifies->data);
287 proxy->priv->pending_notifies =
288 g_list_delete_link (proxy->priv->pending_notifies,
289 proxy->priv->pending_notifies);
293 object_class = G_OBJECT_CLASS (gupnp_service_proxy_parent_class);
294 object_class->dispose (object);
298 gupnp_service_proxy_finalize (GObject *object)
300 GUPnPServiceProxy *proxy;
301 GObjectClass *object_class;
303 proxy = GUPNP_SERVICE_PROXY (object);
305 g_free (proxy->priv->path);
307 g_hash_table_destroy (proxy->priv->notify_hash);
310 object_class = G_OBJECT_CLASS (gupnp_service_proxy_parent_class);
311 object_class->finalize (object);
315 gupnp_service_proxy_class_init (GUPnPServiceProxyClass *klass)
317 GObjectClass *object_class;
319 object_class = G_OBJECT_CLASS (klass);
321 object_class->set_property = gupnp_service_proxy_set_property;
322 object_class->get_property = gupnp_service_proxy_get_property;
323 object_class->dispose = gupnp_service_proxy_dispose;
324 object_class->finalize = gupnp_service_proxy_finalize;
326 g_type_class_add_private (klass, sizeof (GUPnPServiceProxyPrivate));
329 * GUPnPServiceProxy:subscribed:
331 * Whether we are subscribed to this service.
333 g_object_class_install_property
336 g_param_spec_boolean ("subscribed",
338 "Whether we are subscribed to this "
342 G_PARAM_STATIC_NAME |
343 G_PARAM_STATIC_NICK |
344 G_PARAM_STATIC_BLURB));
347 * GUPnPServiceProxy::subscription-lost:
348 * @proxy: The #GUPnPServiceProxy that received the signal
349 * @error: (type GError):A pointer to a #GError describing why the subscription has
352 * Emitted whenever the subscription to this service has been lost due
353 * to an error condition.
355 signals[SUBSCRIPTION_LOST] =
356 g_signal_new ("subscription-lost",
357 GUPNP_TYPE_SERVICE_PROXY,
359 G_STRUCT_OFFSET (GUPnPServiceProxyClass,
363 g_cclosure_marshal_VOID__POINTER,
370 * gupnp_service_proxy_send_action:
371 * @proxy: A #GUPnPServiceProxy
373 * @error: The location where to store any error, or %NULL
374 * @...: tuples of in parameter name, in parameter type, and in parameter
375 * value, followed by %NULL, and then tuples of out parameter name,
376 * out parameter type, and out parameter value location, terminated with %NULL
378 * Sends action @action with parameters @Varargs to the service exposed by
379 * @proxy synchronously. If an error occurred, @error will be set. In case of
380 * a UPnPError the error code will be the same in @error.
382 * Return value: %TRUE if sending the action was succesful.
385 gupnp_service_proxy_send_action (GUPnPServiceProxy *proxy,
393 va_start (var_args, error);
394 ret = gupnp_service_proxy_send_action_valist (proxy,
404 stop_main_loop (G_GNUC_UNUSED GUPnPServiceProxy *proxy,
405 G_GNUC_UNUSED GUPnPServiceProxyAction *action,
408 g_main_loop_quit ((GMainLoop *) user_data);
411 /* This is a skip variant of G_VALUE_LCOPY, same as there is
412 * G_VALUE_COLLECT_SKIP for G_VALUE_COLLECT.
414 #define VALUE_LCOPY_SKIP(value_type, var_args) \
416 GTypeValueTable *_vtable = g_type_value_table_peek (value_type); \
417 const gchar *_lcopy_format = _vtable->lcopy_format; \
419 while (*_lcopy_format) { \
420 switch (*_lcopy_format++) { \
421 case G_VALUE_COLLECT_INT: \
422 va_arg ((var_args), gint); \
424 case G_VALUE_COLLECT_LONG: \
425 va_arg ((var_args), glong); \
427 case G_VALUE_COLLECT_INT64: \
428 va_arg ((var_args), gint64); \
430 case G_VALUE_COLLECT_DOUBLE: \
431 va_arg ((var_args), gdouble); \
433 case G_VALUE_COLLECT_POINTER: \
434 va_arg ((var_args), gpointer); \
437 g_assert_not_reached (); \
442 /* Initializes hash table to hold arg names as keys and GValues of
443 * given type, but without any specific value. Note that if you are
444 * going to use OUT_HASH_TABLE_TO_VAR_ARGS then you have to store a
445 * copy of var_args with G_VA_COPY before using this macro.
447 #define VAR_ARGS_TO_OUT_HASH_TABLE(var_args, hash) \
449 const gchar *arg_name = va_arg (var_args, const gchar *); \
451 while (arg_name != NULL) { \
452 GValue *value = g_new0 (GValue, 1); \
453 GType type = va_arg (var_args, GType); \
455 VALUE_LCOPY_SKIP (type, var_args); \
456 g_value_init (value, type); \
457 g_hash_table_insert (hash, g_strdup (arg_name), value); \
458 arg_name = va_arg (var_args, const gchar *); \
462 /* Initializes hash table to hold arg names as keys and GValues of
463 * given type and value.
465 #define VAR_ARGS_TO_IN_LIST(var_args, names, values) \
467 const gchar *arg_name = va_arg (var_args, const gchar *); \
469 while (arg_name != NULL) { \
470 GValue *value = g_new0 (GValue, 1); \
471 gchar *error = NULL; \
472 GType type = va_arg (var_args, GType); \
474 G_VALUE_COLLECT_INIT (value, \
477 G_VALUE_NOCOPY_CONTENTS, \
479 if (error == NULL) { \
480 names = g_list_prepend (names, g_strdup (arg_name)); \
481 values = g_list_prepend (values, value); \
483 g_warning ("Failed to collect value of type %s for %s: %s", \
484 g_type_name (type), \
489 arg_name = va_arg (var_args, const gchar *); \
491 names = g_list_reverse (names); \
492 values = g_list_reverse (values); \
495 /* Puts values stored in hash table with GValues into var args.
497 #define OUT_HASH_TABLE_TO_VAR_ARGS(hash, var_args) \
499 const gchar *arg_name = va_arg (var_args, const gchar *); \
501 while (arg_name != NULL) { \
502 GValue *value = g_hash_table_lookup (hash, arg_name); \
503 GType type = va_arg (var_args, GType); \
505 if (value == NULL) { \
506 g_warning ("No value for %s", arg_name); \
507 G_VALUE_COLLECT_SKIP (type, var_args); \
508 } else if (G_VALUE_TYPE (value) != type) { \
509 g_warning ("Different GType in value (%s) and in var args (%s) for %s.", \
510 G_VALUE_TYPE_NAME (value), \
511 g_type_name (type), \
514 gchar *error = NULL; \
516 G_VALUE_LCOPY (value, var_args, 0, &error); \
517 if (error != NULL) { \
518 g_warning ("Failed to lcopy the value of type %s for %s: %s", \
519 g_type_name (type), \
525 arg_name = va_arg (var_args, const gchar *); \
529 /* GDestroyNotify for GHashTable holding GValues.
532 value_free (gpointer data)
534 GValue *value = (GValue *) data;
536 g_value_unset (value);
541 * gupnp_service_proxy_send_action_valist:
542 * @proxy: A #GUPnPServiceProxy
544 * @error: The location where to store any error, or %NULL
545 * @var_args: va_list of tuples of in parameter name, in parameter type, and in
546 * parameter value, followed by %NULL, and then tuples of out parameter name,
547 * out parameter type, and out parameter value location
549 * See gupnp_service_proxy_send_action().
551 * Return value: %TRUE if sending the action was succesful.
554 gupnp_service_proxy_send_action_valist (GUPnPServiceProxy *proxy,
559 GList *in_names = NULL, *in_values = NULL;
560 GHashTable *out_hash = NULL;
561 va_list var_args_copy;
564 GMainLoop *main_loop;
565 GUPnPServiceProxyAction *handle;
567 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
568 g_return_val_if_fail (action, FALSE);
571 main_loop = g_main_loop_new (g_main_context_get_thread_default (),
574 VAR_ARGS_TO_IN_LIST (var_args, in_names, in_values);
575 G_VA_COPY (var_args_copy, var_args);
576 out_hash = g_hash_table_new_full (g_str_hash,
580 VAR_ARGS_TO_OUT_HASH_TABLE (var_args, out_hash);
583 handle = gupnp_service_proxy_begin_action_list (proxy,
590 g_main_loop_unref (main_loop);
596 /* Loop till we get a reply (or time out) */
597 if (g_main_loop_is_running (main_loop))
598 g_main_loop_run (main_loop);
600 g_main_loop_unref (main_loop);
602 result = gupnp_service_proxy_end_action_hash (proxy,
607 if (local_error == NULL) {
608 OUT_HASH_TABLE_TO_VAR_ARGS (out_hash, var_args_copy);
610 g_propagate_error (error, local_error);
613 va_end (var_args_copy);
614 g_list_free_full (in_names, g_free);
615 g_list_free_full (in_values, value_free);
616 g_hash_table_unref (out_hash);
622 * gupnp_service_proxy_send_action_hash:
623 * @proxy: A #GUPnPServiceProxy
625 * @error: The location where to store any error, or %NULL
626 * @in_hash: (element-type utf8 GValue) (transfer none): A #GHashTable of in
627 * parameter name and #GValue pairs
628 * @out_hash: (inout) (element-type utf8 GValue) (transfer full): A #GHashTable
629 * of out parameter name and initialized #GValue pairs
631 * See gupnp_service_proxy_send_action(); this version takes a pair of
632 * #GHashTable<!-- -->s for runtime determined parameter lists.
634 * Return value: %TRUE if sending the action was succesful.
637 gupnp_service_proxy_send_action_hash (GUPnPServiceProxy *proxy,
641 GHashTable *out_hash)
643 GMainLoop *main_loop;
644 GUPnPServiceProxyAction *handle;
646 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
647 g_return_val_if_fail (action, FALSE);
649 main_loop = g_main_loop_new (g_main_context_get_thread_default (),
652 handle = gupnp_service_proxy_begin_action_hash (proxy,
658 g_main_loop_unref (main_loop);
663 /* Loop till we get a reply (or time out) */
664 if (g_main_loop_is_running (main_loop))
665 g_main_loop_run (main_loop);
667 g_main_loop_unref (main_loop);
669 if (!gupnp_service_proxy_end_action_hash (proxy,
679 * gupnp_service_proxy_send_action_list:
680 * @proxy: (transfer none) : A #GUPnPServiceProxy
682 * @error: The location where to store any error, or %NULL
683 * @in_names: (element-type utf8) (transfer none): #GList of 'in' parameter
685 * @in_values: (element-type GValue) (transfer none): #GList of values (as
686 * #GValue) that line up with @in_names
687 * @out_names: (element-type utf8) (transfer none): #GList of 'out' parameter
689 * @out_types: (element-type GType) (transfer none): #GList of types (as #GType)
690 * that line up with @out_names
691 * @out_values: (element-type GValue) (transfer full) (out): #GList of values
692 * (as #GValue) that line up with @out_names and @out_types.
694 * The synchronous variant of #gupnp_service_proxy_begin_action_list and
695 * #gupnp_service_proxy_end_action_list.
697 * Return value: %TRUE if sending the action was succesful.
700 gupnp_service_proxy_send_action_list (GUPnPServiceProxy *proxy,
709 GMainLoop *main_loop;
710 GUPnPServiceProxyAction *handle;
712 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
713 g_return_val_if_fail (action, FALSE);
715 main_loop = g_main_loop_new (g_main_context_get_thread_default (),
718 handle = gupnp_service_proxy_begin_action_list (proxy,
725 g_main_loop_unref (main_loop);
730 /* Loop till we get a reply (or time out) */
731 if (g_main_loop_is_running (main_loop))
732 g_main_loop_run (main_loop);
734 g_main_loop_unref (main_loop);
736 if (!gupnp_service_proxy_end_action_list (proxy,
748 * gupnp_service_proxy_begin_action:
749 * @proxy: A #GUPnPServiceProxy
751 * @callback: (scope async): The callback to call when sending the action has succeeded
753 * @user_data: User data for @callback
754 * @...: tuples of in parameter name, in parameter type, and in parameter
755 * value, terminated with %NULL
757 * Sends action @action with parameters @Varargs to the service exposed by
758 * @proxy asynchronously, calling @callback on completion. From @callback, call
759 * gupnp_service_proxy_end_action() to check for errors, to retrieve return
760 * values, and to free the #GUPnPServiceProxyAction.
762 * Returns: (transfer none): A #GUPnPServiceProxyAction handle. This will be freed when
763 * gupnp_service_proxy_cancel_action() or
764 * gupnp_service_proxy_end_action_valist().
766 GUPnPServiceProxyAction *
767 gupnp_service_proxy_begin_action (GUPnPServiceProxy *proxy,
769 GUPnPServiceProxyActionCallback callback,
774 GUPnPServiceProxyAction *ret;
776 va_start (var_args, user_data);
777 ret = gupnp_service_proxy_begin_action_valist (proxy,
787 /* Begins a basic action message */
788 static GUPnPServiceProxyAction *
789 begin_action_msg (GUPnPServiceProxy *proxy,
791 GUPnPServiceProxyActionCallback callback,
794 GUPnPServiceProxyAction *ret;
795 char *control_url, *full_action;
796 const char *service_type;
798 /* Create action structure */
799 ret = g_slice_new (GUPnPServiceProxyAction);
803 ret->callback = callback;
804 ret->user_data = user_data;
810 proxy->priv->pending_actions =
811 g_list_prepend (proxy->priv->pending_actions, ret);
813 /* Make sure we have a service type */
814 service_type = gupnp_service_info_get_service_type
815 (GUPNP_SERVICE_INFO (proxy));
816 if (service_type == NULL) {
817 ret->error = g_error_new (GUPNP_SERVER_ERROR,
818 GUPNP_SERVER_ERROR_OTHER,
819 "No service type defined");
825 control_url = gupnp_service_info_get_control_url
826 (GUPNP_SERVICE_INFO (proxy));
828 if (control_url != NULL) {
829 ret->msg = soup_message_new (SOUP_METHOD_POST, control_url);
831 g_free (control_url);
834 if (ret->msg == NULL) {
835 ret->error = g_error_new (GUPNP_SERVER_ERROR,
836 GUPNP_SERVER_ERROR_INVALID_URL,
837 "No valid control URL defined");
843 full_action = g_strdup_printf ("\"%s#%s\"", service_type, action);
844 soup_message_headers_append (ret->msg->request_headers,
847 g_free (full_action);
849 /* Specify language */
850 http_request_set_accept_language (ret->msg);
852 /* Accept gzip encoding */
853 soup_message_headers_append (ret->msg->request_headers,
854 "Accept-Encoding", "gzip");
856 /* Set up envelope */
857 ret->msg_str = xml_util_new_string ();
859 g_string_append (ret->msg_str,
860 "<?xml version=\"1.0\"?>"
861 "<s:Envelope xmlns:s="
862 "\"http://schemas.xmlsoap.org/soap/envelope/\" "
864 "\"http://schemas.xmlsoap.org/soap/encoding/\">"
867 g_string_append (ret->msg_str, "<u:");
868 g_string_append (ret->msg_str, action);
869 g_string_append (ret->msg_str, " xmlns:u=\"");
870 g_string_append (ret->msg_str, service_type);
871 g_string_append (ret->msg_str, "\">");
876 /* Received response to action message */
878 action_got_response (SoupSession *session,
880 GUPnPServiceProxyAction *action)
882 const char *full_action;
884 switch (msg->status_code) {
885 case SOUP_STATUS_CANCELLED:
886 /* Silently return */
889 case SOUP_STATUS_METHOD_NOT_ALLOWED:
890 /* Retry with M-POST */
891 msg->method = "M-POST";
893 soup_message_headers_append
894 (msg->request_headers,
896 "\"http://schemas.xmlsoap.org/soap/envelope/\"; ns=s");
898 /* Rename "SOAPAction" to "s-SOAPAction" */
899 full_action = soup_message_headers_get_one
900 (msg->request_headers,
902 soup_message_headers_append (msg->request_headers,
905 soup_message_headers_remove (msg->request_headers,
909 soup_session_requeue_message (session, msg);
914 /* Success: Call callback */
915 action->callback (action->proxy, action, action->user_data);
921 /* Finishes an action message and sends it off */
923 finish_action_msg (GUPnPServiceProxyAction *action,
924 const char *action_name)
926 GUPnPContext *context;
927 SoupSession *session;
930 g_string_append (action->msg_str, "</u:");
931 g_string_append (action->msg_str, action_name);
932 g_string_append_c (action->msg_str, '>');
934 g_string_append (action->msg_str,
938 soup_message_set_request (action->msg,
939 "text/xml; charset=\"utf-8\"",
941 action->msg_str->str,
942 action->msg_str->len);
944 g_string_free (action->msg_str, FALSE);
946 /* We need to keep our own reference to the message as well,
947 * in order for send_action() to work. */
948 g_object_ref (action->msg);
950 /* Send the message */
951 context = gupnp_service_info_get_context
952 (GUPNP_SERVICE_INFO (action->proxy));
953 session = gupnp_context_get_session (context);
955 soup_session_queue_message (session,
957 (SoupSessionCallback) action_got_response,
961 /* Writes a parameter name and GValue pair to @msg */
963 write_in_parameter (const char *arg_name,
967 /* Write parameter pair */
968 xml_util_start_element (msg_str, arg_name);
969 gvalue_util_value_append_to_xml_string (value, msg_str);
970 xml_util_end_element (msg_str, arg_name);
974 * gupnp_service_proxy_begin_action_valist:
975 * @proxy: A #GUPnPServiceProxy
977 * @callback: (scope async) : The callback to call when sending the action has succeeded
979 * @user_data: User data for @callback
980 * @var_args: A va_list of tuples of in parameter name, in parameter type, and
983 * See gupnp_service_proxy_begin_action().
985 * Returns: (transfer none): A #GUPnPServiceProxyAction handle. This will
986 * be freed when calling gupnp_service_proxy_cancel_action() or
987 * gupnp_service_proxy_end_action_valist().
989 GUPnPServiceProxyAction *
990 gupnp_service_proxy_begin_action_valist
991 (GUPnPServiceProxy *proxy,
993 GUPnPServiceProxyActionCallback callback,
997 GUPnPServiceProxyAction *ret;
998 GList *in_names = NULL, *in_values = NULL;
1000 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), NULL);
1001 g_return_val_if_fail (action, NULL);
1002 g_return_val_if_fail (callback, NULL);
1004 VAR_ARGS_TO_IN_LIST (var_args, in_names, in_values);
1005 ret = gupnp_service_proxy_begin_action_list (proxy,
1011 g_list_free_full (in_names, g_free);
1012 g_list_free_full (in_values, value_free);
1018 * gupnp_service_proxy_begin_action_list:
1019 * @proxy: (transfer none): A #GUPnPServiceProxy
1020 * @action: An action
1021 * @in_names: (element-type utf8) (transfer none): #GList of 'in' parameter
1022 * names (as strings)
1023 * @in_values: (element-type GValue) (transfer none): #GList of values (as
1024 * #GValue) that line up with @in_names
1025 * @callback: (scope async) : The callback to call when sending the action has succeeded or
1027 * @user_data: User data for @callback
1029 * A variant of #gupnp_service_proxy_begin_action that takes lists of
1030 * in-parameter names, types and values.
1032 * Return value: (transfer none): A #GUPnPServiceProxyAction handle. This will
1033 * be freed when calling #gupnp_service_proxy_cancel_action or
1034 * #gupnp_service_proxy_end_action_list.
1036 GUPnPServiceProxyAction *
1037 gupnp_service_proxy_begin_action_list
1038 (GUPnPServiceProxy *proxy,
1042 GUPnPServiceProxyActionCallback callback,
1045 GUPnPServiceProxyAction *ret;
1049 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), NULL);
1050 g_return_val_if_fail (action, NULL);
1051 g_return_val_if_fail (callback, NULL);
1052 g_return_val_if_fail (g_list_length (in_names) ==
1053 g_list_length (in_values),
1056 /* Create message */
1057 ret = begin_action_msg (proxy, action, callback, user_data);
1060 callback (proxy, ret, user_data);
1068 for (names = in_names; names; names=names->next) {
1069 GValue* val = values->data;
1071 write_in_parameter (names->data,
1075 values = values->next;
1078 /* Finish and send off */
1079 finish_action_msg (ret, action);
1085 * gupnp_service_proxy_begin_action_hash:
1086 * @proxy: A #GUPnPServiceProxy
1087 * @action: An action
1088 * @callback: (scope async): The callback to call when sending the action has succeeded
1090 * @user_data: User data for @callback
1091 * @hash: (element-type utf8 GValue): A #GHashTable of in parameter name and #GValue pairs
1093 * See gupnp_service_proxy_begin_action(); this version takes a #GHashTable
1094 * for runtime generated parameter lists.
1096 * Return value: (transfer none): A #GUPnPServiceProxyAction handle. This will
1097 * be freed when calling gupnp_service_proxy_cancel_action() or
1098 * gupnp_service_proxy_end_action_hash().
1100 GUPnPServiceProxyAction *
1101 gupnp_service_proxy_begin_action_hash
1102 (GUPnPServiceProxy *proxy,
1104 GUPnPServiceProxyActionCallback callback,
1108 GUPnPServiceProxyAction *ret;
1110 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), NULL);
1111 g_return_val_if_fail (action, NULL);
1112 g_return_val_if_fail (callback, NULL);
1114 /* Create message */
1115 ret = begin_action_msg (proxy, action, callback, user_data);
1118 callback (proxy, ret, user_data);
1124 g_hash_table_foreach (hash, (GHFunc) write_in_parameter, ret->msg_str);
1126 /* Finish and send off */
1127 finish_action_msg (ret, action);
1133 * gupnp_service_proxy_end_action:
1134 * @proxy: A #GUPnPServiceProxy
1135 * @action: A #GUPnPServiceProxyAction handle
1136 * @error: The location where to store any error, or %NULL
1137 * @...: tuples of out parameter name, out parameter type, and out parameter
1138 * value location, terminated with %NULL. The out parameter values should be
1141 * Retrieves the result of @action. The out parameters in @Varargs will be
1142 * filled in, and if an error occurred, @error will be set. In case of
1143 * a UPnPError the error code will be the same in @error.
1145 * Return value: %TRUE on success.
1148 gupnp_service_proxy_end_action (GUPnPServiceProxy *proxy,
1149 GUPnPServiceProxyAction *action,
1156 va_start (var_args, error);
1157 ret = gupnp_service_proxy_end_action_valist (proxy,
1166 /* Checks an action response for errors and returns the parsed
1169 check_action_response (G_GNUC_UNUSED GUPnPServiceProxy *proxy,
1170 GUPnPServiceProxyAction *action,
1177 /* Check for errors */
1178 switch (action->msg->status_code) {
1179 case SOUP_STATUS_OK:
1180 case SOUP_STATUS_INTERNAL_SERVER_ERROR:
1183 _gupnp_error_set_server_error (error, action->msg);
1188 /* Parse response */
1189 response = xmlRecoverMemory (action->msg->response_body->data,
1190 action->msg->response_body->length);
1193 if (action->msg->status_code == SOUP_STATUS_OK) {
1196 GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1197 "Could not parse SOAP response");
1202 GUPNP_SERVER_ERROR_INTERNAL_SERVER_ERROR,
1203 action->msg->reason_phrase);
1209 /* Get parameter list */
1210 *params = xml_util_get_element ((xmlNode *) response,
1213 if (*params != NULL)
1214 *params = xml_util_real_node ((*params)->children);
1216 if (*params != NULL) {
1217 if (strcmp ((const char *) (*params)->name, "Header") == 0)
1218 *params = xml_util_real_node ((*params)->next);
1220 if (*params != NULL)
1221 if (strcmp ((const char *) (*params)->name, "Body") != 0)
1225 if (*params != NULL)
1226 *params = xml_util_real_node ((*params)->children);
1228 if (*params == NULL) {
1231 GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1232 "Invalid Envelope");
1234 xmlFreeDoc (response);
1239 /* Check whether we have a Fault */
1240 if (action->msg->status_code == SOUP_STATUS_INTERNAL_SERVER_ERROR) {
1244 param = xml_util_get_element (*params,
1252 GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1255 xmlFreeDoc (response);
1261 code = xml_util_get_child_element_content_int
1262 (param, "errorCode");
1266 GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1269 xmlFreeDoc (response);
1275 desc = xml_util_get_child_element_content_glib
1276 (param, "errorDescription");
1278 desc = g_strdup (action->msg->reason_phrase);
1280 g_set_error_literal (error,
1281 GUPNP_CONTROL_ERROR,
1287 xmlFreeDoc (response);
1295 /* Reads a value into the parameter name and initialised GValue pair
1298 read_out_parameter (const char *arg_name,
1304 /* Try to find a matching parameter in the response*/
1305 param = xml_util_get_element (params,
1309 g_warning ("Could not find variable \"%s\" in response",
1315 gvalue_util_set_value_from_xml_node (value, param);
1319 * gupnp_service_proxy_end_action_valist:
1320 * @proxy: A #GUPnPServiceProxy
1321 * @action: A #GUPnPServiceProxyAction handle
1322 * @error: The location where to store any error, or %NULL
1323 * @var_args: A va_list of tuples of out parameter name, out parameter type,
1324 * and out parameter value location. The out parameter values should be
1327 * See gupnp_service_proxy_end_action().
1329 * Return value: %TRUE on success.
1332 gupnp_service_proxy_end_action_valist (GUPnPServiceProxy *proxy,
1333 GUPnPServiceProxyAction *action,
1337 GHashTable *out_hash;
1338 va_list var_args_copy;
1340 GError *local_error;
1342 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1343 g_return_val_if_fail (action, FALSE);
1344 g_return_val_if_fail (proxy == action->proxy, FALSE);
1346 out_hash = g_hash_table_new_full (g_str_hash,
1350 G_VA_COPY (var_args_copy, var_args);
1351 VAR_ARGS_TO_OUT_HASH_TABLE (var_args, out_hash);
1353 result = gupnp_service_proxy_end_action_hash (proxy,
1358 if (local_error == NULL) {
1359 OUT_HASH_TABLE_TO_VAR_ARGS (out_hash, var_args_copy);
1361 g_propagate_error (error, local_error);
1363 va_end (var_args_copy);
1364 g_hash_table_unref (out_hash);
1370 * gupnp_service_proxy_end_action_list:
1371 * @proxy: A #GUPnPServiceProxy
1372 * @action: A #GUPnPServiceProxyAction handle
1373 * @error: The location where to store any error, or %NULL
1374 * @out_names: (element-type utf8) (transfer none): #GList of 'out' parameter
1375 * names (as strings)
1376 * @out_types: (element-type GType) (transfer none): #GList of types (as #GType)
1377 * that line up with @out_names
1378 * @out_values: (element-type GValue) (transfer full) (out): #GList of values
1379 * (as #GValue) that line up with @out_names and @out_types.
1381 * A variant of #gupnp_service_proxy_end_action that takes lists of
1382 * out-parameter names, types and place-holders for values. The returned list
1383 * in @out_values must be freed using #g_list_free and each element in it using
1384 * #g_value_unset and #g_slice_free.
1386 * Returns: %TRUE on success.
1389 gupnp_service_proxy_end_action_list (GUPnPServiceProxy *proxy,
1390 GUPnPServiceProxyAction *action,
1400 GList *out_values_list;
1402 out_values_list = NULL;
1404 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1405 g_return_val_if_fail (action, FALSE);
1406 g_return_val_if_fail (proxy == action->proxy, FALSE);
1408 /* Check for saved error from begin_action() */
1409 if (action->error) {
1411 *error = action->error;
1413 g_error_free (action->error);
1415 gupnp_service_proxy_action_free (action);
1420 /* Check response for errors and do initial parsing */
1421 response = check_action_response (proxy, action, ¶ms, error);
1422 if (response == NULL) {
1423 gupnp_service_proxy_action_free (action);
1428 /* Read arguments */
1430 for (names = out_names; names; names=names->next) {
1433 val = g_slice_new0 (GValue);
1434 g_value_init (val, (GType) types->data);
1436 read_out_parameter (names->data, val, params);
1438 out_values_list = g_list_append (out_values_list, val);
1440 types = types->next;
1443 *out_values = out_values_list;
1446 gupnp_service_proxy_action_free (action);
1448 xmlFreeDoc (response);
1454 * gupnp_service_proxy_end_action_hash:
1455 * @proxy: A #GUPnPServiceProxy
1456 * @action: A #GUPnPServiceProxyAction handle
1457 * @error: The location where to store any error, or %NULL
1458 * @hash: (element-type utf8 GValue) (inout) (transfer none): A #GHashTable of
1459 * out parameter name and initialised #GValue pairs
1461 * See gupnp_service_proxy_end_action(); this version takes a #GHashTable for
1462 * runtime generated parameter lists.
1464 * Return value: %TRUE on success.
1467 gupnp_service_proxy_end_action_hash (GUPnPServiceProxy *proxy,
1468 GUPnPServiceProxyAction *action,
1475 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1476 g_return_val_if_fail (action, FALSE);
1477 g_return_val_if_fail (proxy == action->proxy, FALSE);
1479 /* Check for saved error from begin_action() */
1480 if (action->error) {
1482 *error = action->error;
1484 g_error_free (action->error);
1486 gupnp_service_proxy_action_free (action);
1491 /* Check response for errors and do initial parsing */
1492 response = check_action_response (proxy, action, ¶ms, error);
1493 if (response == NULL) {
1494 gupnp_service_proxy_action_free (action);
1499 /* Read arguments */
1500 g_hash_table_foreach (hash, (GHFunc) read_out_parameter, params);
1503 gupnp_service_proxy_action_free (action);
1505 xmlFreeDoc (response);
1511 * gupnp_service_proxy_cancel_action:
1512 * @proxy: A #GUPnPServiceProxy
1513 * @action: A #GUPnPServiceProxyAction handle
1515 * Cancels @action, freeing the @action handle.
1518 gupnp_service_proxy_cancel_action (GUPnPServiceProxy *proxy,
1519 GUPnPServiceProxyAction *action)
1521 GUPnPContext *context;
1522 SoupSession *session;
1524 g_return_if_fail (GUPNP_IS_SERVICE_PROXY (proxy));
1525 g_return_if_fail (action);
1526 g_return_if_fail (proxy == action->proxy);
1528 if (action->msg != NULL) {
1529 context = gupnp_service_info_get_context
1530 (GUPNP_SERVICE_INFO (proxy));
1531 session = gupnp_context_get_session (context);
1533 soup_session_cancel_message (session,
1535 SOUP_STATUS_CANCELLED);
1538 if (action->error != NULL)
1539 g_error_free (action->error);
1541 gupnp_service_proxy_action_free (action);
1545 * gupnp_service_proxy_add_notify:
1546 * @proxy: A #GUPnPServiceProxy
1547 * @variable: The variable to add notification for
1548 * @type: The type of the variable
1549 * @callback: (scope async): The callback to call when @variable changes
1550 * @user_data: User data for @callback
1552 * Sets up @callback to be called whenever a change notification for
1553 * @variable is recieved.
1555 * Return value: %TRUE on success.
1558 gupnp_service_proxy_add_notify (GUPnPServiceProxy *proxy,
1559 const char *variable,
1561 GUPnPServiceProxyNotifyCallback callback,
1565 CallbackData *callback_data;
1567 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1568 g_return_val_if_fail (variable, FALSE);
1569 g_return_val_if_fail (type, FALSE);
1570 g_return_val_if_fail (callback, FALSE);
1572 /* See if we already have notifications set up for this variable */
1573 data = g_hash_table_lookup (proxy->priv->notify_hash, variable);
1575 /* No, create one */
1576 data = g_slice_new (NotifyData);
1579 data->callbacks = NULL;
1581 g_hash_table_insert (proxy->priv->notify_hash,
1582 g_strdup (variable),
1585 /* Yes, check that everything is sane .. */
1586 if (data->type != type) {
1587 g_warning ("A notification already exists for %s, but "
1588 "has type %s, not %s.",
1590 g_type_name (data->type),
1591 g_type_name (type));
1597 /* Append callback */
1598 callback_data = g_slice_new (CallbackData);
1600 callback_data->callback = callback;
1601 callback_data->user_data = user_data;
1603 data->callbacks = g_list_append (data->callbacks, callback_data);
1609 * gupnp_service_proxy_remove_notify:
1610 * @proxy: A #GUPnPServiceProxy
1611 * @variable: The variable to add notification for
1612 * @callback: (scope call): The callback to call when @variable changes
1613 * @user_data: User data for @callback
1615 * Cancels the variable change notification for @callback and @user_data.
1617 * This function must not be called directly or indirectly from a
1618 * #GUPnPServiceProxyNotifyCallback associated with this service proxy, even
1619 * if it is for another variable.
1621 * Return value: %TRUE on success.
1624 gupnp_service_proxy_remove_notify (GUPnPServiceProxy *proxy,
1625 const char *variable,
1626 GUPnPServiceProxyNotifyCallback callback,
1633 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1634 g_return_val_if_fail (variable, FALSE);
1635 g_return_val_if_fail (callback, FALSE);
1637 /* Look up NotifyData for variable */
1638 data = g_hash_table_lookup (proxy->priv->notify_hash, variable);
1640 g_warning ("No notifications found for variable %s",
1646 /* Look up our callback-user_data pair */
1649 for (l = data->callbacks; l; l = l->next) {
1650 CallbackData *callback_data;
1652 callback_data = l->data;
1654 if (callback_data->callback == callback &&
1655 callback_data->user_data == user_data) {
1657 g_slice_free (CallbackData, callback_data);
1660 g_list_delete_link (data->callbacks, l);
1661 if (data->callbacks == NULL) {
1662 /* No callbacks left: Remove from hash */
1663 g_hash_table_remove (proxy->priv->notify_hash,
1674 g_warning ("No such callback-user_data pair was found");
1680 emit_notification (GUPnPServiceProxy *proxy,
1684 GValue value = {0, };
1687 data = g_hash_table_lookup (proxy->priv->notify_hash, var_node->name);
1691 /* Make a GValue of the desired type */
1692 g_value_init (&value, data->type);
1694 if (!gvalue_util_set_value_from_xml_node (&value, var_node)) {
1695 g_value_unset (&value);
1700 /* Call callbacks */
1701 for (l = data->callbacks; l; l = l->next) {
1702 CallbackData *callback_data;
1704 callback_data = l->data;
1706 callback_data->callback (proxy,
1707 (const char *) var_node->name,
1709 callback_data->user_data);
1713 g_value_unset (&value);
1717 emit_notifications_for_doc (GUPnPServiceProxy *proxy,
1722 node = xmlDocGetRootElement (doc);
1724 /* Iterate over all provided properties */
1725 for (node = node->children; node; node = node->next) {
1728 /* Although according to the UPnP specs, there should be only
1729 * one variable node inside a 'property' node, we still need to
1730 * entertain the possibility of multiple variables inside it to
1731 * be compatible with implementations using older GUPnP.
1733 for (var_node = node->children;
1735 var_node = var_node->next) {
1736 if (strcmp ((char *) node->name, "property") == 0)
1737 emit_notification (proxy, var_node);
1742 /* Emit pending notifications. See comment below on why we do this. */
1744 emit_notifications (gpointer user_data)
1746 GUPnPServiceProxy *proxy = user_data;
1747 GList *pending_notify;
1748 gboolean resubscribe = FALSE;
1750 g_assert (user_data);
1752 if (proxy->priv->sid == NULL)
1754 if (G_LIKELY (proxy->priv->subscribed))
1755 /* subscription in progress, delay emision! */
1758 for (pending_notify = proxy->priv->pending_notifies;
1759 pending_notify != NULL;
1760 pending_notify = pending_notify->next) {
1761 EmitNotifyData *emit_notify_data;
1763 emit_notify_data = pending_notify->data;
1765 if (emit_notify_data->seq > proxy->priv->seq) {
1766 /* Error procedure on missed event according to
1767 * UDA 1.0, section 4.2, §5:
1768 * Re-subscribe to get a new SID and SEQ */
1774 /* Increment our own event sequence number */
1775 /* UDA 1.0, section 4.2, §3: To prevent overflow, SEQ is set to
1776 * 1, NOT 0, when encountering G_MAXUINT32. SEQ == 0 always
1777 * indicates the initial event message. */
1778 if (proxy->priv->seq < G_MAXUINT32)
1781 proxy->priv->seq = 1;
1783 if (G_LIKELY (proxy->priv->sid != NULL &&
1784 strcmp (emit_notify_data->sid,
1785 proxy->priv->sid) == 0))
1786 /* Our SID, entertain! */
1787 emit_notifications_for_doc (proxy,
1788 emit_notify_data->doc);
1792 while (proxy->priv->pending_notifies != NULL) {
1793 emit_notify_data_free (proxy->priv->pending_notifies->data);
1795 proxy->priv->pending_notifies =
1796 g_list_delete_link (proxy->priv->pending_notifies,
1797 proxy->priv->pending_notifies);
1800 proxy->priv->notify_idle_src = NULL;
1803 unsubscribe (proxy);
1811 * HTTP server received a message. Handle, if this was a NOTIFY
1812 * message with our SID.
1815 server_handler (G_GNUC_UNUSED SoupServer *soup_server,
1817 G_GNUC_UNUSED const char *server_path,
1818 G_GNUC_UNUSED GHashTable *query,
1819 G_GNUC_UNUSED SoupClientContext *soup_client,
1822 GUPnPServiceProxy *proxy;
1823 const char *hdr, *nt, *nts;
1828 EmitNotifyData *emit_notify_data;
1830 proxy = GUPNP_SERVICE_PROXY (user_data);
1832 if (strcmp (msg->method, GENA_METHOD_NOTIFY) != 0) {
1833 /* We don't implement this method */
1834 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
1839 nt = soup_message_headers_get_one (msg->request_headers, "NT");
1840 nts = soup_message_headers_get_one (msg->request_headers, "NTS");
1841 if (nt == NULL || nts == NULL) {
1842 /* Required header is missing */
1843 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
1848 if (strcmp (nt, "upnp:event") != 0 ||
1849 strcmp (nts, "upnp:propchange") != 0) {
1850 /* Unexpected header content */
1851 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1856 hdr = soup_message_headers_get_one (msg->request_headers, "SEQ");
1859 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1865 seq_parsed = strtoul (hdr, NULL, 10);
1866 if (errno != 0 || seq_parsed > G_MAXUINT32) {
1867 /* Invalid SEQ header value */
1868 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1873 seq = (guint32) seq_parsed;
1875 hdr = soup_message_headers_get_one (msg->request_headers, "SID");
1877 strlen (hdr) <= strlen ("uuid:") ||
1878 strncmp (hdr, "uuid:", strlen ("uuid:")) != 0) {
1880 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1885 /* Parse the actual XML message content */
1886 doc = xmlRecoverMemory (msg->request_body->data,
1887 msg->request_body->length);
1890 g_warning ("Failed to parse NOTIFY message body");
1892 soup_message_set_status (msg,
1893 SOUP_STATUS_INTERNAL_SERVER_ERROR);
1898 /* Get root propertyset element */
1899 node = xmlDocGetRootElement (doc);
1900 if (node == NULL || strcmp ((char *) node->name, "propertyset")) {
1901 /* Empty or unsupported */
1904 soup_message_set_status (msg, SOUP_STATUS_OK);
1910 * Some UPnP stacks (hello, myigd/1.0) block when sending a NOTIFY, so
1911 * call the callbacks in an idle handler so that if the client calls the
1912 * device in the notify callback the server can actually respond.
1914 emit_notify_data = emit_notify_data_new (hdr, seq, doc);
1916 proxy->priv->pending_notifies =
1917 g_list_append (proxy->priv->pending_notifies, emit_notify_data);
1918 if (!proxy->priv->notify_idle_src) {
1919 proxy->priv->notify_idle_src = g_idle_source_new();
1920 g_source_set_callback (proxy->priv->notify_idle_src,
1923 g_source_attach (proxy->priv->notify_idle_src,
1924 g_main_context_get_thread_default ());
1926 g_source_unref (proxy->priv->notify_idle_src);
1929 /* Everything went OK */
1930 soup_message_set_status (msg, SOUP_STATUS_OK);
1934 * Generates a timeout header for the subscription timeout specified
1935 * in our GUPnPContext.
1938 make_timeout_header (GUPnPContext *context)
1942 timeout = gupnp_context_get_subscription_timeout (context);
1944 return g_strdup_printf ("Second-%d", timeout);
1946 return g_strdup ("infinite");
1950 * Subscription expired.
1953 subscription_expire (gpointer user_data)
1955 GUPnPServiceProxy *proxy;
1956 GUPnPContext *context;
1958 SoupSession *session;
1959 char *sub_url, *timeout;
1961 proxy = GUPNP_SERVICE_PROXY (user_data);
1963 /* Reset timeout ID */
1964 proxy->priv->subscription_timeout_src = NULL;
1966 /* Send renewal message */
1967 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
1969 /* Create subscription message */
1970 sub_url = gupnp_service_info_get_event_subscription_url
1971 (GUPNP_SERVICE_INFO (proxy));
1973 msg = soup_message_new (GENA_METHOD_SUBSCRIBE, sub_url);
1977 g_return_val_if_fail (msg != NULL, FALSE);
1980 soup_message_headers_append (msg->request_headers,
1984 timeout = make_timeout_header (context);
1985 soup_message_headers_append (msg->request_headers,
1990 /* And send it off */
1991 proxy->priv->pending_messages =
1992 g_list_prepend (proxy->priv->pending_messages, msg);
1994 session = gupnp_context_get_session (context);
1996 soup_session_queue_message (session,
1998 (SoupSessionCallback)
1999 subscribe_got_response,
2006 * Received subscription response.
2009 subscribe_got_response (G_GNUC_UNUSED SoupSession *session,
2011 GUPnPServiceProxy *proxy)
2016 if (msg->status_code == SOUP_STATUS_CANCELLED)
2019 /* Remove from pending messages list */
2020 proxy->priv->pending_messages =
2021 g_list_remove (proxy->priv->pending_messages, msg);
2023 /* Check whether the subscription is still wanted */
2024 if (!proxy->priv->subscribed)
2028 g_free (proxy->priv->sid);
2029 proxy->priv->sid = NULL;
2031 /* Check message status */
2032 if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
2038 hdr = soup_message_headers_get_one (msg->response_headers,
2042 (GUPNP_EVENTING_ERROR,
2043 GUPNP_EVENTING_ERROR_SUBSCRIPTION_LOST,
2044 "No SID in SUBSCRIBE response");
2049 proxy->priv->sid = g_strdup (hdr);
2051 /* Figure out when the subscription times out */
2052 hdr = soup_message_headers_get_one (msg->response_headers,
2055 g_warning ("No Timeout in SUBSCRIBE response.");
2060 if (strncmp (hdr, "Second-", strlen ("Second-")) == 0) {
2061 /* We have a finite timeout */
2062 timeout = atoi (hdr + strlen ("Second-"));
2065 g_warning ("Invalid time-out specified. "
2066 "Assuming default value of %d.",
2067 GENA_DEFAULT_TIMEOUT);
2069 timeout = GENA_DEFAULT_TIMEOUT;
2072 /* We want to resubscribe before the subscription
2074 timeout = g_random_int_range (1, timeout / 2);
2076 /* Add actual timeout */
2077 proxy->priv->subscription_timeout_src =
2078 g_timeout_source_new_seconds (timeout);
2079 g_source_set_callback
2080 (proxy->priv->subscription_timeout_src,
2081 subscription_expire,
2083 g_source_attach (proxy->priv->subscription_timeout_src,
2084 g_main_context_get_thread_default ());
2086 g_source_unref (proxy->priv->subscription_timeout_src);
2089 GUPnPContext *context;
2092 /* Subscription failed. */
2093 error = g_error_new_literal
2094 (GUPNP_EVENTING_ERROR,
2095 GUPNP_EVENTING_ERROR_SUBSCRIPTION_FAILED,
2096 msg->reason_phrase);
2099 /* Remove listener */
2100 context = gupnp_service_info_get_context
2101 (GUPNP_SERVICE_INFO (proxy));
2103 server = gupnp_context_get_server (context);
2104 soup_server_remove_handler (server, proxy->priv->path);
2106 proxy->priv->subscribed = FALSE;
2108 g_object_notify (G_OBJECT (proxy), "subscribed");
2110 /* Emit subscription-lost */
2111 g_signal_emit (proxy,
2112 signals[SUBSCRIPTION_LOST],
2116 g_error_free (error);
2121 * Subscribe to this service.
2124 subscribe (GUPnPServiceProxy *proxy)
2126 GUPnPContext *context;
2128 SoupSession *session;
2130 const char *server_url;
2131 char *sub_url, *delivery_url, *timeout;
2133 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
2135 /* Create subscription message */
2136 sub_url = gupnp_service_info_get_event_subscription_url
2137 (GUPNP_SERVICE_INFO (proxy));
2140 if (sub_url != NULL) {
2141 msg = soup_message_new (GENA_METHOD_SUBSCRIBE, sub_url);
2149 /* Subscription failed. */
2150 proxy->priv->subscribed = FALSE;
2152 g_object_notify (G_OBJECT (proxy), "subscribed");
2154 /* Emit subscription-lost */
2155 error = g_error_new (GUPNP_SERVER_ERROR,
2156 GUPNP_SERVER_ERROR_INVALID_URL,
2157 "No valid subscription URL defined");
2159 g_signal_emit (proxy,
2160 signals[SUBSCRIPTION_LOST],
2164 g_error_free (error);
2170 server_url = _gupnp_context_get_server_url (context);
2171 delivery_url = g_strdup_printf ("<%s%s>",
2174 soup_message_headers_append (msg->request_headers,
2177 g_free (delivery_url);
2179 soup_message_headers_append (msg->request_headers,
2183 timeout = make_timeout_header (context);
2184 soup_message_headers_append (msg->request_headers,
2189 /* Listen for events */
2190 server = gupnp_context_get_server (context);
2192 soup_server_add_handler (server,
2198 /* And send our subscription message off */
2199 proxy->priv->pending_messages =
2200 g_list_prepend (proxy->priv->pending_messages, msg);
2202 session = gupnp_context_get_session (context);
2204 soup_session_queue_message (session,
2206 (SoupSessionCallback)
2207 subscribe_got_response,
2212 * Unsubscribe from this service.
2215 unsubscribe (GUPnPServiceProxy *proxy)
2217 GUPnPContext *context;
2218 SoupSession *session;
2221 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
2223 /* Remove server handler */
2224 server = gupnp_context_get_server (context);
2225 soup_server_remove_handler (server, proxy->priv->path);
2227 if (proxy->priv->sid != NULL) {
2231 /* Create unsubscription message */
2232 sub_url = gupnp_service_info_get_event_subscription_url
2233 (GUPNP_SERVICE_INFO (proxy));
2235 msg = soup_message_new (GENA_METHOD_UNSUBSCRIBE, sub_url);
2241 soup_message_headers_append (msg->request_headers,
2246 session = gupnp_context_get_session (context);
2248 soup_session_queue_message (session, msg, NULL, NULL);
2252 g_free (proxy->priv->sid);
2253 proxy->priv->sid = NULL;
2255 /* Reset sequence number */
2256 proxy->priv->seq = 0;
2259 /* Remove subscription timeout */
2260 if (proxy->priv->subscription_timeout_src) {
2261 g_source_destroy (proxy->priv->subscription_timeout_src);
2262 proxy->priv->subscription_timeout_src = NULL;
2267 * gupnp_service_proxy_set_subscribed:
2268 * @proxy: A #GUPnPServiceProxy
2269 * @subscribed: %TRUE to subscribe to this service
2271 * (Un)subscribes to this service.
2273 * Note that the relevant messages are not immediately sent but queued.
2274 * If you want to unsubcribe from this service because the application
2275 * is quitting, rely on automatic synchronised unsubscription on object
2276 * destruction instead.
2279 gupnp_service_proxy_set_subscribed (GUPnPServiceProxy *proxy,
2280 gboolean subscribed)
2282 g_return_if_fail (GUPNP_IS_SERVICE_PROXY (proxy));
2284 if (proxy->priv->subscribed == subscribed)
2287 proxy->priv->subscribed = subscribed;
2292 unsubscribe (proxy);
2294 g_object_notify (G_OBJECT (proxy), "subscribed");
2298 * gupnp_service_proxy_get_subscribed:
2299 * @proxy: A #GUPnPServiceProxy
2301 * Returns if we are subscribed to this service.
2303 * Return value: %TRUE if we are subscribed to this service, otherwise %FALSE.
2306 gupnp_service_proxy_get_subscribed (GUPnPServiceProxy *proxy)
2308 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
2310 return proxy->priv->subscribed;