[gupnp] service-proxy: Add new API for calling actions



commit f434d30cb71061f5416ecf97b525231c6540aabf
Author: Jens Georg <mail jensge org>
Date:   Fri Dec 14 23:14:58 2018 +0100

    service-proxy: Add new API for calling actions

 doc/client-tutorial.xml                       | 103 +--
 doc/gupnp-sections.txt                        |  11 +
 examples/light-client.c                       |  36 +-
 libgupnp/gupnp-service-proxy-action-private.h | 176 +++++
 libgupnp/gupnp-service-proxy-action.c         | 506 ++++++++++++++
 libgupnp/gupnp-service-proxy-private.h        |  14 +
 libgupnp/gupnp-service-proxy.c                | 958 +++++++++-----------------
 libgupnp/gupnp-service-proxy.h                |  74 +-
 libgupnp/gvalue-util.c                        |  11 +
 libgupnp/gvalue-util.h                        |   3 +
 libgupnp/meson.build                          |   1 +
 tests/gtest/test-bugs.c                       |   2 +
 tests/test-proxy.c                            |  20 +-
 13 files changed, 1215 insertions(+), 700 deletions(-)
---
diff --git a/doc/client-tutorial.xml b/doc/client-tutorial.xml
index 0373d68..98d528c 100644
--- a/doc/client-tutorial.xml
+++ b/doc/client-tutorial.xml
@@ -1,9 +1,8 @@
-<?xml version="1.0"?>
-<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" 
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd";>
-
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- This document was created with Syntext Serna Free. -->
+<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" 
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"; []>
 <chapter id="client-tutorial">
-  <title>Writing a UPnP Client</title>
-
+  <title>Writing an UPnP Client</title>
   <simplesect>
     <title>Introduction</title>
     <para>
@@ -12,15 +11,13 @@
       <glossterm>Control Point</glossterm> is created, which searches for
       services of the type
       <literal>urn:schemas-upnp-org:service:WANIPConnection:1</literal> (part of
-      the <ulink url="http://upnp.org/standardizeddcps/igd.asp";>Internet Gateway
-      Device</ulink> specification).  As services are discovered
+      the <ulink url="http://upnp.org/standardizeddcps/igd.asp";>Internet Gateway Device</ulink> 
specification).  As services are discovered
       <firstterm>Service Proxy</firstterm> objects are created by GUPnP to allow
       interaction with the service, on which we can invoke the action
       <function>GetExternalIPAddress</function> to fetch the external IP
       address.
     </para>
   </simplesect>
-
   <simplesect>
     <title>Finding Services</title>
     <para>
@@ -45,27 +42,22 @@ main (int argc, char **argv)
 {
   GUPnPContext *context;
   GUPnPControlPoint *cp;
-  
-  /* Required initialisation */
-  #if !GLIB_CHECK_VERSION(2,35,0)
-    g_type_init ();
-  #endif
 
   /* Create a new GUPnP Context.  By here we are using the default GLib main
-     context, and connecting to the current machine's default IP on an
+     context, and connecting to the current machine&apos;s default IP on an
      automatically generated port. */
-  context = gupnp_context_new (NULL, NULL, 0, NULL);
+  context = gupnp_context_new (NULL, 0, NULL);
 
   /* Create a Control Point targeting WAN IP Connection services */
   cp = gupnp_control_point_new
-    (context, "urn:schemas-upnp-org:service:WANIPConnection:1");
+    (context, &quot;urn:schemas-upnp-org:service:WANIPConnection:1&quot;);
 
   /* The service-proxy-available signal is emitted when any services which match
      our target are found, so connect to it */
   g_signal_connect (cp,
-                   "service-proxy-available",
-                   G_CALLBACK (service_proxy_available_cb),
-                   NULL);
+      &quot;service-proxy-available&quot;,
+      G_CALLBACK (service_proxy_available_cb),
+      NULL);
 
   /* Tell the Control Point to start searching */
   gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (cp), TRUE);
@@ -83,7 +75,6 @@ main (int argc, char **argv)
   return 0;
 }</programlisting>
   </simplesect>
-
   <simplesect>
     <title>Invoking Actions</title>
     <para>
@@ -91,7 +82,7 @@ main (int argc, char **argv)
       calls <function>service_proxy_available_cb</function> for each one it
       found.  To get the external IP address we need to invoke the
       <literal>GetExternalIPAddress</literal> action.  This action takes no in
-      arguments, and has a single out argument called "NewExternalIPAddress".
+      arguments, and has a single out argument called &quot;NewExternalIPAddress&quot;.
       GUPnP has a set of methods to invoke actions (which will be very familiar
       to anyone who has used <literal>dbus-glib</literal>) where you pass a
       <constant>NULL</constant>-terminated varargs list of (name, GType, value)
@@ -106,39 +97,54 @@ service_proxy_available_cb (GUPnPControlPoint *cp,
 {
   GError *error = NULL;
   char *ip = NULL;
+  GUPnPServiceProxyAction *action = NULL;
   
-  gupnp_service_proxy_send_action (proxy,
-                                  /* Action name and error location */
-                                  "GetExternalIPAddress", &amp;error,
-                                  /* IN args */
-                                  NULL,
-                                  /* OUT args */
-                                  "NewExternalIPAddress",
-                                  G_TYPE_STRING, &amp;ip,
-                                  NULL);
+  action = gupnp_service_proxy_action_new (
+       /* Action name */
+       &quot;GetExternalIPAddress&quot;,
+       /* IN args */
+       NULL);
+  gupnp_service_proxy_call_action (proxy,
+                                   action,
+                                   NULL,
+                                   &amp;error);
+  if (error != NULL) {
+    goto out;
+  }
+
+  gupnp_service_proxy_action_get_result (action,
+       /* Error location */
+       &amp;error,
+       /* OUT args */
+       &quot;NewExternalIPAddress&quot;,
+       G_TYPE_STRING, &amp;ip,
+       NULL);
   
   if (error == NULL) {
-    g_print ("External IP address is %s\n", ip);
+    g_print (&quot;External IP address is %s\n&quot;, ip);
     g_free (ip);
-  } else {
-    g_printerr ("Error: %s\n", error-&gt;message);
+  }
+
+out:
+  if (error != NULL) {
+    g_printerr (&quot;Error: %s\n&quot;, error-&gt;message);
     g_error_free (error);
   }
+
+  gupnp_service_proxy_action_unref (action);
   g_main_loop_quit (main_loop);
 }</programlisting>
-    <para>
-      Note that gupnp_service_proxy_send_action() blocks until the service has
+    <para>Note that gupnp_service_proxy_call_action() blocks until the service has
       replied.  If you need to make non-blocking calls then use
-      gupnp_service_proxy_begin_action(), which takes a callback that will be
+      gupnp_service_proxy_call_action_async(), which takes a callback that will be
       called from the mainloop when the reply is received.
     </para>
   </simplesect>
-
   <simplesect>
     <title>Subscribing to state variable change notifications</title>
     <para>
       It is possible to get change notifications for the service state variables 
-      that have attribute <literal>sendEvents="yes"</literal>. We'll demonstrate
+      that have attribute <literal>sendEvents=&quot;yes&quot;</literal>. We&apos;ll demonstrate
       this by modifying <function>service_proxy_available_cb</function> and using
       gupnp_service_proxy_add_notify() to setup a notification callback:
     </para>
@@ -148,7 +154,7 @@ external_ip_address_changed (GUPnPServiceProxy *proxy,
                              GValue            *value,
                              gpointer           userdata)
 {
-  g_print ("External IP address changed: %s\n", g_value_get_string (value));
+  g_print (&quot;External IP address changed: %s\n&quot;, g_value_get_string (value));
 }
 
 static void
@@ -156,29 +162,28 @@ service_proxy_available_cb (GUPnPControlPoint *cp,
                             GUPnPServiceProxy *proxy,
                             gpointer           userdata)
 {
-  g_print ("Found a WAN IP Connection service\n");
+  g_print (&quot;Found a WAN IP Connection service\n&quot;);
   
   gupnp_service_proxy_set_subscribed (proxy, TRUE);
   if (!gupnp_service_proxy_add_notify (proxy,
-                                       "ExternalIPAddress",
+                                       &quot;ExternalIPAddress&quot;,
                                        G_TYPE_STRING,
                                        external_ip_address_changed,
                                        NULL)) {
-    g_printerr ("Failed to add notify");
+    g_printerr (&quot;Failed to add notify&quot;);
   }
 }</programlisting>
   </simplesect>
-
   <simplesect>
     <title>Generating Wrappers</title>
     <para>
-      Using gupnp_service_proxy_send_action() and gupnp_service_proxy_add_notify ()
+      Using gupnp_service_proxy_call_action() and gupnp_service_proxy_add_notify ()
       can become tedious, because of the requirement to specify the types and deal
       with GValues.  An
       alternative is to use <xref linkend="gupnp-binding-tool"/>, which
       generates wrappers that hide the boilerplate code from you.  Using a 
-      wrapper generated with prefix 'ipconn' would replace 
-      gupnp_service_proxy_send_action() with this code:
+      wrapper generated with prefix &apos;ipconn&apos; would replace
+      gupnp_service_proxy_call_action() with this code:
     </para>
     <programlisting>ipconn_get_external_ip_address (proxy, &amp;ip, &amp;error);</programlisting>
     <para>
@@ -189,7 +194,7 @@ external_ip_address_changed (GUPnPServiceProxy *proxy,
                              const gchar       *external_ip_address,
                              gpointer           userdata)
 {
-  g_print ("External IP address changed: '%s'\n", external_ip_address);
+  g_print (&quot;External IP address changed: &apos;%s&apos;\n&quot;, external_ip_address);
 }
 
 static void
@@ -197,13 +202,13 @@ service_proxy_available_cb (GUPnPControlPoint *cp,
                             GUPnPServiceProxy *proxy
                             gpointer           userdata)
 {
-  g_print ("Found a WAN IP Connection service\n");
+  g_print (&quot;Found a WAN IP Connection service\n&quot;);
   
   gupnp_service_proxy_set_subscribed (proxy, TRUE);
   if (!ipconn_external_ip_address_add_notify (proxy,
                                               external_ip_address_changed,
                                               NULL)) {
-    g_printerr ("Failed to add notify");
+    g_printerr (&quot;Failed to add notify&quot;);
   }
 }</programlisting>
   </simplesect>
diff --git a/doc/gupnp-sections.txt b/doc/gupnp-sections.txt
index f548ba5..0eca13d 100644
--- a/doc/gupnp-sections.txt
+++ b/doc/gupnp-sections.txt
@@ -71,6 +71,9 @@ GUPnPServiceProxy
 GUPnPServiceProxyAction
 GUPnPServiceProxyActionCallback
 GUPnPServiceProxyNotifyCallback
+gupnp_service_proxy_call_action
+gupnp_service_proxy_call_action_async
+gupnp_service_proxy_call_action_finish
 gupnp_service_proxy_send_action
 gupnp_service_proxy_send_action_valist
 gupnp_service_proxy_send_action_list
@@ -89,6 +92,14 @@ gupnp_service_proxy_remove_notify
 gupnp_service_proxy_remove_raw_notify
 gupnp_service_proxy_set_subscribed
 gupnp_service_proxy_get_subscribed
+gupnp_service_proxy_action_get_result
+gupnp_service_proxy_action_get_result_hash
+gupnp_service_proxy_action_get_result_list
+gupnp_service_proxy_action_get_type
+gupnp_service_proxy_action_new
+gupnp_service_proxy_action_new_from_list
+gupnp_service_proxy_action_ref
+gupnp_service_proxy_action_unref
 <SUBSECTION Standard>
 GUPnPServiceProxyClass
 GUPNP_SERVICE_PROXY
diff --git a/examples/light-client.c b/examples/light-client.c
index 82ee54c..359bc3e 100644
--- a/examples/light-client.c
+++ b/examples/light-client.c
@@ -39,16 +39,28 @@ send_cmd (GUPnPServiceProxy *proxy)
   GError *error = NULL;
   gboolean target;
 
+  GUPnPServiceProxyAction *action;
+
   if (mode == TOGGLE) {
     /* We're toggling, so first fetch the current status */
-    if (!gupnp_service_proxy_send_action
-        (proxy, "GetStatus", &error,
-         /* IN args */ NULL,
-         /* OUT args */ "ResultStatus", G_TYPE_BOOLEAN, &target, NULL)) {
+
+    action = gupnp_service_proxy_action_new ("GetStatus", NULL);
+
+    gupnp_service_proxy_call_action (proxy, action, NULL, &error);
+    if (error != NULL)
       goto error;
-    }
+
+    gupnp_service_proxy_action_get_result (action,
+                                           &error,
+                                           "ResultStatus", G_TYPE_BOOLEAN, &target, NULL);
+    g_clear_pointer (&action, gupnp_service_proxy_action_unref);
+
+    if (error != NULL)
+      goto error;
+
     /* And then toggle it */
     target = ! target;
+
   } else {
     /* Mode is a boolean, so the target is the mode thanks to our well chosen
        enumeration values. */
@@ -56,12 +68,13 @@ send_cmd (GUPnPServiceProxy *proxy)
   }
 
   /* Set the target */
-  if (!gupnp_service_proxy_send_action (proxy, "SetTarget", &error,
-                                        /* IN args */
-                                        "newTargetValue", G_TYPE_BOOLEAN, target, NULL,
-                                        /* OUT args */
-                                        NULL)) {
-    goto error;
+
+  action = gupnp_service_proxy_action_new ("SetTarget",
+                                           "newTargetValue", G_TYPE_BOOLEAN, target, NULL);
+  gupnp_service_proxy_call_action (proxy, action, NULL, &error);
+  g_clear_pointer (&action, gupnp_service_proxy_action_unref);
+  if (error != NULL) {
+          goto error;
   } else {
     if (!quiet) {
       g_print ("Set switch to %s.\n", target ? "on" : "off");
@@ -76,6 +89,7 @@ send_cmd (GUPnPServiceProxy *proxy)
   return;
 
  error:
+  g_clear_pointer (&action, gupnp_service_proxy_action_unref);
   g_printerr ("Cannot set switch: %s\n", error->message);
   g_error_free (error);
   goto done;
diff --git a/libgupnp/gupnp-service-proxy-action-private.h b/libgupnp/gupnp-service-proxy-action-private.h
new file mode 100644
index 0000000..31927a4
--- /dev/null
+++ b/libgupnp/gupnp-service-proxy-action-private.h
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2018,2019 The GUPnP maintainers.
+ *
+ * Author: Jens Georg <mail jensge org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef GUPNP_SERVICE_PROXY_ACTION_H
+#define GUPNP_SERVICE_PROXY_ACTION_H
+
+#include <gobject/gvaluecollector.h>
+
+G_BEGIN_DECLS
+
+/* Initializes hash table to hold arg names as keys and GValues of
+ * given type and value.
+ */
+#define VAR_ARGS_TO_IN_LIST(var_args, names, values) \
+        G_STMT_START { \
+                const gchar *arg_name = va_arg (var_args, const gchar *); \
+         \
+                while (arg_name != NULL) { \
+                        GValue *value = g_new0 (GValue, 1); \
+                        gchar *__error = NULL; \
+                        GType type = va_arg (var_args, GType); \
+         \
+                        G_VALUE_COLLECT_INIT (value, \
+                                              type, \
+                                              var_args, \
+                                              G_VALUE_NOCOPY_CONTENTS, \
+                                              &__error); \
+                        if (__error == NULL) { \
+                                names = g_list_prepend (names, g_strdup (arg_name)); \
+                                values = g_list_prepend (values, value); \
+                        } else { \
+                                g_warning ("Failed to collect value of type %s for %s: %s", \
+                                           g_type_name (type), \
+                                           arg_name, \
+                                           __error); \
+                                g_free (__error); \
+                        } \
+                        arg_name = va_arg (var_args, const gchar *); \
+                } \
+                names = g_list_reverse (names); \
+                values = g_list_reverse (values); \
+        } G_STMT_END
+
+/* This is a skip variant of G_VALUE_LCOPY, same as there is
+ * G_VALUE_COLLECT_SKIP for G_VALUE_COLLECT.
+ */
+#define VALUE_LCOPY_SKIP(value_type, var_args) \
+        G_STMT_START { \
+                GTypeValueTable *_vtable = g_type_value_table_peek (value_type); \
+                const gchar *_lcopy_format = _vtable->lcopy_format; \
+         \
+                while (*_lcopy_format) { \
+                        switch (*_lcopy_format++) { \
+                        case G_VALUE_COLLECT_INT: \
+                                va_arg ((var_args), gint); \
+                                break; \
+                        case G_VALUE_COLLECT_LONG: \
+                                va_arg ((var_args), glong); \
+                                break; \
+                        case G_VALUE_COLLECT_INT64: \
+                                va_arg ((var_args), gint64); \
+                                break; \
+                        case G_VALUE_COLLECT_DOUBLE: \
+                                va_arg ((var_args), gdouble); \
+                                break; \
+                        case G_VALUE_COLLECT_POINTER: \
+                                va_arg ((var_args), gpointer); \
+                                break; \
+                        default: \
+                                g_assert_not_reached (); \
+                        } \
+                } \
+        } G_STMT_END
+
+/* Initializes hash table to hold arg names as keys and GValues of
+ * given type, but without any specific value. Note that if you are
+ * going to use OUT_HASH_TABLE_TO_VAR_ARGS then you have to store a
+ * copy of var_args with G_VA_COPY before using this macro.
+ */
+#define VAR_ARGS_TO_OUT_HASH_TABLE(var_args, hash) \
+        G_STMT_START { \
+                const gchar *arg_name = va_arg (var_args, const gchar *); \
+         \
+                while (arg_name != NULL) { \
+                        GValue *value = g_new0 (GValue, 1); \
+                        GType type = va_arg (var_args, GType); \
+         \
+                        VALUE_LCOPY_SKIP (type, var_args); \
+                        g_value_init (value, type); \
+                        g_hash_table_insert (hash, g_strdup (arg_name), value); \
+                        arg_name = va_arg (var_args, const gchar *); \
+                } \
+        } G_STMT_END
+
+/* Puts values stored in hash table with GValues into var args.
+ */
+#define OUT_HASH_TABLE_TO_VAR_ARGS(hash, var_args) \
+        G_STMT_START { \
+                const gchar *arg_name = va_arg (var_args, const gchar *); \
+         \
+                while (arg_name != NULL) { \
+                        GValue *value = g_hash_table_lookup (hash, arg_name); \
+                        GType type = va_arg (var_args, GType); \
+         \
+                        if (value == NULL) { \
+                                g_warning ("No value for %s", arg_name); \
+                                G_VALUE_COLLECT_SKIP (type, var_args); \
+                        } else if (G_VALUE_TYPE (value) != type) { \
+                                g_warning ("Different GType in value (%s) and in var args (%s) for %s.", \
+                                           G_VALUE_TYPE_NAME (value), \
+                                           g_type_name (type), \
+                                           arg_name); \
+                        } else { \
+                                gchar *__error = NULL; \
+         \
+                                G_VALUE_LCOPY (value, var_args, 0, &__error); \
+                                if (__error != NULL) { \
+                                        g_warning ("Failed to lcopy the value of type %s for %s: %s", \
+                                                   g_type_name (type), \
+                                                   arg_name, \
+                                                   __error); \
+                                        g_free (__error); \
+                                } \
+                        } \
+                        arg_name = va_arg (var_args, const gchar *); \
+                } \
+        } G_STMT_END
+
+
+struct _GUPnPServiceProxyAction {
+        GUPnPServiceProxy *proxy;
+        char *name;
+        gint header_pos;
+
+        SoupMessage *msg;
+        GString *msg_str;
+
+        GCancellable *cancellable;
+        gulong cancellable_connection_id;
+
+        GUPnPServiceProxyActionCallback callback;
+        gpointer user_data;
+
+        GError *error;    /* If non-NULL, description of error that
+                             occurred when preparing message */
+};
+
+G_GNUC_INTERNAL GUPnPServiceProxyAction *
+gupnp_service_proxy_action_new_internal (const char *action);
+
+G_GNUC_INTERNAL gboolean
+gupnp_service_proxy_action_get_result_valist (GUPnPServiceProxyAction *action,
+                                              GError                 **error,
+                                              va_list                  var_args);
+
+G_END_DECLS
+
+#endif /* GUPNP_SERVICE_PROXY_ACTION_H */
diff --git a/libgupnp/gupnp-service-proxy-action.c b/libgupnp/gupnp-service-proxy-action.c
new file mode 100644
index 0000000..493a1e5
--- /dev/null
+++ b/libgupnp/gupnp-service-proxy-action.c
@@ -0,0 +1,506 @@
+/*
+ * Copyright (C) 2006, 2007, 2008 OpenedHand Ltd.
+ *
+ * Author: Jorn Baayen <jorn openedhand com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "gupnp-error-private.h"
+#include "gupnp-service-proxy.h"
+#include "gupnp-service-proxy-private.h"
+#include "gupnp-service-proxy-action-private.h"
+#include "gvalue-util.h"
+#include "xml-util.h"
+
+/* Reads a value into the parameter name and initialised GValue pair
+ * from @response */
+static void
+read_out_parameter (const char *arg_name,
+                    GValue     *value,
+                    xmlNode    *params)
+{
+        xmlNode *param;
+
+        /* Try to find a matching parameter in the response*/
+        param = xml_util_get_element (params,
+                                      arg_name,
+                                      NULL);
+        if (!param) {
+                g_warning ("Could not find variable \"%s\" in response",
+                           arg_name);
+
+                return;
+        }
+
+        gvalue_util_set_value_from_xml_node (value, param);
+}
+
+
+/* Checks an action response for errors and returns the parsed
+ * xmlDoc object. */
+static xmlDoc *
+check_action_response (G_GNUC_UNUSED GUPnPServiceProxy *proxy,
+                       GUPnPServiceProxyAction         *action,
+                       xmlNode                        **params,
+                       GError                         **error)
+{
+        xmlDoc *response;
+        int code;
+
+        /* Check for errors */
+        switch (action->msg->status_code) {
+        case SOUP_STATUS_OK:
+        case SOUP_STATUS_INTERNAL_SERVER_ERROR:
+                break;
+        default:
+                _gupnp_error_set_server_error (error, action->msg);
+
+                return NULL;
+        }
+
+        /* Parse response */
+        response = xmlRecoverMemory (action->msg->response_body->data,
+                                     action->msg->response_body->length);
+
+        if (!response) {
+                if (action->msg->status_code == SOUP_STATUS_OK) {
+                        g_set_error (error,
+                                     GUPNP_SERVER_ERROR,
+                                     GUPNP_SERVER_ERROR_INVALID_RESPONSE,
+                                     "Could not parse SOAP response");
+                } else {
+                        g_set_error_literal
+                                    (error,
+                                     GUPNP_SERVER_ERROR,
+                                     GUPNP_SERVER_ERROR_INTERNAL_SERVER_ERROR,
+                                     action->msg->reason_phrase);
+                }
+
+                return NULL;
+        }
+
+        /* Get parameter list */
+        *params = xml_util_get_element ((xmlNode *) response,
+                                        "Envelope",
+                                        NULL);
+        if (*params != NULL)
+                *params = xml_util_real_node ((*params)->children);
+
+        if (*params != NULL) {
+                if (strcmp ((const char *) (*params)->name, "Header") == 0)
+                        *params = xml_util_real_node ((*params)->next);
+
+                if (*params != NULL)
+                        if (strcmp ((const char *) (*params)->name, "Body") != 0)
+                                *params = NULL;
+        }
+
+        if (*params != NULL)
+                *params = xml_util_real_node ((*params)->children);
+
+        if (*params == NULL) {
+                g_set_error (error,
+                             GUPNP_SERVER_ERROR,
+                             GUPNP_SERVER_ERROR_INVALID_RESPONSE,
+                             "Invalid Envelope");
+
+                xmlFreeDoc (response);
+
+                return NULL;
+        }
+
+        /* Check whether we have a Fault */
+        if (action->msg->status_code == SOUP_STATUS_INTERNAL_SERVER_ERROR) {
+                xmlNode *param;
+                char *desc;
+
+                param = xml_util_get_element (*params,
+                                              "detail",
+                                              "UPnPError",
+                                              NULL);
+
+                if (!param) {
+                        g_set_error (error,
+                                     GUPNP_SERVER_ERROR,
+                                     GUPNP_SERVER_ERROR_INVALID_RESPONSE,
+                                     "Invalid Fault");
+
+                        xmlFreeDoc (response);
+
+                        return NULL;
+                }
+
+                /* Code */
+                code = xml_util_get_child_element_content_int
+                                        (param, "errorCode");
+                if (code == -1) {
+                        g_set_error (error,
+                                     GUPNP_SERVER_ERROR,
+                                     GUPNP_SERVER_ERROR_INVALID_RESPONSE,
+                                     "Invalid Fault");
+
+                        xmlFreeDoc (response);
+
+                        return NULL;
+                }
+
+                /* Description */
+                desc = xml_util_get_child_element_content_glib
+                                        (param, "errorDescription");
+                if (desc == NULL)
+                        desc = g_strdup (action->msg->reason_phrase);
+
+                g_set_error_literal (error,
+                                     GUPNP_CONTROL_ERROR,
+                                     code,
+                                     desc);
+
+                g_free (desc);
+
+                xmlFreeDoc (response);
+
+                return NULL;
+        }
+
+        return response;
+}
+
+
+/* GDestroyNotify for GHashTable holding GValues.
+ */
+G_GNUC_INTERNAL void
+_value_free (gpointer data);
+
+GUPnPServiceProxyAction *
+gupnp_service_proxy_action_new_internal (const char *action) {
+        GUPnPServiceProxyAction *ret;
+
+        g_return_val_if_fail (action != NULL, NULL);
+
+        ret = g_atomic_rc_box_new0 (GUPnPServiceProxyAction);
+        ret->name = g_strdup (action);
+
+        return ret;
+}
+
+GUPnPServiceProxyAction *
+gupnp_service_proxy_action_ref (GUPnPServiceProxyAction *action)
+{
+        g_return_val_if_fail (action, NULL);
+
+        return g_atomic_rc_box_acquire (action);
+}
+
+static void
+action_dispose (GUPnPServiceProxyAction *action)
+{
+        if (action->proxy != NULL) {
+                g_object_remove_weak_pointer (G_OBJECT (action->proxy),
+                                (gpointer *)&(action->proxy));
+                gupnp_service_proxy_remove_action (action->proxy, action);
+        }
+
+        if (action->cancellable != NULL && action->cancellable_connection_id != 0) {
+                g_cancellable_disconnect (action->cancellable,
+                                          action->cancellable_connection_id);
+        }
+        g_clear_object (&action->cancellable);
+        g_clear_error (&action->error);
+        g_clear_object (&action->msg);
+        g_free (action->name);
+}
+
+void
+gupnp_service_proxy_action_unref (GUPnPServiceProxyAction *action)
+{
+        g_return_if_fail (action);
+
+        g_atomic_rc_box_release_full (action, (GDestroyNotify) action_dispose);
+}
+
+G_DEFINE_BOXED_TYPE (GUPnPServiceProxyAction,
+                     gupnp_service_proxy_action,
+                     gupnp_service_proxy_action_ref,
+                     gupnp_service_proxy_action_unref)
+
+/* Writes a parameter name and GValue pair to @msg */
+static void
+write_in_parameter (const char *arg_name,
+                    GValue     *value,
+                    GString    *msg_str)
+{
+        /* Write parameter pair */
+        xml_util_start_element (msg_str, arg_name);
+        gvalue_util_value_append_to_xml_string (value, msg_str);
+        xml_util_end_element (msg_str, arg_name);
+}
+
+static void
+write_footer (GUPnPServiceProxyAction *action)
+{
+        /* Finish message */
+        g_string_append (action->msg_str, "</u:");
+        g_string_append (action->msg_str, action->name);
+        g_string_append_c (action->msg_str, '>');
+
+        g_string_append (action->msg_str,
+                         "</s:Body>"
+                         "</s:Envelope>");
+}
+
+GUPnPServiceProxyAction *
+gupnp_service_proxy_action_new (const char *action,
+                                ...)
+{
+        GList *in_names = NULL;
+        GList *in_values = NULL;
+        GUPnPServiceProxyAction *result = NULL;
+        va_list var_args;
+
+        g_return_val_if_fail (action != NULL, NULL);
+
+        va_start (var_args, action);
+        VAR_ARGS_TO_IN_LIST (var_args, in_names, in_values);
+        va_end (var_args);
+
+        result = gupnp_service_proxy_action_new_from_list (action,
+                                                           in_names,
+                                                           in_values);;
+        g_list_free_full (in_names, g_free);
+        g_list_free_full (in_values, gvalue_free);
+
+        return result;
+}
+
+/**
+ * gupnp_service_proxy_action_new_from_list:
+ * @action: An action
+ * @in_names: (element-type utf8) (transfer none): #GList of 'in' parameter
+ * names (as strings)
+ * @in_values: (element-type GValue) (transfer none): #GList of values (as
+ * #GValue) that line up with @in_names
+ */
+GUPnPServiceProxyAction *
+gupnp_service_proxy_action_new_from_list (const char *action_name,
+                                          GList      *in_names,
+                                          GList      *in_values)
+{
+        GUPnPServiceProxyAction *action;
+        GList *names, *values;
+
+        action = gupnp_service_proxy_action_new_internal (action_name);
+        action->msg_str = xml_util_new_string ();
+
+        g_string_append (action->msg_str,
+                         "<?xml version=\"1.0\"?>"
+                         "<s:Envelope xmlns:s="
+                                "\"http://schemas.xmlsoap.org/soap/envelope/\"; "
+                          "s:encodingStyle="
+                                "\"http://schemas.xmlsoap.org/soap/encoding/\";>"
+                         "<s:Body>");
+        action->header_pos = action->msg_str->len;
+
+        /* Arguments */
+        for (names = in_names, values = in_values;
+             names && values;
+             names=names->next, values = values->next) {
+                GValue* val = values->data;
+
+                write_in_parameter (names->data,
+                                    val,
+                                    action->msg_str);
+        }
+
+        /* Finish and send off */
+        write_footer (action);
+
+        return action;
+}
+
+/**
+ * gupnp_service_proxy_action_get_result_list:
+ * @action: A #GUPnPServiceProxyAction handle
+ * @out_names: (element-type utf8) (transfer none): #GList of 'out' parameter
+ * names (as strings)
+ * @out_types: (element-type GType) (transfer none): #GList of types (as #GType)
+ * that line up with @out_names
+ * @out_values: (element-type GValue) (transfer full) (out): #GList of values
+ * (as #GValue) that line up with @out_names and @out_types.
+ * @error: The location where to store any error, or %NULL
+ *
+ * A variant of gupnp_service_proxy_action_get_result() that takes lists of
+ * out-parameter names, types and place-holders for values. The returned list
+ * in @out_values must be freed using #g_list_free and each element in it using
+ * #g_value_unset and #g_free.
+ *
+ * Return value : %TRUE on success.
+ *
+ **/
+gboolean
+gupnp_service_proxy_action_get_result_list (GUPnPServiceProxyAction *action,
+                                            GList                   *out_names,
+                                            GList                   *out_types,
+                                            GList                  **out_values,
+                                            GError                 **error)
+{
+        xmlDoc *response;
+        xmlNode *params;
+        GList *names;
+        GList *types;
+        GList *out_values_list;
+
+        out_values_list = NULL;
+
+        g_return_val_if_fail (action, FALSE);
+
+        /* Check for saved error from begin_action() */
+        if (action->error) {
+                g_propagate_error (error, g_error_copy (action->error));
+
+                return FALSE;
+        }
+
+        /* Check response for errors and do initial parsing */
+        response = check_action_response (NULL, action, &params, &action->error);
+        if (response == NULL) {
+                g_propagate_error (error, g_error_copy (action->error));
+
+                return FALSE;
+        }
+
+        /* Read arguments */
+        types = out_types;
+        for (names = out_names; names; names=names->next) {
+                GValue *val;
+
+                val = g_new0 (GValue, 1);
+                g_value_init (val, (GType) types->data);
+
+                read_out_parameter (names->data, val, params);
+
+                out_values_list = g_list_append (out_values_list, val);
+
+                types = types->next;
+        }
+
+        *out_values = out_values_list;
+
+        /* Cleanup */
+        xmlFreeDoc (response);
+
+        return TRUE;
+
+}
+
+/**
+ * gupnp_service_proxy_action_get_result_hash:
+ * @action: A #GUPnPServiceProxyAction handle
+ * @out_hash: (element-type utf8 GValue) (inout) (transfer none): A #GHashTable of
+ * out parameter name and initialised #GValue pairs
+ * @error: The location where to store any error, or %NULL
+ *
+ * See gupnp_service_proxy_action_get_result(); this version takes a #GHashTable for
+ * runtime generated parameter lists.
+ *
+ * Return value: %TRUE on success.
+ *
+ **/
+gboolean
+gupnp_service_proxy_action_get_result_hash (GUPnPServiceProxyAction *action,
+                                            GHashTable              *hash,
+                                            GError                 **error)
+{
+        xmlDoc *response;
+        xmlNode *params;
+
+        g_return_val_if_fail (action, FALSE);
+
+        /* Check for saved error from begin_action() */
+        if (action->error) {
+                g_propagate_error (error, g_error_copy (action->error));
+
+                return FALSE;
+        }
+
+        /* Check response for errors and do initial parsing */
+        response = check_action_response (NULL, action, &params, &action->error);
+        if (response == NULL) {
+                g_propagate_error (error, g_error_copy (action->error));
+
+                return FALSE;
+        }
+
+        /* Read arguments */
+        g_hash_table_foreach (hash, (GHFunc) read_out_parameter, params);
+
+        /* Cleanup */
+        xmlFreeDoc (response);
+
+        return TRUE;
+
+}
+
+gboolean
+gupnp_service_proxy_action_get_result (GUPnPServiceProxyAction *action,
+                                       GError                 **error,
+                                       ...)
+{
+        va_list var_args;
+        gboolean ret;
+
+        va_start (var_args, error);
+        ret = gupnp_service_proxy_action_get_result_valist (action,
+                                                            error,
+                                                            var_args);
+        va_end (var_args);
+
+        return ret;
+}
+
+gboolean
+gupnp_service_proxy_action_get_result_valist (GUPnPServiceProxyAction *action,
+                                              GError                 **error,
+                                              va_list                  var_args)
+{
+        GHashTable *out_hash;
+        va_list var_args_copy;
+        gboolean result;
+        GError *local_error;
+
+        g_return_val_if_fail (action, FALSE);
+
+        out_hash = g_hash_table_new_full (g_str_hash,
+                                          g_str_equal,
+                                          g_free,
+                                          gvalue_free);
+        G_VA_COPY (var_args_copy, var_args);
+        VAR_ARGS_TO_OUT_HASH_TABLE (var_args, out_hash);
+        local_error = NULL;
+        result = gupnp_service_proxy_action_get_result_hash (action,
+                                                             out_hash,
+                                                             &local_error);
+
+        if (local_error == NULL) {
+                OUT_HASH_TABLE_TO_VAR_ARGS (out_hash, var_args_copy);
+        } else {
+                g_propagate_error (error, local_error);
+        }
+        va_end (var_args_copy);
+        g_hash_table_unref (out_hash);
+
+        return result;
+}
diff --git a/libgupnp/gupnp-service-proxy-private.h b/libgupnp/gupnp-service-proxy-private.h
new file mode 100644
index 0000000..ae07d08
--- /dev/null
+++ b/libgupnp/gupnp-service-proxy-private.h
@@ -0,0 +1,14 @@
+#ifndef GUPNP_SERVICE_PROXY_PRIVATE_H
+#define GUPNP_SERVICE_PROXY_PRIVATE_H
+
+#include "gupnp-service-proxy.h"
+
+G_BEGIN_DECLS
+
+G_GNUC_INTERNAL void
+gupnp_service_proxy_remove_action (GUPnPServiceProxy       *proxy,
+                                   GUPnPServiceProxyAction *action);
+
+G_END_DECLS
+
+#endif /* GUPNP_SERVICE_PROXY_ACTION_H */
diff --git a/libgupnp/gupnp-service-proxy.c b/libgupnp/gupnp-service-proxy.c
index 58b8b1e..a78d7ca 100644
--- a/libgupnp/gupnp-service-proxy.c
+++ b/libgupnp/gupnp-service-proxy.c
@@ -35,11 +35,10 @@
 #include <errno.h>
 
 #include "gupnp-service-proxy.h"
+#include "gupnp-service-proxy-action-private.h"
 #include "gupnp-context-private.h"
 #include "gupnp-error.h"
-#include "gupnp-error-private.h"
 #include "gupnp-types.h"
-#include "xml-util.h"
 #include "gena-protocol.h"
 #include "http-headers.h"
 #include "gvalue-util.h"
@@ -81,20 +80,6 @@ enum {
 
 static guint signals[LAST_SIGNAL];
 
-struct _GUPnPServiceProxyAction {
-        volatile gint ref_count;
-        GUPnPServiceProxy *proxy;
-
-        SoupMessage *msg;
-        GString *msg_str;
-
-        GUPnPServiceProxyActionCallback callback;
-        gpointer user_data;
-
-        GError *error;    /* If non-NULL, description of error that
-                             occurred when preparing message */
-};
-
 typedef struct {
         GType type;       /* type of the variable this notification is for */
 
@@ -124,13 +109,7 @@ subscribe (GUPnPServiceProxy *proxy);
 static void
 unsubscribe (GUPnPServiceProxy *proxy);
 
-static GUPnPServiceProxyAction *
-gupnp_service_proxy_action_ref (GUPnPServiceProxyAction *action);
-
-static void
-gupnp_service_proxy_action_unref (GUPnPServiceProxyAction *action);
-
-static void
+void
 gupnp_service_proxy_remove_action (GUPnPServiceProxy       *proxy,
                                    GUPnPServiceProxyAction *action)
 {
@@ -142,44 +121,6 @@ gupnp_service_proxy_remove_action (GUPnPServiceProxy       *proxy,
                                                action);
 }
 
-static GUPnPServiceProxyAction *
-gupnp_service_proxy_action_ref (GUPnPServiceProxyAction *action)
-{
-        g_return_val_if_fail (action, NULL);
-        g_return_val_if_fail (action->ref_count > 0, NULL);
-
-        g_atomic_int_inc (&action->ref_count);
-
-        return action;
-}
-
-static void
-gupnp_service_proxy_action_unref (GUPnPServiceProxyAction *action)
-{
-
-        g_return_if_fail (action);
-        g_return_if_fail (action->ref_count > 0);
-
-
-        if (g_atomic_int_dec_and_test (&action->ref_count)) {
-                if (action->proxy != NULL) {
-                        g_object_remove_weak_pointer (G_OBJECT (action->proxy),
-                                                      (gpointer *)&(action->proxy));
-                        gupnp_service_proxy_remove_action (action->proxy, action);
-                }
-
-                if (action->msg != NULL)
-                        g_object_unref (action->msg);
-
-                g_slice_free (GUPnPServiceProxyAction, action);
-        }
-}
-
-G_DEFINE_BOXED_TYPE (GUPnPServiceProxyAction,
-                     gupnp_service_proxy_action,
-                     gupnp_service_proxy_action_ref,
-                     gupnp_service_proxy_action_unref)
-
 static void
 callback_data_free (CallbackData *data)
 {
@@ -324,7 +265,7 @@ gupnp_service_proxy_dispose (GObject *object)
                         g_list_delete_link (priv->pending_actions,
                                             priv->pending_actions);
 
-                gupnp_service_proxy_cancel_action (proxy, action);
+                g_cancellable_cancel (action->cancellable);
         }
 
         /* Cancel pending messages */
@@ -459,152 +400,17 @@ gupnp_service_proxy_send_action (GUPnPServiceProxy *proxy,
         gboolean ret;
 
         va_start (var_args, error);
+        G_GNUC_BEGIN_IGNORE_DEPRECATIONS
         ret = gupnp_service_proxy_send_action_valist (proxy,
                                                       action,
                                                       error,
                                                       var_args);
+        G_GNUC_END_IGNORE_DEPRECATIONS
         va_end (var_args);
 
         return ret;
 }
 
-static void
-stop_main_loop (G_GNUC_UNUSED GUPnPServiceProxy       *proxy,
-                G_GNUC_UNUSED GUPnPServiceProxyAction *action,
-                gpointer                               user_data)
-{
-        g_main_loop_quit ((GMainLoop *) user_data);
-}
-
-/* This is a skip variant of G_VALUE_LCOPY, same as there is
- * G_VALUE_COLLECT_SKIP for G_VALUE_COLLECT.
- */
-#define VALUE_LCOPY_SKIP(value_type, var_args) \
-        G_STMT_START { \
-                GTypeValueTable *_vtable = g_type_value_table_peek (value_type); \
-                const gchar *_lcopy_format = _vtable->lcopy_format; \
-         \
-                while (*_lcopy_format) { \
-                        switch (*_lcopy_format++) { \
-                        case G_VALUE_COLLECT_INT: \
-                                va_arg ((var_args), gint); \
-                                break; \
-                        case G_VALUE_COLLECT_LONG: \
-                                va_arg ((var_args), glong); \
-                                break; \
-                        case G_VALUE_COLLECT_INT64: \
-                                va_arg ((var_args), gint64); \
-                                break; \
-                        case G_VALUE_COLLECT_DOUBLE: \
-                                va_arg ((var_args), gdouble); \
-                                break; \
-                        case G_VALUE_COLLECT_POINTER: \
-                                va_arg ((var_args), gpointer); \
-                                break; \
-                        default: \
-                                g_assert_not_reached (); \
-                        } \
-                } \
-        } G_STMT_END
-
-/* Initializes hash table to hold arg names as keys and GValues of
- * given type, but without any specific value. Note that if you are
- * going to use OUT_HASH_TABLE_TO_VAR_ARGS then you have to store a
- * copy of var_args with G_VA_COPY before using this macro.
- */
-#define VAR_ARGS_TO_OUT_HASH_TABLE(var_args, hash) \
-        G_STMT_START { \
-                const gchar *arg_name = va_arg (var_args, const gchar *); \
-         \
-                while (arg_name != NULL) { \
-                        GValue *value = g_new0 (GValue, 1); \
-                        GType type = va_arg (var_args, GType); \
-         \
-                        VALUE_LCOPY_SKIP (type, var_args); \
-                        g_value_init (value, type); \
-                        g_hash_table_insert (hash, g_strdup (arg_name), value); \
-                        arg_name = va_arg (var_args, const gchar *); \
-                } \
-        } G_STMT_END
-
-/* Initializes hash table to hold arg names as keys and GValues of
- * given type and value.
- */
-#define VAR_ARGS_TO_IN_LIST(var_args, names, values) \
-        G_STMT_START { \
-                const gchar *arg_name = va_arg (var_args, const gchar *); \
-         \
-                while (arg_name != NULL) { \
-                        GValue *value = g_new0 (GValue, 1); \
-                        gchar *__error = NULL; \
-                        GType type = va_arg (var_args, GType); \
-         \
-                        G_VALUE_COLLECT_INIT (value, \
-                                              type, \
-                                              var_args, \
-                                              G_VALUE_NOCOPY_CONTENTS, \
-                                              &__error); \
-                        if (__error == NULL) { \
-                                names = g_list_prepend (names, g_strdup (arg_name)); \
-                                values = g_list_prepend (values, value); \
-                        } else { \
-                                g_warning ("Failed to collect value of type %s for %s: %s", \
-                                           g_type_name (type), \
-                                           arg_name, \
-                                           __error); \
-                                g_free (__error); \
-                        } \
-                        arg_name = va_arg (var_args, const gchar *); \
-                } \
-                names = g_list_reverse (names); \
-                values = g_list_reverse (values); \
-        } G_STMT_END
-
-/* Puts values stored in hash table with GValues into var args.
- */
-#define OUT_HASH_TABLE_TO_VAR_ARGS(hash, var_args) \
-        G_STMT_START { \
-                const gchar *arg_name = va_arg (var_args, const gchar *); \
-         \
-                while (arg_name != NULL) { \
-                        GValue *value = g_hash_table_lookup (hash, arg_name); \
-                        GType type = va_arg (var_args, GType); \
-         \
-                        if (value == NULL) { \
-                                g_warning ("No value for %s", arg_name); \
-                                G_VALUE_COLLECT_SKIP (type, var_args); \
-                        } else if (G_VALUE_TYPE (value) != type) { \
-                                g_warning ("Different GType in value (%s) and in var args (%s) for %s.", \
-                                           G_VALUE_TYPE_NAME (value), \
-                                           g_type_name (type), \
-                                           arg_name); \
-                        } else { \
-                                gchar *__error = NULL; \
-         \
-                                G_VALUE_LCOPY (value, var_args, 0, &__error); \
-                                if (__error != NULL) { \
-                                        g_warning ("Failed to lcopy the value of type %s for %s: %s", \
-                                                   g_type_name (type), \
-                                                   arg_name, \
-                                                   __error); \
-                                        g_free (__error); \
-                                } \
-                        } \
-                        arg_name = va_arg (var_args, const gchar *); \
-                } \
-        } G_STMT_END
-
-/* GDestroyNotify for GHashTable holding GValues.
- */
-static void
-value_free (gpointer data)
-{
-  GValue *value = (GValue *) data;
-
-  g_value_unset (value);
-  g_free (value);
-}
-
 /**
  * gupnp_service_proxy_send_action_valist:
  * @proxy: A #GUPnPServiceProxy
@@ -620,7 +426,7 @@ value_free (gpointer data)
  **/
 gboolean
 gupnp_service_proxy_send_action_valist (GUPnPServiceProxy *proxy,
-                                        const char        *action,
+                                        const char        *action_name,
                                         GError           **error,
                                         va_list            var_args)
 {
@@ -629,48 +435,30 @@ gupnp_service_proxy_send_action_valist (GUPnPServiceProxy *proxy,
         va_list var_args_copy;
         gboolean result;
         GError *local_error;
-        GMainLoop *main_loop;
         GUPnPServiceProxyAction *handle;
 
         g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
-        g_return_val_if_fail (action, FALSE);
+        g_return_val_if_fail (action_name, FALSE);
 
 
-        main_loop = g_main_loop_new (g_main_context_get_thread_default (),
-                                     TRUE);
-
         VAR_ARGS_TO_IN_LIST (var_args, in_names, in_values);
         G_VA_COPY (var_args_copy, var_args);
         out_hash = g_hash_table_new_full (g_str_hash,
                                           g_str_equal,
                                           g_free,
-                                          value_free);
+                                          gvalue_free);
         VAR_ARGS_TO_OUT_HASH_TABLE (var_args, out_hash);
 
-        local_error = NULL;
-        handle = gupnp_service_proxy_begin_action_list (proxy,
-                                                        action,
-                                                        in_names,
-                                                        in_values,
-                                                        stop_main_loop,
-                                                        main_loop);
-        if (!handle) {
-                g_main_loop_unref (main_loop);
-                result = FALSE;
+        handle = gupnp_service_proxy_action_new_from_list (action_name, in_names, in_values);
 
+        if (gupnp_service_proxy_call_action (proxy, handle, NULL,  error) != NULL) {
+                result = FALSE;
                 goto out;
         }
 
-        /* Loop till we get a reply (or time out) */
-        if (g_main_loop_is_running (main_loop))
-                g_main_loop_run (main_loop);
-
-        g_main_loop_unref (main_loop);
-
-        result = gupnp_service_proxy_end_action_hash (proxy,
-                                                      handle,
-                                                      out_hash,
-                                                      &local_error);
+        result = gupnp_service_proxy_action_get_result_hash (handle,
+                                                             out_hash,
+                                                             &local_error);
 
         if (local_error == NULL) {
                 OUT_HASH_TABLE_TO_VAR_ARGS (out_hash, var_args_copy);
@@ -678,9 +466,10 @@ gupnp_service_proxy_send_action_valist (GUPnPServiceProxy *proxy,
                 g_propagate_error (error, local_error);
         }
 out:
+        gupnp_service_proxy_action_unref (handle);
         va_end (var_args_copy);
         g_list_free_full (in_names, g_free);
-        g_list_free_full (in_values, value_free);
+        g_list_free_full (in_values, gvalue_free);
         g_hash_table_unref (out_hash);
 
         return result;
@@ -707,6 +496,8 @@ out:
  *
  * Return value: %TRUE if sending the action was succesful.
  *
+ * Deprecated: 1.1.2: Use gupnp_service_proxy_action_new_from_list() and gupnp_service_proxy_call_action()
+ *
  **/
 gboolean
 gupnp_service_proxy_send_action_list (GUPnPServiceProxy *proxy,
@@ -718,42 +509,41 @@ gupnp_service_proxy_send_action_list (GUPnPServiceProxy *proxy,
                                       GList            **out_values,
                                       GError           **error)
 {
-        GMainLoop *main_loop;
         GUPnPServiceProxyAction *handle;
+        gboolean result = FALSE;
 
         g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
         g_return_val_if_fail (action, FALSE);
 
-        main_loop = g_main_loop_new (g_main_context_get_thread_default (),
-                                     TRUE);
+        handle = gupnp_service_proxy_action_new_from_list (action, in_names, in_values);
 
-        handle = gupnp_service_proxy_begin_action_list (proxy,
-                                                        action,
-                                                        in_names,
-                                                        in_values,
-                                                        stop_main_loop,
-                                                        main_loop);
-        if (!handle) {
-                g_main_loop_unref (main_loop);
+        if (gupnp_service_proxy_call_action (proxy, handle, NULL, error) != NULL) {
+                result = FALSE;
 
-                return FALSE;
+                goto out;
         }
 
-        /* Loop till we get a reply (or time out) */
-        if (g_main_loop_is_running (main_loop))
-                g_main_loop_run (main_loop);
+        result = gupnp_service_proxy_action_get_result_list (handle,
+                                                             out_names,
+                                                             out_types,
+                                                             out_values,
+                                                             error);
+out:
+        gupnp_service_proxy_action_unref (handle);
 
-        g_main_loop_unref (main_loop);
+        return result;
+}
 
-        if (!gupnp_service_proxy_end_action_list (proxy,
-                                                  handle,
-                                                  out_names,
-                                                  out_types,
-                                                  out_values,
-                                                  error))
-                return FALSE;
+static void
+on_legacy_async_callback (GObject *source, GAsyncResult *res, gpointer user_data)
+{
+        GError *error = NULL;
+        GUPnPServiceProxyAction *action;
 
-        return TRUE;
+        gupnp_service_proxy_call_action_finish (GUPNP_SERVICE_PROXY (source), res, &error);
+        action = (GUPnPServiceProxyAction *) user_data;
+        if (action->callback != NULL)
+                action->callback (action->proxy, action, action->user_data);
 }
 
 /**
@@ -774,6 +564,8 @@ gupnp_service_proxy_send_action_list (GUPnPServiceProxy *proxy,
  * Returns: (transfer none): A #GUPnPServiceProxyAction handle. This will be freed when
  * gupnp_service_proxy_cancel_action() or
  * gupnp_service_proxy_end_action_valist().
+ *
+ * Deprecated: 1.1.1: Use gupnp_service_proxy_action_new() and gupnp_service_proxy_action_call_action_async()
  **/
 GUPnPServiceProxyAction *
 gupnp_service_proxy_begin_action (GUPnPServiceProxy              *proxy,
@@ -782,50 +574,78 @@ gupnp_service_proxy_begin_action (GUPnPServiceProxy              *proxy,
                                   gpointer                        user_data,
                                   ...)
 {
-        va_list var_args;
         GUPnPServiceProxyAction *ret;
+        GList *in_names = NULL;
+        GList *in_values = NULL;
+        va_list var_args;
 
         va_start (var_args, user_data);
-        ret = gupnp_service_proxy_begin_action_valist (proxy,
-                                                       action,
-                                                       callback,
-                                                       user_data,
-                                                       var_args);
+        VAR_ARGS_TO_IN_LIST (var_args, in_names, in_values);
         va_end (var_args);
 
-        return ret;
+        ret = gupnp_service_proxy_action_new_from_list (action, in_names, in_values);
+        g_list_free_full (in_names, g_free);
+        g_list_free_full (in_values, gvalue_free);
+
+        ret->callback = callback;
+        ret->user_data = user_data;
+
+        return gupnp_service_proxy_call_action_async (proxy,
+                                                      ret,
+                                                      NULL,
+                                                      on_legacy_async_callback,
+                                                      ret);
+}
+
+static void
+on_action_cancelled (GCancellable *cancellable, gpointer user_data)
+{
+        GUPnPServiceProxyAction *action = (GUPnPServiceProxyAction *) user_data;
+
+        GUPnPContext *context;
+        SoupSession *session;
+
+        if (action->msg != NULL) {
+                context = gupnp_service_info_get_context
+                                        (GUPNP_SERVICE_INFO (action->proxy));
+                session = gupnp_context_get_session (context);
+
+                soup_session_cancel_message (session,
+                                             action->msg,
+                                             SOUP_STATUS_CANCELLED);
+                g_cancellable_disconnect (action->cancellable,
+                                          action->cancellable_connection_id);
+                action->cancellable_connection_id = 0;
+        }
 }
 
 /* Begins a basic action message */
-static GUPnPServiceProxyAction *
-begin_action_msg (GUPnPServiceProxy              *proxy,
-                  const char                     *action,
-                  GUPnPServiceProxyActionCallback callback,
-                  gpointer                        user_data)
+static void
+prepare_action_msg (GUPnPServiceProxy              *proxy,
+                    GUPnPServiceProxyAction        *ret,
+                    GCancellable                   *cancellable)
 {
-        GUPnPServiceProxyAction *ret;
         GUPnPServiceProxyPrivate *priv;
         char *control_url, *full_action;
         const char *service_type;
 
         priv = gupnp_service_proxy_get_instance_private (proxy);
 
-        /* Create action structure */
-        ret = g_slice_new (GUPnPServiceProxyAction);
-        ret->ref_count = 1;
-
         ret->proxy = proxy;
         g_object_add_weak_pointer (G_OBJECT (proxy), (gpointer *)&(ret->proxy));
 
-        ret->callback  = callback;
-        ret->user_data = user_data;
-
-        ret->msg = NULL;
-
-        ret->error = NULL;
-
         priv->pending_actions = g_list_prepend (priv->pending_actions, ret);
 
+        if (cancellable != NULL) {
+                ret->cancellable = g_object_ref (cancellable);
+        } else {
+                ret->cancellable = g_cancellable_new ();
+        }
+        ret->cancellable_connection_id = g_cancellable_connect (ret->cancellable,
+                                                                G_CALLBACK (on_action_cancelled),
+                                                                ret,
+                                                                NULL);
+
         /* Make sure we have a service type */
         service_type = gupnp_service_info_get_service_type
                                         (GUPNP_SERVICE_INFO (proxy));
@@ -834,7 +654,7 @@ begin_action_msg (GUPnPServiceProxy              *proxy,
                                           GUPNP_SERVER_ERROR_OTHER,
                                           "No service type defined");
 
-                return ret;
+                return;
         }
 
         /* Create message */
@@ -861,11 +681,11 @@ begin_action_msg (GUPnPServiceProxy              *proxy,
                                           GUPNP_SERVER_ERROR_INVALID_URL,
                                           "No valid control URL defined");
 
-                return ret;
+                return;
         }
 
         /* Specify action */
-        full_action = g_strdup_printf ("\"%s#%s\"", service_type, action);
+        full_action = g_strdup_printf ("\"%s#%s\"", service_type, ret->name);
         soup_message_headers_append (ret->msg->request_headers,
                                      "SOAPAction",
                                      full_action);
@@ -876,74 +696,122 @@ begin_action_msg (GUPnPServiceProxy              *proxy,
 
         /* Accept gzip encoding */
         soup_message_headers_append (ret->msg->request_headers,
-                                    "Accept-Encoding", "gzip");
-
-        /* Set up envelope */
-        ret->msg_str = xml_util_new_string ();
-
-        g_string_append (ret->msg_str,
-                         "<?xml version=\"1.0\"?>"
-                         "<s:Envelope xmlns:s="
-                                "\"http://schemas.xmlsoap.org/soap/envelope/\"; "
-                          "s:encodingStyle="
-                                "\"http://schemas.xmlsoap.org/soap/encoding/\";>"
-                         "<s:Body>");
-
-        g_string_append (ret->msg_str, "<u:");
-        g_string_append (ret->msg_str, action);
-        g_string_append (ret->msg_str, " xmlns:u=\"");
-        g_string_append (ret->msg_str, service_type);
-        g_string_append (ret->msg_str, "\">");
+                                     "Accept-Encoding", "gzip");
+
+        g_string_insert (ret->msg_str, ret->header_pos, "<u:");
+        ret->header_pos += strlen("<u:");
+        g_string_insert (ret->msg_str, ret->header_pos, ret->name);
+        ret->header_pos += strlen (ret->name);
+        g_string_insert (ret->msg_str, ret->header_pos, " xmlns:u=\"");
+        ret->header_pos += strlen(" xmlns:u=\"");
+        g_string_insert (ret->msg_str, ret->header_pos, service_type);
+        ret->header_pos += strlen (service_type);
+        g_string_insert (ret->msg_str, ret->header_pos, "\">");
+
+        soup_message_set_request (ret->msg,
+                                  "text/xml; charset=\"utf-8\"",
+                                  SOUP_MEMORY_TAKE,
+                                  ret->msg_str->str,
+                                  ret->msg_str->len);
 
-        return ret;
+        g_string_free (ret->msg_str, FALSE);
 }
 
-/* Received response to action message */
 static void
-action_got_response (SoupSession             *session,
-                     SoupMessage             *msg,
-                     GUPnPServiceProxyAction *action)
+update_message_after_not_allowed (SoupMessage *msg)
 {
         const char *full_action;
 
-        switch (msg->status_code) {
-        case SOUP_STATUS_CANCELLED:
-                /* Silently return */
-                break;
-
-        case SOUP_STATUS_METHOD_NOT_ALLOWED:
-                /* Retry with M-POST */
-                msg->method = "M-POST";
+        /* Retry with M-POST */
+        msg->method = "M-POST";
 
-                soup_message_headers_append
+        soup_message_headers_append
                         (msg->request_headers,
                          "Man",
                          "\"http://schemas.xmlsoap.org/soap/envelope/\";; ns=s");
 
-                /* Rename "SOAPAction" to "s-SOAPAction" */
-                full_action = soup_message_headers_get_one
+        /* Rename "SOAPAction" to "s-SOAPAction" */
+        full_action = soup_message_headers_get_one
                         (msg->request_headers,
                          "SOAPAction");
-                soup_message_headers_append (msg->request_headers,
-                                             "s-SOAPAction",
-                                             full_action);
-                soup_message_headers_remove (msg->request_headers,
-                                            "SOAPAction");
+        soup_message_headers_append (msg->request_headers,
+                                     "s-SOAPAction",
+                                     full_action);
+        soup_message_headers_remove (msg->request_headers,
+                                     "SOAPAction");
+}
+
+static void
+action_task_got_response (SoupSession *session,
+                          SoupMessage *msg,
+                          gpointer    user_data)
+{
+        GTask *task = G_TASK (user_data);
+        GUPnPServiceProxyAction *action = (GUPnPServiceProxyAction *) g_task_get_task_data (task);
+
+        switch (msg->status_code) {
+        case SOUP_STATUS_CANCELLED:
+                if (action->cancellable != NULL && action->cancellable_connection_id != 0) {
+                        g_cancellable_disconnect (action->cancellable,
+                                        action->cancellable_connection_id);
+                        action->cancellable_connection_id = 0;
+                }
+
+                g_task_return_new_error (task,
+                                         G_IO_ERROR,
+                                         G_IO_ERROR_CANCELLED,
+                                         "Action message was cancelled");
+                g_object_unref (task);
+                break;
 
+        case SOUP_STATUS_METHOD_NOT_ALLOWED:
                 /* And re-queue */
+                update_message_after_not_allowed (msg);
                 soup_session_requeue_message (session, msg);
 
                 break;
 
         default:
-                /* Success: Call callback */
-                action->callback (action->proxy, action, action->user_data);
+                if (action->cancellable != NULL && action->cancellable_connection_id != 0) {
+                        g_cancellable_disconnect (action->cancellable,
+                                        action->cancellable_connection_id);
+                        action->cancellable_connection_id = 0;
+                }
+
+                g_task_return_pointer (task,
+                                       g_task_get_task_data (task),
+                                       (GDestroyNotify) gupnp_service_proxy_action_unref);
+
+                g_object_unref (task);
 
                 break;
         }
 }
 
+static void
+gupnp_service_proxy_action_queue_task (GTask *task)
+{
+        GUPnPContext *context;
+        SoupSession *session;
+        GUPnPServiceProxyAction *action = g_task_get_task_data (task);
+
+        /* We need to keep our own reference to the message as well,
+         * in order for send_action() to work. */
+        g_object_ref (action->msg);
+
+        /* Send the message */
+        context = gupnp_service_info_get_context
+                                (GUPNP_SERVICE_INFO (action->proxy));
+        session = gupnp_context_get_session (context);
+
+        soup_session_queue_message (session,
+                                    action->msg,
+                                    (SoupSessionCallback) action_task_got_response,
+                                    task);
+}
+
 /* Finishes an action message and sends it off */
+#if 0
 static void
 finish_action_msg (GUPnPServiceProxyAction *action,
                    const char              *action_name)
@@ -982,28 +850,7 @@ finish_action_msg (GUPnPServiceProxyAction *action,
                                     (SoupSessionCallback) action_got_response,
                                     action);
 }
-
-/* Writes a parameter name and GValue pair to @msg */
-static void
-write_in_parameter (const char *arg_name,
-                    GValue     *value,
-                    GString    *msg_str)
-{
-        /* Write parameter pair */
-        xml_util_start_element (msg_str, arg_name);
-        gvalue_util_value_append_to_xml_string (value, msg_str);
-        xml_util_end_element (msg_str, arg_name);
-}
-
-static gboolean
-action_error_idle_cb (gpointer user_data)
-{
-        GUPnPServiceProxyAction *action = (GUPnPServiceProxyAction *) user_data;
-
-        action->callback (action->proxy, action, action->user_data);
-
-        return FALSE;
-}
+#endif
 
 /**
  * gupnp_service_proxy_begin_action_valist:
@@ -1020,6 +867,8 @@ action_error_idle_cb (gpointer user_data)
  * Returns: (transfer none): A #GUPnPServiceProxyAction handle. This will
  * be freed when calling gupnp_service_proxy_cancel_action() or
  * gupnp_service_proxy_end_action_valist().
+ *
+ * Deprecated: 1.1.1: Use ove of
  **/
 GUPnPServiceProxyAction *
 gupnp_service_proxy_begin_action_valist
@@ -1030,23 +879,20 @@ gupnp_service_proxy_begin_action_valist
                                     va_list                         var_args)
 {
         GUPnPServiceProxyAction *ret;
-        GList *in_names = NULL, *in_values = NULL;
-
-        g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), NULL);
-        g_return_val_if_fail (action, NULL);
-        g_return_val_if_fail (callback, NULL);
+        GList *in_names = NULL;
+        GList *in_values = NULL;
 
         VAR_ARGS_TO_IN_LIST (var_args, in_names, in_values);
-        ret = gupnp_service_proxy_begin_action_list (proxy,
-                                                     action,
-                                                     in_names,
-                                                     in_values,
-                                                     callback,
-                                                     user_data);
+
+        ret = gupnp_service_proxy_action_new_from_list (action, in_names, in_values);
         g_list_free_full (in_names, g_free);
-        g_list_free_full (in_values, value_free);
+        g_list_free_full (in_values, gvalue_free);
+
+        ret->callback = callback;
+        ret->user_data = user_data;
+
+        return gupnp_service_proxy_call_action_async (proxy, ret, NULL, on_legacy_async_callback, ret);
 
-        return ret;
 }
 
 /**
@@ -1080,42 +926,13 @@ gupnp_service_proxy_begin_action_list
                                     gpointer                         user_data)
 {
         GUPnPServiceProxyAction *ret;
-        GList *names;
-        GList *values;
-
-        g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), NULL);
-        g_return_val_if_fail (action, NULL);
-        g_return_val_if_fail (callback, NULL);
-        g_return_val_if_fail (g_list_length (in_names) ==
-                              g_list_length (in_values),
-                              NULL);
-
-        /* Create message */
-        ret = begin_action_msg (proxy, action, callback, user_data);
-
-        if (ret->error) {
-                g_idle_add (action_error_idle_cb, ret);
-
-                return ret;
-        }
-
-        /* Arguments */
-        values = in_values;
-
-        for (names = in_names; names; names=names->next) {
-                GValue* val = values->data;
 
-                write_in_parameter (names->data,
-                                    val,
-                                    ret->msg_str);
+        ret = gupnp_service_proxy_action_new_from_list (action, in_names, in_values);
 
-                values = values->next;
-        }
-
-        /* Finish and send off */
-        finish_action_msg (ret, action);
+        ret->callback = callback;
+        ret->user_data = user_data;
 
-        return ret;
+        return gupnp_service_proxy_call_action_async (proxy, ret, NULL, on_legacy_async_callback, ret);
 }
 
 /**
@@ -1142,166 +959,25 @@ gupnp_service_proxy_end_action (GUPnPServiceProxy       *proxy,
         va_list var_args;
         gboolean ret;
 
-        va_start (var_args, error);
-        ret = gupnp_service_proxy_end_action_valist (proxy,
-                                                     action,
-                                                     error,
-                                                     var_args);
-        va_end (var_args);
-
-        return ret;
-}
-
-/* Checks an action response for errors and returns the parsed
- * xmlDoc object. */
-static xmlDoc *
-check_action_response (G_GNUC_UNUSED GUPnPServiceProxy *proxy,
-                       GUPnPServiceProxyAction         *action,
-                       xmlNode                        **params,
-                       GError                         **error)
-{
-        xmlDoc *response;
-        int code;
-
-        /* Check for errors */
-        switch (action->msg->status_code) {
-        case SOUP_STATUS_OK:
-        case SOUP_STATUS_INTERNAL_SERVER_ERROR:
-                break;
-        default:
-                _gupnp_error_set_server_error (error, action->msg);
-
-                return NULL;
-        }
-
-        /* Parse response */
-        response = xmlRecoverMemory (action->msg->response_body->data,
-                                     action->msg->response_body->length);
-
-        if (!response) {
-                if (action->msg->status_code == SOUP_STATUS_OK) {
-                        g_set_error (error,
-                                     GUPNP_SERVER_ERROR,
-                                     GUPNP_SERVER_ERROR_INVALID_RESPONSE,
-                                     "Could not parse SOAP response");
-                } else {
-                        g_set_error_literal
-                                    (error,
-                                     GUPNP_SERVER_ERROR,
-                                     GUPNP_SERVER_ERROR_INTERNAL_SERVER_ERROR,
-                                     action->msg->reason_phrase);
-                }
-
-                return NULL;
-        }
-
-        /* Get parameter list */
-        *params = xml_util_get_element ((xmlNode *) response,
-                                        "Envelope",
-                                        NULL);
-        if (*params != NULL)
-                *params = xml_util_real_node ((*params)->children);
-
-        if (*params != NULL) {
-                if (strcmp ((const char *) (*params)->name, "Header") == 0)
-                        *params = xml_util_real_node ((*params)->next);
-
-                if (*params != NULL)
-                        if (strcmp ((const char *) (*params)->name, "Body") != 0)
-                                *params = NULL;
-        }
-
-        if (*params != NULL)
-                *params = xml_util_real_node ((*params)->children);
-
-        if (*params == NULL) {
-                g_set_error (error,
-                             GUPNP_SERVER_ERROR,
-                             GUPNP_SERVER_ERROR_INVALID_RESPONSE,
-                             "Invalid Envelope");
-
-                xmlFreeDoc (response);
-
-                return NULL;
-        }
-
-        /* Check whether we have a Fault */
-        if (action->msg->status_code == SOUP_STATUS_INTERNAL_SERVER_ERROR) {
-                xmlNode *param;
-                char *desc;
-
-                param = xml_util_get_element (*params,
-                                              "detail",
-                                              "UPnPError",
-                                              NULL);
-
-                if (!param) {
-                        g_set_error (error,
-                                     GUPNP_SERVER_ERROR,
-                                     GUPNP_SERVER_ERROR_INVALID_RESPONSE,
-                                     "Invalid Fault");
-
-                        xmlFreeDoc (response);
-
-                        return NULL;
-                }
-
-                /* Code */
-                code = xml_util_get_child_element_content_int
-                                        (param, "errorCode");
-                if (code == -1) {
-                        g_set_error (error,
-                                     GUPNP_SERVER_ERROR,
-                                     GUPNP_SERVER_ERROR_INVALID_RESPONSE,
-                                     "Invalid Fault");
-
-                        xmlFreeDoc (response);
-
-                        return NULL;
-                }
-
-                /* Description */
-                desc = xml_util_get_child_element_content_glib
-                                        (param, "errorDescription");
-                if (desc == NULL)
-                        desc = g_strdup (action->msg->reason_phrase);
-
-                g_set_error_literal (error,
-                                     GUPNP_CONTROL_ERROR,
-                                     code,
-                                     desc);
-
-                g_free (desc);
+        g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
+        g_return_val_if_fail (action, FALSE);
+        g_return_val_if_fail (proxy == action->proxy, FALSE);
 
-                xmlFreeDoc (response);
+        if (action->error) {
+                g_propagate_error (error, action->error);
+                gupnp_service_proxy_action_unref (action);
 
-                return NULL;
+                return FALSE;
         }
 
-        return response;
-}
-
-/* Reads a value into the parameter name and initialised GValue pair
- * from @response */
-static void
-read_out_parameter (const char *arg_name,
-                    GValue     *value,
-                    xmlNode    *params)
-{
-        xmlNode *param;
 
-        /* Try to find a matching parameter in the response*/
-        param = xml_util_get_element (params,
-                                      arg_name,
-                                      NULL);
-        if (!param) {
-                g_warning ("Could not find variable \"%s\" in response",
-                           arg_name);
+        va_start (var_args, error);
+        ret = gupnp_service_proxy_action_get_result_valist (action, error, var_args);
+        va_end (var_args);
 
-                return;
-        }
+        gupnp_service_proxy_action_unref (action);
 
-        gvalue_util_set_value_from_xml_node (value, param);
+        return ret;
 }
 
 /**
@@ -1323,34 +999,23 @@ gupnp_service_proxy_end_action_valist (GUPnPServiceProxy       *proxy,
                                        GError                 **error,
                                        va_list                  var_args)
 {
-        GHashTable *out_hash;
-        va_list var_args_copy;
         gboolean result;
-        GError *local_error;
 
         g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
         g_return_val_if_fail (action, FALSE);
         g_return_val_if_fail (proxy == action->proxy, FALSE);
 
-        out_hash = g_hash_table_new_full (g_str_hash,
-                                          g_str_equal,
-                                          g_free,
-                                          value_free);
-        G_VA_COPY (var_args_copy, var_args);
-        VAR_ARGS_TO_OUT_HASH_TABLE (var_args, out_hash);
-        local_error = NULL;
-        result = gupnp_service_proxy_end_action_hash (proxy,
-                                                      action,
-                                                      out_hash,
-                                                      &local_error);
+        if (action->error) {
+                g_propagate_error (error, action->error);
+                gupnp_service_proxy_action_unref (action);
 
-        if (local_error == NULL) {
-                OUT_HASH_TABLE_TO_VAR_ARGS (out_hash, var_args_copy);
-        } else {
-                g_propagate_error (error, local_error);
+                return FALSE;
         }
-        va_end (var_args_copy);
-        g_hash_table_unref (out_hash);
+
+        result = gupnp_service_proxy_action_get_result_valist (action,
+                                                               error,
+                                                               var_args);
+        gupnp_service_proxy_action_unref (action);
 
         return result;
 }
@@ -1383,13 +1048,7 @@ gupnp_service_proxy_end_action_list (GUPnPServiceProxy       *proxy,
                                      GList                  **out_values,
                                      GError                 **error)
 {
-        xmlDoc *response;
-        xmlNode *params;
-        GList *names;
-        GList *types;
-        GList *out_values_list;
-
-        out_values_list = NULL;
+        gboolean result;
 
         g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
         g_return_val_if_fail (action, FALSE);
@@ -1403,37 +1062,14 @@ gupnp_service_proxy_end_action_list (GUPnPServiceProxy       *proxy,
                 return FALSE;
         }
 
-        /* Check response for errors and do initial parsing */
-        response = check_action_response (proxy, action, &params, error);
-        if (response == NULL) {
-                gupnp_service_proxy_action_unref (action);
-
-                return FALSE;
-        }
-
-        /* Read arguments */
-        types = out_types;
-        for (names = out_names; names; names=names->next) {
-                GValue *val;
-
-                val = g_slice_new0 (GValue);
-                g_value_init (val, (GType) types->data);
-
-                read_out_parameter (names->data, val, params);
-
-                out_values_list = g_list_append (out_values_list, val);
-
-                types = types->next;
-        }
-
-        *out_values = out_values_list;
-
-        /* Cleanup */
+        result = gupnp_service_proxy_action_get_result_list (action,
+                                                             out_names,
+                                                             out_types,
+                                                             out_values,
+                                                             error);
         gupnp_service_proxy_action_unref (action);
 
-        xmlFreeDoc (response);
-
-        return TRUE;
+        return result;
 }
 
 /**
@@ -1457,8 +1093,7 @@ gupnp_service_proxy_end_action_hash
                                     GHashTable                     *hash,
                                     GError                        **error)
 {
-        xmlDoc *response;
-        xmlNode *params;
+        gboolean result;
 
         g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), FALSE);
         g_return_val_if_fail (action, FALSE);
@@ -1472,23 +1107,13 @@ gupnp_service_proxy_end_action_hash
                 return FALSE;
         }
 
-        /* Check response for errors and do initial parsing */
-        response = check_action_response (proxy, action, &params, error);
-        if (response == NULL) {
-                gupnp_service_proxy_action_unref (action);
-
-                return FALSE;
-        }
-
-        /* Read arguments */
-        g_hash_table_foreach (hash, (GHFunc) read_out_parameter, params);
 
-        /* Cleanup */
+        result = gupnp_service_proxy_action_get_result_hash (action,
+                                                             hash,
+                                                             error);
         gupnp_service_proxy_action_unref (action);
 
-        xmlFreeDoc (response);
-
-        return TRUE;
+        return result;
 }
 
 /**
@@ -1497,31 +1122,22 @@ gupnp_service_proxy_end_action_hash
  * @action: A #GUPnPServiceProxyAction handle
  *
  * Cancels @action, freeing the @action handle.
+ *
+ * Deprecated: 1.1.2: Use the #GCancellable passed to
+ * gupnp_service_proxy_call_action_async() or gupnp_service_proxy_call_action()
  **/
 void
 gupnp_service_proxy_cancel_action (GUPnPServiceProxy       *proxy,
                                    GUPnPServiceProxyAction *action)
 {
-        GUPnPContext *context;
-        SoupSession *session;
-
         g_return_if_fail (GUPNP_IS_SERVICE_PROXY (proxy));
         g_return_if_fail (action);
         g_return_if_fail (proxy == action->proxy);
 
-        if (action->msg != NULL) {
-                context = gupnp_service_info_get_context
-                                        (GUPNP_SERVICE_INFO (proxy));
-                session = gupnp_context_get_session (context);
-
-                soup_session_cancel_message (session,
-                                             action->msg,
-                                             SOUP_STATUS_CANCELLED);
+        if (action->cancellable != NULL) {
+                g_cancellable_cancel (action->cancellable);
         }
 
-        if (action->error != NULL)
-                g_error_free (action->error);
-
         gupnp_service_proxy_action_unref (action);
 }
 
@@ -2466,3 +2082,93 @@ gupnp_service_proxy_get_subscribed (GUPnPServiceProxy *proxy)
 
         return priv->subscribed;
 }
+
+/**
+ * gupnp_service_proxy_call_action_async:
+ */
+GUPnPServiceProxyAction *
+gupnp_service_proxy_call_action_async (GUPnPServiceProxy       *proxy,
+                                       GUPnPServiceProxyAction *action,
+                                       GCancellable            *cancellable,
+                                       GAsyncReadyCallback     callback,
+                                       gpointer                user_data)
+{
+        GTask *task;
+
+        g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), NULL);
+
+        task = g_task_new (proxy, cancellable, callback, user_data);
+        g_task_set_task_data (task,
+                              gupnp_service_proxy_action_ref (action),
+                              (GDestroyNotify) gupnp_service_proxy_action_unref);
+
+        prepare_action_msg (proxy, action, cancellable);
+
+        if (action->error != NULL) {
+                g_task_return_error (task, g_error_copy (action->error));
+                g_object_unref (task);
+
+                return NULL;
+        }
+
+        gupnp_service_proxy_action_queue_task (task);
+
+        return action;
+}
+
+/**
+ * gupnp_service_proxy_call_action_finish:
+ */
+GUPnPServiceProxyAction *
+gupnp_service_proxy_call_action_finish (GUPnPServiceProxy *proxy,
+                                        GAsyncResult      *result,
+                                        GError           **error)
+{
+        g_return_val_if_fail (g_task_is_valid (G_TASK (result), proxy), NULL);
+
+        return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+GUPnPServiceProxyAction *
+gupnp_service_proxy_call_action (GUPnPServiceProxy       *proxy,
+                                 GUPnPServiceProxyAction *action,
+                                 GCancellable            *cancellable,
+                                 GError                 **error)
+{
+        GUPnPContext *context = NULL;
+        SoupSession *session = NULL;
+
+        g_return_val_if_fail (GUPNP_IS_SERVICE_PROXY (proxy), NULL);
+
+        prepare_action_msg (proxy, action, cancellable);
+
+        if (action->error != NULL) {
+                g_propagate_error (error, g_error_copy (action->error));
+
+                return NULL;
+        }
+
+        context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (proxy));
+        session = gupnp_context_get_session (context);
+        soup_session_send_message (session, action->msg);
+
+        /* If not allowed, try again */
+        if (action->msg->status_code == SOUP_STATUS_METHOD_NOT_ALLOWED) {
+                update_message_after_not_allowed (action->msg);
+                soup_session_send_message (session, action->msg);
+        }
+
+        /* prepare_action_msg has queued the message, so remove it */
+        gupnp_service_proxy_remove_action (proxy, action);
+
+        if (action->msg->status_code == SOUP_STATUS_CANCELLED) {
+                action->error = g_error_new (G_IO_ERROR,
+                                             G_IO_ERROR_CANCELLED,
+                                             "Action message was cancelled");
+                g_propagate_error (error, g_error_copy (action->error));
+
+                return NULL;
+        }
+
+        return action;
+}
diff --git a/libgupnp/gupnp-service-proxy.h b/libgupnp/gupnp-service-proxy.h
index d61ac69..7400213 100644
--- a/libgupnp/gupnp-service-proxy.h
+++ b/libgupnp/gupnp-service-proxy.h
@@ -89,20 +89,20 @@ typedef void (* GUPnPServiceProxyNotifyCallback) (GUPnPServiceProxy *proxy,
                                                   GValue            *value,
                                                   gpointer           user_data);
 
-gboolean
+G_GNUC_DEPRECATED gboolean
 gupnp_service_proxy_send_action    (GUPnPServiceProxy              *proxy,
                                     const char                     *action,
                                     GError                        **error,
                                     ...) G_GNUC_NULL_TERMINATED;
 
-gboolean
+G_GNUC_DEPRECATED gboolean
 gupnp_service_proxy_send_action_valist
                                    (GUPnPServiceProxy              *proxy,
                                     const char                     *action,
                                     GError                        **error,
                                     va_list                         var_args);
 
-gboolean
+G_GNUC_DEPRECATED gboolean
 gupnp_service_proxy_send_action_list (GUPnPServiceProxy *proxy,
                                       const char        *action,
                                       GList             *in_names,
@@ -111,14 +111,14 @@ gupnp_service_proxy_send_action_list (GUPnPServiceProxy *proxy,
                                       GList             *out_types,
                                       GList            **out_values,
                                       GError           **error);
-GUPnPServiceProxyAction *
+G_GNUC_DEPRECATED GUPnPServiceProxyAction *
 gupnp_service_proxy_begin_action   (GUPnPServiceProxy              *proxy,
                                     const char                     *action,
                                     GUPnPServiceProxyActionCallback callback,
                                     gpointer                        user_data,
                                     ...) G_GNUC_NULL_TERMINATED;
 
-GUPnPServiceProxyAction *
+G_GNUC_DEPRECATED GUPnPServiceProxyAction *
 gupnp_service_proxy_begin_action_valist
                                    (GUPnPServiceProxy              *proxy,
                                     const char                     *action,
@@ -126,7 +126,7 @@ gupnp_service_proxy_begin_action_valist
                                     gpointer                        user_data,
                                     va_list                         var_args);
 
-GUPnPServiceProxyAction *
+G_GNUC_DEPRECATED GUPnPServiceProxyAction *
 gupnp_service_proxy_begin_action_list
                                    (GUPnPServiceProxy              *proxy,
                                     const char                     *action,
@@ -135,20 +135,20 @@ gupnp_service_proxy_begin_action_list
                                     GUPnPServiceProxyActionCallback callback,
                                     gpointer                        user_data);
 
-gboolean
+G_GNUC_DEPRECATED gboolean
 gupnp_service_proxy_end_action     (GUPnPServiceProxy              *proxy,
                                     GUPnPServiceProxyAction        *action,
                                     GError                        **error,
                                     ...) G_GNUC_NULL_TERMINATED;
 
-gboolean
+G_GNUC_DEPRECATED gboolean
 gupnp_service_proxy_end_action_valist
                                    (GUPnPServiceProxy              *proxy,
                                     GUPnPServiceProxyAction        *action,
                                     GError                        **error,
                                     va_list                         var_args);
 
-gboolean
+G_GNUC_DEPRECATED gboolean
 gupnp_service_proxy_end_action_list
                                   (GUPnPServiceProxy       *proxy,
                                    GUPnPServiceProxyAction *action,
@@ -157,14 +157,14 @@ gupnp_service_proxy_end_action_list
                                    GList                  **out_values,
                                    GError                  **error);
 
-gboolean
+G_GNUC_DEPRECATED gboolean
 gupnp_service_proxy_end_action_hash
                                    (GUPnPServiceProxy              *proxy,
                                     GUPnPServiceProxyAction        *action,
                                     GHashTable                     *hash,
                                     GError                        **error);
 
-void
+G_GNUC_DEPRECATED void
 gupnp_service_proxy_cancel_action  (GUPnPServiceProxy              *proxy,
                                     GUPnPServiceProxyAction        *action);
 
@@ -208,6 +208,58 @@ gupnp_service_proxy_set_subscribed (GUPnPServiceProxy              *proxy,
 gboolean
 gupnp_service_proxy_get_subscribed (GUPnPServiceProxy              *proxy);
 
+/* New action API */
+
+GUPnPServiceProxyAction *
+gupnp_service_proxy_action_new (const char *action,
+                                ...) G_GNUC_NULL_TERMINATED;
+
+GUPnPServiceProxyAction *
+gupnp_service_proxy_action_new_from_list (const char *action,
+                                          GList      *in_names,
+                                          GList      *in_values);
+
+GUPnPServiceProxyAction *
+gupnp_service_proxy_action_ref (GUPnPServiceProxyAction *action);
+
+void
+gupnp_service_proxy_action_unref (GUPnPServiceProxyAction *action);
+
+gboolean
+gupnp_service_proxy_action_get_result (GUPnPServiceProxyAction *action,
+                                       GError                 **error,
+                                       ...) G_GNUC_NULL_TERMINATED;
+
+gboolean
+gupnp_service_proxy_action_get_result_list (GUPnPServiceProxyAction *action,
+                                            GList                   *out_names,
+                                            GList                   *out_types,
+                                            GList                  **out_values,
+                                            GError                 **error);
+gboolean
+gupnp_service_proxy_action_get_result_hash (GUPnPServiceProxyAction *action,
+                                            GHashTable              *out_hash,
+                                            GError                 **error);
+
+
+GUPnPServiceProxyAction *
+gupnp_service_proxy_call_action_async (GUPnPServiceProxy       *proxy,
+                                       GUPnPServiceProxyAction *action,
+                                       GCancellable            *cancellable,
+                                       GAsyncReadyCallback     callback,
+                                       gpointer                user_data);
+
+
+GUPnPServiceProxyAction *
+gupnp_service_proxy_call_action_finish (GUPnPServiceProxy *proxy,
+                                        GAsyncResult      *result,
+                                        GError           **error);
+
+GUPnPServiceProxyAction *
+gupnp_service_proxy_call_action (GUPnPServiceProxy       *proxy,
+                                 GUPnPServiceProxyAction *action,
+                                 GCancellable            *cancellable,
+                                 GError                 **error);
 
 G_END_DECLS
 
diff --git a/libgupnp/gvalue-util.c b/libgupnp/gvalue-util.c
index c5a2912..1682806 100644
--- a/libgupnp/gvalue-util.c
+++ b/libgupnp/gvalue-util.c
@@ -264,3 +264,14 @@ gvalue_util_value_append_to_xml_string (const GValue *value,
                 }
         }
 }
+
+/* GDestroyNotify for GHashTable holding GValues.
+ */
+void
+gvalue_free (gpointer data)
+{
+  GValue *value = (GValue *) data;
+
+  g_value_unset (value);
+  g_free (value);
+}
diff --git a/libgupnp/gvalue-util.h b/libgupnp/gvalue-util.h
index 250bb35..eb5e707 100644
--- a/libgupnp/gvalue-util.h
+++ b/libgupnp/gvalue-util.h
@@ -37,4 +37,7 @@ G_GNUC_INTERNAL gboolean
 gvalue_util_value_append_to_xml_string (const GValue *value,
                                         GString      *str);
 
+G_GNUC_INTERNAL void
+gvalue_free                            (gpointer data);
+
 #endif /* GUPNP_GVALUE_UTIL_H */
diff --git a/libgupnp/meson.build b/libgupnp/meson.build
index 3dfd28a..b832acb 100644
--- a/libgupnp/meson.build
+++ b/libgupnp/meson.build
@@ -80,6 +80,7 @@ sources = files(
     'gupnp-service-info.c',
     'gupnp-service-introspection.c',
     'gupnp-service-proxy.c',
+    'gupnp-service-proxy-action.c',
     'gupnp-simple-context-manager.c',
     'gupnp-types.c',
     'gupnp-white-list.c',
diff --git a/tests/gtest/test-bugs.c b/tests/gtest/test-bugs.c
index 252109f..54343ef 100644
--- a/tests/gtest/test-bugs.c
+++ b/tests/gtest/test-bugs.c
@@ -243,6 +243,7 @@ test_bgo_696762 (void)
     test_run_loop (data.loop);
     g_assert (data.proxy != NULL);
 
+    G_GNUC_BEGIN_IGNORE_DEPRECATIONS
     gupnp_service_proxy_begin_action (data.proxy,
                                       "Browse",
                                       test_bgo_696762_on_browse,
@@ -254,6 +255,7 @@ test_bgo_696762 (void)
                                       "RequestedCount", G_TYPE_UINT, 0,
                                       "SortCriteria", G_TYPE_STRING, "",
                                       NULL);
+    G_GNUC_END_IGNORE_DEPRECATIONS
 
     test_run_loop (data.loop);
 
diff --git a/tests/test-proxy.c b/tests/test-proxy.c
index 6eeb1dc..77530e7 100644
--- a/tests/test-proxy.c
+++ b/tests/test-proxy.c
@@ -61,6 +61,7 @@ service_proxy_available_cb (G_GNUC_UNUSED GUPnPControlPoint *cp,
         char *result = NULL;
         guint count, total;
         GError *error = NULL;
+        GUPnPServiceProxyAction *action = NULL;
 
         location = gupnp_service_info_get_location (GUPNP_SERVICE_INFO (proxy));
 
@@ -84,9 +85,8 @@ service_proxy_available_cb (G_GNUC_UNUSED GUPnPControlPoint *cp,
         gupnp_service_proxy_set_subscribed (proxy, TRUE);
 
         /* And test action IO */
-        gupnp_service_proxy_send_action (proxy,
+        action = gupnp_service_proxy_action_new (
                                          "Browse",
-                                         &error,
                                          /* IN args */
                                          "ObjectID",
                                                 G_TYPE_STRING,
@@ -106,7 +106,19 @@ service_proxy_available_cb (G_GNUC_UNUSED GUPnPControlPoint *cp,
                                          "SortCriteria",
                                                 G_TYPE_STRING,
                                                 "",
-                                         NULL,
+                                         NULL);
+        gupnp_service_proxy_call_action (proxy, action, NULL, &error);
+
+        if (error) {
+                g_printerr ("Error: %s\n", error->message);
+                g_error_free (error);
+                gupnp_service_proxy_action_unref (action);
+
+                return;
+        }
+
+        gupnp_service_proxy_action_get_result (action,
+                                               &error,
                                          /* OUT args */
                                          "Result",
                                                 G_TYPE_STRING,
@@ -122,6 +134,7 @@ service_proxy_available_cb (G_GNUC_UNUSED GUPnPControlPoint *cp,
         if (error) {
                 g_printerr ("Error: %s\n", error->message);
                 g_error_free (error);
+                gupnp_service_proxy_action_unref (action);
 
                 return;
         }
@@ -131,6 +144,7 @@ service_proxy_available_cb (G_GNUC_UNUSED GUPnPControlPoint *cp,
         g_print ("\tNumberReturned: %u\n", count);
         g_print ("\tTotalMatches:   %u\n", total);
 
+        gupnp_service_proxy_action_unref (action);
         g_free (result);
 }
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]