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 * @Varargs: tuples of in parameter name, in parameter type, and in parameter
375 * value, followed by %NULL, and then tuples of out parameter name,
376 * out parameter type, and out parameter value location, terminated with %NULL
378 * Sends action @action with parameters @Varargs to the service exposed by
379 * @proxy synchronously. If an error occurred, @error will be set. In case of
380 * a UPnPError the error code will be the same in @error.
382 * Return value: %TRUE if sending the action was succesful.
385 gupnp_service_proxy_send_action (GUPnPServiceProxy *proxy,
393 va_start (var_args, error);
394 ret = gupnp_service_proxy_send_action_valist (proxy,
404 stop_main_loop (G_GNUC_UNUSED GUPnPServiceProxy *proxy,
405 G_GNUC_UNUSED GUPnPServiceProxyAction *action,
408 g_main_loop_quit ((GMainLoop *) user_data);
411 /* This is a skip variant of G_VALUE_LCOPY, same as there is
412 * G_VALUE_COLLECT_SKIP for G_VALUE_COLLECT.
414 #define VALUE_LCOPY_SKIP(value_type, var_args) \
416 GTypeValueTable *_vtable = g_type_value_table_peek (value_type); \
417 const gchar *_lcopy_format = _vtable->lcopy_format; \
419 while (*_lcopy_format) { \
420 switch (*_lcopy_format++) { \
421 case G_VALUE_COLLECT_INT: \
422 va_arg ((var_args), gint); \
424 case G_VALUE_COLLECT_LONG: \
425 va_arg ((var_args), glong); \
427 case G_VALUE_COLLECT_INT64: \
428 va_arg ((var_args), gint64); \
430 case G_VALUE_COLLECT_DOUBLE: \
431 va_arg ((var_args), gdouble); \
433 case G_VALUE_COLLECT_POINTER: \
434 va_arg ((var_args), gpointer); \
437 g_assert_not_reached (); \
442 /* Initializes hash table to hold arg names as keys and GValues of
443 * given type, but without any specific value. Note that if you are
444 * going to use OUT_HASH_TABLE_TO_VAR_ARGS then you have to store a
445 * copy of var_args with G_VA_COPY before using this macro.
447 #define VAR_ARGS_TO_OUT_HASH_TABLE(var_args, hash) \
449 const gchar *arg_name = va_arg (var_args, const gchar *); \
451 while (arg_name != NULL) { \
452 GValue *value = g_new0 (GValue, 1); \
453 GType type = va_arg (var_args, GType); \
455 VALUE_LCOPY_SKIP (type, var_args); \
456 g_value_init (value, type); \
457 g_hash_table_insert (hash, g_strdup (arg_name), value); \
458 arg_name = va_arg (var_args, const gchar *); \
462 /* Initializes hash table to hold arg names as keys and GValues of
463 * given type and value.
465 #define VAR_ARGS_TO_IN_LIST(var_args, names, values) \
467 const gchar *arg_name = va_arg (var_args, const gchar *); \
469 while (arg_name != NULL) { \
470 GValue *value = g_new0 (GValue, 1); \
471 gchar *error = NULL; \
472 GType type = va_arg (var_args, GType); \
474 G_VALUE_COLLECT_INIT (value, \
477 G_VALUE_NOCOPY_CONTENTS, \
479 if (error == NULL) { \
480 names = g_list_prepend (names, g_strdup (arg_name)); \
481 values = g_list_prepend (values, value); \
483 g_warning ("Failed to collect value of type %s for %s: %s", \
484 g_type_name (type), \
489 arg_name = va_arg (var_args, const gchar *); \
491 names = g_list_reverse (names); \
492 values = g_list_reverse (values); \
495 /* Puts values stored in hash table with GValues into var args.
497 #define OUT_HASH_TABLE_TO_VAR_ARGS(hash, var_args) \
499 const gchar *arg_name = va_arg (var_args, const gchar *); \
501 while (arg_name != NULL) { \
502 GValue *value = g_hash_table_lookup (hash, arg_name); \
503 GType type = va_arg (var_args, GType); \
505 if (value == NULL) { \
506 g_warning ("No value for %s", arg_name); \
507 G_VALUE_COLLECT_SKIP (type, var_args); \
508 } else if (G_VALUE_TYPE (value) != type) { \
509 g_warning ("Different GType in value (%s) and in var args (%s) for %s.", \
510 G_VALUE_TYPE_NAME (value), \
511 g_type_name (type), \
514 gchar *error = NULL; \
516 G_VALUE_LCOPY (value, var_args, 0, &error); \
517 if (error != NULL) { \
518 g_warning ("Failed to lcopy the value of type %s for %s: %s", \
519 g_type_name (type), \
525 arg_name = va_arg (var_args, const gchar *); \
529 /* GDestroyNotify for GHashTable holding GValues.
532 value_free (gpointer data)
534 GValue *value = (GValue *) data;
536 g_value_unset (value);
541 * gupnp_service_proxy_send_action_valist:
542 * @proxy: A #GUPnPServiceProxy
544 * @error: The location where to store any error, or %NULL
545 * @var_args: va_list of tuples of in parameter name, in parameter type, and in
546 * parameter value, followed by %NULL, and then tuples of out parameter name,
547 * out parameter type, and out parameter value location
549 * See gupnp_service_proxy_send_action().
551 * Return value: %TRUE if sending the action was succesful.
554 gupnp_service_proxy_send_action_valist (GUPnPServiceProxy *proxy,
559 GList *in_names = NULL, *in_values = NULL;
560 GHashTable *out_hash = NULL;
561 va_list var_args_copy;
564 GMainLoop *main_loop;
565 GUPnPServiceProxyAction *handle;
567 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
568 g_return_val_if_fail (action, FALSE);
571 main_loop = g_main_loop_new (g_main_context_get_thread_default (),
574 VAR_ARGS_TO_IN_LIST (var_args, in_names, in_values);
575 G_VA_COPY (var_args_copy, var_args);
576 out_hash = g_hash_table_new_full (g_str_hash,
580 VAR_ARGS_TO_OUT_HASH_TABLE (var_args, out_hash);
583 handle = gupnp_service_proxy_begin_action_list (proxy,
590 g_main_loop_unref (main_loop);
595 /* Loop till we get a reply (or time out) */
596 if (g_main_loop_is_running (main_loop))
597 g_main_loop_run (main_loop);
599 g_main_loop_unref (main_loop);
601 result = gupnp_service_proxy_end_action_hash (proxy,
606 if (local_error == NULL) {
607 OUT_HASH_TABLE_TO_VAR_ARGS (out_hash, var_args_copy);
609 g_propagate_error (error, local_error);
611 va_end (var_args_copy);
612 g_list_free_full (in_names, g_free);
613 g_list_free_full (in_values, value_free);
614 g_hash_table_unref (out_hash);
620 * gupnp_service_proxy_send_action_hash:
621 * @proxy: A #GUPnPServiceProxy
623 * @error: The location where to store any error, or %NULL
624 * @in_hash: (element-type utf8 GValue) (transfer none): A #GHashTable of in
625 * parameter name and #GValue pairs
626 * @out_hash: (inout) (element-type utf8 GValue) (transfer full): A #GHashTable
627 * of out parameter name and initialized #GValue pairs
629 * See gupnp_service_proxy_send_action(); this version takes a pair of
630 * #GHashTable<!-- -->s for runtime determined parameter lists.
632 * Return value: %TRUE if sending the action was succesful.
635 gupnp_service_proxy_send_action_hash (GUPnPServiceProxy *proxy,
639 GHashTable *out_hash)
641 GMainLoop *main_loop;
642 GUPnPServiceProxyAction *handle;
644 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
645 g_return_val_if_fail (action, FALSE);
647 main_loop = g_main_loop_new (g_main_context_get_thread_default (),
650 handle = gupnp_service_proxy_begin_action_hash (proxy,
656 g_main_loop_unref (main_loop);
661 /* Loop till we get a reply (or time out) */
662 if (g_main_loop_is_running (main_loop))
663 g_main_loop_run (main_loop);
665 g_main_loop_unref (main_loop);
667 if (!gupnp_service_proxy_end_action_hash (proxy,
677 * gupnp_service_proxy_send_action_list:
678 * @proxy: (transfer none) : A #GUPnPServiceProxy
680 * @error: The location where to store any error, or %NULL
681 * @in_names: (element-type utf8) (transfer none): #GList of 'in' parameter
683 * @in_values: (element-type GValue) (transfer none): #GList of values (as
684 * #GValue) that line up with @in_names
685 * @out_names: (element-type utf8) (transfer none): #GList of 'out' parameter
687 * @out_types: (element-type GType) (transfer none): #GList of types (as #GType)
688 * that line up with @out_names
689 * @out_values: (element-type GValue) (transfer full) (out): #GList of values
690 * (as #GValue) that line up with @out_names and @out_types.
692 * The synchronous variant of #gupnp_service_proxy_begin_action_list and
693 * #gupnp_service_proxy_end_action_list.
695 * Return value: %TRUE if sending the action was succesful.
698 gupnp_service_proxy_send_action_list (GUPnPServiceProxy *proxy,
707 GMainLoop *main_loop;
708 GUPnPServiceProxyAction *handle;
710 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
711 g_return_val_if_fail (action, FALSE);
713 main_loop = g_main_loop_new (g_main_context_get_thread_default (),
716 handle = gupnp_service_proxy_begin_action_list (proxy,
723 g_main_loop_unref (main_loop);
728 /* Loop till we get a reply (or time out) */
729 if (g_main_loop_is_running (main_loop))
730 g_main_loop_run (main_loop);
732 g_main_loop_unref (main_loop);
734 if (!gupnp_service_proxy_end_action_list (proxy,
746 * gupnp_service_proxy_begin_action:
747 * @proxy: A #GUPnPServiceProxy
749 * @callback: (scope async): The callback to call when sending the action has succeeded
751 * @user_data: User data for @callback
752 * @Varargs: tuples of in parameter name, in parameter type, and in parameter
753 * value, terminated with %NULL
755 * Sends action @action with parameters @Varargs to the service exposed by
756 * @proxy asynchronously, calling @callback on completion. From @callback, call
757 * gupnp_service_proxy_end_action() to check for errors, to retrieve return
758 * values, and to free the #GUPnPServiceProxyAction.
760 * Returns: (transfer none): A #GUPnPServiceProxyAction handle. This will be freed when
761 * gupnp_service_proxy_cancel_action() or
762 * gupnp_service_proxy_end_action_valist().
764 GUPnPServiceProxyAction *
765 gupnp_service_proxy_begin_action (GUPnPServiceProxy *proxy,
767 GUPnPServiceProxyActionCallback callback,
772 GUPnPServiceProxyAction *ret;
774 va_start (var_args, user_data);
775 ret = gupnp_service_proxy_begin_action_valist (proxy,
785 /* Begins a basic action message */
786 static GUPnPServiceProxyAction *
787 begin_action_msg (GUPnPServiceProxy *proxy,
789 GUPnPServiceProxyActionCallback callback,
792 GUPnPServiceProxyAction *ret;
793 char *control_url, *full_action;
794 const char *service_type;
796 /* Create action structure */
797 ret = g_slice_new (GUPnPServiceProxyAction);
801 ret->callback = callback;
802 ret->user_data = user_data;
808 proxy->priv->pending_actions =
809 g_list_prepend (proxy->priv->pending_actions, ret);
811 /* Make sure we have a service type */
812 service_type = gupnp_service_info_get_service_type
813 (GUPNP_SERVICE_INFO (proxy));
814 if (service_type == NULL) {
815 ret->error = g_error_new (GUPNP_SERVER_ERROR,
816 GUPNP_SERVER_ERROR_OTHER,
817 "No service type defined");
823 control_url = gupnp_service_info_get_control_url
824 (GUPNP_SERVICE_INFO (proxy));
826 if (control_url != NULL) {
827 ret->msg = soup_message_new (SOUP_METHOD_POST, control_url);
829 g_free (control_url);
832 if (ret->msg == NULL) {
833 ret->error = g_error_new (GUPNP_SERVER_ERROR,
834 GUPNP_SERVER_ERROR_INVALID_URL,
835 "No valid control URL defined");
841 full_action = g_strdup_printf ("\"%s#%s\"", service_type, action);
842 soup_message_headers_append (ret->msg->request_headers,
845 g_free (full_action);
847 /* Specify language */
848 http_request_set_accept_language (ret->msg);
850 /* Accept gzip encoding */
851 soup_message_headers_append (ret->msg->request_headers,
852 "Accept-Encoding", "gzip");
854 /* Set up envelope */
855 ret->msg_str = xml_util_new_string ();
857 g_string_append (ret->msg_str,
858 "<?xml version=\"1.0\"?>"
859 "<s:Envelope xmlns:s="
860 "\"http://schemas.xmlsoap.org/soap/envelope/\" "
862 "\"http://schemas.xmlsoap.org/soap/encoding/\">"
865 g_string_append (ret->msg_str, "<u:");
866 g_string_append (ret->msg_str, action);
867 g_string_append (ret->msg_str, " xmlns:u=\"");
868 g_string_append (ret->msg_str, service_type);
869 g_string_append (ret->msg_str, "\">");
874 /* Received response to action message */
876 action_got_response (SoupSession *session,
878 GUPnPServiceProxyAction *action)
880 const char *full_action;
882 switch (msg->status_code) {
883 case SOUP_STATUS_CANCELLED:
884 /* Silently return */
887 case SOUP_STATUS_METHOD_NOT_ALLOWED:
888 /* Retry with M-POST */
889 msg->method = "M-POST";
891 soup_message_headers_append
892 (msg->request_headers,
894 "\"http://schemas.xmlsoap.org/soap/envelope/\"; ns=s");
896 /* Rename "SOAPAction" to "s-SOAPAction" */
897 full_action = soup_message_headers_get_one
898 (msg->request_headers,
900 soup_message_headers_append (msg->request_headers,
903 soup_message_headers_remove (msg->request_headers,
907 soup_session_requeue_message (session, msg);
912 /* Success: Call callback */
913 action->callback (action->proxy, action, action->user_data);
919 /* Finishes an action message and sends it off */
921 finish_action_msg (GUPnPServiceProxyAction *action,
922 const char *action_name)
924 GUPnPContext *context;
925 SoupSession *session;
928 g_string_append (action->msg_str, "</u:");
929 g_string_append (action->msg_str, action_name);
930 g_string_append_c (action->msg_str, '>');
932 g_string_append (action->msg_str,
936 soup_message_set_request (action->msg,
937 "text/xml; charset=\"utf-8\"",
939 action->msg_str->str,
940 action->msg_str->len);
942 g_string_free (action->msg_str, FALSE);
944 /* We need to keep our own reference to the message as well,
945 * in order for send_action() to work. */
946 g_object_ref (action->msg);
948 /* Send the message */
949 context = gupnp_service_info_get_context
950 (GUPNP_SERVICE_INFO (action->proxy));
951 session = gupnp_context_get_session (context);
953 soup_session_queue_message (session,
955 (SoupSessionCallback) action_got_response,
959 /* Writes a parameter name and GValue pair to @msg */
961 write_in_parameter (const char *arg_name,
965 /* Write parameter pair */
966 xml_util_start_element (msg_str, arg_name);
967 gvalue_util_value_append_to_xml_string (value, msg_str);
968 xml_util_end_element (msg_str, arg_name);
972 * gupnp_service_proxy_begin_action_valist:
973 * @proxy: A #GUPnPServiceProxy
975 * @callback: (scope async) : The callback to call when sending the action has succeeded
977 * @user_data: User data for @callback
978 * @var_args: A va_list of tuples of in parameter name, in parameter type, and
981 * See gupnp_service_proxy_begin_action().
983 * Returns: (transfer none): A #GUPnPServiceProxyAction handle. This will
984 * be freed when calling gupnp_service_proxy_cancel_action() or
985 * gupnp_service_proxy_end_action_valist().
987 GUPnPServiceProxyAction *
988 gupnp_service_proxy_begin_action_valist
989 (GUPnPServiceProxy *proxy,
991 GUPnPServiceProxyActionCallback callback,
995 GUPnPServiceProxyAction *ret;
996 GList *in_names = NULL, *in_values = NULL;
998 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), NULL);
999 g_return_val_if_fail (action, NULL);
1000 g_return_val_if_fail (callback, NULL);
1002 VAR_ARGS_TO_IN_LIST (var_args, in_names, in_values);
1003 ret = gupnp_service_proxy_begin_action_list (proxy,
1009 g_list_free_full (in_names, g_free);
1010 g_list_free_full (in_values, value_free);
1016 * gupnp_service_proxy_begin_action_list:
1017 * @proxy: (transfer none): A #GUPnPServiceProxy
1018 * @action: An action
1019 * @in_names: (element-type utf8) (transfer none): #GList of 'in' parameter
1020 * names (as strings)
1021 * @in_values: (element-type GValue) (transfer none): #GList of values (as
1022 * #GValue) that line up with @in_names
1023 * @callback: (scope async) : The callback to call when sending the action has succeeded or
1025 * @user_data: User data for @callback
1027 * A variant of #gupnp_service_proxy_begin_action that takes lists of
1028 * in-parameter names, types and values.
1030 * Return value: (transfer none): A #GUPnPServiceProxyAction handle. This will
1031 * be freed when calling #gupnp_service_proxy_cancel_action or
1032 * #gupnp_service_proxy_end_action_list.
1034 GUPnPServiceProxyAction *
1035 gupnp_service_proxy_begin_action_list
1036 (GUPnPServiceProxy *proxy,
1040 GUPnPServiceProxyActionCallback callback,
1043 GUPnPServiceProxyAction *ret;
1047 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), NULL);
1048 g_return_val_if_fail (action, NULL);
1049 g_return_val_if_fail (callback, NULL);
1050 g_return_val_if_fail (g_list_length (in_names) ==
1051 g_list_length (in_values),
1054 /* Create message */
1055 ret = begin_action_msg (proxy, action, callback, user_data);
1058 callback (proxy, ret, user_data);
1066 for (names = in_names; names; names=names->next) {
1067 GValue* val = values->data;
1069 write_in_parameter (names->data,
1073 values = values->next;
1076 /* Finish and send off */
1077 finish_action_msg (ret, action);
1083 * gupnp_service_proxy_begin_action_hash:
1084 * @proxy: A #GUPnPServiceProxy
1085 * @action: An action
1086 * @callback: (scope async): The callback to call when sending the action has succeeded
1088 * @user_data: User data for @callback
1089 * @hash: (element-type utf8 GValue): A #GHashTable of in parameter name and #GValue pairs
1091 * See gupnp_service_proxy_begin_action(); this version takes a #GHashTable
1092 * for runtime generated parameter lists.
1094 * Return value: (transfer none): A #GUPnPServiceProxyAction handle. This will
1095 * be freed when calling gupnp_service_proxy_cancel_action() or
1096 * gupnp_service_proxy_end_action_hash().
1098 GUPnPServiceProxyAction *
1099 gupnp_service_proxy_begin_action_hash
1100 (GUPnPServiceProxy *proxy,
1102 GUPnPServiceProxyActionCallback callback,
1106 GUPnPServiceProxyAction *ret;
1108 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), NULL);
1109 g_return_val_if_fail (action, NULL);
1110 g_return_val_if_fail (callback, NULL);
1112 /* Create message */
1113 ret = begin_action_msg (proxy, action, callback, user_data);
1116 callback (proxy, ret, user_data);
1122 g_hash_table_foreach (hash, (GHFunc) write_in_parameter, ret->msg_str);
1124 /* Finish and send off */
1125 finish_action_msg (ret, action);
1131 * gupnp_service_proxy_end_action:
1132 * @proxy: A #GUPnPServiceProxy
1133 * @action: A #GUPnPServiceProxyAction handle
1134 * @error: The location where to store any error, or %NULL
1135 * @Varargs: tuples of out parameter name, out parameter type, and out parameter
1136 * value location, terminated with %NULL. The out parameter values should be
1139 * Retrieves the result of @action. The out parameters in @Varargs will be
1140 * filled in, and if an error occurred, @error will be set. In case of
1141 * a UPnPError the error code will be the same in @error.
1143 * Return value: %TRUE on success.
1146 gupnp_service_proxy_end_action (GUPnPServiceProxy *proxy,
1147 GUPnPServiceProxyAction *action,
1154 va_start (var_args, error);
1155 ret = gupnp_service_proxy_end_action_valist (proxy,
1164 /* Checks an action response for errors and returns the parsed
1167 check_action_response (G_GNUC_UNUSED GUPnPServiceProxy *proxy,
1168 GUPnPServiceProxyAction *action,
1175 /* Check for errors */
1176 switch (action->msg->status_code) {
1177 case SOUP_STATUS_OK:
1178 case SOUP_STATUS_INTERNAL_SERVER_ERROR:
1181 _gupnp_error_set_server_error (error, action->msg);
1186 /* Parse response */
1187 response = xmlRecoverMemory (action->msg->response_body->data,
1188 action->msg->response_body->length);
1191 if (action->msg->status_code == SOUP_STATUS_OK) {
1194 GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1195 "Could not parse SOAP response");
1200 GUPNP_SERVER_ERROR_INTERNAL_SERVER_ERROR,
1201 action->msg->reason_phrase);
1207 /* Get parameter list */
1208 *params = xml_util_get_element ((xmlNode *) response,
1211 if (*params != NULL)
1212 *params = xml_util_real_node ((*params)->children);
1214 if (*params != NULL) {
1215 if (strcmp ((const char *) (*params)->name, "Header") == 0)
1216 *params = xml_util_real_node ((*params)->next);
1218 if (*params != NULL)
1219 if (strcmp ((const char *) (*params)->name, "Body") != 0)
1223 if (*params != NULL)
1224 *params = xml_util_real_node ((*params)->children);
1226 if (*params == NULL) {
1229 GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1230 "Invalid Envelope");
1232 xmlFreeDoc (response);
1237 /* Check whether we have a Fault */
1238 if (action->msg->status_code == SOUP_STATUS_INTERNAL_SERVER_ERROR) {
1242 param = xml_util_get_element (*params,
1250 GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1253 xmlFreeDoc (response);
1259 code = xml_util_get_child_element_content_int
1260 (param, "errorCode");
1264 GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1267 xmlFreeDoc (response);
1273 desc = xml_util_get_child_element_content_glib
1274 (param, "errorDescription");
1276 desc = g_strdup (action->msg->reason_phrase);
1278 g_set_error_literal (error,
1279 GUPNP_CONTROL_ERROR,
1285 xmlFreeDoc (response);
1293 /* Reads a value into the parameter name and initialised GValue pair
1296 read_out_parameter (const char *arg_name,
1302 /* Try to find a matching parameter in the response*/
1303 param = xml_util_get_element (params,
1307 g_warning ("Could not find variable \"%s\" in response",
1313 gvalue_util_set_value_from_xml_node (value, param);
1317 * gupnp_service_proxy_end_action_valist:
1318 * @proxy: A #GUPnPServiceProxy
1319 * @action: A #GUPnPServiceProxyAction handle
1320 * @error: The location where to store any error, or %NULL
1321 * @var_args: A va_list of tuples of out parameter name, out parameter type,
1322 * and out parameter value location. The out parameter values should be
1325 * See gupnp_service_proxy_end_action().
1327 * Return value: %TRUE on success.
1330 gupnp_service_proxy_end_action_valist (GUPnPServiceProxy *proxy,
1331 GUPnPServiceProxyAction *action,
1335 GHashTable *out_hash;
1336 va_list var_args_copy;
1338 GError *local_error;
1340 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1341 g_return_val_if_fail (action, FALSE);
1342 g_return_val_if_fail (proxy == action->proxy, FALSE);
1344 out_hash = g_hash_table_new_full (g_str_hash,
1348 G_VA_COPY (var_args_copy, var_args);
1349 VAR_ARGS_TO_OUT_HASH_TABLE (var_args, out_hash);
1351 result = gupnp_service_proxy_end_action_hash (proxy,
1356 if (local_error == NULL) {
1357 OUT_HASH_TABLE_TO_VAR_ARGS (out_hash, var_args_copy);
1359 g_propagate_error (error, local_error);
1361 va_end (var_args_copy);
1362 g_hash_table_unref (out_hash);
1368 * gupnp_service_proxy_end_action_list:
1369 * @proxy: A #GUPnPServiceProxy
1370 * @action: A #GUPnPServiceProxyAction handle
1371 * @error: The location where to store any error, or %NULL
1372 * @out_names: (element-type utf8) (transfer none): #GList of 'out' parameter
1373 * names (as strings)
1374 * @out_types: (element-type GType) (transfer none): #GList of types (as #GType)
1375 * that line up with @out_names
1376 * @out_values: (element-type GValue) (transfer full) (out): #GList of values
1377 * (as #GValue) that line up with @out_names and @out_types.
1379 * A variant of #gupnp_service_proxy_end_action that takes lists of
1380 * out-parameter names, types and place-holders for values. The returned list
1381 * in @out_values must be freed using #g_list_free and each element in it using
1382 * #g_value_unset and #g_slice_free.
1384 * Return value : %TRUE on success.
1387 gupnp_service_proxy_end_action_list (GUPnPServiceProxy *proxy,
1388 GUPnPServiceProxyAction *action,
1398 GList *out_values_list;
1400 out_values_list = NULL;
1402 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1403 g_return_val_if_fail (action, FALSE);
1404 g_return_val_if_fail (proxy == action->proxy, FALSE);
1406 /* Check for saved error from begin_action() */
1407 if (action->error) {
1409 *error = action->error;
1411 g_error_free (action->error);
1413 gupnp_service_proxy_action_free (action);
1418 /* Check response for errors and do initial parsing */
1419 response = check_action_response (proxy, action, ¶ms, error);
1420 if (response == NULL) {
1421 gupnp_service_proxy_action_free (action);
1426 /* Read arguments */
1428 for (names = out_names; names; names=names->next) {
1431 val = g_slice_new0 (GValue);
1432 g_value_init (val, (GType) types->data);
1434 read_out_parameter (names->data, val, params);
1436 out_values_list = g_list_append (out_values_list, val);
1438 types = types->next;
1441 *out_values = out_values_list;
1444 gupnp_service_proxy_action_free (action);
1446 xmlFreeDoc (response);
1452 * gupnp_service_proxy_end_action_hash:
1453 * @proxy: A #GUPnPServiceProxy
1454 * @action: A #GUPnPServiceProxyAction handle
1455 * @error: The location where to store any error, or %NULL
1456 * @hash: (element-type utf8 GValue) (inout) (transfer none): A #GHashTable of
1457 * out parameter name and initialised #GValue pairs
1459 * See gupnp_service_proxy_end_action(); this version takes a #GHashTable for
1460 * runtime generated parameter lists.
1462 * Return value: %TRUE on success.
1465 gupnp_service_proxy_end_action_hash (GUPnPServiceProxy *proxy,
1466 GUPnPServiceProxyAction *action,
1473 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1474 g_return_val_if_fail (action, FALSE);
1475 g_return_val_if_fail (proxy == action->proxy, FALSE);
1477 /* Check for saved error from begin_action() */
1478 if (action->error) {
1480 *error = action->error;
1482 g_error_free (action->error);
1484 gupnp_service_proxy_action_free (action);
1489 /* Check response for errors and do initial parsing */
1490 response = check_action_response (proxy, action, ¶ms, error);
1491 if (response == NULL) {
1492 gupnp_service_proxy_action_free (action);
1497 /* Read arguments */
1498 g_hash_table_foreach (hash, (GHFunc) read_out_parameter, params);
1501 gupnp_service_proxy_action_free (action);
1503 xmlFreeDoc (response);
1509 * gupnp_service_proxy_cancel_action:
1510 * @proxy: A #GUPnPServiceProxy
1511 * @action: A #GUPnPServiceProxyAction handle
1513 * Cancels @action, freeing the @action handle.
1516 gupnp_service_proxy_cancel_action (GUPnPServiceProxy *proxy,
1517 GUPnPServiceProxyAction *action)
1519 GUPnPContext *context;
1520 SoupSession *session;
1522 g_return_if_fail (GUPNP_IS_SERVICE_PROXY (proxy));
1523 g_return_if_fail (action);
1524 g_return_if_fail (proxy == action->proxy);
1526 if (action->msg != NULL) {
1527 context = gupnp_service_info_get_context
1528 (GUPNP_SERVICE_INFO (proxy));
1529 session = gupnp_context_get_session (context);
1531 soup_session_cancel_message (session,
1533 SOUP_STATUS_CANCELLED);
1536 if (action->error != NULL)
1537 g_error_free (action->error);
1539 gupnp_service_proxy_action_free (action);
1543 * gupnp_service_proxy_add_notify:
1544 * @proxy: A #GUPnPServiceProxy
1545 * @variable: The variable to add notification for
1546 * @type: The type of the variable
1547 * @callback: (scope async): The callback to call when @variable changes
1548 * @user_data: User data for @callback
1550 * Sets up @callback to be called whenever a change notification for
1551 * @variable is recieved.
1553 * Return value: %TRUE on success.
1556 gupnp_service_proxy_add_notify (GUPnPServiceProxy *proxy,
1557 const char *variable,
1559 GUPnPServiceProxyNotifyCallback callback,
1563 CallbackData *callback_data;
1565 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1566 g_return_val_if_fail (variable, FALSE);
1567 g_return_val_if_fail (type, FALSE);
1568 g_return_val_if_fail (callback, FALSE);
1570 /* See if we already have notifications set up for this variable */
1571 data = g_hash_table_lookup (proxy->priv->notify_hash, variable);
1573 /* No, create one */
1574 data = g_slice_new (NotifyData);
1577 data->callbacks = NULL;
1579 g_hash_table_insert (proxy->priv->notify_hash,
1580 g_strdup (variable),
1583 /* Yes, check that everything is sane .. */
1584 if (data->type != type) {
1585 g_warning ("A notification already exists for %s, but "
1586 "has type %s, not %s.",
1588 g_type_name (data->type),
1589 g_type_name (type));
1595 /* Append callback */
1596 callback_data = g_slice_new (CallbackData);
1598 callback_data->callback = callback;
1599 callback_data->user_data = user_data;
1601 data->callbacks = g_list_append (data->callbacks, callback_data);
1607 * gupnp_service_proxy_remove_notify:
1608 * @proxy: A #GUPnPServiceProxy
1609 * @variable: The variable to add notification for
1610 * @callback: (scope call): The callback to call when @variable changes
1611 * @user_data: User data for @callback
1613 * Cancels the variable change notification for @callback and @user_data.
1615 * This function must not be called directly or indirectly from a
1616 * #GUPnPServiceProxyNotifyCallback associated with this service proxy, even
1617 * if it is for another variable.
1619 * Return value: %TRUE on success.
1622 gupnp_service_proxy_remove_notify (GUPnPServiceProxy *proxy,
1623 const char *variable,
1624 GUPnPServiceProxyNotifyCallback callback,
1631 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1632 g_return_val_if_fail (variable, FALSE);
1633 g_return_val_if_fail (callback, FALSE);
1635 /* Look up NotifyData for variable */
1636 data = g_hash_table_lookup (proxy->priv->notify_hash, variable);
1638 g_warning ("No notifications found for variable %s",
1644 /* Look up our callback-user_data pair */
1647 for (l = data->callbacks; l; l = l->next) {
1648 CallbackData *callback_data;
1650 callback_data = l->data;
1652 if (callback_data->callback == callback &&
1653 callback_data->user_data == user_data) {
1655 g_slice_free (CallbackData, callback_data);
1658 g_list_delete_link (data->callbacks, l);
1659 if (data->callbacks == NULL) {
1660 /* No callbacks left: Remove from hash */
1661 g_hash_table_remove (proxy->priv->notify_hash,
1672 g_warning ("No such callback-user_data pair was found");
1678 emit_notification (GUPnPServiceProxy *proxy,
1682 GValue value = {0, };
1685 data = g_hash_table_lookup (proxy->priv->notify_hash, var_node->name);
1689 /* Make a GValue of the desired type */
1690 g_value_init (&value, data->type);
1692 if (!gvalue_util_set_value_from_xml_node (&value, var_node)) {
1693 g_value_unset (&value);
1698 /* Call callbacks */
1699 for (l = data->callbacks; l; l = l->next) {
1700 CallbackData *callback_data;
1702 callback_data = l->data;
1704 callback_data->callback (proxy,
1705 (const char *) var_node->name,
1707 callback_data->user_data);
1711 g_value_unset (&value);
1715 emit_notifications_for_doc (GUPnPServiceProxy *proxy,
1720 node = xmlDocGetRootElement (doc);
1722 /* Iterate over all provided properties */
1723 for (node = node->children; node; node = node->next) {
1726 /* Although according to the UPnP specs, there should be only
1727 * one variable node inside a 'property' node, we still need to
1728 * entertain the possibility of multiple variables inside it to
1729 * be compatible with implementations using older GUPnP.
1731 for (var_node = node->children;
1733 var_node = var_node->next) {
1734 if (strcmp ((char *) node->name, "property") == 0)
1735 emit_notification (proxy, var_node);
1740 /* Emit pending notifications. See comment below on why we do this. */
1742 emit_notifications (gpointer user_data)
1744 GUPnPServiceProxy *proxy = user_data;
1745 GList *pending_notify;
1746 gboolean resubscribe = FALSE;
1748 g_assert (user_data);
1750 if (proxy->priv->sid == NULL)
1752 if (G_LIKELY (proxy->priv->subscribed))
1753 /* subscription in progress, delay emision! */
1756 for (pending_notify = proxy->priv->pending_notifies;
1757 pending_notify != NULL;
1758 pending_notify = pending_notify->next) {
1759 EmitNotifyData *emit_notify_data;
1761 emit_notify_data = pending_notify->data;
1763 if (emit_notify_data->seq > proxy->priv->seq) {
1764 /* Error procedure on missed event according to
1765 * UDA 1.0, section 4.2, §5:
1766 * Re-subscribe to get a new SID and SEQ */
1772 /* Increment our own event sequence number */
1773 /* UDA 1.0, section 4.2, §3: To prevent overflow, SEQ is set to
1774 * 1, NOT 0, when encountering G_MAXUINT32. SEQ == 0 always
1775 * indicates the initial event message. */
1776 if (proxy->priv->seq < G_MAXUINT32)
1779 proxy->priv->seq = 1;
1781 if (G_LIKELY (proxy->priv->sid != NULL &&
1782 strcmp (emit_notify_data->sid,
1783 proxy->priv->sid) == 0))
1784 /* Our SID, entertain! */
1785 emit_notifications_for_doc (proxy,
1786 emit_notify_data->doc);
1790 while (proxy->priv->pending_notifies != NULL) {
1791 emit_notify_data_free (proxy->priv->pending_notifies->data);
1793 proxy->priv->pending_notifies =
1794 g_list_delete_link (proxy->priv->pending_notifies,
1795 proxy->priv->pending_notifies);
1798 proxy->priv->notify_idle_src = NULL;
1801 unsubscribe (proxy);
1809 * HTTP server received a message. Handle, if this was a NOTIFY
1810 * message with our SID.
1813 server_handler (G_GNUC_UNUSED SoupServer *soup_server,
1815 G_GNUC_UNUSED const char *server_path,
1816 G_GNUC_UNUSED GHashTable *query,
1817 G_GNUC_UNUSED SoupClientContext *soup_client,
1820 GUPnPServiceProxy *proxy;
1821 const char *hdr, *nt, *nts;
1826 EmitNotifyData *emit_notify_data;
1828 proxy = GUPNP_SERVICE_PROXY (user_data);
1830 if (strcmp (msg->method, GENA_METHOD_NOTIFY) != 0) {
1831 /* We don't implement this method */
1832 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
1837 nt = soup_message_headers_get_one (msg->request_headers, "NT");
1838 nts = soup_message_headers_get_one (msg->request_headers, "NTS");
1839 if (nt == NULL || nts == NULL) {
1840 /* Required header is missing */
1841 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
1846 if (strcmp (nt, "upnp:event") != 0 ||
1847 strcmp (nts, "upnp:propchange") != 0) {
1848 /* Unexpected header content */
1849 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1854 hdr = soup_message_headers_get_one (msg->request_headers, "SEQ");
1857 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1863 seq_parsed = strtoul (hdr, NULL, 10);
1864 if (errno != 0 || seq_parsed > G_MAXUINT32) {
1865 /* Invalid SEQ header value */
1866 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1871 seq = (guint32) seq_parsed;
1873 hdr = soup_message_headers_get_one (msg->request_headers, "SID");
1875 strlen (hdr) <= strlen ("uuid:") ||
1876 strncmp (hdr, "uuid:", strlen ("uuid:")) != 0) {
1878 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1883 /* Parse the actual XML message content */
1884 doc = xmlRecoverMemory (msg->request_body->data,
1885 msg->request_body->length);
1888 g_warning ("Failed to parse NOTIFY message body");
1890 soup_message_set_status (msg,
1891 SOUP_STATUS_INTERNAL_SERVER_ERROR);
1896 /* Get root propertyset element */
1897 node = xmlDocGetRootElement (doc);
1898 if (node == NULL || strcmp ((char *) node->name, "propertyset")) {
1899 /* Empty or unsupported */
1902 soup_message_set_status (msg, SOUP_STATUS_OK);
1908 * Some UPnP stacks (hello, myigd/1.0) block when sending a NOTIFY, so
1909 * call the callbacks in an idle handler so that if the client calls the
1910 * device in the notify callback the server can actually respond.
1912 emit_notify_data = emit_notify_data_new (hdr, seq, doc);
1914 proxy->priv->pending_notifies =
1915 g_list_append (proxy->priv->pending_notifies, emit_notify_data);
1916 if (!proxy->priv->notify_idle_src) {
1917 proxy->priv->notify_idle_src = g_idle_source_new();
1918 g_source_set_callback (proxy->priv->notify_idle_src,
1921 g_source_attach (proxy->priv->notify_idle_src,
1922 g_main_context_get_thread_default ());
1924 g_source_unref (proxy->priv->notify_idle_src);
1927 /* Everything went OK */
1928 soup_message_set_status (msg, SOUP_STATUS_OK);
1932 * Generates a timeout header for the subscription timeout specified
1933 * in our GUPnPContext.
1936 make_timeout_header (GUPnPContext *context)
1940 timeout = gupnp_context_get_subscription_timeout (context);
1942 return g_strdup_printf ("Second-%d", timeout);
1944 return g_strdup ("infinite");
1948 * Subscription expired.
1951 subscription_expire (gpointer user_data)
1953 GUPnPServiceProxy *proxy;
1954 GUPnPContext *context;
1956 SoupSession *session;
1957 char *sub_url, *timeout;
1959 proxy = GUPNP_SERVICE_PROXY (user_data);
1961 /* Reset timeout ID */
1962 proxy->priv->subscription_timeout_src = NULL;
1964 /* Send renewal message */
1965 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
1967 /* Create subscription message */
1968 sub_url = gupnp_service_info_get_event_subscription_url
1969 (GUPNP_SERVICE_INFO (proxy));
1971 msg = soup_message_new (GENA_METHOD_SUBSCRIBE, sub_url);
1975 g_return_val_if_fail (msg != NULL, FALSE);
1978 soup_message_headers_append (msg->request_headers,
1982 timeout = make_timeout_header (context);
1983 soup_message_headers_append (msg->request_headers,
1988 /* And send it off */
1989 proxy->priv->pending_messages =
1990 g_list_prepend (proxy->priv->pending_messages, msg);
1992 session = gupnp_context_get_session (context);
1994 soup_session_queue_message (session,
1996 (SoupSessionCallback)
1997 subscribe_got_response,
2004 * Received subscription response.
2007 subscribe_got_response (G_GNUC_UNUSED SoupSession *session,
2009 GUPnPServiceProxy *proxy)
2014 if (msg->status_code == SOUP_STATUS_CANCELLED)
2017 /* Remove from pending messages list */
2018 proxy->priv->pending_messages =
2019 g_list_remove (proxy->priv->pending_messages, msg);
2021 /* Check whether the subscription is still wanted */
2022 if (!proxy->priv->subscribed)
2026 g_free (proxy->priv->sid);
2027 proxy->priv->sid = NULL;
2029 /* Check message status */
2030 if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
2036 hdr = soup_message_headers_get_one (msg->response_headers,
2040 (GUPNP_EVENTING_ERROR,
2041 GUPNP_EVENTING_ERROR_SUBSCRIPTION_LOST,
2042 "No SID in SUBSCRIBE response");
2047 proxy->priv->sid = g_strdup (hdr);
2049 /* Figure out when the subscription times out */
2050 hdr = soup_message_headers_get_one (msg->response_headers,
2053 g_warning ("No Timeout in SUBSCRIBE response.");
2058 if (strncmp (hdr, "Second-", strlen ("Second-")) == 0) {
2059 /* We have a finite timeout */
2060 timeout = atoi (hdr + strlen ("Second-"));
2063 g_warning ("Invalid time-out specified. "
2064 "Assuming default value of %d.",
2065 GENA_DEFAULT_TIMEOUT);
2067 timeout = GENA_DEFAULT_TIMEOUT;
2070 /* We want to resubscribe before the subscription
2072 timeout = g_random_int_range (1, timeout / 2);
2074 /* Add actual timeout */
2075 proxy->priv->subscription_timeout_src =
2076 g_timeout_source_new_seconds (timeout);
2077 g_source_set_callback
2078 (proxy->priv->subscription_timeout_src,
2079 subscription_expire,
2081 g_source_attach (proxy->priv->subscription_timeout_src,
2082 g_main_context_get_thread_default ());
2084 g_source_unref (proxy->priv->subscription_timeout_src);
2087 GUPnPContext *context;
2090 /* Subscription failed. */
2091 error = g_error_new_literal
2092 (GUPNP_EVENTING_ERROR,
2093 GUPNP_EVENTING_ERROR_SUBSCRIPTION_FAILED,
2094 msg->reason_phrase);
2097 /* Remove listener */
2098 context = gupnp_service_info_get_context
2099 (GUPNP_SERVICE_INFO (proxy));
2101 server = gupnp_context_get_server (context);
2102 soup_server_remove_handler (server, proxy->priv->path);
2104 proxy->priv->subscribed = FALSE;
2106 g_object_notify (G_OBJECT (proxy), "subscribed");
2108 /* Emit subscription-lost */
2109 g_signal_emit (proxy,
2110 signals[SUBSCRIPTION_LOST],
2114 g_error_free (error);
2119 * Subscribe to this service.
2122 subscribe (GUPnPServiceProxy *proxy)
2124 GUPnPContext *context;
2126 SoupSession *session;
2128 const char *server_url;
2129 char *sub_url, *delivery_url, *timeout;
2131 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
2133 /* Create subscription message */
2134 sub_url = gupnp_service_info_get_event_subscription_url
2135 (GUPNP_SERVICE_INFO (proxy));
2138 if (sub_url != NULL) {
2139 msg = soup_message_new (GENA_METHOD_SUBSCRIBE, sub_url);
2147 /* Subscription failed. */
2148 proxy->priv->subscribed = FALSE;
2150 g_object_notify (G_OBJECT (proxy), "subscribed");
2152 /* Emit subscription-lost */
2153 error = g_error_new (GUPNP_SERVER_ERROR,
2154 GUPNP_SERVER_ERROR_INVALID_URL,
2155 "No valid subscription URL defined");
2157 g_signal_emit (proxy,
2158 signals[SUBSCRIPTION_LOST],
2162 g_error_free (error);
2168 server_url = _gupnp_context_get_server_url (context);
2169 delivery_url = g_strdup_printf ("<%s%s>",
2172 soup_message_headers_append (msg->request_headers,
2175 g_free (delivery_url);
2177 soup_message_headers_append (msg->request_headers,
2181 timeout = make_timeout_header (context);
2182 soup_message_headers_append (msg->request_headers,
2187 /* Listen for events */
2188 server = gupnp_context_get_server (context);
2190 soup_server_add_handler (server,
2196 /* And send our subscription message off */
2197 proxy->priv->pending_messages =
2198 g_list_prepend (proxy->priv->pending_messages, msg);
2200 session = gupnp_context_get_session (context);
2202 soup_session_queue_message (session,
2204 (SoupSessionCallback)
2205 subscribe_got_response,
2210 * Unsubscribe from this service.
2213 unsubscribe (GUPnPServiceProxy *proxy)
2215 GUPnPContext *context;
2216 SoupSession *session;
2219 context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
2221 /* Remove server handler */
2222 server = gupnp_context_get_server (context);
2223 soup_server_remove_handler (server, proxy->priv->path);
2225 if (proxy->priv->sid != NULL) {
2229 /* Create unsubscription message */
2230 sub_url = gupnp_service_info_get_event_subscription_url
2231 (GUPNP_SERVICE_INFO (proxy));
2233 msg = soup_message_new (GENA_METHOD_UNSUBSCRIBE, sub_url);
2239 soup_message_headers_append (msg->request_headers,
2244 session = gupnp_context_get_session (context);
2246 soup_session_queue_message (session, msg, NULL, NULL);
2250 g_free (proxy->priv->sid);
2251 proxy->priv->sid = NULL;
2253 /* Reset sequence number */
2254 proxy->priv->seq = 0;
2257 /* Remove subscription timeout */
2258 if (proxy->priv->subscription_timeout_src) {
2259 g_source_destroy (proxy->priv->subscription_timeout_src);
2260 proxy->priv->subscription_timeout_src = NULL;
2265 * gupnp_service_proxy_set_subscribed:
2266 * @proxy: A #GUPnPServiceProxy
2267 * @subscribed: %TRUE to subscribe to this service
2269 * (Un)subscribes to this service.
2271 * Note that the relevant messages are not immediately sent but queued.
2272 * If you want to unsubcribe from this service because the application
2273 * is quitting, rely on automatic synchronised unsubscription on object
2274 * destruction instead.
2277 gupnp_service_proxy_set_subscribed (GUPnPServiceProxy *proxy,
2278 gboolean subscribed)
2280 g_return_if_fail (GUPNP_IS_SERVICE_PROXY (proxy));
2282 if (proxy->priv->subscribed == subscribed)
2285 proxy->priv->subscribed = subscribed;
2290 unsubscribe (proxy);
2292 g_object_notify (G_OBJECT (proxy), "subscribed");
2296 * gupnp_service_proxy_get_subscribed:
2297 * @proxy: A #GUPnPServiceProxy
2299 * Returns if we are subscribed to this service.
2301 * Return value: %TRUE if we are subscribed to this service, otherwise %FALSE.
2304 gupnp_service_proxy_get_subscribed (GUPnPServiceProxy *proxy)
2306 g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
2308 return proxy->priv->subscribed;