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