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_HASH_TABLE(var_args, hash) \
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 g_hash_table_insert (hash, g_strdup (arg_name), value); \
482 g_warning ("Failed to collect value of type %s for %s: %s", \
483 g_type_name (type), \
488 arg_name = va_arg (var_args, const gchar *); \
492 /* Puts values stored in hash table with GValues into var args.
494 #define OUT_HASH_TABLE_TO_VAR_ARGS(hash, var_args) \
496 const gchar *arg_name = va_arg (var_args, const gchar *); \
498 while (arg_name != NULL) { \
499 GValue *value = g_hash_table_lookup (hash, arg_name); \
500 GType type = va_arg (var_args, GType); \
502 if (value == NULL) { \
503 g_warning ("No value for %s", arg_name); \
504 G_VALUE_COLLECT_SKIP (type, var_args); \
505 } else if (G_VALUE_TYPE (value) != type) { \
506 g_warning ("Different GType in value (%s) and in var args (%s) for %s.", \
507 G_VALUE_TYPE_NAME (value), \
508 g_type_name (type), \
511 gchar *error = NULL; \
513 G_VALUE_LCOPY (value, var_args, 0, &error); \
514 if (error != NULL) { \
515 g_warning ("Failed to lcopy the value of type %s for %s: %s", \
516 g_type_name (type), \
522 arg_name = va_arg (var_args, const gchar *); \
526 /* GDestroyNotify for GHashTable holding GValues.
529 value_free (gpointer data)
531 GValue *value = (GValue *) data;
533 g_value_unset (value);
538 * gupnp_service_proxy_send_action_valist:
539 * @proxy: A #GUPnPServiceProxy
541 * @error: The location where to store any error, or %NULL
542 * @var_args: va_list of tuples of in parameter name, in parameter type, and in
543 * parameter value, followed by %NULL, and then tuples of out parameter name,
544 * out parameter type, and out parameter value location
546 * See gupnp_service_proxy_send_action().
548 * Return value: %TRUE if sending the action was succesful.
551 gupnp_service_proxy_send_action_valist (GUPnPServiceProxy *proxy,
557 GHashTable *out_hash;
558 va_list var_args_copy;
562 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
563 g_return_val_if_fail (action, FALSE);
565 in_hash = g_hash_table_new_full (g_str_hash,
569 VAR_ARGS_TO_IN_HASH_TABLE (var_args, in_hash);
570 G_VA_COPY (var_args_copy, var_args);
571 out_hash = g_hash_table_new_full (g_str_hash,
575 VAR_ARGS_TO_OUT_HASH_TABLE (var_args, out_hash);
578 handle = gupnp_service_proxy_begin_action_list (proxy,
585 g_main_loop_unref (main_loop);
591 /* Loop till we get a reply (or time out) */
592 if (g_main_loop_is_running (main_loop))
593 g_main_loop_run (main_loop);
595 g_main_loop_unref (main_loop);
597 result = gupnp_service_proxy_end_action_hash (proxy,
602 if (local_error == NULL) {
603 OUT_HASH_TABLE_TO_VAR_ARGS (out_hash, var_args_copy);
605 g_propagate_error (error, local_error);
608 va_end (var_args_copy);
609 g_hash_table_unref (in_hash);
610 g_hash_table_unref (out_hash);
616 * gupnp_service_proxy_send_action_hash:
617 * @proxy: A #GUPnPServiceProxy
619 * @error: The location where to store any error, or %NULL
620 * @in_hash: (element-type utf8 GValue) (transfer none): A #GHashTable of in
621 * parameter name and #GValue pairs
622 * @out_hash: (inout) (element-type utf8 GValue) (transfer full): A #GHashTable
623 * of out parameter name and initialized #GValue pairs
625 * See gupnp_service_proxy_send_action(); this version takes a pair of
626 * #GHashTable<!-- -->s for runtime determined parameter lists.
628 * Return value: %TRUE if sending the action was succesful.
631 gupnp_service_proxy_send_action_hash (GUPnPServiceProxy *proxy,
635 GHashTable *out_hash)
637 GMainLoop *main_loop;
638 GUPnPServiceProxyAction *handle;
640 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
641 g_return_val_if_fail (action, FALSE);
643 main_loop = g_main_loop_new (g_main_context_get_thread_default (),
646 handle = gupnp_service_proxy_begin_action_hash (proxy,
652 g_main_loop_unref (main_loop);
657 /* Loop till we get a reply (or time out) */
658 if (g_main_loop_is_running (main_loop))
659 g_main_loop_run (main_loop);
661 g_main_loop_unref (main_loop);
663 if (!gupnp_service_proxy_end_action_hash (proxy,
673 * gupnp_service_proxy_send_action_list:
674 * @proxy: (transfer none) : A #GUPnPServiceProxy
676 * @error: The location where to store any error, or %NULL
677 * @in_names: (element-type utf8) (transfer none): #GList of 'in' parameter
679 * @in_values: (element-type GValue) (transfer none): #GList of values (as
680 * #GValue) that line up with @in_names
681 * @out_names: (element-type utf8) (transfer none): #GList of 'out' parameter
683 * @out_types: (element-type GType) (transfer none): #GList of types (as #GType)
684 * that line up with @out_names
685 * @out_values: (element-type GValue) (transfer full) (out): #GList of values
686 * (as #GValue) that line up with @out_names and @out_types.
688 * The synchronous variant of #gupnp_service_proxy_begin_action_list and
689 * #gupnp_service_proxy_end_action_list.
691 * Return value: %TRUE if sending the action was succesful.
694 gupnp_service_proxy_send_action_list (GUPnPServiceProxy *proxy,
703 GMainLoop *main_loop;
704 GUPnPServiceProxyAction *handle;
706 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
707 g_return_val_if_fail (action, FALSE);
709 main_loop = g_main_loop_new (g_main_context_get_thread_default (),
712 handle = gupnp_service_proxy_begin_action_list (proxy,
719 g_main_loop_unref (main_loop);
724 /* Loop till we get a reply (or time out) */
725 if (g_main_loop_is_running (main_loop))
726 g_main_loop_run (main_loop);
728 g_main_loop_unref (main_loop);
730 if (!gupnp_service_proxy_end_action_list (proxy,
742 * gupnp_service_proxy_begin_action:
743 * @proxy: A #GUPnPServiceProxy
745 * @callback: (scope async): The callback to call when sending the action has succeeded
747 * @user_data: User data for @callback
748 * @...: tuples of in parameter name, in parameter type, and in parameter
749 * value, terminated with %NULL
751 * Sends action @action with parameters @Varargs to the service exposed by
752 * @proxy asynchronously, calling @callback on completion. From @callback, call
753 * gupnp_service_proxy_end_action() to check for errors, to retrieve return
754 * values, and to free the #GUPnPServiceProxyAction.
756 * Returns: (transfer none): A #GUPnPServiceProxyAction handle. This will be freed when
757 * gupnp_service_proxy_cancel_action() or
758 * gupnp_service_proxy_end_action_valist().
760 GUPnPServiceProxyAction *
761 gupnp_service_proxy_begin_action (GUPnPServiceProxy *proxy,
763 GUPnPServiceProxyActionCallback callback,
768 GUPnPServiceProxyAction *ret;
770 va_start (var_args, user_data);
771 ret = gupnp_service_proxy_begin_action_valist (proxy,
781 /* Begins a basic action message */
782 static GUPnPServiceProxyAction *
783 begin_action_msg (GUPnPServiceProxy *proxy,
785 GUPnPServiceProxyActionCallback callback,
788 GUPnPServiceProxyAction *ret;
789 char *control_url, *full_action;
790 const char *service_type;
792 /* Create action structure */
793 ret = g_slice_new (GUPnPServiceProxyAction);
797 ret->callback = callback;
798 ret->user_data = user_data;
804 proxy->priv->pending_actions =
805 g_list_prepend (proxy->priv->pending_actions, ret);
807 /* Make sure we have a service type */
808 service_type = gupnp_service_info_get_service_type
809 (GUPNP_SERVICE_INFO (proxy));
810 if (service_type == NULL) {
811 ret->error = g_error_new (GUPNP_SERVER_ERROR,
812 GUPNP_SERVER_ERROR_OTHER,
813 "No service type defined");
819 control_url = gupnp_service_info_get_control_url
820 (GUPNP_SERVICE_INFO (proxy));
822 if (control_url != NULL) {
823 ret->msg = soup_message_new (SOUP_METHOD_POST, control_url);
825 g_free (control_url);
828 if (ret->msg == NULL) {
829 ret->error = g_error_new (GUPNP_SERVER_ERROR,
830 GUPNP_SERVER_ERROR_INVALID_URL,
831 "No valid control URL defined");
837 full_action = g_strdup_printf ("\"%s#%s\"", service_type, action);
838 soup_message_headers_append (ret->msg->request_headers,
841 g_free (full_action);
843 /* Specify language */
844 http_request_set_accept_language (ret->msg);
846 /* Accept gzip encoding */
847 soup_message_headers_append (ret->msg->request_headers,
848 "Accept-Encoding", "gzip");
850 /* Set up envelope */
851 ret->msg_str = xml_util_new_string ();
853 g_string_append (ret->msg_str,
854 "<?xml version=\"1.0\"?>"
855 "<s:Envelope xmlns:s="
856 "\"http://schemas.xmlsoap.org/soap/envelope/\" "
858 "\"http://schemas.xmlsoap.org/soap/encoding/\">"
861 g_string_append (ret->msg_str, "<u:");
862 g_string_append (ret->msg_str, action);
863 g_string_append (ret->msg_str, " xmlns:u=\"");
864 g_string_append (ret->msg_str, service_type);
865 g_string_append (ret->msg_str, "\">");
870 /* Received response to action message */
872 action_got_response (SoupSession *session,
874 GUPnPServiceProxyAction *action)
876 const char *full_action;
878 switch (msg->status_code) {
879 case SOUP_STATUS_CANCELLED:
880 /* Silently return */
883 case SOUP_STATUS_METHOD_NOT_ALLOWED:
884 /* Retry with M-POST */
885 msg->method = "M-POST";
887 soup_message_headers_append
888 (msg->request_headers,
890 "\"http://schemas.xmlsoap.org/soap/envelope/\"; ns=s");
892 /* Rename "SOAPAction" to "s-SOAPAction" */
893 full_action = soup_message_headers_get_one
894 (msg->request_headers,
896 soup_message_headers_append (msg->request_headers,
899 soup_message_headers_remove (msg->request_headers,
903 soup_session_requeue_message (session, msg);
908 /* Success: Call callback */
909 action->callback (action->proxy, action, action->user_data);
915 /* Finishes an action message and sends it off */
917 finish_action_msg (GUPnPServiceProxyAction *action,
918 const char *action_name)
920 GUPnPContext *context;
921 SoupSession *session;
924 g_string_append (action->msg_str, "</u:");
925 g_string_append (action->msg_str, action_name);
926 g_string_append_c (action->msg_str, '>');
928 g_string_append (action->msg_str,
932 soup_message_set_request (action->msg,
933 "text/xml; charset=\"utf-8\"",
935 action->msg_str->str,
936 action->msg_str->len);
938 g_string_free (action->msg_str, FALSE);
940 /* We need to keep our own reference to the message as well,
941 * in order for send_action() to work. */
942 g_object_ref (action->msg);
944 /* Send the message */
945 context = gupnp_service_info_get_context
946 (GUPNP_SERVICE_INFO (action->proxy));
947 session = gupnp_context_get_session (context);
949 soup_session_queue_message (session,
951 (SoupSessionCallback) action_got_response,
955 /* Writes a parameter name and GValue pair to @msg */
957 write_in_parameter (const char *arg_name,
961 /* Write parameter pair */
962 xml_util_start_element (msg_str, arg_name);
963 gvalue_util_value_append_to_xml_string (value, msg_str);
964 xml_util_end_element (msg_str, arg_name);
968 * gupnp_service_proxy_begin_action_valist:
969 * @proxy: A #GUPnPServiceProxy
971 * @callback: (scope async) : The callback to call when sending the action has succeeded
973 * @user_data: User data for @callback
974 * @var_args: A va_list of tuples of in parameter name, in parameter type, and
977 * See gupnp_service_proxy_begin_action().
979 * Returns: (transfer none): A #GUPnPServiceProxyAction handle. This will
980 * be freed when calling gupnp_service_proxy_cancel_action() or
981 * gupnp_service_proxy_end_action_valist().
983 GUPnPServiceProxyAction *
984 gupnp_service_proxy_begin_action_valist
985 (GUPnPServiceProxy *proxy,
987 GUPnPServiceProxyActionCallback callback,
991 GUPnPServiceProxyAction *ret;
994 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), NULL);
995 g_return_val_if_fail (action, NULL);
996 g_return_val_if_fail (callback, NULL);
999 in_hash = g_hash_table_new_full (g_str_hash,
1003 VAR_ARGS_TO_IN_HASH_TABLE (var_args, in_hash);
1004 ret = gupnp_service_proxy_begin_action_hash (proxy,
1009 g_hash_table_unref (in_hash);
1015 * gupnp_service_proxy_begin_action_list:
1016 * @proxy: (transfer none): A #GUPnPServiceProxy
1017 * @action: An action
1018 * @in_names: (element-type utf8) (transfer none): #GList of 'in' parameter
1019 * names (as strings)
1020 * @in_values: (element-type GValue) (transfer none): #GList of values (as
1021 * #GValue) that line up with @in_names
1022 * @callback: (scope async) : The callback to call when sending the action has succeeded or
1024 * @user_data: User data for @callback
1026 * A variant of #gupnp_service_proxy_begin_action that takes lists of
1027 * in-parameter names, types and values.
1029 * Return value: (transfer none): A #GUPnPServiceProxyAction handle. This will
1030 * be freed when calling #gupnp_service_proxy_cancel_action or
1031 * #gupnp_service_proxy_end_action_list.
1033 GUPnPServiceProxyAction *
1034 gupnp_service_proxy_begin_action_list
1035 (GUPnPServiceProxy *proxy,
1039 GUPnPServiceProxyActionCallback callback,
1042 GUPnPServiceProxyAction *ret;
1046 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), NULL);
1047 g_return_val_if_fail (action, NULL);
1048 g_return_val_if_fail (callback, NULL);
1049 g_return_val_if_fail (g_list_length (in_names) ==
1050 g_list_length (in_values),
1053 /* Create message */
1054 ret = begin_action_msg (proxy, action, callback, user_data);
1057 callback (proxy, ret, user_data);
1065 for (names = in_names; names; names=names->next) {
1066 GValue* val = values->data;
1068 write_in_parameter (names->data,
1072 values = values->next;
1075 /* Finish and send off */
1076 finish_action_msg (ret, action);
1082 * gupnp_service_proxy_begin_action_hash:
1083 * @proxy: A #GUPnPServiceProxy
1084 * @action: An action
1085 * @callback: (scope async): The callback to call when sending the action has succeeded
1087 * @user_data: User data for @callback
1088 * @hash: (element-type utf8 GValue): A #GHashTable of in parameter name and #GValue pairs
1090 * See gupnp_service_proxy_begin_action(); this version takes a #GHashTable
1091 * for runtime generated parameter lists.
1093 * Return value: (transfer none): A #GUPnPServiceProxyAction handle. This will
1094 * be freed when calling gupnp_service_proxy_cancel_action() or
1095 * gupnp_service_proxy_end_action_hash().
1097 GUPnPServiceProxyAction *
1098 gupnp_service_proxy_begin_action_hash
1099 (GUPnPServiceProxy *proxy,
1101 GUPnPServiceProxyActionCallback callback,
1105 GUPnPServiceProxyAction *ret;
1107 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), NULL);
1108 g_return_val_if_fail (action, NULL);
1109 g_return_val_if_fail (callback, NULL);
1111 /* Create message */
1112 ret = begin_action_msg (proxy, action, callback, user_data);
1115 callback (proxy, ret, user_data);
1121 g_hash_table_foreach (hash, (GHFunc) write_in_parameter, ret->msg_str);
1123 /* Finish and send off */
1124 finish_action_msg (ret, action);
1130 * gupnp_service_proxy_end_action:
1131 * @proxy: A #GUPnPServiceProxy
1132 * @action: A #GUPnPServiceProxyAction handle
1133 * @error: The location where to store any error, or %NULL
1134 * @...: tuples of out parameter name, out parameter type, and out parameter
1135 * value location, terminated with %NULL. The out parameter values should be
1138 * Retrieves the result of @action. The out parameters in @Varargs will be
1139 * filled in, and if an error occurred, @error will be set. In case of
1140 * a UPnPError the error code will be the same in @error.
1142 * Return value: %TRUE on success.
1145 gupnp_service_proxy_end_action (GUPnPServiceProxy *proxy,
1146 GUPnPServiceProxyAction *action,
1153 va_start (var_args, error);
1154 ret = gupnp_service_proxy_end_action_valist (proxy,
1163 /* Checks an action response for errors and returns the parsed
1166 check_action_response (G_GNUC_UNUSED GUPnPServiceProxy *proxy,
1167 GUPnPServiceProxyAction *action,
1174 /* Check for errors */
1175 switch (action->msg->status_code) {
1176 case SOUP_STATUS_OK:
1177 case SOUP_STATUS_INTERNAL_SERVER_ERROR:
1180 _gupnp_error_set_server_error (error, action->msg);
1185 /* Parse response */
1186 response = xmlRecoverMemory (action->msg->response_body->data,
1187 action->msg->response_body->length);
1190 if (action->msg->status_code == SOUP_STATUS_OK) {
1193 GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1194 "Could not parse SOAP response");
1199 GUPNP_SERVER_ERROR_INTERNAL_SERVER_ERROR,
1200 action->msg->reason_phrase);
1206 /* Get parameter list */
1207 *params = xml_util_get_element ((xmlNode *) response,
1210 if (*params != NULL)
1211 *params = xml_util_real_node ((*params)->children);
1213 if (*params != NULL) {
1214 if (strcmp ((const char *) (*params)->name, "Header") == 0)
1215 *params = xml_util_real_node ((*params)->next);
1217 if (*params != NULL)
1218 if (strcmp ((const char *) (*params)->name, "Body") != 0)
1222 if (*params != NULL)
1223 *params = xml_util_real_node ((*params)->children);
1225 if (*params == NULL) {
1228 GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1229 "Invalid Envelope");
1231 xmlFreeDoc (response);
1236 /* Check whether we have a Fault */
1237 if (action->msg->status_code == SOUP_STATUS_INTERNAL_SERVER_ERROR) {
1241 param = xml_util_get_element (*params,
1249 GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1252 xmlFreeDoc (response);
1258 code = xml_util_get_child_element_content_int
1259 (param, "errorCode");
1263 GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1266 xmlFreeDoc (response);
1272 desc = xml_util_get_child_element_content_glib
1273 (param, "errorDescription");
1275 desc = g_strdup (action->msg->reason_phrase);
1277 g_set_error_literal (error,
1278 GUPNP_CONTROL_ERROR,
1284 xmlFreeDoc (response);
1292 /* Reads a value into the parameter name and initialised GValue pair
1295 read_out_parameter (const char *arg_name,
1301 /* Try to find a matching parameter in the response*/
1302 param = xml_util_get_element (params,
1306 g_warning ("Could not find variable \"%s\" in response",
1312 gvalue_util_set_value_from_xml_node (value, param);
1316 * gupnp_service_proxy_end_action_valist:
1317 * @proxy: A #GUPnPServiceProxy
1318 * @action: A #GUPnPServiceProxyAction handle
1319 * @error: The location where to store any error, or %NULL
1320 * @var_args: A va_list of tuples of out parameter name, out parameter type,
1321 * and out parameter value location. The out parameter values should be
1324 * See gupnp_service_proxy_end_action().
1326 * Return value: %TRUE on success.
1329 gupnp_service_proxy_end_action_valist (GUPnPServiceProxy *proxy,
1330 GUPnPServiceProxyAction *action,
1334 GHashTable *out_hash;
1335 va_list var_args_copy;
1337 GError *local_error;
1339 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1340 g_return_val_if_fail (action, FALSE);
1341 g_return_val_if_fail (proxy == action->proxy, FALSE);
1343 out_hash = g_hash_table_new_full (g_str_hash,
1347 G_VA_COPY (var_args_copy, var_args);
1348 VAR_ARGS_TO_OUT_HASH_TABLE (var_args, out_hash);
1350 result = gupnp_service_proxy_end_action_hash (proxy,
1355 if (local_error == NULL) {
1356 OUT_HASH_TABLE_TO_VAR_ARGS (out_hash, var_args_copy);
1358 g_propagate_error (error, local_error);
1360 va_end (var_args_copy);
1361 g_hash_table_unref (out_hash);
1367 * gupnp_service_proxy_end_action_list:
1368 * @proxy: A #GUPnPServiceProxy
1369 * @action: A #GUPnPServiceProxyAction handle
1370 * @error: The location where to store any error, or %NULL
1371 * @out_names: (element-type utf8) (transfer none): #GList of 'out' parameter
1372 * names (as strings)
1373 * @out_types: (element-type GType) (transfer none): #GList of types (as #GType)
1374 * that line up with @out_names
1375 * @out_values: (element-type GValue) (transfer full) (out): #GList of values
1376 * (as #GValue) that line up with @out_names and @out_types.
1378 * A variant of #gupnp_service_proxy_end_action that takes lists of
1379 * out-parameter names, types and place-holders for values. The returned list
1380 * in @out_values must be freed using #g_list_free and each element in it using
1381 * #g_value_unset and #g_slice_free.
1383 * Returns: %TRUE on success.
1386 gupnp_service_proxy_end_action_list (GUPnPServiceProxy *proxy,
1387 GUPnPServiceProxyAction *action,
1397 GList *out_values_list;
1399 out_values_list = NULL;
1401 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1402 g_return_val_if_fail (action, FALSE);
1403 g_return_val_if_fail (proxy == action->proxy, FALSE);
1405 /* Check for saved error from begin_action() */
1406 if (action->error) {
1408 *error = action->error;
1410 g_error_free (action->error);
1412 gupnp_service_proxy_action_free (action);
1417 /* Check response for errors and do initial parsing */
1418 response = check_action_response (proxy, action, ¶ms, error);
1419 if (response == NULL) {
1420 gupnp_service_proxy_action_free (action);
1425 /* Read arguments */
1427 for (names = out_names; names; names=names->next) {
1430 val = g_slice_new0 (GValue);
1431 g_value_init (val, (GType) types->data);
1433 read_out_parameter (names->data, val, params);
1435 out_values_list = g_list_append (out_values_list, val);
1437 types = types->next;
1440 *out_values = out_values_list;
1443 gupnp_service_proxy_action_free (action);
1445 xmlFreeDoc (response);
1451 * gupnp_service_proxy_end_action_hash:
1452 * @proxy: A #GUPnPServiceProxy
1453 * @action: A #GUPnPServiceProxyAction handle
1454 * @error: The location where to store any error, or %NULL
1455 * @hash: (element-type utf8 GValue) (inout) (transfer none): A #GHashTable of
1456 * out parameter name and initialised #GValue pairs
1458 * See gupnp_service_proxy_end_action(); this version takes a #GHashTable for
1459 * runtime generated parameter lists.
1461 * Return value: %TRUE on success.
1464 gupnp_service_proxy_end_action_hash (GUPnPServiceProxy *proxy,
1465 GUPnPServiceProxyAction *action,
1472 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1473 g_return_val_if_fail (action, FALSE);
1474 g_return_val_if_fail (proxy == action->proxy, FALSE);
1476 /* Check for saved error from begin_action() */
1477 if (action->error) {
1479 *error = action->error;
1481 g_error_free (action->error);
1483 gupnp_service_proxy_action_free (action);
1488 /* Check response for errors and do initial parsing */
1489 response = check_action_response (proxy, action, ¶ms, error);
1490 if (response == NULL) {
1491 gupnp_service_proxy_action_free (action);
1496 /* Read arguments */
1497 g_hash_table_foreach (hash, (GHFunc) read_out_parameter, params);
1500 gupnp_service_proxy_action_free (action);
1502 xmlFreeDoc (response);
1508 * gupnp_service_proxy_cancel_action:
1509 * @proxy: A #GUPnPServiceProxy
1510 * @action: A #GUPnPServiceProxyAction handle
1512 * Cancels @action, freeing the @action handle.
1515 gupnp_service_proxy_cancel_action (GUPnPServiceProxy *proxy,
1516 GUPnPServiceProxyAction *action)
1518 GUPnPContext *context;
1519 SoupSession *session;
1521 g_return_if_fail (GUPNP_IS_SERVICE_PROXY (proxy));
1522 g_return_if_fail (action);
1523 g_return_if_fail (proxy == action->proxy);
1525 if (action->msg != NULL) {
1526 context = gupnp_service_info_get_context
1527 (GUPNP_SERVICE_INFO (proxy));
1528 session = gupnp_context_get_session (context);
1530 soup_session_cancel_message (session,
1532 SOUP_STATUS_CANCELLED);
1535 if (action->error != NULL)
1536 g_error_free (action->error);
1538 gupnp_service_proxy_action_free (action);
1542 * gupnp_service_proxy_add_notify:
1543 * @proxy: A #GUPnPServiceProxy
1544 * @variable: The variable to add notification for
1545 * @type: The type of the variable
1546 * @callback: (scope async): The callback to call when @variable changes
1547 * @user_data: User data for @callback
1549 * Sets up @callback to be called whenever a change notification for
1550 * @variable is recieved.
1552 * Return value: %TRUE on success.
1555 gupnp_service_proxy_add_notify (GUPnPServiceProxy *proxy,
1556 const char *variable,
1558 GUPnPServiceProxyNotifyCallback callback,
1562 CallbackData *callback_data;
1564 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1565 g_return_val_if_fail (variable, FALSE);
1566 g_return_val_if_fail (type, FALSE);
1567 g_return_val_if_fail (callback, FALSE);
1569 /* See if we already have notifications set up for this variable */
1570 data = g_hash_table_lookup (proxy->priv->notify_hash, variable);
1572 /* No, create one */
1573 data = g_slice_new (NotifyData);
1576 data->callbacks = NULL;
1578 g_hash_table_insert (proxy->priv->notify_hash,
1579 g_strdup (variable),
1582 /* Yes, check that everything is sane .. */
1583 if (data->type != type) {
1584 g_warning ("A notification already exists for %s, but "
1585 "has type %s, not %s.",
1587 g_type_name (data->type),
1588 g_type_name (type));
1594 /* Append callback */
1595 callback_data = g_slice_new (CallbackData);
1597 callback_data->callback = callback;
1598 callback_data->user_data = user_data;
1600 data->callbacks = g_list_append (data->callbacks, callback_data);
1606 * gupnp_service_proxy_remove_notify:
1607 * @proxy: A #GUPnPServiceProxy
1608 * @variable: The variable to add notification for
1609 * @callback: (scope call): The callback to call when @variable changes
1610 * @user_data: User data for @callback
1612 * Cancels the variable change notification for @callback and @user_data.
1614 * This function must not be called directly or indirectly from a
1615 * #GUPnPServiceProxyNotifyCallback associated with this service proxy, even
1616 * if it is for another variable.
1618 * Return value: %TRUE on success.
1621 gupnp_service_proxy_remove_notify (GUPnPServiceProxy *proxy,
1622 const char *variable,
1623 GUPnPServiceProxyNotifyCallback callback,
1630 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1631 g_return_val_if_fail (variable, FALSE);
1632 g_return_val_if_fail (callback, FALSE);
1634 /* Look up NotifyData for variable */
1635 data = g_hash_table_lookup (proxy->priv->notify_hash, variable);
1637 g_warning ("No notifications found for variable %s",
1643 /* Look up our callback-user_data pair */
1646 for (l = data->callbacks; l; l = l->next) {
1647 CallbackData *callback_data;
1649 callback_data = l->data;
1651 if (callback_data->callback == callback &&
1652 callback_data->user_data == user_data) {
1654 g_slice_free (CallbackData, callback_data);
1657 g_list_delete_link (data->callbacks, l);
1658 if (data->callbacks == NULL) {
1659 /* No callbacks left: Remove from hash */
1660 g_hash_table_remove (proxy->priv->notify_hash,
1671 g_warning ("No such callback-user_data pair was found");
1677 emit_notification (GUPnPServiceProxy *proxy,
1681 GValue value = {0, };
1684 data = g_hash_table_lookup (proxy->priv->notify_hash, var_node->name);
1688 /* Make a GValue of the desired type */
1689 g_value_init (&value, data->type);
1691 if (!gvalue_util_set_value_from_xml_node (&value, var_node)) {
1692 g_value_unset (&value);
1697 /* Call callbacks */
1698 for (l = data->callbacks; l; l = l->next) {
1699 CallbackData *callback_data;
1701 callback_data = l->data;
1703 callback_data->callback (proxy,
1704 (const char *) var_node->name,
1706 callback_data->user_data);
1710 g_value_unset (&value);
1714 emit_notifications_for_doc (GUPnPServiceProxy *proxy,
1719 node = xmlDocGetRootElement (doc);
1721 /* Iterate over all provided properties */
1722 for (node = node->children; node; node = node->next) {
1725 /* Although according to the UPnP specs, there should be only
1726 * one variable node inside a 'property' node, we still need to
1727 * entertain the possibility of multiple variables inside it to
1728 * be compatible with implementations using older GUPnP.
1730 for (var_node = node->children;
1732 var_node = var_node->next) {
1733 if (strcmp ((char *) node->name, "property") == 0)
1734 emit_notification (proxy, var_node);
1739 /* Emit pending notifications. See comment below on why we do this. */
1741 emit_notifications (gpointer user_data)
1743 GUPnPServiceProxy *proxy = user_data;
1744 GList *pending_notify;
1745 gboolean resubscribe = FALSE;
1747 g_assert (user_data);
1749 if (proxy->priv->sid == NULL)
1751 if (G_LIKELY (proxy->priv->subscribed))
1752 /* subscription in progress, delay emision! */
1755 for (pending_notify = proxy->priv->pending_notifies;
1756 pending_notify != NULL;
1757 pending_notify = pending_notify->next) {
1758 EmitNotifyData *emit_notify_data;
1760 emit_notify_data = pending_notify->data;
1762 if (emit_notify_data->seq > proxy->priv->seq) {
1763 /* Error procedure on missed event according to
1764 * UDA 1.0, section 4.2, §5:
1765 * Re-subscribe to get a new SID and SEQ */
1771 /* Increment our own event sequence number */
1772 /* UDA 1.0, section 4.2, §3: To prevent overflow, SEQ is set to
1773 * 1, NOT 0, when encountering G_MAXUINT32. SEQ == 0 always
1774 * indicates the initial event message. */
1775 if (proxy->priv->seq < G_MAXUINT32)
1778 proxy->priv->seq = 1;
1780 if (G_LIKELY (proxy->priv->sid != NULL &&
1781 strcmp (emit_notify_data->sid,
1782 proxy->priv->sid) == 0))
1783 /* Our SID, entertain! */
1784 emit_notifications_for_doc (proxy,
1785 emit_notify_data->doc);
1789 while (proxy->priv->pending_notifies != NULL) {
1790 emit_notify_data_free (proxy->priv->pending_notifies->data);
1792 proxy->priv->pending_notifies =
1793 g_list_delete_link (proxy->priv->pending_notifies,
1794 proxy->priv->pending_notifies);
1797 proxy->priv->notify_idle_src = NULL;
1800 unsubscribe (proxy);
1808 * HTTP server received a message. Handle, if this was a NOTIFY
1809 * message with our SID.
1812 server_handler (G_GNUC_UNUSED SoupServer *soup_server,
1814 G_GNUC_UNUSED const char *server_path,
1815 G_GNUC_UNUSED GHashTable *query,
1816 G_GNUC_UNUSED SoupClientContext *soup_client,
1819 GUPnPServiceProxy *proxy;
1820 const char *hdr, *nt, *nts;
1825 EmitNotifyData *emit_notify_data;
1827 proxy = GUPNP_SERVICE_PROXY (user_data);
1829 if (strcmp (msg->method, GENA_METHOD_NOTIFY) != 0) {
1830 /* We don't implement this method */
1831 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
1836 nt = soup_message_headers_get_one (msg->request_headers, "NT");
1837 nts = soup_message_headers_get_one (msg->request_headers, "NTS");
1838 if (nt == NULL || nts == NULL) {
1839 /* Required header is missing */
1840 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
1845 if (strcmp (nt, "upnp:event") != 0 ||
1846 strcmp (nts, "upnp:propchange") != 0) {
1847 /* Unexpected header content */
1848 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1853 hdr = soup_message_headers_get_one (msg->request_headers, "SEQ");
1856 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1862 seq_parsed = strtoul (hdr, NULL, 10);
1863 if (errno != 0 || seq_parsed > G_MAXUINT32) {
1864 /* Invalid SEQ header value */
1865 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1870 seq = (guint32) seq_parsed;
1872 hdr = soup_message_headers_get_one (msg->request_headers, "SID");
1874 strlen (hdr) <= strlen ("uuid:") ||
1875 strncmp (hdr, "uuid:", strlen ("uuid:")) != 0) {
1877 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1882 /* Parse the actual XML message content */
1883 doc = xmlRecoverMemory (msg->request_body->data,
1884 msg->request_body->length);
1887 g_warning ("Failed to parse NOTIFY message body");
1889 soup_message_set_status (msg,
1890 SOUP_STATUS_INTERNAL_SERVER_ERROR);
1895 /* Get root propertyset element */
1896 node = xmlDocGetRootElement (doc);
1897 if (node == NULL || strcmp ((char *) node->name, "propertyset")) {
1898 /* Empty or unsupported */
1901 soup_message_set_status (msg, SOUP_STATUS_OK);
1907 * Some UPnP stacks (hello, myigd/1.0) block when sending a NOTIFY, so
1908 * call the callbacks in an idle handler so that if the client calls the
1909 * device in the notify callback the server can actually respond.
1911 emit_notify_data = emit_notify_data_new (hdr, seq, doc);
1913 proxy->priv->pending_notifies =
1914 g_list_append (proxy->priv->pending_notifies, emit_notify_data);
1915 if (!proxy->priv->notify_idle_src) {
1916 proxy->priv->notify_idle_src = g_idle_source_new();
1917 g_source_set_callback (proxy->priv->notify_idle_src,
1920 g_source_attach (proxy->priv->notify_idle_src,
1921 g_main_context_get_thread_default ());
1923 g_source_unref (proxy->priv->notify_idle_src);
1926 /* Everything went OK */
1927 soup_message_set_status (msg, SOUP_STATUS_OK);
1931 * Generates a timeout header for the subscription timeout specified
1932 * in our GUPnPContext.
1935 make_timeout_header (GUPnPContext *context)
1939 timeout = gupnp_context_get_subscription_timeout (context);
1941 return g_strdup_printf ("Second-%d", timeout);
1943 return g_strdup ("infinite");
1947 * Subscription expired.
1950 subscription_expire (gpointer user_data)
1952 GUPnPServiceProxy *proxy;
1953 GUPnPContext *context;
1955 SoupSession *session;
1956 char *sub_url, *timeout;
1958 proxy = GUPNP_SERVICE_PROXY (user_data);
1960 /* Reset timeout ID */
1961 proxy->priv->subscription_timeout_src = NULL;
1963 /* Send renewal message */
1964 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
1966 /* Create subscription message */
1967 sub_url = gupnp_service_info_get_event_subscription_url
1968 (GUPNP_SERVICE_INFO (proxy));
1970 msg = soup_message_new (GENA_METHOD_SUBSCRIBE, sub_url);
1974 g_return_val_if_fail (msg != NULL, FALSE);
1977 soup_message_headers_append (msg->request_headers,
1981 timeout = make_timeout_header (context);
1982 soup_message_headers_append (msg->request_headers,
1987 /* And send it off */
1988 proxy->priv->pending_messages =
1989 g_list_prepend (proxy->priv->pending_messages, msg);
1991 session = gupnp_context_get_session (context);
1993 soup_session_queue_message (session,
1995 (SoupSessionCallback)
1996 subscribe_got_response,
2003 * Received subscription response.
2006 subscribe_got_response (G_GNUC_UNUSED SoupSession *session,
2008 GUPnPServiceProxy *proxy)
2013 if (msg->status_code == SOUP_STATUS_CANCELLED)
2016 /* Remove from pending messages list */
2017 proxy->priv->pending_messages =
2018 g_list_remove (proxy->priv->pending_messages, msg);
2020 /* Check whether the subscription is still wanted */
2021 if (!proxy->priv->subscribed)
2025 g_free (proxy->priv->sid);
2026 proxy->priv->sid = NULL;
2028 /* Check message status */
2029 if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
2035 hdr = soup_message_headers_get_one (msg->response_headers,
2039 (GUPNP_EVENTING_ERROR,
2040 GUPNP_EVENTING_ERROR_SUBSCRIPTION_LOST,
2041 "No SID in SUBSCRIBE response");
2046 proxy->priv->sid = g_strdup (hdr);
2048 /* Figure out when the subscription times out */
2049 hdr = soup_message_headers_get_one (msg->response_headers,
2052 g_warning ("No Timeout in SUBSCRIBE response.");
2057 if (strncmp (hdr, "Second-", strlen ("Second-")) == 0) {
2058 /* We have a finite timeout */
2059 timeout = atoi (hdr + strlen ("Second-"));
2062 g_warning ("Invalid time-out specified. "
2063 "Assuming default value of %d.",
2064 GENA_DEFAULT_TIMEOUT);
2066 timeout = GENA_DEFAULT_TIMEOUT;
2069 /* We want to resubscribe before the subscription
2071 timeout = g_random_int_range (1, timeout / 2);
2073 /* Add actual timeout */
2074 proxy->priv->subscription_timeout_src =
2075 g_timeout_source_new_seconds (timeout);
2076 g_source_set_callback
2077 (proxy->priv->subscription_timeout_src,
2078 subscription_expire,
2080 g_source_attach (proxy->priv->subscription_timeout_src,
2081 g_main_context_get_thread_default ());
2083 g_source_unref (proxy->priv->subscription_timeout_src);
2086 GUPnPContext *context;
2089 /* Subscription failed. */
2090 error = g_error_new_literal
2091 (GUPNP_EVENTING_ERROR,
2092 GUPNP_EVENTING_ERROR_SUBSCRIPTION_FAILED,
2093 msg->reason_phrase);
2096 /* Remove listener */
2097 context = gupnp_service_info_get_context
2098 (GUPNP_SERVICE_INFO (proxy));
2100 server = gupnp_context_get_server (context);
2101 soup_server_remove_handler (server, proxy->priv->path);
2103 proxy->priv->subscribed = FALSE;
2105 g_object_notify (G_OBJECT (proxy), "subscribed");
2107 /* Emit subscription-lost */
2108 g_signal_emit (proxy,
2109 signals[SUBSCRIPTION_LOST],
2113 g_error_free (error);
2118 * Subscribe to this service.
2121 subscribe (GUPnPServiceProxy *proxy)
2123 GUPnPContext *context;
2125 SoupSession *session;
2127 const char *server_url;
2128 char *sub_url, *delivery_url, *timeout;
2130 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
2132 /* Create subscription message */
2133 sub_url = gupnp_service_info_get_event_subscription_url
2134 (GUPNP_SERVICE_INFO (proxy));
2137 if (sub_url != NULL) {
2138 msg = soup_message_new (GENA_METHOD_SUBSCRIBE, sub_url);
2146 /* Subscription failed. */
2147 proxy->priv->subscribed = FALSE;
2149 g_object_notify (G_OBJECT (proxy), "subscribed");
2151 /* Emit subscription-lost */
2152 error = g_error_new (GUPNP_SERVER_ERROR,
2153 GUPNP_SERVER_ERROR_INVALID_URL,
2154 "No valid subscription URL defined");
2156 g_signal_emit (proxy,
2157 signals[SUBSCRIPTION_LOST],
2161 g_error_free (error);
2167 server_url = _gupnp_context_get_server_url (context);
2168 delivery_url = g_strdup_printf ("<%s%s>",
2171 soup_message_headers_append (msg->request_headers,
2174 g_free (delivery_url);
2176 soup_message_headers_append (msg->request_headers,
2180 timeout = make_timeout_header (context);
2181 soup_message_headers_append (msg->request_headers,
2186 /* Listen for events */
2187 server = gupnp_context_get_server (context);
2189 soup_server_add_handler (server,
2195 /* And send our subscription message off */
2196 proxy->priv->pending_messages =
2197 g_list_prepend (proxy->priv->pending_messages, msg);
2199 session = gupnp_context_get_session (context);
2201 soup_session_queue_message (session,
2203 (SoupSessionCallback)
2204 subscribe_got_response,
2209 * Unsubscribe from this service.
2212 unsubscribe (GUPnPServiceProxy *proxy)
2214 GUPnPContext *context;
2215 SoupSession *session;
2218 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
2220 /* Remove server handler */
2221 server = gupnp_context_get_server (context);
2222 soup_server_remove_handler (server, proxy->priv->path);
2224 if (proxy->priv->sid != NULL) {
2228 /* Create unsubscription message */
2229 sub_url = gupnp_service_info_get_event_subscription_url
2230 (GUPNP_SERVICE_INFO (proxy));
2232 msg = soup_message_new (GENA_METHOD_UNSUBSCRIBE, sub_url);
2238 soup_message_headers_append (msg->request_headers,
2243 session = gupnp_context_get_session (context);
2245 soup_session_queue_message (session, msg, NULL, NULL);
2249 g_free (proxy->priv->sid);
2250 proxy->priv->sid = NULL;
2252 /* Reset sequence number */
2253 proxy->priv->seq = 0;
2256 /* Remove subscription timeout */
2257 if (proxy->priv->subscription_timeout_src) {
2258 g_source_destroy (proxy->priv->subscription_timeout_src);
2259 proxy->priv->subscription_timeout_src = NULL;
2264 * gupnp_service_proxy_set_subscribed:
2265 * @proxy: A #GUPnPServiceProxy
2266 * @subscribed: %TRUE to subscribe to this service
2268 * (Un)subscribes to this service.
2270 * Note that the relevant messages are not immediately sent but queued.
2271 * If you want to unsubcribe from this service because the application
2272 * is quitting, rely on automatic synchronised unsubscription on object
2273 * destruction instead.
2276 gupnp_service_proxy_set_subscribed (GUPnPServiceProxy *proxy,
2277 gboolean subscribed)
2279 g_return_if_fail (GUPNP_IS_SERVICE_PROXY (proxy));
2281 if (proxy->priv->subscribed == subscribed)
2284 proxy->priv->subscribed = subscribed;
2289 unsubscribe (proxy);
2291 g_object_notify (G_OBJECT (proxy), "subscribed");
2295 * gupnp_service_proxy_get_subscribed:
2296 * @proxy: A #GUPnPServiceProxy
2298 * Returns if we are subscribed to this service.
2300 * Return value: %TRUE if we are subscribed to this service, otherwise %FALSE.
2303 gupnp_service_proxy_get_subscribed (GUPnPServiceProxy *proxy)
2305 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
2307 return proxy->priv->subscribed;