Submit version 0.20.1 of GUPnP (4186015)
[profile/ivi/GUPnP.git] / libgupnp / gupnp-service-proxy.c
1 /*
2  * Copyright (C) 2006, 2007, 2008 OpenedHand Ltd.
3  *
4  * Author: Jorn Baayen <jorn@openedhand.com>
5  *
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.
10  *
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.
15  *
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.
20  */
21
22 /**
23  * SECTION:gupnp-service-proxy
24  * @short_description: Proxy class for remote services.
25  *
26  * #GUPnPServiceProxy sends commands to a remote UPnP service and handles
27  * incoming event notifications. #GUPnPServiceProxy implements the
28  * #GUPnPServiceInfo interface.
29  */
30
31 #include <libsoup/soup.h>
32 #include <gobject/gvaluecollector.h>
33 #include <string.h>
34 #include <locale.h>
35
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"
41 #include "xml-util.h"
42 #include "gena-protocol.h"
43 #include "http-headers.h"
44 #include "gvalue-util.h"
45
46 G_DEFINE_TYPE (GUPnPServiceProxy,
47                gupnp_service_proxy,
48                GUPNP_TYPE_SERVICE_INFO);
49
50 struct _GUPnPServiceProxyPrivate {
51         gboolean subscribed;
52
53         GList *pending_actions;
54
55         char *path; /* Path to this proxy */
56
57         char *sid; /* Subscription ID */
58         GSource *subscription_timeout_src;
59
60         int seq; /* Event sequence number */
61
62         GHashTable *notify_hash;
63
64         GList *pending_messages; /* Pending SoupMessages from this proxy */
65
66         GList *pending_notifies; /* Pending notifications to be sent (xmlDoc) */
67         GSource *notify_idle_src; /* Idle handler src of notification emiter */
68 };
69
70 enum {
71         PROP_0,
72         PROP_SUBSCRIBED
73 };
74
75 enum {
76         SUBSCRIPTION_LOST,
77         LAST_SIGNAL
78 };
79
80 static guint signals[LAST_SIGNAL];
81
82 struct _GUPnPServiceProxyAction {
83         GUPnPServiceProxy *proxy;
84
85         SoupMessage *msg;
86         GString *msg_str;
87
88         GUPnPServiceProxyActionCallback callback;
89         gpointer user_data;
90
91         GError *error;    /* If non-NULL, description of error that
92                              occurred when preparing message */
93 };
94
95 typedef struct {
96         GType type;
97
98         GList *callbacks;
99 } NotifyData;
100
101 typedef struct {
102         GUPnPServiceProxyNotifyCallback callback;
103         gpointer user_data;
104 } CallbackData;
105
106 typedef struct {
107         char *sid;
108         int seq;
109
110         xmlDoc *doc;
111 } EmitNotifyData;
112
113 static void
114 subscribe_got_response (SoupSession       *session,
115                         SoupMessage       *msg,
116                         GUPnPServiceProxy *proxy);
117 static void
118 subscribe (GUPnPServiceProxy *proxy);
119 static void
120 unsubscribe (GUPnPServiceProxy *proxy);
121
122 static void
123 gupnp_service_proxy_action_free (GUPnPServiceProxyAction *action)
124 {
125         action->proxy->priv->pending_actions =
126                 g_list_remove (action->proxy->priv->pending_actions, action);
127
128         if (action->msg != NULL)
129                 g_object_unref (action->msg);
130
131         g_slice_free (GUPnPServiceProxyAction, action);
132 }
133
134 static void
135 notify_data_free (NotifyData *data)
136 {
137         g_list_free (data->callbacks);
138
139         g_slice_free (NotifyData, data);
140 }
141
142 /* Steals doc reference */
143 static EmitNotifyData *
144 emit_notify_data_new (const char *sid,
145                       int         seq,
146                       xmlDoc     *doc)
147 {
148         EmitNotifyData *data;
149
150         data = g_slice_new (EmitNotifyData);
151
152         data->sid = g_strdup (sid);
153         data->seq = seq;
154         data->doc = doc;
155
156         return data;
157 }
158
159 static void
160 emit_notify_data_free (EmitNotifyData *data)
161 {
162         g_free (data->sid);
163         xmlFreeDoc (data->doc);
164
165         g_slice_free (EmitNotifyData, data);
166 }
167
168 static void
169 gupnp_service_proxy_init (GUPnPServiceProxy *proxy)
170 {
171         static int proxy_counter = 0;
172
173         proxy->priv = G_TYPE_INSTANCE_GET_PRIVATE (proxy,
174                                                    GUPNP_TYPE_SERVICE_PROXY,
175                                                    GUPnPServiceProxyPrivate);
176
177         /* Generate unique path */
178         proxy->priv->path = g_strdup_printf ("/ServiceProxy%d", proxy_counter);
179         proxy_counter++;
180
181         /* Set up notify hash */
182         proxy->priv->notify_hash =
183                 g_hash_table_new_full (g_str_hash,
184                                        g_str_equal,
185                                        g_free,
186                                        (GDestroyNotify) notify_data_free);
187 }
188
189 static void
190 gupnp_service_proxy_set_property (GObject      *object,
191                                   guint         property_id,
192                                   const GValue *value,
193                                   GParamSpec   *pspec)
194 {
195         GUPnPServiceProxy *proxy;
196
197         proxy = GUPNP_SERVICE_PROXY (object);
198
199         switch (property_id) {
200         case PROP_SUBSCRIBED:
201                 gupnp_service_proxy_set_subscribed
202                                 (proxy, g_value_get_boolean (value));
203                 break;
204         default:
205                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
206                 break;
207         }
208 }
209
210 static void
211 gupnp_service_proxy_get_property (GObject    *object,
212                                   guint       property_id,
213                                   GValue     *value,
214                                   GParamSpec *pspec)
215 {
216         GUPnPServiceProxy *proxy;
217
218         proxy = GUPNP_SERVICE_PROXY (object);
219
220         switch (property_id) {
221         case PROP_SUBSCRIBED:
222                 g_value_set_boolean (value,
223                                      proxy->priv->subscribed);
224                 break;
225         default:
226                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
227                 break;
228         }
229 }
230
231 static void
232 gupnp_service_proxy_dispose (GObject *object)
233 {
234         GUPnPServiceProxy *proxy;
235         GObjectClass *object_class;
236         GUPnPContext *context;
237         SoupSession *session;
238
239         proxy = GUPNP_SERVICE_PROXY (object);
240
241         /* Unsubscribe */
242         if (proxy->priv->subscribed) {
243                 unsubscribe (proxy);
244
245                 proxy->priv->subscribed = FALSE;
246         }
247
248         /* Cancel pending actions */
249         while (proxy->priv->pending_actions) {
250                 GUPnPServiceProxyAction *action;
251
252                 action = proxy->priv->pending_actions->data;
253
254                 gupnp_service_proxy_cancel_action (proxy, action);
255         }
256
257         /* Cancel pending messages */
258         context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
259         if (context)
260                 session = gupnp_context_get_session (context);
261         else
262                 session = NULL; /* Not the first time dispose is called. */
263
264         while (proxy->priv->pending_messages) {
265                 SoupMessage *msg;
266
267                 msg = proxy->priv->pending_messages->data;
268
269                 soup_session_cancel_message (session,
270                                              msg,
271                                              SOUP_STATUS_CANCELLED);
272
273                 proxy->priv->pending_messages =
274                         g_list_delete_link (proxy->priv->pending_messages,
275                                             proxy->priv->pending_messages);
276         }
277
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;
282         }
283         
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);
289         }
290         
291         /* Call super */
292         object_class = G_OBJECT_CLASS (gupnp_service_proxy_parent_class);
293         object_class->dispose (object);
294 }
295
296 static void
297 gupnp_service_proxy_finalize (GObject *object)
298 {
299         GUPnPServiceProxy *proxy;
300         GObjectClass *object_class;
301
302         proxy = GUPNP_SERVICE_PROXY (object);
303
304         g_free (proxy->priv->path);
305
306         g_hash_table_destroy (proxy->priv->notify_hash);
307
308         /* Call super */
309         object_class = G_OBJECT_CLASS (gupnp_service_proxy_parent_class);
310         object_class->finalize (object);
311 }
312
313 static void
314 gupnp_service_proxy_class_init (GUPnPServiceProxyClass *klass)
315 {
316         GObjectClass *object_class;
317
318         object_class = G_OBJECT_CLASS (klass);
319
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;
324
325         g_type_class_add_private (klass, sizeof (GUPnPServiceProxyPrivate));
326
327         /**
328          * GUPnPServiceProxy:subscribed:
329          *
330          * Whether we are subscribed to this service.
331          **/
332         g_object_class_install_property
333                 (object_class,
334                  PROP_SUBSCRIBED,
335                  g_param_spec_boolean ("subscribed",
336                                        "Subscribed",
337                                        "Whether we are subscribed to this "
338                                        "service",
339                                        FALSE,
340                                        G_PARAM_READWRITE |
341                                        G_PARAM_STATIC_NAME |
342                                        G_PARAM_STATIC_NICK |
343                                        G_PARAM_STATIC_BLURB));
344
345         /**
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
349          * been lost
350          *
351          * Emitted whenever the subscription to this service has been lost due
352          * to an error condition.
353          **/
354         signals[SUBSCRIPTION_LOST] =
355                 g_signal_new ("subscription-lost",
356                               GUPNP_TYPE_SERVICE_PROXY,
357                               G_SIGNAL_RUN_LAST,
358                               G_STRUCT_OFFSET (GUPnPServiceProxyClass,
359                                                subscription_lost),
360                               NULL,
361                               NULL,
362                               g_cclosure_marshal_VOID__POINTER,
363                               G_TYPE_NONE,
364                               1,
365                               G_TYPE_POINTER);
366 }
367
368 /**
369  * gupnp_service_proxy_send_action:
370  * @proxy: A #GUPnPServiceProxy
371  * @action: An action
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
376  *
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.
380  *
381  * Return value: %TRUE if sending the action was succesful.
382  **/
383 gboolean
384 gupnp_service_proxy_send_action (GUPnPServiceProxy *proxy,
385                                  const char        *action,
386                                  GError           **error,
387                                  ...)
388 {
389         va_list var_args;
390         gboolean ret;
391
392         va_start (var_args, error);
393         ret = gupnp_service_proxy_send_action_valist (proxy,
394                                                       action,
395                                                       error,
396                                                       var_args);
397         va_end (var_args);
398
399         return ret;
400 }
401
402 static void
403 stop_main_loop (GUPnPServiceProxy       *proxy,
404                 GUPnPServiceProxyAction *action,
405                 gpointer                 user_data)
406 {
407         g_main_loop_quit ((GMainLoop *) user_data);
408 }
409
410 /* This is a skip variant of G_VALUE_LCOPY, same as there is
411  * G_VALUE_COLLECT_SKIP for G_VALUE_COLLECT.
412  */
413 #define VALUE_LCOPY_SKIP(value_type, var_args) \
414         G_STMT_START { \
415                 GTypeValueTable *_vtable = g_type_value_table_peek (value_type); \
416                 const gchar *_lcopy_format = _vtable->lcopy_format; \
417          \
418                 while (*_lcopy_format) { \
419                         switch (*_lcopy_format++) { \
420                         case G_VALUE_COLLECT_INT: \
421                                 va_arg ((var_args), gint); \
422                                 break; \
423                         case G_VALUE_COLLECT_LONG: \
424                                 va_arg ((var_args), glong); \
425                                 break; \
426                         case G_VALUE_COLLECT_INT64: \
427                                 va_arg ((var_args), gint64); \
428                                 break; \
429                         case G_VALUE_COLLECT_DOUBLE: \
430                                 va_arg ((var_args), gdouble); \
431                                 break; \
432                         case G_VALUE_COLLECT_POINTER: \
433                                 va_arg ((var_args), gpointer); \
434                                 break; \
435                         default: \
436                                 g_assert_not_reached (); \
437                         } \
438                 } \
439         } G_STMT_END
440
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.
445  */
446 #define VAR_ARGS_TO_OUT_HASH_TABLE(var_args, hash) \
447         G_STMT_START { \
448                 const gchar *arg_name = va_arg (var_args, const gchar *); \
449          \
450                 while (arg_name != NULL) { \
451                         GValue *value = g_new0 (GValue, 1); \
452                         GType type = va_arg (var_args, GType); \
453          \
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 *); \
458                 } \
459         } G_STMT_END
460
461 /* Initializes hash table to hold arg names as keys and GValues of
462  * given type and value.
463  */
464 #define VAR_ARGS_TO_IN_HASH_TABLE(var_args, hash) \
465         G_STMT_START { \
466                 const gchar *arg_name = va_arg (var_args, const gchar *); \
467          \
468                 while (arg_name != NULL) { \
469                         GValue *value = g_new0 (GValue, 1); \
470                         gchar *error = NULL; \
471                         GType type = va_arg (var_args, GType); \
472          \
473                         G_VALUE_COLLECT_INIT (value, \
474                                               type, \
475                                               var_args, \
476                                               G_VALUE_NOCOPY_CONTENTS, \
477                                               &error); \
478                         if (error == NULL) { \
479                                 g_hash_table_insert (hash, g_strdup (arg_name), value); \
480                         } else { \
481                                 g_warning ("Failed to collect value of type %s for %s: %s", \
482                                            g_type_name (type), \
483                                            arg_name, \
484                                            error); \
485                                 g_free (error); \
486                         } \
487                         arg_name = va_arg (var_args, const gchar *); \
488                 } \
489         } G_STMT_END
490
491 /* Puts values stored in hash table with GValues into var args.
492  */
493 #define OUT_HASH_TABLE_TO_VAR_ARGS(hash, var_args) \
494         G_STMT_START { \
495                 const gchar *arg_name = va_arg (var_args, const gchar *); \
496          \
497                 while (arg_name != NULL) { \
498                         GValue *value = g_hash_table_lookup (hash, arg_name); \
499                         GType type = va_arg (var_args, GType); \
500          \
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), \
508                                            arg_name); \
509                         } else { \
510                                 gchar *error = NULL; \
511          \
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), \
516                                                    arg_name, \
517                                                    error); \
518                                         g_free (error); \
519                                 } \
520                         } \
521                         arg_name = va_arg (var_args, const gchar *); \
522                 } \
523         } G_STMT_END
524
525 /* GDestroyNotify for GHashTable holding GValues.
526  */
527 static void
528 value_free (gpointer data)
529 {
530   GValue *value = (GValue *) data;
531
532   g_value_unset (value);
533   g_free (value);
534 }
535
536 /**
537  * gupnp_service_proxy_send_action_valist:
538  * @proxy: A #GUPnPServiceProxy
539  * @action: An action
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
544  *
545  * See gupnp_service_proxy_send_action().
546  *
547  * Return value: %TRUE if sending the action was succesful.
548  **/
549 gboolean
550 gupnp_service_proxy_send_action_valist (GUPnPServiceProxy *proxy,
551                                         const char        *action,
552                                         GError           **error,
553                                         va_list            var_args)
554 {
555         GHashTable *in_hash;
556         GHashTable *out_hash;
557         va_list var_args_copy;
558         gboolean result;
559         GError *local_error;
560
561         g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
562         g_return_val_if_fail (action, FALSE);
563
564         in_hash = g_hash_table_new_full (g_str_hash,
565                                          g_str_equal,
566                                          g_free,
567                                          value_free);
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,
571                                           g_str_equal,
572                                           g_free,
573                                           value_free);
574         VAR_ARGS_TO_OUT_HASH_TABLE (var_args, out_hash);
575
576         local_error = NULL;
577         result = gupnp_service_proxy_send_action_hash (proxy,
578                                                        action,
579                                                        &local_error,
580                                                        in_hash,
581                                                        out_hash);
582
583         if (local_error == NULL) {
584                 OUT_HASH_TABLE_TO_VAR_ARGS (out_hash, var_args_copy);
585         } else {
586                 g_propagate_error (error, local_error);
587         }
588         va_end (var_args_copy);
589         g_hash_table_unref (in_hash);
590         g_hash_table_unref (out_hash);
591
592         return result;
593 }
594
595 /**
596  * gupnp_service_proxy_send_action_hash:
597  * @proxy: A #GUPnPServiceProxy
598  * @action: An action
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
604  *
605  * See gupnp_service_proxy_send_action(); this version takes a pair of
606  * #GHashTable<!-- -->s for runtime determined parameter lists.
607  *
608  * Return value: %TRUE if sending the action was succesful.
609  **/
610 gboolean
611 gupnp_service_proxy_send_action_hash (GUPnPServiceProxy *proxy,
612                                       const char        *action,
613                                       GError           **error,
614                                       GHashTable        *in_hash,
615                                       GHashTable        *out_hash)
616 {
617         GMainLoop *main_loop;
618         GUPnPServiceProxyAction *handle;
619
620         g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
621         g_return_val_if_fail (action, FALSE);
622
623         main_loop = g_main_loop_new (g_main_context_get_thread_default (),
624                                      TRUE);
625
626         handle = gupnp_service_proxy_begin_action_hash (proxy,
627                                                         action,
628                                                         stop_main_loop,
629                                                         main_loop,
630                                                         in_hash);
631         if (!handle) {
632                 g_main_loop_unref (main_loop);
633
634                 return FALSE;
635         }
636
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);
640
641         g_main_loop_unref (main_loop);
642
643         if (!gupnp_service_proxy_end_action_hash (proxy,
644                                                   handle,
645                                                   error,
646                                                   out_hash))
647                 return FALSE;
648
649         return TRUE;
650 }
651
652 /**
653  * gupnp_service_proxy_send_action_list:
654  * @proxy: (transfer none) : A #GUPnPServiceProxy
655  * @action: An action
656  * @error: The location where to store any error, or %NULL
657  * @in_names: (element-type utf8) (transfer none): #GList of 'in' parameter
658  * names (as strings)
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
662  * names (as strings)
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.
667  *
668  * The synchronous variant of #gupnp_service_proxy_begin_action_list and
669  * #gupnp_service_proxy_end_action_list.
670  *
671  * Return value: %TRUE if sending the action was succesful.
672  **/
673 gboolean
674 gupnp_service_proxy_send_action_list (GUPnPServiceProxy *proxy,
675                                       const char        *action,
676                                       GError           **error,
677                                       GList             *in_names,
678                                       GList             *in_values,
679                                       GList             *out_names,
680                                       GList             *out_types,
681                                       GList            **out_values)
682 {
683         GMainLoop *main_loop;
684         GUPnPServiceProxyAction *handle;
685
686         g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
687         g_return_val_if_fail (action, FALSE);
688
689         main_loop = g_main_loop_new (g_main_context_get_thread_default (),
690                                      TRUE);
691
692         handle = gupnp_service_proxy_begin_action_list (proxy,
693                                                         action,
694                                                         in_names,
695                                                         in_values,
696                                                         stop_main_loop,
697                                                         main_loop);
698         if (!handle) {
699                 g_main_loop_unref (main_loop);
700
701                 return FALSE;
702         }
703
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);
707
708         g_main_loop_unref (main_loop);
709
710         if (!gupnp_service_proxy_end_action_list (proxy,
711                                                   handle,
712                                                   error,
713                                                   out_names,
714                                                   out_types,
715                                                   out_values))
716                 return FALSE;
717
718         return TRUE;
719 }
720
721 /**
722  * gupnp_service_proxy_begin_action:
723  * @proxy: A #GUPnPServiceProxy
724  * @action: An action
725  * @callback: (scope async): The callback to call when sending the action has succeeded
726  * or failed
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
730  *
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.
735  *
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().
739  **/
740 GUPnPServiceProxyAction *
741 gupnp_service_proxy_begin_action (GUPnPServiceProxy              *proxy,
742                                   const char                     *action,
743                                   GUPnPServiceProxyActionCallback callback,
744                                   gpointer                        user_data,
745                                   ...)
746 {
747         va_list var_args;
748         GUPnPServiceProxyAction *ret;
749
750         va_start (var_args, user_data);
751         ret = gupnp_service_proxy_begin_action_valist (proxy,
752                                                        action,
753                                                        callback,
754                                                        user_data,
755                                                        var_args);
756         va_end (var_args);
757
758         return ret;
759 }
760
761 /* Begins a basic action message */
762 static GUPnPServiceProxyAction *
763 begin_action_msg (GUPnPServiceProxy              *proxy,
764                   const char                     *action,
765                   GUPnPServiceProxyActionCallback callback,
766                   gpointer                        user_data)
767 {
768         GUPnPServiceProxyAction *ret;
769         char *control_url, *full_action;
770         const char *service_type;
771
772         /* Create action structure */
773         ret = g_slice_new (GUPnPServiceProxyAction);
774
775         ret->proxy = proxy;
776
777         ret->callback  = callback;
778         ret->user_data = user_data;
779
780         ret->msg = NULL;
781
782         ret->error = NULL;
783
784         proxy->priv->pending_actions =
785                 g_list_prepend (proxy->priv->pending_actions, ret);
786
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");
794
795                 return ret;
796         }
797
798         /* Create message */
799         control_url = gupnp_service_info_get_control_url
800                                         (GUPNP_SERVICE_INFO (proxy));
801
802         if (control_url != NULL) {
803                 ret->msg = soup_message_new (SOUP_METHOD_POST, control_url);
804
805                 g_free (control_url);
806         }
807
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");
812
813                 return ret;
814         }
815
816         /* Specify action */
817         full_action = g_strdup_printf ("\"%s#%s\"", service_type, action);
818         soup_message_headers_append (ret->msg->request_headers,
819                                      "SOAPAction",
820                                      full_action);
821         g_free (full_action);
822
823         /* Specify language */
824         http_request_set_accept_language (ret->msg);
825
826         /* Accept gzip encoding */
827         soup_message_headers_append (ret->msg->request_headers,
828                                      "Accept-Encoding", "gzip");
829
830         /* Set up envelope */
831         ret->msg_str = xml_util_new_string ();
832
833         g_string_append (ret->msg_str,
834                          "<?xml version=\"1.0\"?>"
835                          "<s:Envelope xmlns:s="
836                                 "\"http://schemas.xmlsoap.org/soap/envelope/\" "
837                           "s:encodingStyle="
838                                 "\"http://schemas.xmlsoap.org/soap/encoding/\">"
839                          "<s:Body>");
840
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, "\">");
846
847         return ret;
848 }
849
850 /* Received response to action message */
851 static void
852 action_got_response (SoupSession             *session,
853                      SoupMessage             *msg,
854                      GUPnPServiceProxyAction *action)
855 {
856         const char *full_action;
857
858         switch (msg->status_code) {
859         case SOUP_STATUS_CANCELLED:
860                 /* Silently return */
861                 break;
862
863         case SOUP_STATUS_METHOD_NOT_ALLOWED:
864                 /* Retry with M-POST */
865                 msg->method = "M-POST";
866
867                 soup_message_headers_append
868                         (msg->request_headers,
869                          "Man",
870                          "\"http://schemas.xmlsoap.org/soap/envelope/\"; ns=s");
871
872                 /* Rename "SOAPAction" to "s-SOAPAction" */
873                 full_action = soup_message_headers_get_one
874                         (msg->request_headers,
875                          "SOAPAction");
876                 soup_message_headers_append (msg->request_headers,
877                                              "s-SOAPAction",
878                                              full_action);
879                 soup_message_headers_remove (msg->request_headers,
880                                             "SOAPAction");
881
882                 /* And re-queue */
883                 soup_session_requeue_message (session, msg);
884
885                 break;
886
887         default:
888                 /* Success: Call callback */
889                 action->callback (action->proxy, action, action->user_data);
890
891                 break;
892         }
893 }
894
895 /* Finishes an action message and sends it off */
896 static void
897 finish_action_msg (GUPnPServiceProxyAction *action,
898                    const char              *action_name)
899 {
900         GUPnPContext *context;
901         SoupSession *session;
902
903         /* Finish message */
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, '>');
907
908         g_string_append (action->msg_str,
909                          "</s:Body>"
910                          "</s:Envelope>");
911
912         soup_message_set_request (action->msg,
913                                   "text/xml; charset=\"utf-8\"",
914                                   SOUP_MEMORY_TAKE,
915                                   action->msg_str->str,
916                                   action->msg_str->len);
917
918         g_string_free (action->msg_str, FALSE);
919
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);
923
924         /* Send the message */
925         context = gupnp_service_info_get_context
926                                 (GUPNP_SERVICE_INFO (action->proxy));
927         session = gupnp_context_get_session (context);
928
929         soup_session_queue_message (session,
930                                     action->msg,
931                                     (SoupSessionCallback) action_got_response,
932                                     action);
933 }
934
935 /* Writes a parameter name and GValue pair to @msg */
936 static void
937 write_in_parameter (const char *arg_name,
938                     GValue     *value,
939                     GString    *msg_str)
940 {
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);
945 }
946
947 /**
948  * gupnp_service_proxy_begin_action_valist:
949  * @proxy: A #GUPnPServiceProxy
950  * @action: An action
951  * @callback: (scope async) : The callback to call when sending the action has succeeded
952  * or failed
953  * @user_data: User data for @callback
954  * @var_args: A va_list of tuples of in parameter name, in parameter type, and
955  * in parameter value
956  *
957  * See gupnp_service_proxy_begin_action().
958  *
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().
962  **/
963 GUPnPServiceProxyAction *
964 gupnp_service_proxy_begin_action_valist
965                                    (GUPnPServiceProxy              *proxy,
966                                     const char                     *action,
967                                     GUPnPServiceProxyActionCallback callback,
968                                     gpointer                        user_data,
969                                     va_list                         var_args)
970 {
971         GUPnPServiceProxyAction *ret;
972         GHashTable *in_hash;
973
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);
977
978
979         in_hash = g_hash_table_new_full (g_str_hash,
980                                          g_str_equal,
981                                          g_free,
982                                          value_free);
983         VAR_ARGS_TO_IN_HASH_TABLE (var_args, in_hash);
984         ret = gupnp_service_proxy_begin_action_hash (proxy,
985                                                      action,
986                                                      callback,
987                                                      user_data,
988                                                      in_hash);
989         g_hash_table_unref (in_hash);
990
991         return ret;
992 }
993
994 /**
995  * gupnp_service_proxy_begin_action_list:
996  * @proxy: (transfer none): A #GUPnPServiceProxy
997  * @action: An action
998  * @in_names: (element-type utf8) (transfer none): #GList of 'in' parameter
999  * names (as strings)
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
1003  * failed
1004  * @user_data: User data for @callback
1005  *
1006  * A variant of #gupnp_service_proxy_begin_action that takes lists of
1007  * in-parameter names, types and values.
1008  *
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.
1012  **/
1013 GUPnPServiceProxyAction *
1014 gupnp_service_proxy_begin_action_list
1015                                    (GUPnPServiceProxy               *proxy,
1016                                     const char                      *action,
1017                                     GList                           *in_names,
1018                                     GList                           *in_values,
1019                                     GUPnPServiceProxyActionCallback  callback,
1020                                     gpointer                         user_data)
1021 {
1022         GUPnPServiceProxyAction *ret;
1023         GList *names;
1024         GList *values;
1025
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),
1031                               NULL);
1032
1033         /* Create message */
1034         ret = begin_action_msg (proxy, action, callback, user_data);
1035
1036         if (ret->error) {
1037                 callback (proxy, ret, user_data);
1038
1039                 return ret;
1040         }
1041
1042         /* Arguments */
1043         values = in_values;
1044
1045         for (names = in_names; names; names=names->next) {
1046                 GValue* val = values->data;
1047
1048                 write_in_parameter (names->data,
1049                                     val,
1050                                     ret->msg_str);
1051
1052                 values = values->next;
1053         }
1054
1055         /* Finish and send off */
1056         finish_action_msg (ret, action);
1057
1058         return ret;
1059 }
1060
1061 /**
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
1066  * or failed
1067  * @user_data: User data for @callback
1068  * @hash: (element-type utf8 GValue): A #GHashTable of in parameter name and #GValue pairs
1069  *
1070  * See gupnp_service_proxy_begin_action(); this version takes a #GHashTable
1071  * for runtime generated parameter lists.
1072  *
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().
1076  **/
1077 GUPnPServiceProxyAction *
1078 gupnp_service_proxy_begin_action_hash
1079                                    (GUPnPServiceProxy              *proxy,
1080                                     const char                     *action,
1081                                     GUPnPServiceProxyActionCallback callback,
1082                                     gpointer                        user_data,
1083                                     GHashTable                     *hash)
1084 {
1085         GUPnPServiceProxyAction *ret;
1086
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);
1090
1091         /* Create message */
1092         ret = begin_action_msg (proxy, action, callback, user_data);
1093
1094         if (ret->error) {
1095                 callback (proxy, ret, user_data);
1096
1097                 return ret;
1098         }
1099
1100         /* Arguments */
1101         g_hash_table_foreach (hash, (GHFunc) write_in_parameter, ret->msg_str);
1102
1103         /* Finish and send off */
1104         finish_action_msg (ret, action);
1105
1106         return ret;
1107 }
1108
1109 /**
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
1116  * freed after use
1117  *
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.
1121  *
1122  * Return value: %TRUE on success.
1123  **/
1124 gboolean
1125 gupnp_service_proxy_end_action (GUPnPServiceProxy       *proxy,
1126                                 GUPnPServiceProxyAction *action,
1127                                 GError                 **error,
1128                                 ...)
1129 {
1130         va_list var_args;
1131         gboolean ret;
1132
1133         va_start (var_args, error);
1134         ret = gupnp_service_proxy_end_action_valist (proxy,
1135                                                      action,
1136                                                      error,
1137                                                      var_args);
1138         va_end (var_args);
1139
1140         return ret;
1141 }
1142
1143 /* Checks an action response for errors and returns the parsed
1144  * xmlDoc object. */
1145 static xmlDoc *
1146 check_action_response (GUPnPServiceProxy       *proxy,
1147                        GUPnPServiceProxyAction *action,
1148                        xmlNode                **params,
1149                        GError                 **error)
1150 {
1151         xmlDoc *response;
1152         int code;
1153
1154         /* Check for errors */
1155         switch (action->msg->status_code) {
1156         case SOUP_STATUS_OK:
1157         case SOUP_STATUS_INTERNAL_SERVER_ERROR:
1158                 break;
1159         default:
1160                 _gupnp_error_set_server_error (error, action->msg);
1161
1162                 return NULL;
1163         }
1164
1165         /* Parse response */
1166         response = xmlRecoverMemory (action->msg->response_body->data,
1167                                      action->msg->response_body->length);
1168
1169         if (!response) {
1170                 if (action->msg->status_code == SOUP_STATUS_OK) {
1171                         g_set_error (error,
1172                                      GUPNP_SERVER_ERROR,
1173                                      GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1174                                      "Could not parse SOAP response");
1175                 } else {
1176                         g_set_error_literal
1177                                     (error,
1178                                      GUPNP_SERVER_ERROR,
1179                                      GUPNP_SERVER_ERROR_INTERNAL_SERVER_ERROR,
1180                                      action->msg->reason_phrase);
1181                 }
1182
1183                 return NULL;
1184         }
1185
1186         /* Get parameter list */
1187         *params = xml_util_get_element ((xmlNode *) response,
1188                                         "Envelope",
1189                                         NULL);
1190         if (*params != NULL)
1191                 *params = xml_util_real_node ((*params)->children);
1192
1193         if (*params != NULL) {
1194                 if (strcmp ((const char *) (*params)->name, "Header") == 0)
1195                         *params = xml_util_real_node ((*params)->next);
1196
1197                 if (*params != NULL)
1198                         if (strcmp ((const char *) (*params)->name, "Body") != 0)
1199                                 *params = NULL;
1200         }
1201
1202         if (*params != NULL)
1203                 *params = xml_util_real_node ((*params)->children);
1204
1205         if (*params == NULL) {
1206                 g_set_error (error,
1207                              GUPNP_SERVER_ERROR,
1208                              GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1209                              "Invalid Envelope");
1210
1211                 xmlFreeDoc (response);
1212
1213                 return NULL;
1214         }
1215
1216         /* Check whether we have a Fault */
1217         if (action->msg->status_code == SOUP_STATUS_INTERNAL_SERVER_ERROR) {
1218                 xmlNode *param;
1219                 char *desc;
1220
1221                 param = xml_util_get_element (*params,
1222                                               "detail",
1223                                               "UPnPError",
1224                                               NULL);
1225
1226                 if (!param) {
1227                         g_set_error (error,
1228                                      GUPNP_SERVER_ERROR,
1229                                      GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1230                                      "Invalid Fault");
1231
1232                         xmlFreeDoc (response);
1233
1234                         return NULL;
1235                 }
1236
1237                 /* Code */
1238                 code = xml_util_get_child_element_content_int
1239                                         (param, "errorCode");
1240                 if (code == -1) {
1241                         g_set_error (error,
1242                                      GUPNP_SERVER_ERROR,
1243                                      GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1244                                      "Invalid Fault");
1245
1246                         xmlFreeDoc (response);
1247
1248                         return NULL;
1249                 }
1250
1251                 /* Description */
1252                 desc = xml_util_get_child_element_content_glib
1253                                         (param, "errorDescription");
1254                 if (desc == NULL)
1255                         desc = g_strdup (action->msg->reason_phrase);
1256
1257                 g_set_error_literal (error,
1258                                      GUPNP_CONTROL_ERROR,
1259                                      code,
1260                                      desc);
1261
1262                 g_free (desc);
1263
1264                 xmlFreeDoc (response);
1265
1266                 return NULL;
1267         }
1268
1269         return response;
1270 }
1271
1272 /* Reads a value into the parameter name and initialised GValue pair
1273  * from @response */
1274 static void
1275 read_out_parameter (const char *arg_name,
1276                     GValue     *value,
1277                     xmlNode    *params)
1278 {
1279         xmlNode *param;
1280
1281         /* Try to find a matching parameter in the response*/
1282         param = xml_util_get_element (params,
1283                                       arg_name,
1284                                       NULL);
1285         if (!param) {
1286                 g_warning ("Could not find variable \"%s\" in response",
1287                            arg_name);
1288
1289                 return;
1290         }
1291
1292         gvalue_util_set_value_from_xml_node (value, param);
1293 }
1294
1295 /**
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
1302  * freed after use
1303  *
1304  * See gupnp_service_proxy_end_action().
1305  *
1306  * Return value: %TRUE on success.
1307  **/
1308 gboolean
1309 gupnp_service_proxy_end_action_valist (GUPnPServiceProxy       *proxy,
1310                                        GUPnPServiceProxyAction *action,
1311                                        GError                 **error,
1312                                        va_list                  var_args)
1313 {
1314         GHashTable *out_hash;
1315         va_list var_args_copy;
1316         gboolean result;
1317         GError *local_error;
1318
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);
1322
1323         out_hash = g_hash_table_new_full (g_str_hash,
1324                                           g_str_equal,
1325                                           g_free,
1326                                           value_free);
1327         G_VA_COPY (var_args_copy, var_args);
1328         VAR_ARGS_TO_OUT_HASH_TABLE (var_args, out_hash);
1329         local_error = NULL;
1330         result = gupnp_service_proxy_end_action_hash (proxy,
1331                                                       action,
1332                                                       &local_error,
1333                                                       out_hash);
1334
1335         if (local_error == NULL) {
1336                 OUT_HASH_TABLE_TO_VAR_ARGS (out_hash, var_args_copy);
1337         } else {
1338                 g_propagate_error (error, local_error);
1339         }
1340         va_end (var_args_copy);
1341         g_hash_table_unref (out_hash);
1342
1343         return result;
1344 }
1345
1346 /**
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.
1357  *
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.
1362  *
1363  * Return value : %TRUE on success.
1364  **/
1365 gboolean
1366 gupnp_service_proxy_end_action_list (GUPnPServiceProxy       *proxy,
1367                                      GUPnPServiceProxyAction *action,
1368                                      GError                 **error,
1369                                      GList                   *out_names,
1370                                      GList                   *out_types,
1371                                      GList                  **out_values)
1372 {
1373         xmlDoc *response;
1374         xmlNode *params;
1375         GList *names;
1376         GList *types;
1377         GList *out_values_list;
1378
1379         out_values_list = NULL;
1380
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);
1384
1385         /* Check for saved error from begin_action() */
1386         if (action->error) {
1387                 if (error)
1388                         *error = action->error;
1389                 else
1390                         g_error_free (action->error);
1391
1392                 gupnp_service_proxy_action_free (action);
1393
1394                 return FALSE;
1395         }
1396
1397         /* Check response for errors and do initial parsing */
1398         response = check_action_response (proxy, action, &params, error);
1399         if (response == NULL) {
1400                 gupnp_service_proxy_action_free (action);
1401
1402                 return FALSE;
1403         }
1404
1405         /* Read arguments */
1406         types = out_types;
1407         for (names = out_names; names; names=names->next) {
1408                 GValue *val;
1409
1410                 val = g_slice_new0 (GValue);
1411                 g_value_init (val, (GType) types->data);
1412
1413                 read_out_parameter (names->data, val, params);
1414
1415                 out_values_list = g_list_append (out_values_list, val);
1416
1417                 types = types->next;
1418         }
1419
1420         *out_values = out_values_list;
1421
1422         /* Cleanup */
1423         gupnp_service_proxy_action_free (action);
1424
1425         xmlFreeDoc (response);
1426
1427         return TRUE;
1428 }
1429
1430 /**
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
1437  *
1438  * See gupnp_service_proxy_end_action(); this version takes a #GHashTable for
1439  * runtime generated parameter lists.
1440  *
1441  * Return value: %TRUE on success.
1442  **/
1443 gboolean
1444 gupnp_service_proxy_end_action_hash (GUPnPServiceProxy       *proxy,
1445                                      GUPnPServiceProxyAction *action,
1446                                      GError                 **error,
1447                                      GHashTable              *hash)
1448 {
1449         xmlDoc *response;
1450         xmlNode *params;
1451
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);
1455
1456         /* Check for saved error from begin_action() */
1457         if (action->error) {
1458                 if (error)
1459                         *error = action->error;
1460                 else
1461                         g_error_free (action->error);
1462
1463                 gupnp_service_proxy_action_free (action);
1464
1465                 return FALSE;
1466         }
1467
1468         /* Check response for errors and do initial parsing */
1469         response = check_action_response (proxy, action, &params, error);
1470         if (response == NULL) {
1471                 gupnp_service_proxy_action_free (action);
1472
1473                 return FALSE;
1474         }
1475
1476         /* Read arguments */
1477         g_hash_table_foreach (hash, (GHFunc) read_out_parameter, params);
1478
1479         /* Cleanup */
1480         gupnp_service_proxy_action_free (action);
1481
1482         xmlFreeDoc (response);
1483
1484         return TRUE;
1485 }
1486
1487 /**
1488  * gupnp_service_proxy_cancel_action:
1489  * @proxy: A #GUPnPServiceProxy
1490  * @action: A #GUPnPServiceProxyAction handle
1491  *
1492  * Cancels @action, freeing the @action handle.
1493  **/
1494 void
1495 gupnp_service_proxy_cancel_action (GUPnPServiceProxy       *proxy,
1496                                    GUPnPServiceProxyAction *action)
1497 {
1498         GUPnPContext *context;
1499         SoupSession *session;
1500
1501         g_return_if_fail (GUPNP_IS_SERVICE_PROXY (proxy));
1502         g_return_if_fail (action);
1503         g_return_if_fail (proxy == action->proxy);
1504
1505         if (action->msg != NULL) {
1506                 context = gupnp_service_info_get_context
1507                                         (GUPNP_SERVICE_INFO (proxy));
1508                 session = gupnp_context_get_session (context);
1509
1510                 soup_session_cancel_message (session,
1511                                              action->msg,
1512                                              SOUP_STATUS_CANCELLED);
1513         }
1514
1515         if (action->error != NULL)
1516                 g_error_free (action->error);
1517
1518         gupnp_service_proxy_action_free (action);
1519 }
1520
1521 /**
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
1528  *
1529  * Sets up @callback to be called whenever a change notification for
1530  * @variable is recieved.
1531  *
1532  * Return value: %TRUE on success.
1533  **/
1534 gboolean
1535 gupnp_service_proxy_add_notify (GUPnPServiceProxy              *proxy,
1536                                 const char                     *variable,
1537                                 GType                           type,
1538                                 GUPnPServiceProxyNotifyCallback callback,
1539                                 gpointer                        user_data)
1540 {
1541         NotifyData *data;
1542         CallbackData *callback_data;
1543
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);
1548
1549         /* See if we already have notifications set up for this variable */
1550         data = g_hash_table_lookup (proxy->priv->notify_hash, variable);
1551         if (data == NULL) {
1552                 /* No, create one */
1553                 data = g_slice_new (NotifyData);
1554
1555                 data->type       = type;
1556                 data->callbacks  = NULL;
1557
1558                 g_hash_table_insert (proxy->priv->notify_hash,
1559                                      g_strdup (variable),
1560                                      data);
1561         } else {
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.",
1566                                    variable,
1567                                    g_type_name (data->type),
1568                                    g_type_name (type));
1569
1570                         return FALSE;
1571                 }
1572         }
1573
1574         /* Append callback */
1575         callback_data = g_slice_new (CallbackData);
1576
1577         callback_data->callback  = callback;
1578         callback_data->user_data = user_data;
1579
1580         data->callbacks = g_list_append (data->callbacks, callback_data);
1581
1582         return TRUE;
1583 }
1584
1585 /**
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
1591  *
1592  * Cancels the variable change notification for @callback and @user_data.
1593  *
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.
1597  *
1598  * Return value: %TRUE on success.
1599  **/
1600 gboolean
1601 gupnp_service_proxy_remove_notify (GUPnPServiceProxy              *proxy,
1602                                    const char                     *variable,
1603                                    GUPnPServiceProxyNotifyCallback callback,
1604                                    gpointer                        user_data)
1605 {
1606         NotifyData *data;
1607         gboolean found;
1608         GList *l;
1609
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);
1613
1614         /* Look up NotifyData for variable */
1615         data = g_hash_table_lookup (proxy->priv->notify_hash, variable);
1616         if (data == NULL) {
1617                 g_warning ("No notifications found for variable %s",
1618                            variable);
1619
1620                 return FALSE;
1621         }
1622
1623         /* Look up our callback-user_data pair */
1624         found = FALSE;
1625
1626         for (l = data->callbacks; l; l = l->next) {
1627                 CallbackData *callback_data;
1628
1629                 callback_data = l->data;
1630
1631                 if (callback_data->callback  == callback &&
1632                     callback_data->user_data == user_data) {
1633                         /* Gotcha! */
1634                         g_slice_free (CallbackData, callback_data);
1635
1636                         data->callbacks =
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,
1641                                                      variable);
1642                         }
1643
1644                         found = TRUE;
1645
1646                         break;
1647                 }
1648         }
1649
1650         if (found == FALSE)
1651                 g_warning ("No such callback-user_data pair was found");
1652
1653         return found;
1654 }
1655
1656 static void
1657 emit_notification (GUPnPServiceProxy *proxy,
1658                    xmlNode           *var_node)
1659 {
1660         NotifyData *data;
1661         GValue value = {0, };
1662         GList *l;
1663
1664         data = g_hash_table_lookup (proxy->priv->notify_hash, var_node->name);
1665         if (data == NULL)
1666                 return;
1667
1668         /* Make a GValue of the desired type */
1669         g_value_init (&value, data->type);
1670
1671         if (!gvalue_util_set_value_from_xml_node (&value, var_node)) {
1672                 g_value_unset (&value);
1673
1674                 return;
1675         }
1676
1677         /* Call callbacks */
1678         for (l = data->callbacks; l; l = l->next) {
1679                 CallbackData *callback_data;
1680
1681                 callback_data = l->data;
1682
1683                 callback_data->callback (proxy,
1684                                          (const char *) var_node->name,
1685                                          &value,
1686                                          callback_data->user_data);
1687         }
1688
1689         /* Cleanup */
1690         g_value_unset (&value);
1691 }
1692
1693 static void
1694 emit_notifications_for_doc (GUPnPServiceProxy *proxy,
1695                             xmlDoc            *doc)
1696 {
1697         xmlNode *node;
1698
1699         node = xmlDocGetRootElement (doc);
1700
1701         /* Iterate over all provided properties */
1702         for (node = node->children; node; node = node->next) {
1703                 xmlNode *var_node;
1704
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.
1709                  */
1710                 for (var_node = node->children;
1711                      var_node;
1712                      var_node = var_node->next) {
1713                         if (strcmp ((char *) node->name, "property") == 0)
1714                                 emit_notification (proxy, var_node);
1715                 }
1716         }
1717 }
1718
1719 /* Emit pending notifications. See comment below on why we do this. */
1720 static gboolean
1721 emit_notifications (gpointer user_data)
1722 {
1723         GUPnPServiceProxy *proxy = user_data;
1724         GList *pending_notify;
1725         gboolean resubscribe = FALSE;
1726
1727         g_assert (user_data);
1728
1729         if (proxy->priv->sid == NULL)
1730                 /* No SID */
1731                 if (G_LIKELY (proxy->priv->subscribed))
1732                         /* subscription in progress, delay emision! */
1733                         return TRUE;
1734
1735         for (pending_notify = proxy->priv->pending_notifies;
1736              pending_notify != NULL;
1737              pending_notify = pending_notify->next) {
1738                 EmitNotifyData *emit_notify_data;
1739
1740                 emit_notify_data = pending_notify->data;
1741
1742                 if (emit_notify_data->seq > proxy->priv->seq) {
1743                         /* Oops, we missed a notify. Resubscribe .. */
1744                         resubscribe = TRUE;
1745
1746                         break;
1747                 }
1748
1749                 /* Increment our own event sequence number */
1750                 if (proxy->priv->seq < G_MAXINT32)
1751                         proxy->priv->seq++;
1752                 else
1753                         proxy->priv->seq = 1;
1754
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);
1761         }
1762
1763         /* Cleanup */
1764         while (proxy->priv->pending_notifies != NULL) {
1765                 emit_notify_data_free (proxy->priv->pending_notifies->data);
1766
1767                 proxy->priv->pending_notifies =
1768                         g_list_delete_link (proxy->priv->pending_notifies,
1769                                             proxy->priv->pending_notifies);
1770         }
1771
1772         proxy->priv->notify_idle_src = NULL;
1773
1774         if (resubscribe) {
1775                 unsubscribe (proxy);
1776                 subscribe (proxy);
1777         }
1778
1779         return FALSE;
1780 }
1781
1782 /*
1783  * HTTP server received a message. Handle, if this was a NOTIFY
1784  * message with our SID.
1785  */
1786 static void
1787 server_handler (SoupServer        *soup_server,
1788                 SoupMessage       *msg, 
1789                 const char        *server_path,
1790                 GHashTable        *query,
1791                 SoupClientContext *soup_client,
1792                 gpointer           user_data)
1793 {
1794         GUPnPServiceProxy *proxy;
1795         const char *hdr;
1796         int seq;
1797         xmlDoc *doc;
1798         xmlNode *node;
1799         EmitNotifyData *emit_notify_data;
1800
1801         proxy = GUPNP_SERVICE_PROXY (user_data);
1802
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);
1806
1807                 return;
1808         }
1809
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);
1814
1815                 return;
1816         }
1817
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);
1822
1823                 return;
1824         }
1825
1826         hdr = soup_message_headers_get_one (msg->request_headers, "SEQ");
1827         if (hdr == NULL) {
1828                 /* No SEQ header */
1829                 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1830
1831                 return;
1832         }
1833
1834         seq = atoi (hdr);
1835
1836         hdr = soup_message_headers_get_one (msg->request_headers, "SID");
1837         if (hdr == NULL) {
1838                 /* No SID */
1839                 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1840
1841                 return;
1842         }
1843
1844         /* Parse the actual XML message content */
1845         doc = xmlRecoverMemory (msg->request_body->data,
1846                                 msg->request_body->length);
1847         if (doc == NULL) {
1848                 /* Failed */
1849                 g_warning ("Failed to parse NOTIFY message body");
1850
1851                 soup_message_set_status (msg,
1852                                          SOUP_STATUS_INTERNAL_SERVER_ERROR);
1853
1854                 return;
1855         }
1856
1857         /* Get root propertyset element */
1858         node = xmlDocGetRootElement (doc);
1859         if (node == NULL || strcmp ((char *) node->name, "propertyset")) {
1860                 /* Empty or unsupported */
1861                 xmlFreeDoc (doc);
1862
1863                 soup_message_set_status (msg, SOUP_STATUS_OK);
1864
1865                 return;
1866         }
1867
1868         /*
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.
1872          */
1873         emit_notify_data = emit_notify_data_new (hdr, seq, doc);
1874
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,
1880                                        emit_notifications,
1881                                        proxy, NULL);
1882                 g_source_attach (proxy->priv->notify_idle_src,
1883                                  g_main_context_get_thread_default ());
1884
1885                 g_source_unref (proxy->priv->notify_idle_src);
1886         }
1887         
1888         /* Everything went OK */
1889         soup_message_set_status (msg, SOUP_STATUS_OK);
1890 }
1891
1892 /*
1893  * Generates a timeout header for the subscription timeout specified
1894  * in our GUPnPContext.
1895  */
1896 static char *
1897 make_timeout_header (GUPnPContext *context)
1898 {
1899         guint timeout;
1900
1901         timeout = gupnp_context_get_subscription_timeout (context);
1902         if (timeout > 0)
1903                 return g_strdup_printf ("Second-%d", timeout);
1904         else
1905                 return g_strdup ("infinite");
1906 }
1907
1908 /*
1909  * Subscription expired.
1910  */
1911 static gboolean
1912 subscription_expire (gpointer user_data)
1913 {
1914         GUPnPServiceProxy *proxy;
1915         GUPnPContext *context;
1916         SoupMessage *msg;
1917         SoupSession *session;
1918         char *sub_url, *timeout;
1919
1920         proxy = GUPNP_SERVICE_PROXY (user_data);
1921
1922         /* Reset timeout ID */
1923         proxy->priv->subscription_timeout_src = NULL;
1924
1925         /* Send renewal message */
1926         context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
1927
1928         /* Create subscription message */
1929         sub_url = gupnp_service_info_get_event_subscription_url
1930                                                 (GUPNP_SERVICE_INFO (proxy));
1931
1932         msg = soup_message_new (GENA_METHOD_SUBSCRIBE, sub_url);
1933
1934         g_free (sub_url);
1935
1936         g_return_val_if_fail (msg != NULL, FALSE);
1937
1938         /* Add headers */
1939         soup_message_headers_append (msg->request_headers,
1940                                     "SID",
1941                                     proxy->priv->sid);
1942
1943         timeout = make_timeout_header (context);
1944         soup_message_headers_append (msg->request_headers,
1945                                      "Timeout",
1946                                      timeout);
1947         g_free (timeout);
1948
1949         /* And send it off */
1950         proxy->priv->pending_messages =
1951                 g_list_prepend (proxy->priv->pending_messages, msg);
1952
1953         session = gupnp_context_get_session (context);
1954
1955         soup_session_queue_message (session,
1956                                     msg,
1957                                     (SoupSessionCallback)
1958                                         subscribe_got_response,
1959                                     proxy);
1960
1961         return FALSE;
1962 }
1963
1964 /*
1965  * Received subscription response.
1966  */
1967 static void
1968 subscribe_got_response (SoupSession       *session,
1969                         SoupMessage       *msg,
1970                         GUPnPServiceProxy *proxy)
1971 {
1972         GError *error;
1973
1974         /* Cancelled? */
1975         if (msg->status_code == SOUP_STATUS_CANCELLED)
1976                 return;
1977
1978         /* Remove from pending messages list */
1979         proxy->priv->pending_messages =
1980                 g_list_remove (proxy->priv->pending_messages, msg);
1981
1982         /* Check whether the subscription is still wanted */
1983         if (!proxy->priv->subscribed)
1984                 return;
1985
1986         /* Reset SID */
1987         g_free (proxy->priv->sid);
1988         proxy->priv->sid = NULL;
1989
1990         /* Check message status */
1991         if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
1992                 /* Success. */
1993                 const char *hdr;
1994                 int timeout;
1995
1996                 /* Save SID. */
1997                 hdr = soup_message_headers_get_one (msg->response_headers,
1998                                                     "SID");
1999                 if (hdr == NULL) {
2000                         error = g_error_new
2001                                         (GUPNP_EVENTING_ERROR,
2002                                          GUPNP_EVENTING_ERROR_SUBSCRIPTION_LOST,
2003                                          "No SID in SUBSCRIBE response");
2004
2005                         goto hdr_err;
2006                 }
2007
2008                 proxy->priv->sid = g_strdup (hdr);
2009
2010                 /* Figure out when the subscription times out */
2011                 hdr = soup_message_headers_get_one (msg->response_headers,
2012                                                     "Timeout");
2013                 if (hdr == NULL) {
2014                         g_warning ("No Timeout in SUBSCRIBE response.");
2015
2016                         return;
2017                 }
2018
2019                 if (strncmp (hdr, "Second-", strlen ("Second-")) == 0) {
2020                         /* We have a finite timeout */
2021                         timeout = atoi (hdr + strlen ("Second-"));
2022
2023                         if (timeout < 0) {
2024                                 g_warning ("Invalid time-out specified. "
2025                                            "Assuming default value of %d.",
2026                                            GENA_DEFAULT_TIMEOUT);
2027
2028                                 timeout = GENA_DEFAULT_TIMEOUT;
2029                         }
2030
2031                         /* We want to resubscribe before the subscription
2032                          * expires. */
2033                         timeout = g_random_int_range (1, timeout / 2);
2034
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,
2041                                  proxy, NULL);
2042                         g_source_attach (proxy->priv->subscription_timeout_src,
2043                                          g_main_context_get_thread_default ());
2044
2045                         g_source_unref (proxy->priv->subscription_timeout_src);
2046                 }
2047         } else {
2048                 GUPnPContext *context;
2049                 SoupServer *server;
2050
2051                 /* Subscription failed. */
2052                 error = g_error_new_literal
2053                                 (GUPNP_EVENTING_ERROR,
2054                                  GUPNP_EVENTING_ERROR_SUBSCRIPTION_FAILED,
2055                                  msg->reason_phrase);
2056
2057 hdr_err:
2058                 /* Remove listener */
2059                 context = gupnp_service_info_get_context
2060                                         (GUPNP_SERVICE_INFO (proxy));
2061
2062                 server = gupnp_context_get_server (context);
2063                 soup_server_remove_handler (server, proxy->priv->path);
2064
2065                 proxy->priv->subscribed = FALSE;
2066
2067                 g_object_notify (G_OBJECT (proxy), "subscribed");
2068
2069                 /* Emit subscription-lost */
2070                 g_signal_emit (proxy,
2071                                signals[SUBSCRIPTION_LOST],
2072                                0,
2073                                error);
2074
2075                 g_error_free (error);
2076         }
2077 }
2078
2079 /*
2080  * Subscribe to this service.
2081  */
2082 static void
2083 subscribe (GUPnPServiceProxy *proxy)
2084 {
2085         GUPnPContext *context;
2086         SoupMessage *msg;
2087         SoupSession *session;
2088         SoupServer *server;
2089         const char *server_url;
2090         char *sub_url, *delivery_url, *timeout;
2091
2092         context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
2093
2094         /* Create subscription message */
2095         sub_url = gupnp_service_info_get_event_subscription_url
2096                                                 (GUPNP_SERVICE_INFO (proxy));
2097
2098         msg = NULL;
2099         if (sub_url != NULL) {
2100                 msg = soup_message_new (GENA_METHOD_SUBSCRIBE, sub_url);
2101
2102                 g_free (sub_url);
2103         }
2104
2105         if (msg == NULL) {
2106                 GError *error;
2107
2108                 /* Subscription failed. */
2109                 proxy->priv->subscribed = FALSE;
2110
2111                 g_object_notify (G_OBJECT (proxy), "subscribed");
2112
2113                 /* Emit subscription-lost */
2114                 error = g_error_new (GUPNP_SERVER_ERROR,
2115                                      GUPNP_SERVER_ERROR_INVALID_URL,
2116                                      "No valid subscription URL defined");
2117
2118                 g_signal_emit (proxy,
2119                                signals[SUBSCRIPTION_LOST],
2120                                0,
2121                                error);
2122
2123                 g_error_free (error);
2124
2125                 return;
2126         }
2127
2128         /* Add headers */
2129         server_url = _gupnp_context_get_server_url (context);
2130         delivery_url = g_strdup_printf ("<%s%s>",
2131                                         server_url,
2132                                         proxy->priv->path);
2133         soup_message_headers_append (msg->request_headers,
2134                                      "Callback",
2135                                      delivery_url);
2136         g_free (delivery_url);
2137
2138         soup_message_headers_append (msg->request_headers,
2139                                      "NT",
2140                                      "upnp:event");
2141
2142         timeout = make_timeout_header (context);
2143         soup_message_headers_append (msg->request_headers,
2144                                      "Timeout",
2145                                      timeout);
2146         g_free (timeout);
2147
2148         /* Listen for events */
2149         server = gupnp_context_get_server (context);
2150
2151         soup_server_add_handler (server,
2152                                  proxy->priv->path,
2153                                  server_handler,
2154                                  proxy,
2155                                  NULL);
2156
2157         /* And send our subscription message off */
2158         proxy->priv->pending_messages =
2159                 g_list_prepend (proxy->priv->pending_messages, msg);
2160
2161         session = gupnp_context_get_session (context);
2162
2163         soup_session_queue_message (session,
2164                                     msg,
2165                                     (SoupSessionCallback)
2166                                         subscribe_got_response,
2167                                     proxy);
2168 }
2169
2170 /*
2171  * Unsubscribe from this service.
2172  */
2173 static void
2174 unsubscribe (GUPnPServiceProxy *proxy)
2175 {
2176         GUPnPContext *context;
2177         SoupSession *session;
2178         SoupServer *server;
2179
2180         context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
2181
2182         /* Remove server handler */
2183         server = gupnp_context_get_server (context);
2184         soup_server_remove_handler (server, proxy->priv->path);
2185
2186         if (proxy->priv->sid != NULL) {
2187                 SoupMessage *msg;
2188                 char *sub_url;
2189
2190                 /* Create unsubscription message */
2191                 sub_url = gupnp_service_info_get_event_subscription_url
2192                                                    (GUPNP_SERVICE_INFO (proxy));
2193
2194                 msg = soup_message_new (GENA_METHOD_UNSUBSCRIBE, sub_url);
2195
2196                 g_free (sub_url);
2197
2198                 if (msg != NULL) {
2199                         /* Add headers */
2200                         soup_message_headers_append (msg->request_headers,
2201                                                      "SID",
2202                                                      proxy->priv->sid);
2203
2204                         /* And queue it */
2205                         session = gupnp_context_get_session (context);
2206
2207                         soup_session_queue_message (session, msg, NULL, NULL);
2208                 }
2209
2210                 /* Reset SID */
2211                 g_free (proxy->priv->sid);
2212                 proxy->priv->sid = NULL;
2213
2214                 /* Reset sequence number */
2215                 proxy->priv->seq = 0;
2216         }
2217
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;
2222         }
2223 }
2224
2225 /**
2226  * gupnp_service_proxy_set_subscribed:
2227  * @proxy: A #GUPnPServiceProxy
2228  * @subscribed: %TRUE to subscribe to this service
2229  *
2230  * (Un)subscribes to this service.
2231  *
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.
2236  **/
2237 void
2238 gupnp_service_proxy_set_subscribed (GUPnPServiceProxy *proxy,
2239                                     gboolean           subscribed)
2240 {
2241         g_return_if_fail (GUPNP_IS_SERVICE_PROXY (proxy));
2242
2243         if (proxy->priv->subscribed == subscribed)
2244                 return;
2245
2246         proxy->priv->subscribed = subscribed;
2247
2248         if (subscribed)
2249                 subscribe (proxy);
2250         else
2251                 unsubscribe (proxy);
2252
2253         g_object_notify (G_OBJECT (proxy), "subscribed");
2254 }
2255
2256 /**
2257  * gupnp_service_proxy_get_subscribed:
2258  * @proxy: A #GUPnPServiceProxy
2259  *
2260  * Returns if we are subscribed to this service.
2261  *
2262  * Return value: %TRUE if we are subscribed to this service, otherwise %FALSE.
2263  **/
2264 gboolean
2265 gupnp_service_proxy_get_subscribed (GUPnPServiceProxy *proxy)
2266 {
2267         g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
2268
2269         return proxy->priv->subscribed;
2270 }