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>
36 #include "gupnp-service-proxy.h"
37 #include "gupnp-context-private.h"
38 #include "gupnp-error.h"
39 #include "gupnp-error-private.h"
40 #include "gupnp-types.h"
42 #include "gena-protocol.h"
43 #include "http-headers.h"
44 #include "gvalue-util.h"
46 G_DEFINE_TYPE (GUPnPServiceProxy,
48 GUPNP_TYPE_SERVICE_INFO);
50 struct _GUPnPServiceProxyPrivate {
53 GList *pending_actions;
55 char *path; /* Path to this proxy */
57 char *sid; /* Subscription ID */
58 GSource *subscription_timeout_src;
60 int seq; /* Event sequence number */
62 GHashTable *notify_hash;
64 GList *pending_messages; /* Pending SoupMessages from this proxy */
66 GList *pending_notifies; /* Pending notifications to be sent (xmlDoc) */
67 GSource *notify_idle_src; /* Idle handler src of notification emiter */
80 static guint signals[LAST_SIGNAL];
82 struct _GUPnPServiceProxyAction {
83 GUPnPServiceProxy *proxy;
88 GUPnPServiceProxyActionCallback callback;
91 GError *error; /* If non-NULL, description of error that
92 occurred when preparing message */
102 GUPnPServiceProxyNotifyCallback callback;
114 subscribe_got_response (SoupSession *session,
116 GUPnPServiceProxy *proxy);
118 subscribe (GUPnPServiceProxy *proxy);
120 unsubscribe (GUPnPServiceProxy *proxy);
123 gupnp_service_proxy_action_free (GUPnPServiceProxyAction *action)
125 action->proxy->priv->pending_actions =
126 g_list_remove (action->proxy->priv->pending_actions, action);
128 if (action->msg != NULL)
129 g_object_unref (action->msg);
131 g_slice_free (GUPnPServiceProxyAction, action);
135 notify_data_free (NotifyData *data)
137 g_list_free (data->callbacks);
139 g_slice_free (NotifyData, data);
142 /* Steals doc reference */
143 static EmitNotifyData *
144 emit_notify_data_new (const char *sid,
148 EmitNotifyData *data;
150 data = g_slice_new (EmitNotifyData);
152 data->sid = g_strdup (sid);
160 emit_notify_data_free (EmitNotifyData *data)
163 xmlFreeDoc (data->doc);
165 g_slice_free (EmitNotifyData, data);
169 gupnp_service_proxy_init (GUPnPServiceProxy *proxy)
171 static int proxy_counter = 0;
173 proxy->priv = G_TYPE_INSTANCE_GET_PRIVATE (proxy,
174 GUPNP_TYPE_SERVICE_PROXY,
175 GUPnPServiceProxyPrivate);
177 /* Generate unique path */
178 proxy->priv->path = g_strdup_printf ("/ServiceProxy%d", proxy_counter);
181 /* Set up notify hash */
182 proxy->priv->notify_hash =
183 g_hash_table_new_full (g_str_hash,
186 (GDestroyNotify) notify_data_free);
190 gupnp_service_proxy_set_property (GObject *object,
195 GUPnPServiceProxy *proxy;
197 proxy = GUPNP_SERVICE_PROXY (object);
199 switch (property_id) {
200 case PROP_SUBSCRIBED:
201 gupnp_service_proxy_set_subscribed
202 (proxy, g_value_get_boolean (value));
205 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
211 gupnp_service_proxy_get_property (GObject *object,
216 GUPnPServiceProxy *proxy;
218 proxy = GUPNP_SERVICE_PROXY (object);
220 switch (property_id) {
221 case PROP_SUBSCRIBED:
222 g_value_set_boolean (value,
223 proxy->priv->subscribed);
226 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
232 gupnp_service_proxy_dispose (GObject *object)
234 GUPnPServiceProxy *proxy;
235 GObjectClass *object_class;
236 GUPnPContext *context;
237 SoupSession *session;
239 proxy = GUPNP_SERVICE_PROXY (object);
242 if (proxy->priv->subscribed) {
245 proxy->priv->subscribed = FALSE;
248 /* Cancel pending actions */
249 while (proxy->priv->pending_actions) {
250 GUPnPServiceProxyAction *action;
252 action = proxy->priv->pending_actions->data;
254 gupnp_service_proxy_cancel_action (proxy, action);
257 /* Cancel pending messages */
258 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
260 session = gupnp_context_get_session (context);
262 session = NULL; /* Not the first time dispose is called. */
264 while (proxy->priv->pending_messages) {
267 msg = proxy->priv->pending_messages->data;
269 soup_session_cancel_message (session,
271 SOUP_STATUS_CANCELLED);
273 proxy->priv->pending_messages =
274 g_list_delete_link (proxy->priv->pending_messages,
275 proxy->priv->pending_messages);
278 /* Cancel pending notifications */
279 if (proxy->priv->notify_idle_src) {
280 g_source_destroy (proxy->priv->notify_idle_src);
281 proxy->priv->notify_idle_src = NULL;
284 while (proxy->priv->pending_notifies) {
285 emit_notify_data_free (proxy->priv->pending_notifies->data);
286 proxy->priv->pending_notifies =
287 g_list_delete_link (proxy->priv->pending_notifies,
288 proxy->priv->pending_notifies);
292 object_class = G_OBJECT_CLASS (gupnp_service_proxy_parent_class);
293 object_class->dispose (object);
297 gupnp_service_proxy_finalize (GObject *object)
299 GUPnPServiceProxy *proxy;
300 GObjectClass *object_class;
302 proxy = GUPNP_SERVICE_PROXY (object);
304 g_free (proxy->priv->path);
306 g_hash_table_destroy (proxy->priv->notify_hash);
309 object_class = G_OBJECT_CLASS (gupnp_service_proxy_parent_class);
310 object_class->finalize (object);
314 gupnp_service_proxy_class_init (GUPnPServiceProxyClass *klass)
316 GObjectClass *object_class;
318 object_class = G_OBJECT_CLASS (klass);
320 object_class->set_property = gupnp_service_proxy_set_property;
321 object_class->get_property = gupnp_service_proxy_get_property;
322 object_class->dispose = gupnp_service_proxy_dispose;
323 object_class->finalize = gupnp_service_proxy_finalize;
325 g_type_class_add_private (klass, sizeof (GUPnPServiceProxyPrivate));
328 * GUPnPServiceProxy:subscribed:
330 * Whether we are subscribed to this service.
332 g_object_class_install_property
335 g_param_spec_boolean ("subscribed",
337 "Whether we are subscribed to this "
341 G_PARAM_STATIC_NAME |
342 G_PARAM_STATIC_NICK |
343 G_PARAM_STATIC_BLURB));
346 * GUPnPServiceProxy::subscription-lost:
347 * @proxy: The #GUPnPServiceProxy that received the signal
348 * @error: (type GError):A pointer to a #GError describing why the subscription has
351 * Emitted whenever the subscription to this service has been lost due
352 * to an error condition.
354 signals[SUBSCRIPTION_LOST] =
355 g_signal_new ("subscription-lost",
356 GUPNP_TYPE_SERVICE_PROXY,
358 G_STRUCT_OFFSET (GUPnPServiceProxyClass,
362 g_cclosure_marshal_VOID__POINTER,
369 * gupnp_service_proxy_send_action:
370 * @proxy: A #GUPnPServiceProxy
372 * @error: The location where to store any error, or %NULL
373 * @Varargs: tuples of in parameter name, in parameter type, and in parameter
374 * value, followed by %NULL, and then tuples of out parameter name,
375 * out parameter type, and out parameter value location, terminated with %NULL
377 * Sends action @action with parameters @Varargs to the service exposed by
378 * @proxy synchronously. If an error occurred, @error will be set. In case of
379 * a UPnPError the error code will be the same in @error.
381 * Return value: %TRUE if sending the action was succesful.
384 gupnp_service_proxy_send_action (GUPnPServiceProxy *proxy,
392 va_start (var_args, error);
393 ret = gupnp_service_proxy_send_action_valist (proxy,
403 stop_main_loop (GUPnPServiceProxy *proxy,
404 GUPnPServiceProxyAction *action,
407 g_main_loop_quit ((GMainLoop *) user_data);
410 /* This is a skip variant of G_VALUE_LCOPY, same as there is
411 * G_VALUE_COLLECT_SKIP for G_VALUE_COLLECT.
413 #define VALUE_LCOPY_SKIP(value_type, var_args) \
415 GTypeValueTable *_vtable = g_type_value_table_peek (value_type); \
416 const gchar *_lcopy_format = _vtable->lcopy_format; \
418 while (*_lcopy_format) { \
419 switch (*_lcopy_format++) { \
420 case G_VALUE_COLLECT_INT: \
421 va_arg ((var_args), gint); \
423 case G_VALUE_COLLECT_LONG: \
424 va_arg ((var_args), glong); \
426 case G_VALUE_COLLECT_INT64: \
427 va_arg ((var_args), gint64); \
429 case G_VALUE_COLLECT_DOUBLE: \
430 va_arg ((var_args), gdouble); \
432 case G_VALUE_COLLECT_POINTER: \
433 va_arg ((var_args), gpointer); \
436 g_assert_not_reached (); \
441 /* Initializes hash table to hold arg names as keys and GValues of
442 * given type, but without any specific value. Note that if you are
443 * going to use OUT_HASH_TABLE_TO_VAR_ARGS then you have to store a
444 * copy of var_args with G_VA_COPY before using this macro.
446 #define VAR_ARGS_TO_OUT_HASH_TABLE(var_args, hash) \
448 const gchar *arg_name = va_arg (var_args, const gchar *); \
450 while (arg_name != NULL) { \
451 GValue *value = g_new0 (GValue, 1); \
452 GType type = va_arg (var_args, GType); \
454 VALUE_LCOPY_SKIP (type, var_args); \
455 g_value_init (value, type); \
456 g_hash_table_insert (hash, g_strdup (arg_name), value); \
457 arg_name = va_arg (var_args, const gchar *); \
461 /* Initializes hash table to hold arg names as keys and GValues of
462 * given type and value.
464 #define VAR_ARGS_TO_IN_HASH_TABLE(var_args, hash) \
466 const gchar *arg_name = va_arg (var_args, const gchar *); \
468 while (arg_name != NULL) { \
469 GValue *value = g_new0 (GValue, 1); \
470 gchar *error = NULL; \
471 GType type = va_arg (var_args, GType); \
473 G_VALUE_COLLECT_INIT (value, \
476 G_VALUE_NOCOPY_CONTENTS, \
478 if (error == NULL) { \
479 g_hash_table_insert (hash, g_strdup (arg_name), value); \
481 g_warning ("Failed to collect value of type %s for %s: %s", \
482 g_type_name (type), \
487 arg_name = va_arg (var_args, const gchar *); \
491 /* Puts values stored in hash table with GValues into var args.
493 #define OUT_HASH_TABLE_TO_VAR_ARGS(hash, var_args) \
495 const gchar *arg_name = va_arg (var_args, const gchar *); \
497 while (arg_name != NULL) { \
498 GValue *value = g_hash_table_lookup (hash, arg_name); \
499 GType type = va_arg (var_args, GType); \
501 if (value == NULL) { \
502 g_warning ("No value for %s", arg_name); \
503 G_VALUE_COLLECT_SKIP (type, var_args); \
504 } else if (G_VALUE_TYPE (value) != type) { \
505 g_warning ("Different GType in value (%s) and in var args (%s) for %s.", \
506 G_VALUE_TYPE_NAME (value), \
507 g_type_name (type), \
510 gchar *error = NULL; \
512 G_VALUE_LCOPY (value, var_args, 0, &error); \
513 if (error != NULL) { \
514 g_warning ("Failed to lcopy the value of type %s for %s: %s", \
515 g_type_name (type), \
521 arg_name = va_arg (var_args, const gchar *); \
525 /* GDestroyNotify for GHashTable holding GValues.
528 value_free (gpointer data)
530 GValue *value = (GValue *) data;
532 g_value_unset (value);
537 * gupnp_service_proxy_send_action_valist:
538 * @proxy: A #GUPnPServiceProxy
540 * @error: The location where to store any error, or %NULL
541 * @var_args: va_list of tuples of in parameter name, in parameter type, and in
542 * parameter value, followed by %NULL, and then tuples of out parameter name,
543 * out parameter type, and out parameter value location
545 * See gupnp_service_proxy_send_action().
547 * Return value: %TRUE if sending the action was succesful.
550 gupnp_service_proxy_send_action_valist (GUPnPServiceProxy *proxy,
556 GHashTable *out_hash;
557 va_list var_args_copy;
561 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
562 g_return_val_if_fail (action, FALSE);
564 in_hash = g_hash_table_new_full (g_str_hash,
568 VAR_ARGS_TO_IN_HASH_TABLE (var_args, in_hash);
569 G_VA_COPY (var_args_copy, var_args);
570 out_hash = g_hash_table_new_full (g_str_hash,
574 VAR_ARGS_TO_OUT_HASH_TABLE (var_args, out_hash);
577 result = gupnp_service_proxy_send_action_hash (proxy,
583 if (local_error == NULL) {
584 OUT_HASH_TABLE_TO_VAR_ARGS (out_hash, var_args_copy);
586 g_propagate_error (error, local_error);
588 va_end (var_args_copy);
589 g_hash_table_unref (in_hash);
590 g_hash_table_unref (out_hash);
596 * gupnp_service_proxy_send_action_hash:
597 * @proxy: A #GUPnPServiceProxy
599 * @error: The location where to store any error, or %NULL
600 * @in_hash: (element-type utf8 GValue) (transfer none): A #GHashTable of in
601 * parameter name and #GValue pairs
602 * @out_hash: (inout) (element-type utf8 GValue) (transfer full): A #GHashTable
603 * of out parameter name and initialized #GValue pairs
605 * See gupnp_service_proxy_send_action(); this version takes a pair of
606 * #GHashTable<!-- -->s for runtime determined parameter lists.
608 * Return value: %TRUE if sending the action was succesful.
611 gupnp_service_proxy_send_action_hash (GUPnPServiceProxy *proxy,
615 GHashTable *out_hash)
617 GMainLoop *main_loop;
618 GUPnPServiceProxyAction *handle;
620 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
621 g_return_val_if_fail (action, FALSE);
623 main_loop = g_main_loop_new (g_main_context_get_thread_default (),
626 handle = gupnp_service_proxy_begin_action_hash (proxy,
632 g_main_loop_unref (main_loop);
637 /* Loop till we get a reply (or time out) */
638 if (g_main_loop_is_running (main_loop))
639 g_main_loop_run (main_loop);
641 g_main_loop_unref (main_loop);
643 if (!gupnp_service_proxy_end_action_hash (proxy,
653 * gupnp_service_proxy_send_action_list:
654 * @proxy: (transfer none) : A #GUPnPServiceProxy
656 * @error: The location where to store any error, or %NULL
657 * @in_names: (element-type utf8) (transfer none): #GList of 'in' parameter
659 * @in_values: (element-type GValue) (transfer none): #GList of values (as
660 * #GValue) that line up with @in_names
661 * @out_names: (element-type utf8) (transfer none): #GList of 'out' parameter
663 * @out_types: (element-type GType) (transfer none): #GList of types (as #GType)
664 * that line up with @out_names
665 * @out_values: (element-type GValue) (transfer full) (out): #GList of values
666 * (as #GValue) that line up with @out_names and @out_types.
668 * The synchronous variant of #gupnp_service_proxy_begin_action_list and
669 * #gupnp_service_proxy_end_action_list.
671 * Return value: %TRUE if sending the action was succesful.
674 gupnp_service_proxy_send_action_list (GUPnPServiceProxy *proxy,
683 GMainLoop *main_loop;
684 GUPnPServiceProxyAction *handle;
686 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
687 g_return_val_if_fail (action, FALSE);
689 main_loop = g_main_loop_new (g_main_context_get_thread_default (),
692 handle = gupnp_service_proxy_begin_action_list (proxy,
699 g_main_loop_unref (main_loop);
704 /* Loop till we get a reply (or time out) */
705 if (g_main_loop_is_running (main_loop))
706 g_main_loop_run (main_loop);
708 g_main_loop_unref (main_loop);
710 if (!gupnp_service_proxy_end_action_list (proxy,
722 * gupnp_service_proxy_begin_action:
723 * @proxy: A #GUPnPServiceProxy
725 * @callback: (scope async): The callback to call when sending the action has succeeded
727 * @user_data: User data for @callback
728 * @Varargs: tuples of in parameter name, in parameter type, and in parameter
729 * value, terminated with %NULL
731 * Sends action @action with parameters @Varargs to the service exposed by
732 * @proxy asynchronously, calling @callback on completion. From @callback, call
733 * gupnp_service_proxy_end_action() to check for errors, to retrieve return
734 * values, and to free the #GUPnPServiceProxyAction.
736 * Returns: (transfer none): A #GUPnPServiceProxyAction handle. This will be freed when
737 * gupnp_service_proxy_cancel_action() or
738 * gupnp_service_proxy_end_action_valist().
740 GUPnPServiceProxyAction *
741 gupnp_service_proxy_begin_action (GUPnPServiceProxy *proxy,
743 GUPnPServiceProxyActionCallback callback,
748 GUPnPServiceProxyAction *ret;
750 va_start (var_args, user_data);
751 ret = gupnp_service_proxy_begin_action_valist (proxy,
761 /* Begins a basic action message */
762 static GUPnPServiceProxyAction *
763 begin_action_msg (GUPnPServiceProxy *proxy,
765 GUPnPServiceProxyActionCallback callback,
768 GUPnPServiceProxyAction *ret;
769 char *control_url, *full_action;
770 const char *service_type;
772 /* Create action structure */
773 ret = g_slice_new (GUPnPServiceProxyAction);
777 ret->callback = callback;
778 ret->user_data = user_data;
784 proxy->priv->pending_actions =
785 g_list_prepend (proxy->priv->pending_actions, ret);
787 /* Make sure we have a service type */
788 service_type = gupnp_service_info_get_service_type
789 (GUPNP_SERVICE_INFO (proxy));
790 if (service_type == NULL) {
791 ret->error = g_error_new (GUPNP_SERVER_ERROR,
792 GUPNP_SERVER_ERROR_OTHER,
793 "No service type defined");
799 control_url = gupnp_service_info_get_control_url
800 (GUPNP_SERVICE_INFO (proxy));
802 if (control_url != NULL) {
803 ret->msg = soup_message_new (SOUP_METHOD_POST, control_url);
805 g_free (control_url);
808 if (ret->msg == NULL) {
809 ret->error = g_error_new (GUPNP_SERVER_ERROR,
810 GUPNP_SERVER_ERROR_INVALID_URL,
811 "No valid control URL defined");
817 full_action = g_strdup_printf ("\"%s#%s\"", service_type, action);
818 soup_message_headers_append (ret->msg->request_headers,
821 g_free (full_action);
823 /* Specify language */
824 http_request_set_accept_language (ret->msg);
826 /* Accept gzip encoding */
827 soup_message_headers_append (ret->msg->request_headers,
828 "Accept-Encoding", "gzip");
830 /* Set up envelope */
831 ret->msg_str = xml_util_new_string ();
833 g_string_append (ret->msg_str,
834 "<?xml version=\"1.0\"?>"
835 "<s:Envelope xmlns:s="
836 "\"http://schemas.xmlsoap.org/soap/envelope/\" "
838 "\"http://schemas.xmlsoap.org/soap/encoding/\">"
841 g_string_append (ret->msg_str, "<u:");
842 g_string_append (ret->msg_str, action);
843 g_string_append (ret->msg_str, " xmlns:u=\"");
844 g_string_append (ret->msg_str, service_type);
845 g_string_append (ret->msg_str, "\">");
850 /* Received response to action message */
852 action_got_response (SoupSession *session,
854 GUPnPServiceProxyAction *action)
856 const char *full_action;
858 switch (msg->status_code) {
859 case SOUP_STATUS_CANCELLED:
860 /* Silently return */
863 case SOUP_STATUS_METHOD_NOT_ALLOWED:
864 /* Retry with M-POST */
865 msg->method = "M-POST";
867 soup_message_headers_append
868 (msg->request_headers,
870 "\"http://schemas.xmlsoap.org/soap/envelope/\"; ns=s");
872 /* Rename "SOAPAction" to "s-SOAPAction" */
873 full_action = soup_message_headers_get_one
874 (msg->request_headers,
876 soup_message_headers_append (msg->request_headers,
879 soup_message_headers_remove (msg->request_headers,
883 soup_session_requeue_message (session, msg);
888 /* Success: Call callback */
889 action->callback (action->proxy, action, action->user_data);
895 /* Finishes an action message and sends it off */
897 finish_action_msg (GUPnPServiceProxyAction *action,
898 const char *action_name)
900 GUPnPContext *context;
901 SoupSession *session;
904 g_string_append (action->msg_str, "</u:");
905 g_string_append (action->msg_str, action_name);
906 g_string_append_c (action->msg_str, '>');
908 g_string_append (action->msg_str,
912 soup_message_set_request (action->msg,
913 "text/xml; charset=\"utf-8\"",
915 action->msg_str->str,
916 action->msg_str->len);
918 g_string_free (action->msg_str, FALSE);
920 /* We need to keep our own reference to the message as well,
921 * in order for send_action() to work. */
922 g_object_ref (action->msg);
924 /* Send the message */
925 context = gupnp_service_info_get_context
926 (GUPNP_SERVICE_INFO (action->proxy));
927 session = gupnp_context_get_session (context);
929 soup_session_queue_message (session,
931 (SoupSessionCallback) action_got_response,
935 /* Writes a parameter name and GValue pair to @msg */
937 write_in_parameter (const char *arg_name,
941 /* Write parameter pair */
942 xml_util_start_element (msg_str, arg_name);
943 gvalue_util_value_append_to_xml_string (value, msg_str);
944 xml_util_end_element (msg_str, arg_name);
948 * gupnp_service_proxy_begin_action_valist:
949 * @proxy: A #GUPnPServiceProxy
951 * @callback: (scope async) : The callback to call when sending the action has succeeded
953 * @user_data: User data for @callback
954 * @var_args: A va_list of tuples of in parameter name, in parameter type, and
957 * See gupnp_service_proxy_begin_action().
959 * Returns: (transfer none): A #GUPnPServiceProxyAction handle. This will
960 * be freed when calling gupnp_service_proxy_cancel_action() or
961 * gupnp_service_proxy_end_action_valist().
963 GUPnPServiceProxyAction *
964 gupnp_service_proxy_begin_action_valist
965 (GUPnPServiceProxy *proxy,
967 GUPnPServiceProxyActionCallback callback,
971 GUPnPServiceProxyAction *ret;
974 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), NULL);
975 g_return_val_if_fail (action, NULL);
976 g_return_val_if_fail (callback, NULL);
979 in_hash = g_hash_table_new_full (g_str_hash,
983 VAR_ARGS_TO_IN_HASH_TABLE (var_args, in_hash);
984 ret = gupnp_service_proxy_begin_action_hash (proxy,
989 g_hash_table_unref (in_hash);
995 * gupnp_service_proxy_begin_action_list:
996 * @proxy: (transfer none): A #GUPnPServiceProxy
998 * @in_names: (element-type utf8) (transfer none): #GList of 'in' parameter
1000 * @in_values: (element-type GValue) (transfer none): #GList of values (as
1001 * #GValue) that line up with @in_names
1002 * @callback: (scope async) : The callback to call when sending the action has succeeded or
1004 * @user_data: User data for @callback
1006 * A variant of #gupnp_service_proxy_begin_action that takes lists of
1007 * in-parameter names, types and values.
1009 * Return value: (transfer none): A #GUPnPServiceProxyAction handle. This will
1010 * be freed when calling #gupnp_service_proxy_cancel_action or
1011 * #gupnp_service_proxy_end_action_list.
1013 GUPnPServiceProxyAction *
1014 gupnp_service_proxy_begin_action_list
1015 (GUPnPServiceProxy *proxy,
1019 GUPnPServiceProxyActionCallback callback,
1022 GUPnPServiceProxyAction *ret;
1026 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), NULL);
1027 g_return_val_if_fail (action, NULL);
1028 g_return_val_if_fail (callback, NULL);
1029 g_return_val_if_fail (g_list_length (in_names) ==
1030 g_list_length (in_values),
1033 /* Create message */
1034 ret = begin_action_msg (proxy, action, callback, user_data);
1037 callback (proxy, ret, user_data);
1045 for (names = in_names; names; names=names->next) {
1046 GValue* val = values->data;
1048 write_in_parameter (names->data,
1052 values = values->next;
1055 /* Finish and send off */
1056 finish_action_msg (ret, action);
1062 * gupnp_service_proxy_begin_action_hash:
1063 * @proxy: A #GUPnPServiceProxy
1064 * @action: An action
1065 * @callback: (scope async): The callback to call when sending the action has succeeded
1067 * @user_data: User data for @callback
1068 * @hash: (element-type utf8 GValue): A #GHashTable of in parameter name and #GValue pairs
1070 * See gupnp_service_proxy_begin_action(); this version takes a #GHashTable
1071 * for runtime generated parameter lists.
1073 * Return value: (transfer none): A #GUPnPServiceProxyAction handle. This will
1074 * be freed when calling gupnp_service_proxy_cancel_action() or
1075 * gupnp_service_proxy_end_action_hash().
1077 GUPnPServiceProxyAction *
1078 gupnp_service_proxy_begin_action_hash
1079 (GUPnPServiceProxy *proxy,
1081 GUPnPServiceProxyActionCallback callback,
1085 GUPnPServiceProxyAction *ret;
1087 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), NULL);
1088 g_return_val_if_fail (action, NULL);
1089 g_return_val_if_fail (callback, NULL);
1091 /* Create message */
1092 ret = begin_action_msg (proxy, action, callback, user_data);
1095 callback (proxy, ret, user_data);
1101 g_hash_table_foreach (hash, (GHFunc) write_in_parameter, ret->msg_str);
1103 /* Finish and send off */
1104 finish_action_msg (ret, action);
1110 * gupnp_service_proxy_end_action:
1111 * @proxy: A #GUPnPServiceProxy
1112 * @action: A #GUPnPServiceProxyAction handle
1113 * @error: The location where to store any error, or %NULL
1114 * @Varargs: tuples of out parameter name, out parameter type, and out parameter
1115 * value location, terminated with %NULL. The out parameter values should be
1118 * Retrieves the result of @action. The out parameters in @Varargs will be
1119 * filled in, and if an error occurred, @error will be set. In case of
1120 * a UPnPError the error code will be the same in @error.
1122 * Return value: %TRUE on success.
1125 gupnp_service_proxy_end_action (GUPnPServiceProxy *proxy,
1126 GUPnPServiceProxyAction *action,
1133 va_start (var_args, error);
1134 ret = gupnp_service_proxy_end_action_valist (proxy,
1143 /* Checks an action response for errors and returns the parsed
1146 check_action_response (GUPnPServiceProxy *proxy,
1147 GUPnPServiceProxyAction *action,
1154 /* Check for errors */
1155 switch (action->msg->status_code) {
1156 case SOUP_STATUS_OK:
1157 case SOUP_STATUS_INTERNAL_SERVER_ERROR:
1160 _gupnp_error_set_server_error (error, action->msg);
1165 /* Parse response */
1166 response = xmlRecoverMemory (action->msg->response_body->data,
1167 action->msg->response_body->length);
1170 if (action->msg->status_code == SOUP_STATUS_OK) {
1173 GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1174 "Could not parse SOAP response");
1179 GUPNP_SERVER_ERROR_INTERNAL_SERVER_ERROR,
1180 action->msg->reason_phrase);
1186 /* Get parameter list */
1187 *params = xml_util_get_element ((xmlNode *) response,
1190 if (*params != NULL)
1191 *params = xml_util_real_node ((*params)->children);
1193 if (*params != NULL) {
1194 if (strcmp ((const char *) (*params)->name, "Header") == 0)
1195 *params = xml_util_real_node ((*params)->next);
1197 if (*params != NULL)
1198 if (strcmp ((const char *) (*params)->name, "Body") != 0)
1202 if (*params != NULL)
1203 *params = xml_util_real_node ((*params)->children);
1205 if (*params == NULL) {
1208 GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1209 "Invalid Envelope");
1211 xmlFreeDoc (response);
1216 /* Check whether we have a Fault */
1217 if (action->msg->status_code == SOUP_STATUS_INTERNAL_SERVER_ERROR) {
1221 param = xml_util_get_element (*params,
1229 GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1232 xmlFreeDoc (response);
1238 code = xml_util_get_child_element_content_int
1239 (param, "errorCode");
1243 GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1246 xmlFreeDoc (response);
1252 desc = xml_util_get_child_element_content_glib
1253 (param, "errorDescription");
1255 desc = g_strdup (action->msg->reason_phrase);
1257 g_set_error_literal (error,
1258 GUPNP_CONTROL_ERROR,
1264 xmlFreeDoc (response);
1272 /* Reads a value into the parameter name and initialised GValue pair
1275 read_out_parameter (const char *arg_name,
1281 /* Try to find a matching parameter in the response*/
1282 param = xml_util_get_element (params,
1286 g_warning ("Could not find variable \"%s\" in response",
1292 gvalue_util_set_value_from_xml_node (value, param);
1296 * gupnp_service_proxy_end_action_valist:
1297 * @proxy: A #GUPnPServiceProxy
1298 * @action: A #GUPnPServiceProxyAction handle
1299 * @error: The location where to store any error, or %NULL
1300 * @var_args: A va_list of tuples of out parameter name, out parameter type,
1301 * and out parameter value location. The out parameter values should be
1304 * See gupnp_service_proxy_end_action().
1306 * Return value: %TRUE on success.
1309 gupnp_service_proxy_end_action_valist (GUPnPServiceProxy *proxy,
1310 GUPnPServiceProxyAction *action,
1314 GHashTable *out_hash;
1315 va_list var_args_copy;
1317 GError *local_error;
1319 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1320 g_return_val_if_fail (action, FALSE);
1321 g_return_val_if_fail (proxy == action->proxy, FALSE);
1323 out_hash = g_hash_table_new_full (g_str_hash,
1327 G_VA_COPY (var_args_copy, var_args);
1328 VAR_ARGS_TO_OUT_HASH_TABLE (var_args, out_hash);
1330 result = gupnp_service_proxy_end_action_hash (proxy,
1335 if (local_error == NULL) {
1336 OUT_HASH_TABLE_TO_VAR_ARGS (out_hash, var_args_copy);
1338 g_propagate_error (error, local_error);
1340 va_end (var_args_copy);
1341 g_hash_table_unref (out_hash);
1347 * gupnp_service_proxy_end_action_list:
1348 * @proxy: A #GUPnPServiceProxy
1349 * @action: A #GUPnPServiceProxyAction handle
1350 * @error: The location where to store any error, or %NULL
1351 * @out_names: (element-type utf8) (transfer none): #GList of 'out' parameter
1352 * names (as strings)
1353 * @out_types: (element-type GType) (transfer none): #GList of types (as #GType)
1354 * that line up with @out_names
1355 * @out_values: (element-type GValue) (transfer full) (out): #GList of values
1356 * (as #GValue) that line up with @out_names and @out_types.
1358 * A variant of #gupnp_service_proxy_end_action that takes lists of
1359 * out-parameter names, types and place-holders for values. The returned list
1360 * in @out_values must be freed using #g_list_free and each element in it using
1361 * #g_value_unset and #g_slice_free.
1363 * Return value : %TRUE on success.
1366 gupnp_service_proxy_end_action_list (GUPnPServiceProxy *proxy,
1367 GUPnPServiceProxyAction *action,
1377 GList *out_values_list;
1379 out_values_list = NULL;
1381 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1382 g_return_val_if_fail (action, FALSE);
1383 g_return_val_if_fail (proxy == action->proxy, FALSE);
1385 /* Check for saved error from begin_action() */
1386 if (action->error) {
1388 *error = action->error;
1390 g_error_free (action->error);
1392 gupnp_service_proxy_action_free (action);
1397 /* Check response for errors and do initial parsing */
1398 response = check_action_response (proxy, action, ¶ms, error);
1399 if (response == NULL) {
1400 gupnp_service_proxy_action_free (action);
1405 /* Read arguments */
1407 for (names = out_names; names; names=names->next) {
1410 val = g_slice_new0 (GValue);
1411 g_value_init (val, (GType) types->data);
1413 read_out_parameter (names->data, val, params);
1415 out_values_list = g_list_append (out_values_list, val);
1417 types = types->next;
1420 *out_values = out_values_list;
1423 gupnp_service_proxy_action_free (action);
1425 xmlFreeDoc (response);
1431 * gupnp_service_proxy_end_action_hash:
1432 * @proxy: A #GUPnPServiceProxy
1433 * @action: A #GUPnPServiceProxyAction handle
1434 * @error: The location where to store any error, or %NULL
1435 * @hash: (element-type utf8 GValue) (inout) (transfer none): A #GHashTable of
1436 * out parameter name and initialised #GValue pairs
1438 * See gupnp_service_proxy_end_action(); this version takes a #GHashTable for
1439 * runtime generated parameter lists.
1441 * Return value: %TRUE on success.
1444 gupnp_service_proxy_end_action_hash (GUPnPServiceProxy *proxy,
1445 GUPnPServiceProxyAction *action,
1452 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1453 g_return_val_if_fail (action, FALSE);
1454 g_return_val_if_fail (proxy == action->proxy, FALSE);
1456 /* Check for saved error from begin_action() */
1457 if (action->error) {
1459 *error = action->error;
1461 g_error_free (action->error);
1463 gupnp_service_proxy_action_free (action);
1468 /* Check response for errors and do initial parsing */
1469 response = check_action_response (proxy, action, ¶ms, error);
1470 if (response == NULL) {
1471 gupnp_service_proxy_action_free (action);
1476 /* Read arguments */
1477 g_hash_table_foreach (hash, (GHFunc) read_out_parameter, params);
1480 gupnp_service_proxy_action_free (action);
1482 xmlFreeDoc (response);
1488 * gupnp_service_proxy_cancel_action:
1489 * @proxy: A #GUPnPServiceProxy
1490 * @action: A #GUPnPServiceProxyAction handle
1492 * Cancels @action, freeing the @action handle.
1495 gupnp_service_proxy_cancel_action (GUPnPServiceProxy *proxy,
1496 GUPnPServiceProxyAction *action)
1498 GUPnPContext *context;
1499 SoupSession *session;
1501 g_return_if_fail (GUPNP_IS_SERVICE_PROXY (proxy));
1502 g_return_if_fail (action);
1503 g_return_if_fail (proxy == action->proxy);
1505 if (action->msg != NULL) {
1506 context = gupnp_service_info_get_context
1507 (GUPNP_SERVICE_INFO (proxy));
1508 session = gupnp_context_get_session (context);
1510 soup_session_cancel_message (session,
1512 SOUP_STATUS_CANCELLED);
1515 if (action->error != NULL)
1516 g_error_free (action->error);
1518 gupnp_service_proxy_action_free (action);
1522 * gupnp_service_proxy_add_notify:
1523 * @proxy: A #GUPnPServiceProxy
1524 * @variable: The variable to add notification for
1525 * @type: The type of the variable
1526 * @callback: (scope async): The callback to call when @variable changes
1527 * @user_data: User data for @callback
1529 * Sets up @callback to be called whenever a change notification for
1530 * @variable is recieved.
1532 * Return value: %TRUE on success.
1535 gupnp_service_proxy_add_notify (GUPnPServiceProxy *proxy,
1536 const char *variable,
1538 GUPnPServiceProxyNotifyCallback callback,
1542 CallbackData *callback_data;
1544 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1545 g_return_val_if_fail (variable, FALSE);
1546 g_return_val_if_fail (type, FALSE);
1547 g_return_val_if_fail (callback, FALSE);
1549 /* See if we already have notifications set up for this variable */
1550 data = g_hash_table_lookup (proxy->priv->notify_hash, variable);
1552 /* No, create one */
1553 data = g_slice_new (NotifyData);
1556 data->callbacks = NULL;
1558 g_hash_table_insert (proxy->priv->notify_hash,
1559 g_strdup (variable),
1562 /* Yes, check that everything is sane .. */
1563 if (data->type != type) {
1564 g_warning ("A notification already exists for %s, but "
1565 "has type %s, not %s.",
1567 g_type_name (data->type),
1568 g_type_name (type));
1574 /* Append callback */
1575 callback_data = g_slice_new (CallbackData);
1577 callback_data->callback = callback;
1578 callback_data->user_data = user_data;
1580 data->callbacks = g_list_append (data->callbacks, callback_data);
1586 * gupnp_service_proxy_remove_notify:
1587 * @proxy: A #GUPnPServiceProxy
1588 * @variable: The variable to add notification for
1589 * @callback: (scope call): The callback to call when @variable changes
1590 * @user_data: User data for @callback
1592 * Cancels the variable change notification for @callback and @user_data.
1594 * This function must not be called directly or indirectly from a
1595 * #GUPnPServiceProxyNotifyCallback associated with this service proxy, even
1596 * if it is for another variable.
1598 * Return value: %TRUE on success.
1601 gupnp_service_proxy_remove_notify (GUPnPServiceProxy *proxy,
1602 const char *variable,
1603 GUPnPServiceProxyNotifyCallback callback,
1610 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1611 g_return_val_if_fail (variable, FALSE);
1612 g_return_val_if_fail (callback, FALSE);
1614 /* Look up NotifyData for variable */
1615 data = g_hash_table_lookup (proxy->priv->notify_hash, variable);
1617 g_warning ("No notifications found for variable %s",
1623 /* Look up our callback-user_data pair */
1626 for (l = data->callbacks; l; l = l->next) {
1627 CallbackData *callback_data;
1629 callback_data = l->data;
1631 if (callback_data->callback == callback &&
1632 callback_data->user_data == user_data) {
1634 g_slice_free (CallbackData, callback_data);
1637 g_list_delete_link (data->callbacks, l);
1638 if (data->callbacks == NULL) {
1639 /* No callbacks left: Remove from hash */
1640 g_hash_table_remove (proxy->priv->notify_hash,
1651 g_warning ("No such callback-user_data pair was found");
1657 emit_notification (GUPnPServiceProxy *proxy,
1661 GValue value = {0, };
1664 data = g_hash_table_lookup (proxy->priv->notify_hash, var_node->name);
1668 /* Make a GValue of the desired type */
1669 g_value_init (&value, data->type);
1671 if (!gvalue_util_set_value_from_xml_node (&value, var_node)) {
1672 g_value_unset (&value);
1677 /* Call callbacks */
1678 for (l = data->callbacks; l; l = l->next) {
1679 CallbackData *callback_data;
1681 callback_data = l->data;
1683 callback_data->callback (proxy,
1684 (const char *) var_node->name,
1686 callback_data->user_data);
1690 g_value_unset (&value);
1694 emit_notifications_for_doc (GUPnPServiceProxy *proxy,
1699 node = xmlDocGetRootElement (doc);
1701 /* Iterate over all provided properties */
1702 for (node = node->children; node; node = node->next) {
1705 /* Although according to the UPnP specs, there should be only
1706 * one variable node inside a 'property' node, we still need to
1707 * entertain the possibility of multiple variables inside it to
1708 * be compatible with implementations using older GUPnP.
1710 for (var_node = node->children;
1712 var_node = var_node->next) {
1713 if (strcmp ((char *) node->name, "property") == 0)
1714 emit_notification (proxy, var_node);
1719 /* Emit pending notifications. See comment below on why we do this. */
1721 emit_notifications (gpointer user_data)
1723 GUPnPServiceProxy *proxy = user_data;
1724 GList *pending_notify;
1725 gboolean resubscribe = FALSE;
1727 g_assert (user_data);
1729 if (proxy->priv->sid == NULL)
1731 if (G_LIKELY (proxy->priv->subscribed))
1732 /* subscription in progress, delay emision! */
1735 for (pending_notify = proxy->priv->pending_notifies;
1736 pending_notify != NULL;
1737 pending_notify = pending_notify->next) {
1738 EmitNotifyData *emit_notify_data;
1740 emit_notify_data = pending_notify->data;
1742 if (emit_notify_data->seq > proxy->priv->seq) {
1743 /* Oops, we missed a notify. Resubscribe .. */
1749 /* Increment our own event sequence number */
1750 if (proxy->priv->seq < G_MAXINT32)
1753 proxy->priv->seq = 1;
1755 if (G_LIKELY (proxy->priv->sid != NULL &&
1756 strcmp (emit_notify_data->sid,
1757 proxy->priv->sid) == 0))
1758 /* Our SID, entertain! */
1759 emit_notifications_for_doc (proxy,
1760 emit_notify_data->doc);
1764 while (proxy->priv->pending_notifies != NULL) {
1765 emit_notify_data_free (proxy->priv->pending_notifies->data);
1767 proxy->priv->pending_notifies =
1768 g_list_delete_link (proxy->priv->pending_notifies,
1769 proxy->priv->pending_notifies);
1772 proxy->priv->notify_idle_src = NULL;
1775 unsubscribe (proxy);
1783 * HTTP server received a message. Handle, if this was a NOTIFY
1784 * message with our SID.
1787 server_handler (SoupServer *soup_server,
1789 const char *server_path,
1791 SoupClientContext *soup_client,
1794 GUPnPServiceProxy *proxy;
1799 EmitNotifyData *emit_notify_data;
1801 proxy = GUPNP_SERVICE_PROXY (user_data);
1803 if (strcmp (msg->method, GENA_METHOD_NOTIFY) != 0) {
1804 /* We don't implement this method */
1805 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
1810 hdr = soup_message_headers_get_one (msg->request_headers, "NT");
1811 if (hdr == NULL || strcmp (hdr, "upnp:event") != 0) {
1812 /* Proper NT header lacking */
1813 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1818 hdr = soup_message_headers_get_one (msg->request_headers, "NTS");
1819 if (hdr == NULL || strcmp (hdr, "upnp:propchange") != 0) {
1820 /* Proper NTS header lacking */
1821 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1826 hdr = soup_message_headers_get_one (msg->request_headers, "SEQ");
1829 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1836 hdr = soup_message_headers_get_one (msg->request_headers, "SID");
1839 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1844 /* Parse the actual XML message content */
1845 doc = xmlRecoverMemory (msg->request_body->data,
1846 msg->request_body->length);
1849 g_warning ("Failed to parse NOTIFY message body");
1851 soup_message_set_status (msg,
1852 SOUP_STATUS_INTERNAL_SERVER_ERROR);
1857 /* Get root propertyset element */
1858 node = xmlDocGetRootElement (doc);
1859 if (node == NULL || strcmp ((char *) node->name, "propertyset")) {
1860 /* Empty or unsupported */
1863 soup_message_set_status (msg, SOUP_STATUS_OK);
1869 * Some UPnP stacks (hello, myigd/1.0) block when sending a NOTIFY, so
1870 * call the callbacks in an idle handler so that if the client calls the
1871 * device in the notify callback the server can actually respond.
1873 emit_notify_data = emit_notify_data_new (hdr, seq, doc);
1875 proxy->priv->pending_notifies =
1876 g_list_append (proxy->priv->pending_notifies, emit_notify_data);
1877 if (!proxy->priv->notify_idle_src) {
1878 proxy->priv->notify_idle_src = g_idle_source_new();
1879 g_source_set_callback (proxy->priv->notify_idle_src,
1882 g_source_attach (proxy->priv->notify_idle_src,
1883 g_main_context_get_thread_default ());
1885 g_source_unref (proxy->priv->notify_idle_src);
1888 /* Everything went OK */
1889 soup_message_set_status (msg, SOUP_STATUS_OK);
1893 * Generates a timeout header for the subscription timeout specified
1894 * in our GUPnPContext.
1897 make_timeout_header (GUPnPContext *context)
1901 timeout = gupnp_context_get_subscription_timeout (context);
1903 return g_strdup_printf ("Second-%d", timeout);
1905 return g_strdup ("infinite");
1909 * Subscription expired.
1912 subscription_expire (gpointer user_data)
1914 GUPnPServiceProxy *proxy;
1915 GUPnPContext *context;
1917 SoupSession *session;
1918 char *sub_url, *timeout;
1920 proxy = GUPNP_SERVICE_PROXY (user_data);
1922 /* Reset timeout ID */
1923 proxy->priv->subscription_timeout_src = NULL;
1925 /* Send renewal message */
1926 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
1928 /* Create subscription message */
1929 sub_url = gupnp_service_info_get_event_subscription_url
1930 (GUPNP_SERVICE_INFO (proxy));
1932 msg = soup_message_new (GENA_METHOD_SUBSCRIBE, sub_url);
1936 g_return_val_if_fail (msg != NULL, FALSE);
1939 soup_message_headers_append (msg->request_headers,
1943 timeout = make_timeout_header (context);
1944 soup_message_headers_append (msg->request_headers,
1949 /* And send it off */
1950 proxy->priv->pending_messages =
1951 g_list_prepend (proxy->priv->pending_messages, msg);
1953 session = gupnp_context_get_session (context);
1955 soup_session_queue_message (session,
1957 (SoupSessionCallback)
1958 subscribe_got_response,
1965 * Received subscription response.
1968 subscribe_got_response (SoupSession *session,
1970 GUPnPServiceProxy *proxy)
1975 if (msg->status_code == SOUP_STATUS_CANCELLED)
1978 /* Remove from pending messages list */
1979 proxy->priv->pending_messages =
1980 g_list_remove (proxy->priv->pending_messages, msg);
1982 /* Check whether the subscription is still wanted */
1983 if (!proxy->priv->subscribed)
1987 g_free (proxy->priv->sid);
1988 proxy->priv->sid = NULL;
1990 /* Check message status */
1991 if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
1997 hdr = soup_message_headers_get_one (msg->response_headers,
2001 (GUPNP_EVENTING_ERROR,
2002 GUPNP_EVENTING_ERROR_SUBSCRIPTION_LOST,
2003 "No SID in SUBSCRIBE response");
2008 proxy->priv->sid = g_strdup (hdr);
2010 /* Figure out when the subscription times out */
2011 hdr = soup_message_headers_get_one (msg->response_headers,
2014 g_warning ("No Timeout in SUBSCRIBE response.");
2019 if (strncmp (hdr, "Second-", strlen ("Second-")) == 0) {
2020 /* We have a finite timeout */
2021 timeout = atoi (hdr + strlen ("Second-"));
2024 g_warning ("Invalid time-out specified. "
2025 "Assuming default value of %d.",
2026 GENA_DEFAULT_TIMEOUT);
2028 timeout = GENA_DEFAULT_TIMEOUT;
2031 /* We want to resubscribe before the subscription
2033 timeout = g_random_int_range (1, timeout / 2);
2035 /* Add actual timeout */
2036 proxy->priv->subscription_timeout_src =
2037 g_timeout_source_new_seconds (timeout);
2038 g_source_set_callback
2039 (proxy->priv->subscription_timeout_src,
2040 subscription_expire,
2042 g_source_attach (proxy->priv->subscription_timeout_src,
2043 g_main_context_get_thread_default ());
2045 g_source_unref (proxy->priv->subscription_timeout_src);
2048 GUPnPContext *context;
2051 /* Subscription failed. */
2052 error = g_error_new_literal
2053 (GUPNP_EVENTING_ERROR,
2054 GUPNP_EVENTING_ERROR_SUBSCRIPTION_FAILED,
2055 msg->reason_phrase);
2058 /* Remove listener */
2059 context = gupnp_service_info_get_context
2060 (GUPNP_SERVICE_INFO (proxy));
2062 server = gupnp_context_get_server (context);
2063 soup_server_remove_handler (server, proxy->priv->path);
2065 proxy->priv->subscribed = FALSE;
2067 g_object_notify (G_OBJECT (proxy), "subscribed");
2069 /* Emit subscription-lost */
2070 g_signal_emit (proxy,
2071 signals[SUBSCRIPTION_LOST],
2075 g_error_free (error);
2080 * Subscribe to this service.
2083 subscribe (GUPnPServiceProxy *proxy)
2085 GUPnPContext *context;
2087 SoupSession *session;
2089 const char *server_url;
2090 char *sub_url, *delivery_url, *timeout;
2092 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
2094 /* Create subscription message */
2095 sub_url = gupnp_service_info_get_event_subscription_url
2096 (GUPNP_SERVICE_INFO (proxy));
2099 if (sub_url != NULL) {
2100 msg = soup_message_new (GENA_METHOD_SUBSCRIBE, sub_url);
2108 /* Subscription failed. */
2109 proxy->priv->subscribed = FALSE;
2111 g_object_notify (G_OBJECT (proxy), "subscribed");
2113 /* Emit subscription-lost */
2114 error = g_error_new (GUPNP_SERVER_ERROR,
2115 GUPNP_SERVER_ERROR_INVALID_URL,
2116 "No valid subscription URL defined");
2118 g_signal_emit (proxy,
2119 signals[SUBSCRIPTION_LOST],
2123 g_error_free (error);
2129 server_url = _gupnp_context_get_server_url (context);
2130 delivery_url = g_strdup_printf ("<%s%s>",
2133 soup_message_headers_append (msg->request_headers,
2136 g_free (delivery_url);
2138 soup_message_headers_append (msg->request_headers,
2142 timeout = make_timeout_header (context);
2143 soup_message_headers_append (msg->request_headers,
2148 /* Listen for events */
2149 server = gupnp_context_get_server (context);
2151 soup_server_add_handler (server,
2157 /* And send our subscription message off */
2158 proxy->priv->pending_messages =
2159 g_list_prepend (proxy->priv->pending_messages, msg);
2161 session = gupnp_context_get_session (context);
2163 soup_session_queue_message (session,
2165 (SoupSessionCallback)
2166 subscribe_got_response,
2171 * Unsubscribe from this service.
2174 unsubscribe (GUPnPServiceProxy *proxy)
2176 GUPnPContext *context;
2177 SoupSession *session;
2180 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
2182 /* Remove server handler */
2183 server = gupnp_context_get_server (context);
2184 soup_server_remove_handler (server, proxy->priv->path);
2186 if (proxy->priv->sid != NULL) {
2190 /* Create unsubscription message */
2191 sub_url = gupnp_service_info_get_event_subscription_url
2192 (GUPNP_SERVICE_INFO (proxy));
2194 msg = soup_message_new (GENA_METHOD_UNSUBSCRIBE, sub_url);
2200 soup_message_headers_append (msg->request_headers,
2205 session = gupnp_context_get_session (context);
2207 soup_session_queue_message (session, msg, NULL, NULL);
2211 g_free (proxy->priv->sid);
2212 proxy->priv->sid = NULL;
2214 /* Reset sequence number */
2215 proxy->priv->seq = 0;
2218 /* Remove subscription timeout */
2219 if (proxy->priv->subscription_timeout_src) {
2220 g_source_destroy (proxy->priv->subscription_timeout_src);
2221 proxy->priv->subscription_timeout_src = NULL;
2226 * gupnp_service_proxy_set_subscribed:
2227 * @proxy: A #GUPnPServiceProxy
2228 * @subscribed: %TRUE to subscribe to this service
2230 * (Un)subscribes to this service.
2232 * Note that the relevant messages are not immediately sent but queued.
2233 * If you want to unsubcribe from this service because the application
2234 * is quitting, rely on automatic synchronised unsubscription on object
2235 * destruction instead.
2238 gupnp_service_proxy_set_subscribed (GUPnPServiceProxy *proxy,
2239 gboolean subscribed)
2241 g_return_if_fail (GUPNP_IS_SERVICE_PROXY (proxy));
2243 if (proxy->priv->subscribed == subscribed)
2246 proxy->priv->subscribed = subscribed;
2251 unsubscribe (proxy);
2253 g_object_notify (G_OBJECT (proxy), "subscribed");
2257 * gupnp_service_proxy_get_subscribed:
2258 * @proxy: A #GUPnPServiceProxy
2260 * Returns if we are subscribed to this service.
2262 * Return value: %TRUE if we are subscribed to this service, otherwise %FALSE.
2265 gupnp_service_proxy_get_subscribed (GUPnPServiceProxy *proxy)
2267 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
2269 return proxy->priv->subscribed;