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