Update gupnp to 0.20.5 (fdeb6f9f)
[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  * @...: 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                 result = FALSE;
592
593                 goto out;
594         }
595
596         /* Loop till we get a reply (or time out) */
597         if (g_main_loop_is_running (main_loop))
598                 g_main_loop_run (main_loop);
599
600         g_main_loop_unref (main_loop);
601
602         result = gupnp_service_proxy_end_action_hash (proxy,
603                                                       handle,
604                                                       &local_error,
605                                                       out_hash);
606
607         if (local_error == NULL) {
608                 OUT_HASH_TABLE_TO_VAR_ARGS (out_hash, var_args_copy);
609         } else {
610                 g_propagate_error (error, local_error);
611         }
612 out:
613         va_end (var_args_copy);
614         g_list_free_full (in_names, g_free);
615         g_list_free_full (in_values, value_free);
616         g_hash_table_unref (out_hash);
617
618         return result;
619 }
620
621 /**
622  * gupnp_service_proxy_send_action_hash:
623  * @proxy: A #GUPnPServiceProxy
624  * @action: An action
625  * @error: The location where to store any error, or %NULL
626  * @in_hash: (element-type utf8 GValue) (transfer none): A #GHashTable of in
627  * parameter name and #GValue pairs
628  * @out_hash: (inout) (element-type utf8 GValue) (transfer full): A #GHashTable
629  * of out parameter name and initialized #GValue pairs
630  *
631  * See gupnp_service_proxy_send_action(); this version takes a pair of
632  * #GHashTable<!-- -->s for runtime determined parameter lists.
633  *
634  * Return value: %TRUE if sending the action was succesful.
635  **/
636 gboolean
637 gupnp_service_proxy_send_action_hash (GUPnPServiceProxy *proxy,
638                                       const char        *action,
639                                       GError           **error,
640                                       GHashTable        *in_hash,
641                                       GHashTable        *out_hash)
642 {
643         GMainLoop *main_loop;
644         GUPnPServiceProxyAction *handle;
645
646         g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
647         g_return_val_if_fail (action, FALSE);
648
649         main_loop = g_main_loop_new (g_main_context_get_thread_default (),
650                                      TRUE);
651
652         handle = gupnp_service_proxy_begin_action_hash (proxy,
653                                                         action,
654                                                         stop_main_loop,
655                                                         main_loop,
656                                                         in_hash);
657         if (!handle) {
658                 g_main_loop_unref (main_loop);
659
660                 return FALSE;
661         }
662
663         /* Loop till we get a reply (or time out) */
664         if (g_main_loop_is_running (main_loop))
665                 g_main_loop_run (main_loop);
666
667         g_main_loop_unref (main_loop);
668
669         if (!gupnp_service_proxy_end_action_hash (proxy,
670                                                   handle,
671                                                   error,
672                                                   out_hash))
673                 return FALSE;
674
675         return TRUE;
676 }
677
678 /**
679  * gupnp_service_proxy_send_action_list:
680  * @proxy: (transfer none) : A #GUPnPServiceProxy
681  * @action: An action
682  * @error: The location where to store any error, or %NULL
683  * @in_names: (element-type utf8) (transfer none): #GList of 'in' parameter
684  * names (as strings)
685  * @in_values: (element-type GValue) (transfer none): #GList of values (as
686  * #GValue) that line up with @in_names
687  * @out_names: (element-type utf8) (transfer none): #GList of 'out' parameter
688  * names (as strings)
689  * @out_types: (element-type GType) (transfer none): #GList of types (as #GType)
690  * that line up with @out_names
691  * @out_values: (element-type GValue) (transfer full) (out): #GList of values
692  * (as #GValue) that line up with @out_names and @out_types.
693  *
694  * The synchronous variant of #gupnp_service_proxy_begin_action_list and
695  * #gupnp_service_proxy_end_action_list.
696  *
697  * Return value: %TRUE if sending the action was succesful.
698  **/
699 gboolean
700 gupnp_service_proxy_send_action_list (GUPnPServiceProxy *proxy,
701                                       const char        *action,
702                                       GError           **error,
703                                       GList             *in_names,
704                                       GList             *in_values,
705                                       GList             *out_names,
706                                       GList             *out_types,
707                                       GList            **out_values)
708 {
709         GMainLoop *main_loop;
710         GUPnPServiceProxyAction *handle;
711
712         g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
713         g_return_val_if_fail (action, FALSE);
714
715         main_loop = g_main_loop_new (g_main_context_get_thread_default (),
716                                      TRUE);
717
718         handle = gupnp_service_proxy_begin_action_list (proxy,
719                                                         action,
720                                                         in_names,
721                                                         in_values,
722                                                         stop_main_loop,
723                                                         main_loop);
724         if (!handle) {
725                 g_main_loop_unref (main_loop);
726
727                 return FALSE;
728         }
729
730         /* Loop till we get a reply (or time out) */
731         if (g_main_loop_is_running (main_loop))
732                 g_main_loop_run (main_loop);
733
734         g_main_loop_unref (main_loop);
735
736         if (!gupnp_service_proxy_end_action_list (proxy,
737                                                   handle,
738                                                   error,
739                                                   out_names,
740                                                   out_types,
741                                                   out_values))
742                 return FALSE;
743
744         return TRUE;
745 }
746
747 /**
748  * gupnp_service_proxy_begin_action:
749  * @proxy: A #GUPnPServiceProxy
750  * @action: An action
751  * @callback: (scope async): The callback to call when sending the action has succeeded
752  * or failed
753  * @user_data: User data for @callback
754  * @...: tuples of in parameter name, in parameter type, and in parameter
755  * value, terminated with %NULL
756  *
757  * Sends action @action with parameters @Varargs to the service exposed by
758  * @proxy asynchronously, calling @callback on completion. From @callback, call
759  * gupnp_service_proxy_end_action() to check for errors, to retrieve return
760  * values, and to free the #GUPnPServiceProxyAction.
761  *
762  * Returns: (transfer none): A #GUPnPServiceProxyAction handle. This will be freed when
763  * gupnp_service_proxy_cancel_action() or
764  * gupnp_service_proxy_end_action_valist().
765  **/
766 GUPnPServiceProxyAction *
767 gupnp_service_proxy_begin_action (GUPnPServiceProxy              *proxy,
768                                   const char                     *action,
769                                   GUPnPServiceProxyActionCallback callback,
770                                   gpointer                        user_data,
771                                   ...)
772 {
773         va_list var_args;
774         GUPnPServiceProxyAction *ret;
775
776         va_start (var_args, user_data);
777         ret = gupnp_service_proxy_begin_action_valist (proxy,
778                                                        action,
779                                                        callback,
780                                                        user_data,
781                                                        var_args);
782         va_end (var_args);
783
784         return ret;
785 }
786
787 /* Begins a basic action message */
788 static GUPnPServiceProxyAction *
789 begin_action_msg (GUPnPServiceProxy              *proxy,
790                   const char                     *action,
791                   GUPnPServiceProxyActionCallback callback,
792                   gpointer                        user_data)
793 {
794         GUPnPServiceProxyAction *ret;
795         char *control_url, *full_action;
796         const char *service_type;
797
798         /* Create action structure */
799         ret = g_slice_new (GUPnPServiceProxyAction);
800
801         ret->proxy = proxy;
802
803         ret->callback  = callback;
804         ret->user_data = user_data;
805
806         ret->msg = NULL;
807
808         ret->error = NULL;
809
810         proxy->priv->pending_actions =
811                 g_list_prepend (proxy->priv->pending_actions, ret);
812
813         /* Make sure we have a service type */
814         service_type = gupnp_service_info_get_service_type
815                                         (GUPNP_SERVICE_INFO (proxy));
816         if (service_type == NULL) {
817                 ret->error = g_error_new (GUPNP_SERVER_ERROR,
818                                           GUPNP_SERVER_ERROR_OTHER,
819                                           "No service type defined");
820
821                 return ret;
822         }
823
824         /* Create message */
825         control_url = gupnp_service_info_get_control_url
826                                         (GUPNP_SERVICE_INFO (proxy));
827
828         if (control_url != NULL) {
829                 ret->msg = soup_message_new (SOUP_METHOD_POST, control_url);
830
831                 g_free (control_url);
832         }
833
834         if (ret->msg == NULL) {
835                 ret->error = g_error_new (GUPNP_SERVER_ERROR,
836                                           GUPNP_SERVER_ERROR_INVALID_URL,
837                                           "No valid control URL defined");
838
839                 return ret;
840         }
841
842         /* Specify action */
843         full_action = g_strdup_printf ("\"%s#%s\"", service_type, action);
844         soup_message_headers_append (ret->msg->request_headers,
845                                      "SOAPAction",
846                                      full_action);
847         g_free (full_action);
848
849         /* Specify language */
850         http_request_set_accept_language (ret->msg);
851
852         /* Accept gzip encoding */
853         soup_message_headers_append (ret->msg->request_headers,
854                                      "Accept-Encoding", "gzip");
855
856         /* Set up envelope */
857         ret->msg_str = xml_util_new_string ();
858
859         g_string_append (ret->msg_str,
860                          "<?xml version=\"1.0\"?>"
861                          "<s:Envelope xmlns:s="
862                                 "\"http://schemas.xmlsoap.org/soap/envelope/\" "
863                           "s:encodingStyle="
864                                 "\"http://schemas.xmlsoap.org/soap/encoding/\">"
865                          "<s:Body>");
866
867         g_string_append (ret->msg_str, "<u:");
868         g_string_append (ret->msg_str, action);
869         g_string_append (ret->msg_str, " xmlns:u=\"");
870         g_string_append (ret->msg_str, service_type);
871         g_string_append (ret->msg_str, "\">");
872
873         return ret;
874 }
875
876 /* Received response to action message */
877 static void
878 action_got_response (SoupSession             *session,
879                      SoupMessage             *msg,
880                      GUPnPServiceProxyAction *action)
881 {
882         const char *full_action;
883
884         switch (msg->status_code) {
885         case SOUP_STATUS_CANCELLED:
886                 /* Silently return */
887                 break;
888
889         case SOUP_STATUS_METHOD_NOT_ALLOWED:
890                 /* Retry with M-POST */
891                 msg->method = "M-POST";
892
893                 soup_message_headers_append
894                         (msg->request_headers,
895                          "Man",
896                          "\"http://schemas.xmlsoap.org/soap/envelope/\"; ns=s");
897
898                 /* Rename "SOAPAction" to "s-SOAPAction" */
899                 full_action = soup_message_headers_get_one
900                         (msg->request_headers,
901                          "SOAPAction");
902                 soup_message_headers_append (msg->request_headers,
903                                              "s-SOAPAction",
904                                              full_action);
905                 soup_message_headers_remove (msg->request_headers,
906                                             "SOAPAction");
907
908                 /* And re-queue */
909                 soup_session_requeue_message (session, msg);
910
911                 break;
912
913         default:
914                 /* Success: Call callback */
915                 action->callback (action->proxy, action, action->user_data);
916
917                 break;
918         }
919 }
920
921 /* Finishes an action message and sends it off */
922 static void
923 finish_action_msg (GUPnPServiceProxyAction *action,
924                    const char              *action_name)
925 {
926         GUPnPContext *context;
927         SoupSession *session;
928
929         /* Finish message */
930         g_string_append (action->msg_str, "</u:");
931         g_string_append (action->msg_str, action_name);
932         g_string_append_c (action->msg_str, '>');
933
934         g_string_append (action->msg_str,
935                          "</s:Body>"
936                          "</s:Envelope>");
937
938         soup_message_set_request (action->msg,
939                                   "text/xml; charset=\"utf-8\"",
940                                   SOUP_MEMORY_TAKE,
941                                   action->msg_str->str,
942                                   action->msg_str->len);
943
944         g_string_free (action->msg_str, FALSE);
945
946         /* We need to keep our own reference to the message as well,
947          * in order for send_action() to work. */
948         g_object_ref (action->msg);
949
950         /* Send the message */
951         context = gupnp_service_info_get_context
952                                 (GUPNP_SERVICE_INFO (action->proxy));
953         session = gupnp_context_get_session (context);
954
955         soup_session_queue_message (session,
956                                     action->msg,
957                                     (SoupSessionCallback) action_got_response,
958                                     action);
959 }
960
961 /* Writes a parameter name and GValue pair to @msg */
962 static void
963 write_in_parameter (const char *arg_name,
964                     GValue     *value,
965                     GString    *msg_str)
966 {
967         /* Write parameter pair */
968         xml_util_start_element (msg_str, arg_name);
969         gvalue_util_value_append_to_xml_string (value, msg_str);
970         xml_util_end_element (msg_str, arg_name);
971 }
972
973 /**
974  * gupnp_service_proxy_begin_action_valist:
975  * @proxy: A #GUPnPServiceProxy
976  * @action: An action
977  * @callback: (scope async) : The callback to call when sending the action has succeeded
978  * or failed
979  * @user_data: User data for @callback
980  * @var_args: A va_list of tuples of in parameter name, in parameter type, and
981  * in parameter value
982  *
983  * See gupnp_service_proxy_begin_action().
984  *
985  * Returns: (transfer none): A #GUPnPServiceProxyAction handle. This will
986  * be freed when calling gupnp_service_proxy_cancel_action() or
987  * gupnp_service_proxy_end_action_valist().
988  **/
989 GUPnPServiceProxyAction *
990 gupnp_service_proxy_begin_action_valist
991                                    (GUPnPServiceProxy              *proxy,
992                                     const char                     *action,
993                                     GUPnPServiceProxyActionCallback callback,
994                                     gpointer                        user_data,
995                                     va_list                         var_args)
996 {
997         GUPnPServiceProxyAction *ret;
998         GList *in_names = NULL, *in_values = NULL;
999
1000         g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), NULL);
1001         g_return_val_if_fail (action, NULL);
1002         g_return_val_if_fail (callback, NULL);
1003
1004         VAR_ARGS_TO_IN_LIST (var_args, in_names, in_values);
1005         ret = gupnp_service_proxy_begin_action_list (proxy,
1006                                                      action,
1007                                                      in_names,
1008                                                      in_values,
1009                                                      callback,
1010                                                      user_data);
1011         g_list_free_full (in_names, g_free);
1012         g_list_free_full (in_values, value_free);
1013
1014         return ret;
1015 }
1016
1017 /**
1018  * gupnp_service_proxy_begin_action_list:
1019  * @proxy: (transfer none): A #GUPnPServiceProxy
1020  * @action: An action
1021  * @in_names: (element-type utf8) (transfer none): #GList of 'in' parameter
1022  * names (as strings)
1023  * @in_values: (element-type GValue) (transfer none): #GList of values (as
1024  * #GValue) that line up with @in_names
1025  * @callback: (scope async) : The callback to call when sending the action has succeeded or
1026  * failed
1027  * @user_data: User data for @callback
1028  *
1029  * A variant of #gupnp_service_proxy_begin_action that takes lists of
1030  * in-parameter names, types and values.
1031  *
1032  * Return value: (transfer none): A #GUPnPServiceProxyAction handle. This will
1033  * be freed when calling #gupnp_service_proxy_cancel_action or
1034  * #gupnp_service_proxy_end_action_list.
1035  **/
1036 GUPnPServiceProxyAction *
1037 gupnp_service_proxy_begin_action_list
1038                                    (GUPnPServiceProxy               *proxy,
1039                                     const char                      *action,
1040                                     GList                           *in_names,
1041                                     GList                           *in_values,
1042                                     GUPnPServiceProxyActionCallback  callback,
1043                                     gpointer                         user_data)
1044 {
1045         GUPnPServiceProxyAction *ret;
1046         GList *names;
1047         GList *values;
1048
1049         g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), NULL);
1050         g_return_val_if_fail (action, NULL);
1051         g_return_val_if_fail (callback, NULL);
1052         g_return_val_if_fail (g_list_length (in_names) ==
1053                               g_list_length (in_values),
1054                               NULL);
1055
1056         /* Create message */
1057         ret = begin_action_msg (proxy, action, callback, user_data);
1058
1059         if (ret->error) {
1060                 callback (proxy, ret, user_data);
1061
1062                 return ret;
1063         }
1064
1065         /* Arguments */
1066         values = in_values;
1067
1068         for (names = in_names; names; names=names->next) {
1069                 GValue* val = values->data;
1070
1071                 write_in_parameter (names->data,
1072                                     val,
1073                                     ret->msg_str);
1074
1075                 values = values->next;
1076         }
1077
1078         /* Finish and send off */
1079         finish_action_msg (ret, action);
1080
1081         return ret;
1082 }
1083
1084 /**
1085  * gupnp_service_proxy_begin_action_hash:
1086  * @proxy: A #GUPnPServiceProxy
1087  * @action: An action
1088  * @callback: (scope async): The callback to call when sending the action has succeeded
1089  * or failed
1090  * @user_data: User data for @callback
1091  * @hash: (element-type utf8 GValue): A #GHashTable of in parameter name and #GValue pairs
1092  *
1093  * See gupnp_service_proxy_begin_action(); this version takes a #GHashTable
1094  * for runtime generated parameter lists.
1095  *
1096  * Return value: (transfer none): A #GUPnPServiceProxyAction handle. This will
1097  * be freed when calling gupnp_service_proxy_cancel_action() or
1098  * gupnp_service_proxy_end_action_hash().
1099  **/
1100 GUPnPServiceProxyAction *
1101 gupnp_service_proxy_begin_action_hash
1102                                    (GUPnPServiceProxy              *proxy,
1103                                     const char                     *action,
1104                                     GUPnPServiceProxyActionCallback callback,
1105                                     gpointer                        user_data,
1106                                     GHashTable                     *hash)
1107 {
1108         GUPnPServiceProxyAction *ret;
1109
1110         g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), NULL);
1111         g_return_val_if_fail (action, NULL);
1112         g_return_val_if_fail (callback, NULL);
1113
1114         /* Create message */
1115         ret = begin_action_msg (proxy, action, callback, user_data);
1116
1117         if (ret->error) {
1118                 callback (proxy, ret, user_data);
1119
1120                 return ret;
1121         }
1122
1123         /* Arguments */
1124         g_hash_table_foreach (hash, (GHFunc) write_in_parameter, ret->msg_str);
1125
1126         /* Finish and send off */
1127         finish_action_msg (ret, action);
1128
1129         return ret;
1130 }
1131
1132 /**
1133  * gupnp_service_proxy_end_action:
1134  * @proxy: A #GUPnPServiceProxy
1135  * @action: A #GUPnPServiceProxyAction handle
1136  * @error: The location where to store any error, or %NULL
1137  * @...: tuples of out parameter name, out parameter type, and out parameter
1138  * value location, terminated with %NULL. The out parameter values should be
1139  * freed after use
1140  *
1141  * Retrieves the result of @action. The out parameters in @Varargs will be
1142  * filled in, and if an error occurred, @error will be set. In case of
1143  * a UPnPError the error code will be the same in @error.
1144  *
1145  * Return value: %TRUE on success.
1146  **/
1147 gboolean
1148 gupnp_service_proxy_end_action (GUPnPServiceProxy       *proxy,
1149                                 GUPnPServiceProxyAction *action,
1150                                 GError                 **error,
1151                                 ...)
1152 {
1153         va_list var_args;
1154         gboolean ret;
1155
1156         va_start (var_args, error);
1157         ret = gupnp_service_proxy_end_action_valist (proxy,
1158                                                      action,
1159                                                      error,
1160                                                      var_args);
1161         va_end (var_args);
1162
1163         return ret;
1164 }
1165
1166 /* Checks an action response for errors and returns the parsed
1167  * xmlDoc object. */
1168 static xmlDoc *
1169 check_action_response (G_GNUC_UNUSED GUPnPServiceProxy *proxy,
1170                        GUPnPServiceProxyAction         *action,
1171                        xmlNode                        **params,
1172                        GError                         **error)
1173 {
1174         xmlDoc *response;
1175         int code;
1176
1177         /* Check for errors */
1178         switch (action->msg->status_code) {
1179         case SOUP_STATUS_OK:
1180         case SOUP_STATUS_INTERNAL_SERVER_ERROR:
1181                 break;
1182         default:
1183                 _gupnp_error_set_server_error (error, action->msg);
1184
1185                 return NULL;
1186         }
1187
1188         /* Parse response */
1189         response = xmlRecoverMemory (action->msg->response_body->data,
1190                                      action->msg->response_body->length);
1191
1192         if (!response) {
1193                 if (action->msg->status_code == SOUP_STATUS_OK) {
1194                         g_set_error (error,
1195                                      GUPNP_SERVER_ERROR,
1196                                      GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1197                                      "Could not parse SOAP response");
1198                 } else {
1199                         g_set_error_literal
1200                                     (error,
1201                                      GUPNP_SERVER_ERROR,
1202                                      GUPNP_SERVER_ERROR_INTERNAL_SERVER_ERROR,
1203                                      action->msg->reason_phrase);
1204                 }
1205
1206                 return NULL;
1207         }
1208
1209         /* Get parameter list */
1210         *params = xml_util_get_element ((xmlNode *) response,
1211                                         "Envelope",
1212                                         NULL);
1213         if (*params != NULL)
1214                 *params = xml_util_real_node ((*params)->children);
1215
1216         if (*params != NULL) {
1217                 if (strcmp ((const char *) (*params)->name, "Header") == 0)
1218                         *params = xml_util_real_node ((*params)->next);
1219
1220                 if (*params != NULL)
1221                         if (strcmp ((const char *) (*params)->name, "Body") != 0)
1222                                 *params = NULL;
1223         }
1224
1225         if (*params != NULL)
1226                 *params = xml_util_real_node ((*params)->children);
1227
1228         if (*params == NULL) {
1229                 g_set_error (error,
1230                              GUPNP_SERVER_ERROR,
1231                              GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1232                              "Invalid Envelope");
1233
1234                 xmlFreeDoc (response);
1235
1236                 return NULL;
1237         }
1238
1239         /* Check whether we have a Fault */
1240         if (action->msg->status_code == SOUP_STATUS_INTERNAL_SERVER_ERROR) {
1241                 xmlNode *param;
1242                 char *desc;
1243
1244                 param = xml_util_get_element (*params,
1245                                               "detail",
1246                                               "UPnPError",
1247                                               NULL);
1248
1249                 if (!param) {
1250                         g_set_error (error,
1251                                      GUPNP_SERVER_ERROR,
1252                                      GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1253                                      "Invalid Fault");
1254
1255                         xmlFreeDoc (response);
1256
1257                         return NULL;
1258                 }
1259
1260                 /* Code */
1261                 code = xml_util_get_child_element_content_int
1262                                         (param, "errorCode");
1263                 if (code == -1) {
1264                         g_set_error (error,
1265                                      GUPNP_SERVER_ERROR,
1266                                      GUPNP_SERVER_ERROR_INVALID_RESPONSE,
1267                                      "Invalid Fault");
1268
1269                         xmlFreeDoc (response);
1270
1271                         return NULL;
1272                 }
1273
1274                 /* Description */
1275                 desc = xml_util_get_child_element_content_glib
1276                                         (param, "errorDescription");
1277                 if (desc == NULL)
1278                         desc = g_strdup (action->msg->reason_phrase);
1279
1280                 g_set_error_literal (error,
1281                                      GUPNP_CONTROL_ERROR,
1282                                      code,
1283                                      desc);
1284
1285                 g_free (desc);
1286
1287                 xmlFreeDoc (response);
1288
1289                 return NULL;
1290         }
1291
1292         return response;
1293 }
1294
1295 /* Reads a value into the parameter name and initialised GValue pair
1296  * from @response */
1297 static void
1298 read_out_parameter (const char *arg_name,
1299                     GValue     *value,
1300                     xmlNode    *params)
1301 {
1302         xmlNode *param;
1303
1304         /* Try to find a matching parameter in the response*/
1305         param = xml_util_get_element (params,
1306                                       arg_name,
1307                                       NULL);
1308         if (!param) {
1309                 g_warning ("Could not find variable \"%s\" in response",
1310                            arg_name);
1311
1312                 return;
1313         }
1314
1315         gvalue_util_set_value_from_xml_node (value, param);
1316 }
1317
1318 /**
1319  * gupnp_service_proxy_end_action_valist:
1320  * @proxy: A #GUPnPServiceProxy
1321  * @action: A #GUPnPServiceProxyAction handle
1322  * @error: The location where to store any error, or %NULL
1323  * @var_args: A va_list of tuples of out parameter name, out parameter type,
1324  * and out parameter value location. The out parameter values should be
1325  * freed after use
1326  *
1327  * See gupnp_service_proxy_end_action().
1328  *
1329  * Return value: %TRUE on success.
1330  **/
1331 gboolean
1332 gupnp_service_proxy_end_action_valist (GUPnPServiceProxy       *proxy,
1333                                        GUPnPServiceProxyAction *action,
1334                                        GError                 **error,
1335                                        va_list                  var_args)
1336 {
1337         GHashTable *out_hash;
1338         va_list var_args_copy;
1339         gboolean result;
1340         GError *local_error;
1341
1342         g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1343         g_return_val_if_fail (action, FALSE);
1344         g_return_val_if_fail (proxy == action->proxy, FALSE);
1345
1346         out_hash = g_hash_table_new_full (g_str_hash,
1347                                           g_str_equal,
1348                                           g_free,
1349                                           value_free);
1350         G_VA_COPY (var_args_copy, var_args);
1351         VAR_ARGS_TO_OUT_HASH_TABLE (var_args, out_hash);
1352         local_error = NULL;
1353         result = gupnp_service_proxy_end_action_hash (proxy,
1354                                                       action,
1355                                                       &local_error,
1356                                                       out_hash);
1357
1358         if (local_error == NULL) {
1359                 OUT_HASH_TABLE_TO_VAR_ARGS (out_hash, var_args_copy);
1360         } else {
1361                 g_propagate_error (error, local_error);
1362         }
1363         va_end (var_args_copy);
1364         g_hash_table_unref (out_hash);
1365
1366         return result;
1367 }
1368
1369 /**
1370  * gupnp_service_proxy_end_action_list:
1371  * @proxy: A #GUPnPServiceProxy
1372  * @action: A #GUPnPServiceProxyAction handle
1373  * @error: The location where to store any error, or %NULL
1374  * @out_names: (element-type utf8) (transfer none): #GList of 'out' parameter
1375  * names (as strings)
1376  * @out_types: (element-type GType) (transfer none): #GList of types (as #GType)
1377  * that line up with @out_names
1378  * @out_values: (element-type GValue) (transfer full) (out): #GList of values
1379  * (as #GValue) that line up with @out_names and @out_types.
1380  *
1381  * A variant of #gupnp_service_proxy_end_action that takes lists of
1382  * out-parameter names, types and place-holders for values. The returned list
1383  * in @out_values must be freed using #g_list_free and each element in it using
1384  * #g_value_unset and #g_slice_free.
1385  *
1386  * Returns: %TRUE on success.
1387  **/
1388 gboolean
1389 gupnp_service_proxy_end_action_list (GUPnPServiceProxy       *proxy,
1390                                      GUPnPServiceProxyAction *action,
1391                                      GError                 **error,
1392                                      GList                   *out_names,
1393                                      GList                   *out_types,
1394                                      GList                  **out_values)
1395 {
1396         xmlDoc *response;
1397         xmlNode *params;
1398         GList *names;
1399         GList *types;
1400         GList *out_values_list;
1401
1402         out_values_list = NULL;
1403
1404         g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1405         g_return_val_if_fail (action, FALSE);
1406         g_return_val_if_fail (proxy == action->proxy, FALSE);
1407
1408         /* Check for saved error from begin_action() */
1409         if (action->error) {
1410                 if (error)
1411                         *error = action->error;
1412                 else
1413                         g_error_free (action->error);
1414
1415                 gupnp_service_proxy_action_free (action);
1416
1417                 return FALSE;
1418         }
1419
1420         /* Check response for errors and do initial parsing */
1421         response = check_action_response (proxy, action, &params, error);
1422         if (response == NULL) {
1423                 gupnp_service_proxy_action_free (action);
1424
1425                 return FALSE;
1426         }
1427
1428         /* Read arguments */
1429         types = out_types;
1430         for (names = out_names; names; names=names->next) {
1431                 GValue *val;
1432
1433                 val = g_slice_new0 (GValue);
1434                 g_value_init (val, (GType) types->data);
1435
1436                 read_out_parameter (names->data, val, params);
1437
1438                 out_values_list = g_list_append (out_values_list, val);
1439
1440                 types = types->next;
1441         }
1442
1443         *out_values = out_values_list;
1444
1445         /* Cleanup */
1446         gupnp_service_proxy_action_free (action);
1447
1448         xmlFreeDoc (response);
1449
1450         return TRUE;
1451 }
1452
1453 /**
1454  * gupnp_service_proxy_end_action_hash:
1455  * @proxy: A #GUPnPServiceProxy
1456  * @action: A #GUPnPServiceProxyAction handle
1457  * @error: The location where to store any error, or %NULL
1458  * @hash: (element-type utf8 GValue) (inout) (transfer none): A #GHashTable of
1459  * out parameter name and initialised #GValue pairs
1460  *
1461  * See gupnp_service_proxy_end_action(); this version takes a #GHashTable for
1462  * runtime generated parameter lists.
1463  *
1464  * Return value: %TRUE on success.
1465  **/
1466 gboolean
1467 gupnp_service_proxy_end_action_hash (GUPnPServiceProxy       *proxy,
1468                                      GUPnPServiceProxyAction *action,
1469                                      GError                 **error,
1470                                      GHashTable              *hash)
1471 {
1472         xmlDoc *response;
1473         xmlNode *params;
1474
1475         g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1476         g_return_val_if_fail (action, FALSE);
1477         g_return_val_if_fail (proxy == action->proxy, FALSE);
1478
1479         /* Check for saved error from begin_action() */
1480         if (action->error) {
1481                 if (error)
1482                         *error = action->error;
1483                 else
1484                         g_error_free (action->error);
1485
1486                 gupnp_service_proxy_action_free (action);
1487
1488                 return FALSE;
1489         }
1490
1491         /* Check response for errors and do initial parsing */
1492         response = check_action_response (proxy, action, &params, error);
1493         if (response == NULL) {
1494                 gupnp_service_proxy_action_free (action);
1495
1496                 return FALSE;
1497         }
1498
1499         /* Read arguments */
1500         g_hash_table_foreach (hash, (GHFunc) read_out_parameter, params);
1501
1502         /* Cleanup */
1503         gupnp_service_proxy_action_free (action);
1504
1505         xmlFreeDoc (response);
1506
1507         return TRUE;
1508 }
1509
1510 /**
1511  * gupnp_service_proxy_cancel_action:
1512  * @proxy: A #GUPnPServiceProxy
1513  * @action: A #GUPnPServiceProxyAction handle
1514  *
1515  * Cancels @action, freeing the @action handle.
1516  **/
1517 void
1518 gupnp_service_proxy_cancel_action (GUPnPServiceProxy       *proxy,
1519                                    GUPnPServiceProxyAction *action)
1520 {
1521         GUPnPContext *context;
1522         SoupSession *session;
1523
1524         g_return_if_fail (GUPNP_IS_SERVICE_PROXY (proxy));
1525         g_return_if_fail (action);
1526         g_return_if_fail (proxy == action->proxy);
1527
1528         if (action->msg != NULL) {
1529                 context = gupnp_service_info_get_context
1530                                         (GUPNP_SERVICE_INFO (proxy));
1531                 session = gupnp_context_get_session (context);
1532
1533                 soup_session_cancel_message (session,
1534                                              action->msg,
1535                                              SOUP_STATUS_CANCELLED);
1536         }
1537
1538         if (action->error != NULL)
1539                 g_error_free (action->error);
1540
1541         gupnp_service_proxy_action_free (action);
1542 }
1543
1544 /**
1545  * gupnp_service_proxy_add_notify:
1546  * @proxy: A #GUPnPServiceProxy
1547  * @variable: The variable to add notification for
1548  * @type: The type of the variable
1549  * @callback: (scope async): The callback to call when @variable changes
1550  * @user_data: User data for @callback
1551  *
1552  * Sets up @callback to be called whenever a change notification for
1553  * @variable is recieved.
1554  *
1555  * Return value: %TRUE on success.
1556  **/
1557 gboolean
1558 gupnp_service_proxy_add_notify (GUPnPServiceProxy              *proxy,
1559                                 const char                     *variable,
1560                                 GType                           type,
1561                                 GUPnPServiceProxyNotifyCallback callback,
1562                                 gpointer                        user_data)
1563 {
1564         NotifyData *data;
1565         CallbackData *callback_data;
1566
1567         g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1568         g_return_val_if_fail (variable, FALSE);
1569         g_return_val_if_fail (type, FALSE);
1570         g_return_val_if_fail (callback, FALSE);
1571
1572         /* See if we already have notifications set up for this variable */
1573         data = g_hash_table_lookup (proxy->priv->notify_hash, variable);
1574         if (data == NULL) {
1575                 /* No, create one */
1576                 data = g_slice_new (NotifyData);
1577
1578                 data->type       = type;
1579                 data->callbacks  = NULL;
1580
1581                 g_hash_table_insert (proxy->priv->notify_hash,
1582                                      g_strdup (variable),
1583                                      data);
1584         } else {
1585                 /* Yes, check that everything is sane .. */
1586                 if (data->type != type) {
1587                         g_warning ("A notification already exists for %s, but "
1588                                    "has type %s, not %s.",
1589                                    variable,
1590                                    g_type_name (data->type),
1591                                    g_type_name (type));
1592
1593                         return FALSE;
1594                 }
1595         }
1596
1597         /* Append callback */
1598         callback_data = g_slice_new (CallbackData);
1599
1600         callback_data->callback  = callback;
1601         callback_data->user_data = user_data;
1602
1603         data->callbacks = g_list_append (data->callbacks, callback_data);
1604
1605         return TRUE;
1606 }
1607
1608 /**
1609  * gupnp_service_proxy_remove_notify:
1610  * @proxy: A #GUPnPServiceProxy
1611  * @variable: The variable to add notification for
1612  * @callback: (scope call): The callback to call when @variable changes
1613  * @user_data: User data for @callback
1614  *
1615  * Cancels the variable change notification for @callback and @user_data.
1616  *
1617  * This function must not be called directly or indirectly from a
1618  * #GUPnPServiceProxyNotifyCallback associated with this service proxy, even
1619  * if it is for another variable.
1620  *
1621  * Return value: %TRUE on success.
1622  **/
1623 gboolean
1624 gupnp_service_proxy_remove_notify (GUPnPServiceProxy              *proxy,
1625                                    const char                     *variable,
1626                                    GUPnPServiceProxyNotifyCallback callback,
1627                                    gpointer                        user_data)
1628 {
1629         NotifyData *data;
1630         gboolean found;
1631         GList *l;
1632
1633         g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
1634         g_return_val_if_fail (variable, FALSE);
1635         g_return_val_if_fail (callback, FALSE);
1636
1637         /* Look up NotifyData for variable */
1638         data = g_hash_table_lookup (proxy->priv->notify_hash, variable);
1639         if (data == NULL) {
1640                 g_warning ("No notifications found for variable %s",
1641                            variable);
1642
1643                 return FALSE;
1644         }
1645
1646         /* Look up our callback-user_data pair */
1647         found = FALSE;
1648
1649         for (l = data->callbacks; l; l = l->next) {
1650                 CallbackData *callback_data;
1651
1652                 callback_data = l->data;
1653
1654                 if (callback_data->callback  == callback &&
1655                     callback_data->user_data == user_data) {
1656                         /* Gotcha! */
1657                         g_slice_free (CallbackData, callback_data);
1658
1659                         data->callbacks =
1660                                 g_list_delete_link (data->callbacks, l);
1661                         if (data->callbacks == NULL) {
1662                                 /* No callbacks left: Remove from hash */
1663                                 g_hash_table_remove (proxy->priv->notify_hash,
1664                                                      variable);
1665                         }
1666
1667                         found = TRUE;
1668
1669                         break;
1670                 }
1671         }
1672
1673         if (found == FALSE)
1674                 g_warning ("No such callback-user_data pair was found");
1675
1676         return found;
1677 }
1678
1679 static void
1680 emit_notification (GUPnPServiceProxy *proxy,
1681                    xmlNode           *var_node)
1682 {
1683         NotifyData *data;
1684         GValue value = {0, };
1685         GList *l;
1686
1687         data = g_hash_table_lookup (proxy->priv->notify_hash, var_node->name);
1688         if (data == NULL)
1689                 return;
1690
1691         /* Make a GValue of the desired type */
1692         g_value_init (&value, data->type);
1693
1694         if (!gvalue_util_set_value_from_xml_node (&value, var_node)) {
1695                 g_value_unset (&value);
1696
1697                 return;
1698         }
1699
1700         /* Call callbacks */
1701         for (l = data->callbacks; l; l = l->next) {
1702                 CallbackData *callback_data;
1703
1704                 callback_data = l->data;
1705
1706                 callback_data->callback (proxy,
1707                                          (const char *) var_node->name,
1708                                          &value,
1709                                          callback_data->user_data);
1710         }
1711
1712         /* Cleanup */
1713         g_value_unset (&value);
1714 }
1715
1716 static void
1717 emit_notifications_for_doc (GUPnPServiceProxy *proxy,
1718                             xmlDoc            *doc)
1719 {
1720         xmlNode *node;
1721
1722         node = xmlDocGetRootElement (doc);
1723
1724         /* Iterate over all provided properties */
1725         for (node = node->children; node; node = node->next) {
1726                 xmlNode *var_node;
1727
1728                 /* Although according to the UPnP specs, there should be only
1729                  * one variable node inside a 'property' node, we still need to
1730                  * entertain the possibility of multiple variables inside it to
1731                  * be compatible with implementations using older GUPnP.
1732                  */
1733                 for (var_node = node->children;
1734                      var_node;
1735                      var_node = var_node->next) {
1736                         if (strcmp ((char *) node->name, "property") == 0)
1737                                 emit_notification (proxy, var_node);
1738                 }
1739         }
1740 }
1741
1742 /* Emit pending notifications. See comment below on why we do this. */
1743 static gboolean
1744 emit_notifications (gpointer user_data)
1745 {
1746         GUPnPServiceProxy *proxy = user_data;
1747         GList *pending_notify;
1748         gboolean resubscribe = FALSE;
1749
1750         g_assert (user_data);
1751
1752         if (proxy->priv->sid == NULL)
1753                 /* No SID */
1754                 if (G_LIKELY (proxy->priv->subscribed))
1755                         /* subscription in progress, delay emision! */
1756                         return TRUE;
1757
1758         for (pending_notify = proxy->priv->pending_notifies;
1759              pending_notify != NULL;
1760              pending_notify = pending_notify->next) {
1761                 EmitNotifyData *emit_notify_data;
1762
1763                 emit_notify_data = pending_notify->data;
1764
1765                 if (emit_notify_data->seq > proxy->priv->seq) {
1766                         /* Error procedure on missed event according to
1767                          * UDA 1.0, section 4.2, Â§5:
1768                          * Re-subscribe to get a new SID and SEQ */
1769                         resubscribe = TRUE;
1770
1771                         break;
1772                 }
1773
1774                 /* Increment our own event sequence number */
1775                 /* UDA 1.0, section 4.2, Â§3: To prevent overflow, SEQ is set to
1776                  * 1, NOT 0, when encountering G_MAXUINT32. SEQ == 0 always
1777                  * indicates the initial event message. */
1778                 if (proxy->priv->seq < G_MAXUINT32)
1779                         proxy->priv->seq++;
1780                 else
1781                         proxy->priv->seq = 1;
1782
1783                 if (G_LIKELY (proxy->priv->sid != NULL &&
1784                               strcmp (emit_notify_data->sid,
1785                                       proxy->priv->sid) == 0))
1786                         /* Our SID, entertain! */
1787                         emit_notifications_for_doc (proxy,
1788                                                     emit_notify_data->doc);
1789         }
1790
1791         /* Cleanup */
1792         while (proxy->priv->pending_notifies != NULL) {
1793                 emit_notify_data_free (proxy->priv->pending_notifies->data);
1794
1795                 proxy->priv->pending_notifies =
1796                         g_list_delete_link (proxy->priv->pending_notifies,
1797                                             proxy->priv->pending_notifies);
1798         }
1799
1800         proxy->priv->notify_idle_src = NULL;
1801
1802         if (resubscribe) {
1803                 unsubscribe (proxy);
1804                 subscribe (proxy);
1805         }
1806
1807         return FALSE;
1808 }
1809
1810 /*
1811  * HTTP server received a message. Handle, if this was a NOTIFY
1812  * message with our SID.
1813  */
1814 static void
1815 server_handler (G_GNUC_UNUSED SoupServer        *soup_server,
1816                 SoupMessage                     *msg,
1817                 G_GNUC_UNUSED const char        *server_path,
1818                 G_GNUC_UNUSED GHashTable        *query,
1819                 G_GNUC_UNUSED SoupClientContext *soup_client,
1820                 gpointer                         user_data)
1821 {
1822         GUPnPServiceProxy *proxy;
1823         const char *hdr, *nt, *nts;
1824         guint32 seq;
1825         guint64 seq_parsed;
1826         xmlDoc *doc;
1827         xmlNode *node;
1828         EmitNotifyData *emit_notify_data;
1829
1830         proxy = GUPNP_SERVICE_PROXY (user_data);
1831
1832         if (strcmp (msg->method, GENA_METHOD_NOTIFY) != 0) {
1833                 /* We don't implement this method */
1834                 soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
1835
1836                 return;
1837         }
1838
1839         nt = soup_message_headers_get_one (msg->request_headers, "NT");
1840         nts = soup_message_headers_get_one (msg->request_headers, "NTS");
1841         if (nt == NULL || nts == NULL) {
1842                 /* Required header is missing */
1843                 soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST);
1844
1845                 return;
1846         }
1847
1848         if (strcmp (nt, "upnp:event") != 0 ||
1849             strcmp (nts, "upnp:propchange") != 0) {
1850                 /* Unexpected header content */
1851                 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1852
1853                 return;
1854         }
1855
1856         hdr = soup_message_headers_get_one (msg->request_headers, "SEQ");
1857         if (hdr == NULL) {
1858                 /* No SEQ header */
1859                 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1860
1861                 return;
1862         }
1863
1864         errno = 0;
1865         seq_parsed = strtoul (hdr, NULL, 10);
1866         if (errno != 0 || seq_parsed > G_MAXUINT32) {
1867                 /* Invalid SEQ header value */
1868                 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1869
1870                 return;
1871         }
1872
1873         seq = (guint32) seq_parsed;
1874
1875         hdr = soup_message_headers_get_one (msg->request_headers, "SID");
1876         if (hdr == NULL ||
1877             strlen (hdr) <= strlen ("uuid:") ||
1878             strncmp (hdr, "uuid:", strlen ("uuid:")) != 0) {
1879                 /* No SID */
1880                 soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED);
1881
1882                 return;
1883         }
1884
1885         /* Parse the actual XML message content */
1886         doc = xmlRecoverMemory (msg->request_body->data,
1887                                 msg->request_body->length);
1888         if (doc == NULL) {
1889                 /* Failed */
1890                 g_warning ("Failed to parse NOTIFY message body");
1891
1892                 soup_message_set_status (msg,
1893                                          SOUP_STATUS_INTERNAL_SERVER_ERROR);
1894
1895                 return;
1896         }
1897
1898         /* Get root propertyset element */
1899         node = xmlDocGetRootElement (doc);
1900         if (node == NULL || strcmp ((char *) node->name, "propertyset")) {
1901                 /* Empty or unsupported */
1902                 xmlFreeDoc (doc);
1903
1904                 soup_message_set_status (msg, SOUP_STATUS_OK);
1905
1906                 return;
1907         }
1908
1909         /*
1910          * Some UPnP stacks (hello, myigd/1.0) block when sending a NOTIFY, so
1911          * call the callbacks in an idle handler so that if the client calls the
1912          * device in the notify callback the server can actually respond.
1913          */
1914         emit_notify_data = emit_notify_data_new (hdr, seq, doc);
1915
1916         proxy->priv->pending_notifies =
1917                 g_list_append (proxy->priv->pending_notifies, emit_notify_data);
1918         if (!proxy->priv->notify_idle_src) {
1919                 proxy->priv->notify_idle_src = g_idle_source_new();
1920                 g_source_set_callback (proxy->priv->notify_idle_src,
1921                                        emit_notifications,
1922                                        proxy, NULL);
1923                 g_source_attach (proxy->priv->notify_idle_src,
1924                                  g_main_context_get_thread_default ());
1925
1926                 g_source_unref (proxy->priv->notify_idle_src);
1927         }
1928
1929         /* Everything went OK */
1930         soup_message_set_status (msg, SOUP_STATUS_OK);
1931 }
1932
1933 /*
1934  * Generates a timeout header for the subscription timeout specified
1935  * in our GUPnPContext.
1936  */
1937 static char *
1938 make_timeout_header (GUPnPContext *context)
1939 {
1940         guint timeout;
1941
1942         timeout = gupnp_context_get_subscription_timeout (context);
1943         if (timeout > 0)
1944                 return g_strdup_printf ("Second-%d", timeout);
1945         else
1946                 return g_strdup ("infinite");
1947 }
1948
1949 /*
1950  * Subscription expired.
1951  */
1952 static gboolean
1953 subscription_expire (gpointer user_data)
1954 {
1955         GUPnPServiceProxy *proxy;
1956         GUPnPContext *context;
1957         SoupMessage *msg;
1958         SoupSession *session;
1959         char *sub_url, *timeout;
1960
1961         proxy = GUPNP_SERVICE_PROXY (user_data);
1962
1963         /* Reset timeout ID */
1964         proxy->priv->subscription_timeout_src = NULL;
1965
1966         /* Send renewal message */
1967         context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
1968
1969         /* Create subscription message */
1970         sub_url = gupnp_service_info_get_event_subscription_url
1971                                                 (GUPNP_SERVICE_INFO (proxy));
1972
1973         msg = soup_message_new (GENA_METHOD_SUBSCRIBE, sub_url);
1974
1975         g_free (sub_url);
1976
1977         g_return_val_if_fail (msg != NULL, FALSE);
1978
1979         /* Add headers */
1980         soup_message_headers_append (msg->request_headers,
1981                                     "SID",
1982                                     proxy->priv->sid);
1983
1984         timeout = make_timeout_header (context);
1985         soup_message_headers_append (msg->request_headers,
1986                                      "Timeout",
1987                                      timeout);
1988         g_free (timeout);
1989
1990         /* And send it off */
1991         proxy->priv->pending_messages =
1992                 g_list_prepend (proxy->priv->pending_messages, msg);
1993
1994         session = gupnp_context_get_session (context);
1995
1996         soup_session_queue_message (session,
1997                                     msg,
1998                                     (SoupSessionCallback)
1999                                         subscribe_got_response,
2000                                     proxy);
2001
2002         return FALSE;
2003 }
2004
2005 /*
2006  * Received subscription response.
2007  */
2008 static void
2009 subscribe_got_response (G_GNUC_UNUSED SoupSession *session,
2010                         SoupMessage               *msg,
2011                         GUPnPServiceProxy         *proxy)
2012 {
2013         GError *error;
2014
2015         /* Cancelled? */
2016         if (msg->status_code == SOUP_STATUS_CANCELLED)
2017                 return;
2018
2019         /* Remove from pending messages list */
2020         proxy->priv->pending_messages =
2021                 g_list_remove (proxy->priv->pending_messages, msg);
2022
2023         /* Check whether the subscription is still wanted */
2024         if (!proxy->priv->subscribed)
2025                 return;
2026
2027         /* Reset SID */
2028         g_free (proxy->priv->sid);
2029         proxy->priv->sid = NULL;
2030
2031         /* Check message status */
2032         if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
2033                 /* Success. */
2034                 const char *hdr;
2035                 int timeout;
2036
2037                 /* Save SID. */
2038                 hdr = soup_message_headers_get_one (msg->response_headers,
2039                                                     "SID");
2040                 if (hdr == NULL) {
2041                         error = g_error_new
2042                                         (GUPNP_EVENTING_ERROR,
2043                                          GUPNP_EVENTING_ERROR_SUBSCRIPTION_LOST,
2044                                          "No SID in SUBSCRIBE response");
2045
2046                         goto hdr_err;
2047                 }
2048
2049                 proxy->priv->sid = g_strdup (hdr);
2050
2051                 /* Figure out when the subscription times out */
2052                 hdr = soup_message_headers_get_one (msg->response_headers,
2053                                                     "Timeout");
2054                 if (hdr == NULL) {
2055                         g_warning ("No Timeout in SUBSCRIBE response.");
2056
2057                         return;
2058                 }
2059
2060                 if (strncmp (hdr, "Second-", strlen ("Second-")) == 0) {
2061                         /* We have a finite timeout */
2062                         timeout = atoi (hdr + strlen ("Second-"));
2063
2064                         if (timeout < 0) {
2065                                 g_warning ("Invalid time-out specified. "
2066                                            "Assuming default value of %d.",
2067                                            GENA_DEFAULT_TIMEOUT);
2068
2069                                 timeout = GENA_DEFAULT_TIMEOUT;
2070                         }
2071
2072                         /* We want to resubscribe before the subscription
2073                          * expires. */
2074                         timeout = g_random_int_range (1, timeout / 2);
2075
2076                         /* Add actual timeout */
2077                         proxy->priv->subscription_timeout_src =
2078                                 g_timeout_source_new_seconds (timeout);
2079                         g_source_set_callback
2080                                 (proxy->priv->subscription_timeout_src,
2081                                  subscription_expire,
2082                                  proxy, NULL);
2083                         g_source_attach (proxy->priv->subscription_timeout_src,
2084                                          g_main_context_get_thread_default ());
2085
2086                         g_source_unref (proxy->priv->subscription_timeout_src);
2087                 }
2088         } else {
2089                 GUPnPContext *context;
2090                 SoupServer *server;
2091
2092                 /* Subscription failed. */
2093                 error = g_error_new_literal
2094                                 (GUPNP_EVENTING_ERROR,
2095                                  GUPNP_EVENTING_ERROR_SUBSCRIPTION_FAILED,
2096                                  msg->reason_phrase);
2097
2098 hdr_err:
2099                 /* Remove listener */
2100                 context = gupnp_service_info_get_context
2101                                         (GUPNP_SERVICE_INFO (proxy));
2102
2103                 server = gupnp_context_get_server (context);
2104                 soup_server_remove_handler (server, proxy->priv->path);
2105
2106                 proxy->priv->subscribed = FALSE;
2107
2108                 g_object_notify (G_OBJECT (proxy), "subscribed");
2109
2110                 /* Emit subscription-lost */
2111                 g_signal_emit (proxy,
2112                                signals[SUBSCRIPTION_LOST],
2113                                0,
2114                                error);
2115
2116                 g_error_free (error);
2117         }
2118 }
2119
2120 /*
2121  * Subscribe to this service.
2122  */
2123 static void
2124 subscribe (GUPnPServiceProxy *proxy)
2125 {
2126         GUPnPContext *context;
2127         SoupMessage *msg;
2128         SoupSession *session;
2129         SoupServer *server;
2130         const char *server_url;
2131         char *sub_url, *delivery_url, *timeout;
2132
2133         context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
2134
2135         /* Create subscription message */
2136         sub_url = gupnp_service_info_get_event_subscription_url
2137                                                 (GUPNP_SERVICE_INFO (proxy));
2138
2139         msg = NULL;
2140         if (sub_url != NULL) {
2141                 msg = soup_message_new (GENA_METHOD_SUBSCRIBE, sub_url);
2142
2143                 g_free (sub_url);
2144         }
2145
2146         if (msg == NULL) {
2147                 GError *error;
2148
2149                 /* Subscription failed. */
2150                 proxy->priv->subscribed = FALSE;
2151
2152                 g_object_notify (G_OBJECT (proxy), "subscribed");
2153
2154                 /* Emit subscription-lost */
2155                 error = g_error_new (GUPNP_SERVER_ERROR,
2156                                      GUPNP_SERVER_ERROR_INVALID_URL,
2157                                      "No valid subscription URL defined");
2158
2159                 g_signal_emit (proxy,
2160                                signals[SUBSCRIPTION_LOST],
2161                                0,
2162                                error);
2163
2164                 g_error_free (error);
2165
2166                 return;
2167         }
2168
2169         /* Add headers */
2170         server_url = _gupnp_context_get_server_url (context);
2171         delivery_url = g_strdup_printf ("<%s%s>",
2172                                         server_url,
2173                                         proxy->priv->path);
2174         soup_message_headers_append (msg->request_headers,
2175                                      "Callback",
2176                                      delivery_url);
2177         g_free (delivery_url);
2178
2179         soup_message_headers_append (msg->request_headers,
2180                                      "NT",
2181                                      "upnp:event");
2182
2183         timeout = make_timeout_header (context);
2184         soup_message_headers_append (msg->request_headers,
2185                                      "Timeout",
2186                                      timeout);
2187         g_free (timeout);
2188
2189         /* Listen for events */
2190         server = gupnp_context_get_server (context);
2191
2192         soup_server_add_handler (server,
2193                                  proxy->priv->path,
2194                                  server_handler,
2195                                  proxy,
2196                                  NULL);
2197
2198         /* And send our subscription message off */
2199         proxy->priv->pending_messages =
2200                 g_list_prepend (proxy->priv->pending_messages, msg);
2201
2202         session = gupnp_context_get_session (context);
2203
2204         soup_session_queue_message (session,
2205                                     msg,
2206                                     (SoupSessionCallback)
2207                                         subscribe_got_response,
2208                                     proxy);
2209 }
2210
2211 /*
2212  * Unsubscribe from this service.
2213  */
2214 static void
2215 unsubscribe (GUPnPServiceProxy *proxy)
2216 {
2217         GUPnPContext *context;
2218         SoupSession *session;
2219         SoupServer *server;
2220
2221         context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
2222
2223         /* Remove server handler */
2224         server = gupnp_context_get_server (context);
2225         soup_server_remove_handler (server, proxy->priv->path);
2226
2227         if (proxy->priv->sid != NULL) {
2228                 SoupMessage *msg;
2229                 char *sub_url;
2230
2231                 /* Create unsubscription message */
2232                 sub_url = gupnp_service_info_get_event_subscription_url
2233                                                    (GUPNP_SERVICE_INFO (proxy));
2234
2235                 msg = soup_message_new (GENA_METHOD_UNSUBSCRIBE, sub_url);
2236
2237                 g_free (sub_url);
2238
2239                 if (msg != NULL) {
2240                         /* Add headers */
2241                         soup_message_headers_append (msg->request_headers,
2242                                                      "SID",
2243                                                      proxy->priv->sid);
2244
2245                         /* And queue it */
2246                         session = gupnp_context_get_session (context);
2247
2248                         soup_session_queue_message (session, msg, NULL, NULL);
2249                 }
2250
2251                 /* Reset SID */
2252                 g_free (proxy->priv->sid);
2253                 proxy->priv->sid = NULL;
2254
2255                 /* Reset sequence number */
2256                 proxy->priv->seq = 0;
2257         }
2258
2259         /* Remove subscription timeout */
2260         if (proxy->priv->subscription_timeout_src) {
2261                 g_source_destroy (proxy->priv->subscription_timeout_src);
2262                 proxy->priv->subscription_timeout_src = NULL;
2263         }
2264 }
2265
2266 /**
2267  * gupnp_service_proxy_set_subscribed:
2268  * @proxy: A #GUPnPServiceProxy
2269  * @subscribed: %TRUE to subscribe to this service
2270  *
2271  * (Un)subscribes to this service.
2272  *
2273  * Note that the relevant messages are not immediately sent but queued.
2274  * If you want to unsubcribe from this service because the application
2275  * is quitting, rely on automatic synchronised unsubscription on object
2276  * destruction instead.
2277  **/
2278 void
2279 gupnp_service_proxy_set_subscribed (GUPnPServiceProxy *proxy,
2280                                     gboolean           subscribed)
2281 {
2282         g_return_if_fail (GUPNP_IS_SERVICE_PROXY (proxy));
2283
2284         if (proxy->priv->subscribed == subscribed)
2285                 return;
2286
2287         proxy->priv->subscribed = subscribed;
2288
2289         if (subscribed)
2290                 subscribe (proxy);
2291         else
2292                 unsubscribe (proxy);
2293
2294         g_object_notify (G_OBJECT (proxy), "subscribed");
2295 }
2296
2297 /**
2298  * gupnp_service_proxy_get_subscribed:
2299  * @proxy: A #GUPnPServiceProxy
2300  *
2301  * Returns if we are subscribed to this service.
2302  *
2303  * Return value: %TRUE if we are subscribed to this service, otherwise %FALSE.
2304  **/
2305 gboolean
2306 gupnp_service_proxy_get_subscribed (GUPnPServiceProxy *proxy)
2307 {
2308         g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
2309
2310         return proxy->priv->subscribed;
2311 }