[gnome-builder/wip/chergert/langserv] jsonrpc-glib: vendor our new jsonrpc-glib library



commit 458a44a8775f8c63ff9cd816c08bf19d9eb5ed8c
Author: Christian Hergert <chergert redhat com>
Date:   Tue Oct 25 15:36:21 2016 -0700

    jsonrpc-glib: vendor our new jsonrpc-glib library
    
    This will eventually become it's own library some day, but for now it can
    just live inside of Builder. Once we can depend on it externally, we can
    drop the vendoring of the library.

 configure.ac                                 |    5 +-
 contrib/Makefile.am                          |    1 +
 contrib/jsonrpc-glib/Makefile.am             |   26 +
 contrib/jsonrpc-glib/jcon.c                  |  672 ++++++++++++++++
 contrib/jsonrpc-glib/jcon.h                  |  130 +++
 contrib/jsonrpc-glib/jsonrpc-client.c        | 1100 ++++++++++++++++++++++++++
 contrib/jsonrpc-glib/jsonrpc-client.h        |   95 +++
 contrib/jsonrpc-glib/jsonrpc-glib.h          |   37 +
 contrib/jsonrpc-glib/jsonrpc-input-stream.c  |  333 ++++++++
 contrib/jsonrpc-glib/jsonrpc-input-stream.h  |   61 ++
 contrib/jsonrpc-glib/jsonrpc-output-stream.c |  305 +++++++
 contrib/jsonrpc-glib/jsonrpc-output-stream.h |   65 ++
 contrib/jsonrpc-glib/jsonrpc-version.h.in    |   98 +++
 libide/Makefile.am                           |   27 +-
 14 files changed, 2941 insertions(+), 14 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 6a75be7..88bcae1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -191,7 +191,6 @@ m4_define([libxml_required_version], [2.9.0])
 m4_define([pangoft2_required_version], [1.38.0])
 m4_define([peas_required_version], [1.18.0])
 m4_define([json_glib_required_version], [1.2.0])
-m4_define([jsonrpc_glib_required_version], [0.1.0])
 
 PKG_CHECK_MODULES(EGG,      [glib-2.0 >= glib_required_version
                              gmodule-2.0 >= glib_required_version
@@ -203,12 +202,12 @@ PKG_CHECK_MODULES(GSTYLE,   [glib-2.0 >= glib_required_version
                              gtk+-3.0 >= gtk_required_version
                              gtksourceview-3.0 >= gtksourceview_required_version])
 PKG_CHECK_MODULES(ICONS,    [gio-2.0 >= glib_required_version])
+PKG_CHECK_MODULES(JSONRPC,  [json-glib-1.0 >= json_glib_required_version])
 PKG_CHECK_MODULES(LIBIDE,   [gio-2.0 >= glib_required_version
                              gio-unix-2.0 >= glib_required_version
                              gtk+-3.0 >= gtk_required_version
                              gtksourceview-3.0 >= gtksourceview_required_version
                              json-glib-1.0 >= json_glib_required_version
-                             jsonrpc-glib-0 >= jsonrpc_glib_required_version
                              libpeas-1.0 >= peas_required_version
                              libxml-2.0 >= libxml_required_version
                              pangoft2 >= pangoft2_required_version])
@@ -512,6 +511,8 @@ AC_CONFIG_FILES([
        contrib/gd/Makefile
        contrib/gstyle/Makefile
        contrib/gstyle/tests/Makefile
+       contrib/jsonrpc-glib/Makefile
+       contrib/jsonrpc-glib/jsonrpc-version.h
        contrib/libeditorconfig/Makefile
        contrib/nautilus/Makefile
        contrib/pnl/Makefile
diff --git a/contrib/Makefile.am b/contrib/Makefile.am
index b7169b0..9d94127 100644
--- a/contrib/Makefile.am
+++ b/contrib/Makefile.am
@@ -2,6 +2,7 @@ SUBDIRS = \
        egg \
        gd \
        libeditorconfig \
+       jsonrpc-glib \
        nautilus \
        pnl \
        rg \
diff --git a/contrib/jsonrpc-glib/Makefile.am b/contrib/jsonrpc-glib/Makefile.am
new file mode 100644
index 0000000..67e931c
--- /dev/null
+++ b/contrib/jsonrpc-glib/Makefile.am
@@ -0,0 +1,26 @@
+noinst_LTLIBRARIES = libjsonrpc-glib.la
+
+libjsonrpc_glib_la_SOURCES = \
+       jcon.c \
+       jcon.h \
+       jsonrpc-client.c \
+       jsonrpc-client.h \
+       jsonrpc-glib.h \
+       jsonrpc-input-stream.c \
+       jsonrpc-input-stream.h \
+       jsonrpc-output-stream.c \
+       jsonrpc-output-stream.h \
+       jsonrpc-version.h \
+       $(NULL)
+
+libjsonrpc_glib_la_CFLAGS = \
+       -DJSONRPC_GLIB_INSIDE \
+       $(DEBUG_CFLAGS) \
+       $(JSONRPC_CFLAGS) \
+       $(OPTIMIZE_CFLAGS) \
+       $(NULL)
+
+libjsonrpc_glib_la_LIBADD = $(JSONRPC_LIBS)
+libjsonrpc_glib_la_LDFLAGS = $(OPTIMIZE_LDFLAGS)
+
+-include $(top_srcdir)/git.mk
diff --git a/contrib/jsonrpc-glib/jcon.c b/contrib/jsonrpc-glib/jcon.c
new file mode 100644
index 0000000..b0d27bc
--- /dev/null
+++ b/contrib/jsonrpc-glib/jcon.c
@@ -0,0 +1,672 @@
+/* jcon.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Copyright 2009-2013 MongoDB, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define G_LOG_DOMAIN "jcon"
+
+#include <stdarg.h>
+#include <string.h>
+
+#include "jcon.h"
+
+#define STACK_DEPTH 50
+
+typedef union
+{
+  const gchar *v_string;
+  gdouble      v_double;
+  JsonObject  *v_object;
+  JsonArray   *v_array;
+  JsonNode    *v_node;
+  gboolean     v_boolean;
+  gint         v_int;
+} JconAppend;
+
+typedef union
+{
+  const gchar  *v_key;
+  const gchar **v_string;
+  gdouble      *v_double;
+  JsonObject  **v_object;
+  JsonArray   **v_array;
+  JsonNode    **v_node;
+  gboolean     *v_boolean;
+  gint         *v_int;
+} JconExtract;
+
+const char *
+jcon_magic  (void)
+{
+  return "THIS_IS_JCON_MAGIC";
+}
+
+const char *
+jcone_magic (void)
+{
+  return "THIS_IS_JCONE_MAGIC";
+}
+
+static JsonNode *
+jcon_append_to_node (JconType    type,
+                     JconAppend *val)
+{
+  JsonNode *node;
+
+  switch (type)
+    {
+    case JCON_TYPE_STRING:
+      node = json_node_new (JSON_NODE_VALUE);
+      json_node_set_string (node, val->v_string);
+      return node;
+
+    case JCON_TYPE_DOUBLE:
+      node = json_node_new (JSON_NODE_VALUE);
+      json_node_set_double (node, val->v_double);
+      return node;
+
+    case JCON_TYPE_BOOLEAN:
+      node = json_node_new (JSON_NODE_VALUE);
+      json_node_set_boolean (node, val->v_boolean);
+      return node;
+
+    case JCON_TYPE_NULL:
+      return json_node_new (JSON_NODE_NULL);
+
+    case JCON_TYPE_INT:
+      node = json_node_new (JSON_NODE_VALUE);
+      json_node_set_int (node, val->v_int);
+      return node;
+
+    case JCON_TYPE_NODE:
+      return json_node_copy (val->v_node);
+
+    case JCON_TYPE_ARRAY:
+      node = json_node_new (JSON_NODE_ARRAY);
+      json_node_set_array (node, val->v_array);
+      return node;
+
+    case JCON_TYPE_OBJECT:
+      node = json_node_new (JSON_NODE_OBJECT);
+      json_node_set_object (node, val->v_object);
+      return node;
+
+    case JCON_TYPE_OBJECT_START:
+    case JCON_TYPE_OBJECT_END:
+    case JCON_TYPE_ARRAY_START:
+    case JCON_TYPE_ARRAY_END:
+    case JCON_TYPE_END:
+    case JCON_TYPE_RAW:
+    default:
+      return NULL;
+    }
+}
+
+static JconType
+jcon_append_tokenize (va_list    *ap,
+                      JconAppend *u)
+{
+  const gchar *mark;
+  JconType type;
+
+  g_assert (ap != NULL);
+  g_assert (u != NULL);
+
+  /* Consumes ap, storing output values into u and returning the type of the
+   * captured token.
+   *
+   * The basic workflow goes like this:
+   *
+   * 1. Look at the current arg.  It will be a gchar *
+   *    a. If it's a NULL, we're done processing.
+   *    b. If it's JCON_MAGIC (a symbol with storage in this module)
+   *       I. The next token is the type
+   *       II. The type specifies how many args to eat and their types
+   *    c. Otherwise it's either recursion related or a raw string
+   *       I. If the first byte is '{', '}', '[', or ']' pass back an
+   *          appropriate recursion token
+   *       II. If not, just call it a v_string token and pass that back
+   */
+
+  memset (u, 0, sizeof *u);
+
+  mark = va_arg (*ap, const gchar *);
+
+  g_assert (mark != JCONE_MAGIC);
+
+  if (mark == NULL)
+    {
+      type = JCON_TYPE_END;
+    }
+  else if (mark == JCON_MAGIC)
+    {
+      type = va_arg (*ap, JconType);
+
+      switch (type)
+        {
+        case JCON_TYPE_STRING:
+          u->v_string = va_arg (*ap, const gchar *);
+          break;
+
+        case JCON_TYPE_DOUBLE:
+          u->v_double = va_arg (*ap, double);
+          break;
+
+        case JCON_TYPE_NODE:
+          u->v_node = va_arg (*ap, JsonNode *);
+          break;
+
+        case JCON_TYPE_OBJECT:
+          u->v_object = va_arg (*ap, JsonObject *);
+          break;
+
+        case JCON_TYPE_ARRAY:
+          u->v_array = va_arg (*ap, JsonArray *);
+          break;
+
+        case JCON_TYPE_BOOLEAN:
+          u->v_boolean = va_arg (*ap, gboolean);
+          break;
+
+        case JCON_TYPE_NULL:
+          break;
+
+        case JCON_TYPE_INT:
+          u->v_int = va_arg (*ap, gint);
+          break;
+
+        case JCON_TYPE_ARRAY_START:
+        case JCON_TYPE_ARRAY_END:
+        case JCON_TYPE_OBJECT_START:
+        case JCON_TYPE_OBJECT_END:
+        case JCON_TYPE_END:
+        case JCON_TYPE_RAW:
+        default:
+          g_assert_not_reached ();
+          break;
+      }
+    }
+  else
+    {
+      switch (mark[0])
+        {
+        case '{':
+          type = JCON_TYPE_OBJECT_START;
+          break;
+
+        case '}':
+          type = JCON_TYPE_OBJECT_END;
+          break;
+
+        case '[':
+          type = JCON_TYPE_ARRAY_START;
+          break;
+
+        case ']':
+          type = JCON_TYPE_ARRAY_END;
+          break;
+
+        default:
+          type = JCON_TYPE_STRING;
+          u->v_string = mark;
+          break;
+        }
+    }
+
+  return type;
+}
+
+static void
+jcon_append_va_list (JsonNode *node,
+                     va_list  *args)
+{
+  g_assert (JSON_NODE_HOLDS_OBJECT (node));
+  g_assert (args != NULL);
+
+  for (;;)
+    {
+      const gchar *key = NULL;
+      JconAppend val = { 0 };
+      JconType type;
+
+      g_assert (node != NULL);
+
+      if (!JSON_NODE_HOLDS_ARRAY (node))
+        {
+          type = jcon_append_tokenize (args, &val);
+
+          if (type == JCON_TYPE_END)
+            return;
+
+          if (type == JCON_TYPE_OBJECT_END)
+            {
+              node = json_node_get_parent (node);
+              continue;
+            }
+
+          if (type != JCON_TYPE_STRING)
+            g_error ("string keys are required for objects");
+
+          key = val.v_string;
+        }
+
+      type = jcon_append_tokenize (args, &val);
+
+      if (type == JCON_TYPE_END)
+        g_error ("implausable time to reach end token");
+
+      if (type == JCON_TYPE_OBJECT_START)
+        {
+          JsonNode *child_node = json_node_new (JSON_NODE_OBJECT);
+          JsonObject *child_object = json_object_new ();
+
+          json_node_take_object (child_node, child_object);
+
+          if (JSON_NODE_HOLDS_ARRAY (node))
+            json_array_add_element (json_node_get_array (node), child_node);
+          else
+            json_object_set_member (json_node_get_object (node), key, child_node);
+
+          json_node_set_parent (child_node, node);
+
+          node = child_node;
+
+          continue;
+        }
+      else if (type == JCON_TYPE_ARRAY_START)
+        {
+          JsonNode *child_node = json_node_new (JSON_NODE_ARRAY);
+          JsonArray *child_array = json_array_new ();
+
+          json_node_take_array (child_node, child_array);
+
+          if (JSON_NODE_HOLDS_ARRAY (node))
+            json_array_add_element (json_node_get_array (node), child_node);
+          else
+            json_object_set_member (json_node_get_object (node), key, child_node);
+
+          json_node_set_parent (child_node, node);
+
+          node = child_node;
+
+          continue;
+        }
+      else if (type == JCON_TYPE_OBJECT_END || type == JCON_TYPE_ARRAY_END)
+        {
+          node = json_node_get_parent (node);
+          continue;
+        }
+
+      if (JSON_NODE_HOLDS_ARRAY (node))
+        json_array_add_element (json_node_get_array (node), jcon_append_to_node (type, &val));
+      else
+        json_object_set_member (json_node_get_object (node), key, jcon_append_to_node (type, &val));
+    }
+}
+
+JsonNode *
+jcon_new (gpointer unused,
+          ...)
+{
+  g_autoptr(JsonNode) node = NULL;
+  va_list args;
+
+  g_return_val_if_fail (unused == NULL, NULL);
+
+  node = json_node_new (JSON_NODE_OBJECT);
+  json_node_take_object (node, json_object_new ());
+
+  va_start (args, unused);
+  jcon_append_va_list (node, &args);
+  va_end (args);
+
+  return g_steal_pointer (&node);
+}
+
+static gboolean
+jcon_extract_tokenize (va_list     *args,
+                       JconExtract *val,
+                       JconType    *type)
+{
+  const char *mark;
+
+  g_assert (args != NULL);
+  g_assert (val != NULL);
+  g_assert (type != NULL);
+
+  memset (val, 0, sizeof *val);
+  *type = -1;
+
+  mark = va_arg (*args, const char *);
+
+  if (mark == NULL)
+    {
+      *type = JCON_TYPE_END;
+      return TRUE;
+    }
+
+  if (mark == JCONE_MAGIC)
+    {
+      *type = va_arg (*args, JconType);
+
+      switch (*type)
+        {
+        case JCON_TYPE_STRING:
+          val->v_string = va_arg (*args, const gchar **);
+          return TRUE;
+
+        case JCON_TYPE_DOUBLE:
+          val->v_double = va_arg (*args, gdouble *);
+          return TRUE;
+
+        case JCON_TYPE_NODE:
+          val->v_node = va_arg (*args, JsonNode **);
+          return TRUE;
+
+        case JCON_TYPE_OBJECT:
+          val->v_object = va_arg (*args, JsonObject **);
+          return TRUE;
+
+        case JCON_TYPE_ARRAY:
+          val->v_array = va_arg (*args, JsonArray **);
+          return TRUE;
+
+        case JCON_TYPE_BOOLEAN:
+          val->v_boolean = va_arg (*args, gboolean *);
+          return TRUE;
+
+        case JCON_TYPE_NULL:
+          return TRUE;
+
+        case JCON_TYPE_INT:
+          val->v_int = va_arg (*args, gint *);
+          return TRUE;
+
+        case JCON_TYPE_ARRAY_START:
+        case JCON_TYPE_ARRAY_END:
+        case JCON_TYPE_OBJECT_START:
+        case JCON_TYPE_OBJECT_END:
+        case JCON_TYPE_END:
+        case JCON_TYPE_RAW:
+        default:
+          return FALSE;
+        }
+    }
+
+  switch (mark[0])
+    {
+    case '{':
+      *type = JCON_TYPE_OBJECT_START;
+      return TRUE;
+
+    case '}':
+      *type = JCON_TYPE_OBJECT_END;
+      return TRUE;
+
+    case '[':
+      *type = JCON_TYPE_ARRAY_START;
+      return TRUE;
+
+    case ']':
+      *type = JCON_TYPE_ARRAY_END;
+      return TRUE;
+
+    default:
+      break;
+    }
+
+  *type = JCON_TYPE_RAW;
+  val->v_key = mark;
+
+  return TRUE;
+}
+
+static void
+jcon_extract_from_node (JsonNode    *node,
+                        JconType     type,
+                        JconExtract *val)
+{
+  g_assert (node != NULL);
+  g_assert (val != NULL);
+
+  switch (type)
+    {
+    case JCON_TYPE_STRING:
+      if (JSON_NODE_HOLDS_VALUE (node))
+        *val->v_string = json_node_get_string (node);
+      else
+        *val->v_string = NULL;
+      break;
+
+    case JCON_TYPE_DOUBLE:
+      if (JSON_NODE_HOLDS_VALUE (node))
+        *val->v_double = json_node_get_double (node);
+      else
+        *val->v_double = 0.0;
+      break;
+
+    case JCON_TYPE_NODE:
+      *val->v_node = node;
+      break;
+
+    case JCON_TYPE_OBJECT:
+      if (JSON_NODE_HOLDS_OBJECT (node))
+        *val->v_object = json_node_get_object (node);
+      else
+        *val->v_object = NULL;
+      break;
+
+    case JCON_TYPE_ARRAY:
+      if (JSON_NODE_HOLDS_ARRAY (node))
+        *val->v_array = json_node_get_array (node);
+      else
+        *val->v_array = NULL;
+      break;
+
+    case JCON_TYPE_BOOLEAN:
+      if (JSON_NODE_HOLDS_VALUE (node))
+        *val->v_boolean = json_node_get_boolean (node);
+      else
+        *val->v_boolean = FALSE;
+      break;
+
+    case JCON_TYPE_NULL:
+      break;
+
+    case JCON_TYPE_INT:
+      if (JSON_NODE_HOLDS_VALUE (node))
+        *val->v_int = json_node_get_int (node);
+      else
+        *val->v_int = 0;
+      break;
+
+    case JCON_TYPE_ARRAY_START:
+    case JCON_TYPE_ARRAY_END:
+    case JCON_TYPE_OBJECT_START:
+    case JCON_TYPE_OBJECT_END:
+    case JCON_TYPE_END:
+    case JCON_TYPE_RAW:
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+}
+
+static JsonNode *
+get_stack_node (JsonNode    *node,
+                const gchar *key,
+                guint        idx)
+{
+  g_assert (node != NULL);
+
+  if (JSON_NODE_HOLDS_ARRAY (node))
+    {
+      JsonArray *array = json_node_get_array (node);
+      if (array == NULL)
+        return NULL;
+      if (idx >= json_array_get_length (array))
+        return NULL;
+      return json_array_get_element (array, idx);
+    }
+
+  if (JSON_NODE_HOLDS_OBJECT (node))
+    {
+      JsonObject *object = json_node_get_object (node);
+      if (object == NULL)
+        return NULL;
+      if (!json_object_has_member (object, key))
+        return NULL;
+      return json_object_get_member (object, key);
+    }
+
+  return NULL;
+}
+
+static gboolean
+jcon_extract_va_list (JsonNode *node,
+                      va_list  *args)
+{
+  const gchar *keys[STACK_DEPTH];
+  guint indexes[STACK_DEPTH];
+  gint sp = 0;
+
+  g_assert (node != NULL);
+  g_assert (args != NULL);
+
+#define POP_STACK() sp--
+#define PUSH_STACK() \
+  G_STMT_START { \
+    sp++; \
+    keys[sp] = NULL; \
+    indexes[sp] = 0; \
+  } G_STMT_END
+
+  keys[0] = NULL;
+  indexes[0] = 0;
+
+  while (node != NULL && sp >= 0 && sp < STACK_DEPTH)
+    {
+      JconExtract val = { 0 };
+      JconType type;
+
+      if (JSON_NODE_HOLDS_OBJECT (node))
+        {
+          JsonObject *object;
+
+          if (!jcon_extract_tokenize (args, &val, &type))
+            return FALSE;
+
+          if (type == JCON_TYPE_END)
+            return TRUE;
+
+          if (type == JCON_TYPE_OBJECT_END)
+            {
+              node = json_node_get_parent (node);
+              POP_STACK();
+              continue;
+            }
+
+          if (type != JCON_TYPE_RAW)
+            return FALSE;
+
+          keys[sp] = val.v_key;
+          object = json_node_get_object (node);
+
+          if (object == NULL || !json_object_has_member (object, keys[sp]))
+            return FALSE;
+        }
+
+      if (!jcon_extract_tokenize (args, &val, &type))
+        return FALSE;
+
+      /* We can have an END here if the root was an array */
+      if (sp == 0 && type == JCON_TYPE_END && JSON_NODE_HOLDS_ARRAY (node))
+        return TRUE;
+
+      if (JSON_NODE_HOLDS_OBJECT (node))
+        {
+          if (type == JCON_TYPE_OBJECT_END || type == JCON_TYPE_ARRAY_END || type == JCON_TYPE_END)
+            return FALSE;
+        }
+
+      if (type == JCON_TYPE_ARRAY_END)
+        {
+          if (!JSON_NODE_HOLDS_ARRAY (node))
+            return FALSE;
+          node = json_node_get_parent (node);
+          POP_STACK();
+          continue;
+        }
+
+      if (type == JCON_TYPE_ARRAY_START)
+        {
+          JsonNode *target = get_stack_node (node, keys[sp], indexes[sp]);
+          if (target == NULL || !JSON_NODE_HOLDS_ARRAY (target))
+            return FALSE;
+          g_assert (node == json_node_get_parent (target));
+          node = target;
+          PUSH_STACK();
+          continue;
+        }
+
+      if (type == JCON_TYPE_OBJECT_START)
+        {
+          JsonNode *target = get_stack_node (node, keys[sp], indexes[sp]);
+          if (target == NULL || !JSON_NODE_HOLDS_OBJECT (target))
+            return FALSE;
+          g_assert (node == json_node_get_parent (target));
+          node = target;
+          PUSH_STACK();
+          continue;
+        }
+
+      jcon_extract_from_node (get_stack_node (node, keys[sp], indexes[sp]), type, &val);
+
+      if (JSON_NODE_HOLDS_ARRAY (node))
+        indexes[sp]++;
+    }
+
+  return FALSE;
+}
+
+gboolean
+jcon_extract (JsonNode *node,
+              ...)
+{
+  gboolean ret;
+  va_list args;
+
+  va_start (args, node);
+  ret = jcon_extract_va_list (node, &args);
+  va_end (args);
+
+  return ret;
+}
diff --git a/contrib/jsonrpc-glib/jcon.h b/contrib/jsonrpc-glib/jcon.h
new file mode 100644
index 0000000..be2969d
--- /dev/null
+++ b/contrib/jsonrpc-glib/jcon.h
@@ -0,0 +1,130 @@
+/* jcon.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Copyright 2009-2013 MongoDB, Inc.
+ * Copyright      2016 Christian Hergert <chergert redhat com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef JSONRPC_JCON_H
+#define JSONRPC_JCON_H
+
+#include <json-glib/json-glib.h>
+
+G_BEGIN_DECLS
+
+#define JCON_ENSURE_DECLARE(fun, type) \
+  static inline type jcon_ensure_##fun (type _t) { return _t; }
+
+#define JCON_ENSURE(fun, val) \
+  jcon_ensure_##fun (val)
+
+#define JCON_ENSURE_STORAGE(fun, val) \
+  jcon_ensure_##fun (&(val))
+
+JCON_ENSURE_DECLARE (const_char_ptr, const char *)
+JCON_ENSURE_DECLARE (const_char_ptr_ptr, const char **)
+JCON_ENSURE_DECLARE (double, double)
+JCON_ENSURE_DECLARE (double_ptr, double *)
+JCON_ENSURE_DECLARE (array_ptr, JsonArray *)
+JCON_ENSURE_DECLARE (array_ptr_ptr, JsonArray **)
+JCON_ENSURE_DECLARE (object_ptr, JsonObject *)
+JCON_ENSURE_DECLARE (object_ptr_ptr, JsonObject **)
+JCON_ENSURE_DECLARE (node_ptr, JsonNode *)
+JCON_ENSURE_DECLARE (node_ptr_ptr, JsonNode **)
+JCON_ENSURE_DECLARE (int, gint)
+JCON_ENSURE_DECLARE (int_ptr, gint *)
+JCON_ENSURE_DECLARE (boolean, gboolean)
+JCON_ENSURE_DECLARE (boolean_ptr, gboolean *)
+
+#define JCON_STRING(_val) \
+  JCON_MAGIC, JCON_TYPE_STRING, JCON_ENSURE (const_char_ptr, (_val))
+#define JCON_DOUBLE(_val) \
+  JCON_MAGIC, JCON_TYPE_DOUBLE, JCON_ENSURE (double, (_val))
+#define JCON_OBJECT(_val) \
+  JCON_MAGIC, JCON_TYPE_OBJECT, JCON_ENSURE (object_ptr, (_val))
+#define JCON_ARRAY(_val) \
+  JCON_MAGIC, JCON_TYPE_ARRAY, JCON_ENSURE (array_ptr, (_val))
+#define JCON_NODE(_val) \
+  JCON_MAGIC, JCON_TYPE_NODE, JCON_ENSURE (node_ptr, (_val))
+#define JCON_BOOLEAN(_val) \
+  JCON_MAGIC, JCON_TYPE_BOOLEAN, JCON_ENSURE (boolean, (_val))
+#define JCON_NULL JCON_MAGIC, JCON_TYPE_NULL
+#define JCON_INT(_val) \
+  JCON_MAGIC, JCON_TYPE_INT, JCON_ENSURE (int, (_val))
+
+#define JCONE_STRING(_val) JCONE_MAGIC, JCON_TYPE_STRING, \
+  JCON_ENSURE_STORAGE (const_char_ptr_ptr, (_val))
+#define JCONE_DOUBLE(_val) JCONE_MAGIC, JCON_TYPE_DOUBLE, \
+  JCON_ENSURE_STORAGE (double_ptr, (_val))
+#define JCONE_OBJECT(_val) JCONE_MAGIC, JCON_TYPE_OBJECT, \
+  JCON_ENSURE_STORAGE (object_ptr_ptr, (_val))
+#define JCONE_ARRAY(_val) JCONE_MAGIC, JCON_TYPE_ARRAY, \
+  JCON_ENSURE_STORAGE (array_ptr_ptr, (_val))
+#define JCONE_NODE(_val) JCONE_MAGIC, JCON_TYPE_NODE, \
+  JCON_ENSURE_STORAGE (node_ptr_ptr, (_val))
+#define JCONE_BOOLEAN(_val) JCONE_MAGIC, JCON_TYPE_BOOLEAN, \
+  JCON_ENSURE_STORAGE (bool_ptr, (_val))
+#define JCONE_NULL JCONE_MAGIC, JCON_TYPE_NULL
+#define JCONE_INT(_val) JCONE_MAGIC, JCON_TYPE_INT, \
+  JCON_ENSURE_STORAGE (int_ptr, (_val))
+
+typedef enum
+{
+  JCON_TYPE_STRING,
+  JCON_TYPE_DOUBLE,
+  JCON_TYPE_OBJECT,
+  JCON_TYPE_ARRAY,
+  JCON_TYPE_NODE,
+  JCON_TYPE_BOOLEAN,
+  JCON_TYPE_NULL,
+  JCON_TYPE_INT,
+  JCON_TYPE_ARRAY_START,
+  JCON_TYPE_ARRAY_END,
+  JCON_TYPE_OBJECT_START,
+  JCON_TYPE_OBJECT_END,
+  JCON_TYPE_END,
+  JCON_TYPE_RAW,
+} JconType;
+
+#define JCON_MAGIC jcon_magic()
+const char *jcon_magic  (void) G_GNUC_CONST;
+
+#define JCONE_MAGIC jcone_magic()
+const char *jcone_magic (void) G_GNUC_CONST;
+
+#define JCON_NEW(...) jcon_new (NULL, __VA_ARGS__, NULL)
+JsonNode *jcon_new (gpointer  unused, ...) G_GNUC_NULL_TERMINATED;
+
+#define JCON_EXTRACT(_node, ...) jcon_extract ((_node), __VA_ARGS__, NULL)
+gboolean jcon_extract (JsonNode *node, ...) G_GNUC_NULL_TERMINATED;
+
+G_END_DECLS
+
+#endif /* JSONRPC_JCON_H */
diff --git a/contrib/jsonrpc-glib/jsonrpc-client.c b/contrib/jsonrpc-glib/jsonrpc-client.c
new file mode 100644
index 0000000..a68ff01
--- /dev/null
+++ b/contrib/jsonrpc-glib/jsonrpc-client.c
@@ -0,0 +1,1100 @@
+/* jsonrpc-client.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "jsonrpc-client"
+
+/**
+ * SECTION:jsonrpc-client:
+ * @title: JsonrpcClient
+ * @short_description: a client for JSON-RPC communication
+ *
+ * The #JsonrpcClient class provides a convenient API to coordinate with a
+ * JSON-RPC server. You can provide the underlying #GIOStream to communicate
+ * with allowing you to control the negotiation of how you setup your
+ * communications channel. One such method might be to use a #GSubprocess and
+ * communicate over stdin and stdout.
+ *
+ * Because JSON-RPC allows for out-of-band notifications from the server to
+ * the client, it is important that the consumer of this API calls
+ * jsonrpc_client_close() or jsonrpc_client_close_async() when they no longer
+ * need the client. This is because #JsonrpcClient contains an asynchronous
+ * read-loop to process incoming messages. Until jsonrpc_client_close() or
+ * jsonrpc_client_close_async() have been called, this read loop will prevent
+ * the object from finalizing (being freed).
+ *
+ * To make an RPC call, use jsonrpc_client_call() or
+ * jsonrpc_client_call_async() and provide the method name and the parameters
+ * as a #JsonNode for call.
+ *
+ * It is a programming error to mix synchronous and asynchronous API calls
+ * of the #JsonrpcClient class.
+ *
+ * For synchronous calls, #JsonrpcClient will use the thread-default
+ * #GMainContext. If you have special needs here ensure you've set the context
+ * before calling into any #JsonrpcClient API.
+ */
+
+#include <glib.h>
+
+#include "jsonrpc-client.h"
+#include "jsonrpc-input-stream.h"
+#include "jsonrpc-output-stream.h"
+
+typedef struct
+{
+  /*
+   * The invocations field contains a hashtable that maps request ids to
+   * the GTask that is awaiting their completion. The tasks are removed
+   * from the hashtable automatically upon completion by connecting to
+   * the GTask::completed signal. When reading a message from the input
+   * stream, we use the request id as a string to lookup the inflight
+   * invocation. The result is passed as the result of the task.
+   */
+  GHashTable *invocations;
+
+  /*
+   * We hold an extra reference to the GIOStream pair to make things
+   * easier to construct and ensure that the streams are in tact in
+   * case they are poorly implemented.
+   */
+  GIOStream *io_stream;
+
+  /*
+   * The input_stream field contains our wrapper input stream around the
+   * underlying input stream provided by JsonrpcClient::io-stream. This
+   * allows us to conveniently write JsonNode instances.
+   */
+  JsonrpcInputStream *input_stream;
+
+  /*
+   * The output_stream field contains our wrapper output stream around the
+   * underlying output stream provided by JsonrpcClient::io-stream. This
+   * allows us to convieniently read JsonNode instances.
+   */
+  JsonrpcOutputStream *output_stream;
+
+  /*
+   * This cancellable is used for our async read loops so that we can
+   * cancel the operation to shutdown the client. Otherwise, we would
+   * indefinitely leak our client due to the self-reference on our
+   * read loop user_data parameter.
+   */
+  GCancellable *read_loop_cancellable;
+
+  /*
+   * Every JSONRPC invocation needs a request id. This is a monotonic
+   * integer that we encode as a string to the server.
+   */
+  gint sequence;
+
+  /*
+   * This bit indicates if we have sent a call yet. Once we send our
+   * first call, we start our read loop which will allow us to also
+   * dispatch notifications out of band.
+   */
+  guint is_first_call : 1;
+
+  /*
+   * This bit is set when the program has called jsonrpc_client_close()
+   * or jsonrpc_client_close_async(). When the read loop returns, it
+   * will check for this and discontinue further asynchronous reads.
+   */
+  guint in_shutdown : 1;
+
+  /*
+   * If we have panic'd, this will be set to TRUE so that we can short
+   * circuit on future operations sooner.
+   */
+  guint failed : 1;
+} JsonrpcClientPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (JsonrpcClient, jsonrpc_client, G_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_IO_STREAM,
+  N_PROPS
+};
+
+enum {
+  NOTIFICATION,
+  N_SIGNALS
+};
+
+static GParamSpec *properties [N_PROPS];
+static guint signals [N_SIGNALS];
+
+/*
+ * Check to see if this looks like a jsonrpc 2.0 reply of any kind.
+ */
+static gboolean
+is_jsonrpc_reply (JsonNode *node)
+{
+  JsonObject *object;
+  const gchar *value;
+
+  return JSON_NODE_HOLDS_OBJECT (node) &&
+         NULL != (object = json_node_get_object (node)) &&
+         json_object_has_member (object, "jsonrpc") &&
+         NULL != (value = json_object_get_string_member (object, "jsonrpc")) &&
+         (g_strcmp0 (value, "2.0") == 0);
+}
+
+/*
+ * Check to see if this looks like a notification reply.
+ */
+static gboolean
+is_jsonrpc_notification (JsonNode *node)
+{
+  JsonObject *object;
+  const gchar *value;
+
+  g_assert (JSON_NODE_HOLDS_OBJECT (node));
+
+  object = json_node_get_object (node);
+
+  return !json_object_has_member (object, "id") &&
+         json_object_has_member (object, "method") &&
+         NULL != (value = json_object_get_string_member (object, "method")) &&
+         value != NULL && *value != '\0';
+}
+
+/*
+ * Check to see if this looks like a proper result for an RPC.
+ */
+static gboolean
+is_jsonrpc_result (JsonNode *node)
+{
+  JsonObject *object;
+  JsonNode *field;
+
+  g_assert (JSON_NODE_HOLDS_OBJECT (node));
+
+  object = json_node_get_object (node);
+
+  return json_object_has_member (object, "id") &&
+         NULL != (field = json_object_get_member (object, "id")) &&
+         JSON_NODE_HOLDS_VALUE (field) &&
+         json_node_get_int (field) > 0 &&
+         json_object_has_member (object, "result");
+}
+
+/*
+ * Try to unwrap the error and possibly set @id to the extracted RPC
+ * request id.
+ */
+static gboolean
+unwrap_jsonrpc_error (JsonNode  *node,
+                      gint      *id,
+                      GError   **error)
+{
+  JsonObject *object;
+  JsonObject *err_obj;
+  JsonNode *field;
+
+  g_assert (node != NULL);
+  g_assert (id != NULL);
+  g_assert (error != NULL);
+
+  if (!JSON_NODE_HOLDS_OBJECT (node))
+    return FALSE;
+
+  object = json_node_get_object (node);
+
+  if (json_object_has_member (object, "id") &&
+      NULL != (field = json_object_get_member (object, "id")) &&
+      JSON_NODE_HOLDS_VALUE (field) &&
+      json_node_get_int (field) > 0)
+    *id = json_node_get_int (field);
+  else
+    *id = -1;
+
+  if (json_object_has_member (object, "error") &&
+      NULL != (field = json_object_get_member (object, "error")) &&
+      JSON_NODE_HOLDS_OBJECT (field) &&
+      NULL != (err_obj = json_node_get_object (field)))
+    {
+      const gchar *message;
+      gint code;
+
+      message = json_object_get_string_member (err_obj, "message");
+      code = json_object_get_int_member (err_obj, "code");
+
+      if (message == NULL || *message == '\0')
+        message = "Unknown error occurred";
+
+      g_set_error_literal (error, JSONRPC_CLIENT_ERROR, code, message);
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+/*
+ * jsonrpc_client_panic:
+ *
+ * This function should be called to "tear down everything" and ensure we
+ * cleanup.
+ */
+static void
+jsonrpc_client_panic (JsonrpcClient *self,
+                      const GError  *error)
+{
+  JsonrpcClientPrivate *priv = jsonrpc_client_get_instance_private (self);
+  g_autoptr(GHashTable) invocations = NULL;
+  GHashTableIter iter;
+  GTask *task;
+
+  g_assert (JSONRPC_IS_CLIENT (self));
+  g_assert (error != NULL);
+
+  priv->failed = TRUE;
+
+  g_warning ("%s", error->message);
+
+  jsonrpc_client_close (self, NULL, NULL);
+
+  /* Steal the tasks so that we don't have to worry about reentry. */
+  invocations = g_steal_pointer (&priv->invocations);
+  priv->invocations = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
+
+  /*
+   * Clear our input and output streams so that new calls
+   * fail immediately due to not being connected.
+   */
+  g_clear_object (&priv->input_stream);
+  g_clear_object (&priv->output_stream);
+
+  /*
+   * Now notify all of the in-flight invocations that they failed due
+   * to an unrecoverable error.
+   */
+  g_hash_table_iter_init (&iter, invocations);
+  while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&task))
+    g_task_return_error (task, g_error_copy (error));
+}
+
+/*
+ * jsonrpc_client_check_ready:
+ *
+ * Checks to see if the client is in a position to make requests.
+ *
+ * Returns: %TRUE if the client is ready for RPCs; otherwise %FALSE
+ *   and @error is set.
+ */
+static gboolean
+jsonrpc_client_check_ready (JsonrpcClient  *self,
+                            GError        **error)
+{
+  JsonrpcClientPrivate *priv = jsonrpc_client_get_instance_private (self);
+
+  g_assert (JSONRPC_IS_CLIENT (self));
+
+  if (priv->failed || priv->in_shutdown || priv->output_stream == NULL || priv->input_stream == NULL)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_NOT_CONNECTED,
+                   "No stream available to deliver invocation");
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static void
+jsonrpc_client_constructed (GObject *object)
+{
+  JsonrpcClient *self = (JsonrpcClient *)object;
+  JsonrpcClientPrivate *priv = jsonrpc_client_get_instance_private (self);
+  GInputStream *input_stream;
+  GOutputStream *output_stream;
+
+  G_OBJECT_CLASS (jsonrpc_client_parent_class)->constructed (object);
+
+  if (priv->io_stream == NULL)
+    {
+      g_warning ("%s requires a GIOStream to communicate. Disabling.",
+                 G_OBJECT_TYPE_NAME (self));
+      return;
+    }
+
+  input_stream = g_io_stream_get_input_stream (priv->io_stream);
+  output_stream = g_io_stream_get_output_stream (priv->io_stream);
+
+  priv->input_stream = jsonrpc_input_stream_new (input_stream);
+  priv->output_stream = jsonrpc_output_stream_new (output_stream);
+}
+
+static void
+jsonrpc_client_finalize (GObject *object)
+{
+  JsonrpcClient *self = (JsonrpcClient *)object;
+  JsonrpcClientPrivate *priv = jsonrpc_client_get_instance_private (self);
+
+  g_clear_pointer (&priv->invocations, g_hash_table_unref);
+
+  g_clear_object (&priv->input_stream);
+  g_clear_object (&priv->output_stream);
+  g_clear_object (&priv->io_stream);
+  g_clear_object (&priv->read_loop_cancellable);
+
+  G_OBJECT_CLASS (jsonrpc_client_parent_class)->finalize (object);
+}
+
+static void
+jsonrpc_client_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+  JsonrpcClient *self = JSONRPC_CLIENT (object);
+  JsonrpcClientPrivate *priv = jsonrpc_client_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_IO_STREAM:
+      priv->io_stream = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+jsonrpc_client_class_init (JsonrpcClientClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed = jsonrpc_client_constructed;
+  object_class->finalize = jsonrpc_client_finalize;
+  object_class->set_property = jsonrpc_client_set_property;
+
+  properties [PROP_IO_STREAM] =
+    g_param_spec_object ("io-stream",
+                         "IO Stream",
+                         "The stream to communicate over",
+                         G_TYPE_IO_STREAM,
+                         (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  signals [NOTIFICATION] =
+    g_signal_new ("notification",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (JsonrpcClientClass, notification),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE,
+                  2,
+                  G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE,
+                  JSON_TYPE_NODE);
+}
+
+static void
+jsonrpc_client_init (JsonrpcClient *self)
+{
+  JsonrpcClientPrivate *priv = jsonrpc_client_get_instance_private (self);
+
+  priv->invocations = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
+  priv->is_first_call = TRUE;
+  priv->read_loop_cancellable = g_cancellable_new ();
+}
+
+/**
+ * jsonrpc_client_new:
+ * @io_stream: A #GIOStream
+ *
+ * Creates a new #JsonrpcClient instance.
+ *
+ * If you want to communicate with a process using stdin/stdout, consider using
+ * #GSubprocess to launch the process and create a #GSimpleIOStream using the
+ * g_subprocess_get_stdin_pipe() and g_subprocess_get_stdout_pipe().
+ *
+ * Returns: (transfer full): A newly created #JsonrpcClient
+ */
+JsonrpcClient *
+jsonrpc_client_new (GIOStream *io_stream)
+{
+  g_return_val_if_fail (G_IS_IO_STREAM (io_stream), NULL);
+
+  return g_object_new (JSONRPC_TYPE_CLIENT,
+                       "io-stream", io_stream,
+                       NULL);
+}
+
+static void
+jsonrpc_client_call_notify_completed (GTask      *task,
+                                      GParamSpec *pspec,
+                                      gpointer    user_data)
+{
+  JsonrpcClientPrivate *priv;
+  JsonrpcClient *self;
+  gpointer id;
+
+  g_assert (G_IS_TASK (task));
+  g_assert (pspec != NULL);
+  g_assert (g_str_equal (pspec->name, "completed"));
+
+  self = g_task_get_source_object (task);
+  priv = jsonrpc_client_get_instance_private (self);
+  id = g_task_get_task_data (task);
+
+  g_hash_table_remove (priv->invocations, id);
+}
+
+static void
+jsonrpc_client_call_write_cb (GObject      *object,
+                              GAsyncResult *result,
+                              gpointer      user_data)
+{
+  JsonrpcOutputStream *stream = (JsonrpcOutputStream *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (JSONRPC_IS_OUTPUT_STREAM (stream));
+  g_assert (G_IS_TASK (task));
+
+  if (!jsonrpc_output_stream_write_message_finish (stream, result, &error))
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  /* We don't need to complete the task because it will get completed when the
+   * server replies with our reply. This is performed using an asynchronous
+   * read that will pump through the messages.
+   */
+}
+
+static void
+jsonrpc_client_call_read_cb (GObject      *object,
+                             GAsyncResult *result,
+                             gpointer      user_data)
+{
+  JsonrpcInputStream *stream = (JsonrpcInputStream *)object;
+  g_autoptr(JsonrpcClient) self = user_data;
+  JsonrpcClientPrivate *priv = jsonrpc_client_get_instance_private (self);
+  g_autoptr(JsonNode) node = NULL;
+  g_autoptr(GError) error = NULL;
+  gint id = -1;
+
+  g_assert (JSONRPC_IS_INPUT_STREAM (stream));
+  g_assert (JSONRPC_IS_CLIENT (self));
+
+  if (!jsonrpc_input_stream_read_message_finish (stream, result, &node, &error))
+    {
+      /*
+       * Handle jsonrpc_client_close() conditions gracefully.
+       */
+      if (priv->in_shutdown && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        return;
+
+      /*
+       * If we fail to read a message, that means we couldn't even receive
+       * a message describing the error. All we can do in this case is panic
+       * and shutdown the whole client.
+       */
+      jsonrpc_client_panic (self, error);
+      return;
+    }
+
+  g_assert (node != NULL);
+
+  /*
+   * If the message is malformed, we'll also need to perform another read.
+   * We do this to try to be relaxed against failures. That seems to be
+   * the JSONRPC way, although I'm not sure I like the idea.
+   */
+  if (!is_jsonrpc_reply (node))
+    {
+      error = g_error_new_literal (G_IO_ERROR,
+                                   G_IO_ERROR_INVALID_DATA,
+                                   "Received malformed response from peer");
+      jsonrpc_client_panic (self, error);
+      return;
+    }
+
+  /*
+   * If the response does not have an "id" field, then it is a "notification"
+   * and we need to emit the "notificiation" signal.
+   */
+  if (is_jsonrpc_notification (node))
+    {
+      g_autoptr(JsonNode) empty_params = NULL;
+      const gchar *method_name;
+      JsonObject *obj;
+      JsonNode *params;
+
+      obj = json_node_get_object (node);
+      method_name = json_object_get_string_member (obj, "method");
+      params = json_object_get_member (obj, "params");
+
+      if (params == NULL)
+        params = empty_params = json_node_new (JSON_NODE_ARRAY);
+
+      g_signal_emit (self, signals [NOTIFICATION], 0, method_name, params);
+
+      goto begin_next_read;
+    }
+
+  if (is_jsonrpc_result (node))
+    {
+      JsonObject *obj;
+      JsonNode *res;
+      GTask *task;
+
+      obj = json_node_get_object (node);
+      id = json_object_get_int_member (obj, "id");
+      res = json_object_get_member (obj, "result");
+
+      task = g_hash_table_lookup (priv->invocations, GINT_TO_POINTER (id));
+
+      if (task != NULL)
+        {
+          g_task_return_pointer (task, json_node_copy (res), (GDestroyNotify)json_node_unref);
+          goto begin_next_read;
+        }
+
+      error = g_error_new_literal (G_IO_ERROR,
+                                   G_IO_ERROR_INVALID_DATA,
+                                   "Reply to missing or invalid task");
+      jsonrpc_client_panic (self, error);
+      return;
+    }
+
+  /*
+   * If we got an error destined for one of our inflight invocations, then
+   * we need to dispatch it now.
+   */
+  if (unwrap_jsonrpc_error (node, &id, &error))
+    {
+      if (id > 0)
+        {
+          GTask *task = g_hash_table_lookup (priv->invocations, GINT_TO_POINTER (id));
+
+          if (task != NULL)
+            {
+              g_task_return_error (task, g_steal_pointer (&error));
+              goto begin_next_read;
+            }
+        }
+
+      /*
+       * Generic error, not tied to any specific task we had in flight. So
+       * take this as a failure case and panic on the line.
+       */
+      jsonrpc_client_panic (self, error);
+      return;
+    }
+
+  {
+    g_autofree gchar *str = json_to_string (node, FALSE);
+    g_warning ("Unhandled message: %s", str);
+  }
+
+begin_next_read:
+  if (priv->input_stream != NULL && priv->in_shutdown == FALSE)
+    jsonrpc_input_stream_read_message_async (priv->input_stream,
+                                             priv->read_loop_cancellable,
+                                             jsonrpc_client_call_read_cb,
+                                             g_steal_pointer (&self));
+}
+
+static void
+jsonrpc_client_call_sync_cb (GObject      *object,
+                             GAsyncResult *result,
+                             gpointer      user_data)
+{
+  JsonrpcClient *self = (JsonrpcClient *)object;
+  GTask *task = user_data;
+  g_autoptr(JsonNode) return_value = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (JSONRPC_IS_CLIENT (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_TASK (task));
+
+  if (!jsonrpc_client_call_finish (self, result, &return_value, &error))
+    g_task_return_error (task, g_steal_pointer (&error));
+  else
+    g_task_return_pointer (task, g_steal_pointer (&return_value), (GDestroyNotify)json_node_unref);
+}
+
+/**
+ * jsonrpc_client_call:
+ * @self: A #JsonrpcClient
+ * @method: the name of the method to call
+ * @params: (transfer full) (nullable): A #JsonNode of parameters or %NULL
+ * @cancellable: (nullable): A #GCancellable or %NULL
+ * @return_value: (nullable) (out): A location for a #JsonNode.
+ *
+ * Synchronously calls @method with @params on the remote peer.
+ *
+ * This function takes ownership of @params.
+ *
+ * once a reply has been received, or failure, this function will return.
+ * If successful, @return_value will be set with the reslut field of
+ * the response.
+ *
+ * Returns; %TRUE on success; otherwise %FALSE and @error is set.
+ */
+gboolean
+jsonrpc_client_call (JsonrpcClient  *self,
+                     const gchar    *method,
+                     JsonNode       *params,
+                     GCancellable   *cancellable,
+                     JsonNode      **return_value,
+                     GError        **error)
+{
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(GMainContext) main_context = NULL;
+  g_autoptr(JsonNode) local_return_value = NULL;
+  gboolean ret;
+
+  g_return_val_if_fail (JSONRPC_IS_CLIENT (self), FALSE);
+  g_return_val_if_fail (method != NULL, FALSE);
+  g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+
+  main_context = g_main_context_ref_thread_default ();
+
+  task = g_task_new (NULL, NULL, NULL, NULL);
+  g_task_set_source_tag (task, jsonrpc_client_call);
+
+  jsonrpc_client_call_async (self,
+                             method,
+                             params,
+                             cancellable,
+                             jsonrpc_client_call_sync_cb,
+                             task);
+
+  while (!g_task_get_completed (task))
+    g_main_context_iteration (main_context, TRUE);
+
+  local_return_value = g_task_propagate_pointer (task, error);
+  ret = local_return_value != NULL;
+
+  if (return_value != NULL)
+    *return_value = g_steal_pointer (&local_return_value);
+
+  return ret;
+}
+
+/**
+ * jsonrpc_client_call_async:
+ * @self: A #JsonrpcClient
+ * @method: the name of the method to call
+ * @params: (transfer full) (nullable): A #JsonNode of parameters or %NULL
+ * @cancellable: (nullable): A #GCancellable or %NULL
+ * @callback: a callback to executed upon completion
+ * @user_data: user data for @callback
+ *
+ * Asynchronously calls @method with @params on the remote peer.
+ *
+ * This function takes ownership of @params.
+ *
+ * Upon completion or failure, @callback is executed and it should
+ * call jsonrpc_client_call_finish() to complete the request and release
+ * any memory held.
+ */
+void
+jsonrpc_client_call_async (JsonrpcClient       *self,
+                           const gchar         *method,
+                           JsonNode            *params,
+                           GCancellable        *cancellable,
+                           GAsyncReadyCallback  callback,
+                           gpointer             user_data)
+{
+  JsonrpcClientPrivate *priv = jsonrpc_client_get_instance_private (self);
+  g_autoptr(JsonObject) object = NULL;
+  g_autoptr(JsonNode) node = NULL;
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(GError) error = NULL;
+  gint id;
+
+  g_return_if_fail (JSONRPC_IS_CLIENT (self));
+  g_return_if_fail (method != NULL);
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, jsonrpc_client_call_async);
+
+  if (!jsonrpc_client_check_ready (self, &error))
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  g_signal_connect (task,
+                    "notify::completed",
+                    G_CALLBACK (jsonrpc_client_call_notify_completed),
+                    NULL);
+
+  id = ++priv->sequence;
+
+  g_task_set_task_data (task, GINT_TO_POINTER (id), NULL);
+
+  if (params == NULL)
+    params = json_node_new (JSON_NODE_NULL);
+
+  object = json_object_new ();
+
+  json_object_set_string_member (object, "jsonrpc", "2.0");
+  json_object_set_int_member (object, "id", id);
+  json_object_set_string_member (object, "method", method);
+  json_object_set_member (object, "params", params);
+
+  node = json_node_new (JSON_NODE_OBJECT);
+  json_node_take_object (node, g_steal_pointer (&object));
+
+  g_hash_table_insert (priv->invocations, GINT_TO_POINTER (id), g_object_ref (task));
+
+  jsonrpc_output_stream_write_message_async (priv->output_stream,
+                                             node,
+                                             cancellable,
+                                             jsonrpc_client_call_write_cb,
+                                             g_steal_pointer (&task));
+
+  /*
+   * If this is our very first message, then we need to start our
+   * async read loop. This will allow us to receive notifications
+   * out-of-band and intermixed with RPC calls.
+   */
+
+  if (priv->is_first_call)
+    {
+      priv->is_first_call = FALSE;
+
+      /*
+       * Because we take a reference here in our read loop, it is important
+       * that the user calls jsonrpc_client_close() or
+       * jsonrpc_client_close_async() so that we can cancel the operation and
+       * allow it to cleanup any outstanding references.
+       */
+      jsonrpc_input_stream_read_message_async (priv->input_stream,
+                                               priv->read_loop_cancellable,
+                                               jsonrpc_client_call_read_cb,
+                                               g_object_ref (self));
+    }
+}
+
+/**
+ * jsonrpc_client_call_finish:
+ * @self: A #JsonrpcClient.
+ * @result: A #GAsyncResult provided to the callback in jsonrpc_client_call_async()
+ * @return_value: (out) (nullable): A location for a #JsonNode or %NULL
+ * @error: a location for a #GError or %NULL
+ *
+ * Completes an asynchronous call to jsonrpc_client_call_async().
+ *
+ * Returns: %TRUE if successful and @return_value is set, otherwise %FALSE and @error is set.
+ */
+gboolean
+jsonrpc_client_call_finish (JsonrpcClient  *self,
+                            GAsyncResult   *result,
+                            JsonNode      **return_value,
+                            GError        **error)
+{
+  g_autoptr(JsonNode) local_return_value = NULL;
+  gboolean ret;
+
+  g_return_val_if_fail (JSONRPC_IS_CLIENT (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  local_return_value = g_task_propagate_pointer (G_TASK (result), error);
+  ret = local_return_value != NULL;
+
+  if (return_value != NULL)
+    *return_value = g_steal_pointer (&local_return_value);
+
+  return ret;
+}
+
+GQuark
+jsonrpc_client_error_quark (void)
+{
+  return g_quark_from_static_string ("jsonrpc-client-error-quark");
+}
+
+static void
+jsonrpc_client_notification_write_cb (GObject      *object,
+                                      GAsyncResult *result,
+                                      gpointer      user_data)
+{
+  JsonrpcOutputStream *stream = (JsonrpcOutputStream *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (JSONRPC_IS_OUTPUT_STREAM (stream));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_TASK (task));
+
+  if (!jsonrpc_output_stream_write_message_finish (stream, result, &error))
+    g_task_return_error (task, g_steal_pointer (&error));
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+/**
+ * jsonrpc_client_notification:
+ * @self: A #JsonrpcClient
+ * @method: the name of the method to call
+ * @params: (transfer full) (nullable): A #JsonNode of parameters or %NULL
+ * @cancellable: (nullable): A #GCancellable or %NULL
+ *
+ * Synchronously calls @method with @params on the remote peer.
+ * This function will not wait or expect a reply from the peer.
+ *
+ * This function takes ownership of @params.
+ *
+ * Returns; %TRUE on success; otherwise %FALSE and @error is set.
+ */
+gboolean
+jsonrpc_client_notification (JsonrpcClient  *self,
+                             const gchar    *method,
+                             JsonNode       *params,
+                             GCancellable   *cancellable,
+                             GError        **error)
+{
+  JsonrpcClientPrivate *priv = jsonrpc_client_get_instance_private (self);
+  g_autoptr(JsonObject) object = NULL;
+  g_autoptr(JsonNode) node = NULL;
+  g_autoptr(GTask) task = NULL;
+
+  g_return_val_if_fail (JSONRPC_IS_CLIENT (self), FALSE);
+  g_return_val_if_fail (method != NULL, FALSE);
+  g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+
+  if (!jsonrpc_client_check_ready (self, error))
+    return FALSE;
+
+  if (params == NULL)
+    params = json_node_new (JSON_NODE_NULL);
+
+  object = json_object_new ();
+
+  json_object_set_string_member (object, "jsonrpc", "2.0");
+  json_object_set_string_member (object, "method", method);
+  json_object_set_member (object, "params", params);
+
+  node = json_node_new (JSON_NODE_OBJECT);
+  json_node_take_object (node, g_steal_pointer (&object));
+
+  return jsonrpc_output_stream_write_message (priv->output_stream, node, cancellable, error);
+}
+
+/**
+ * jsonrpc_client_notification_async:
+ * @self: A #JsonrpcClient
+ * @method: the name of the method to call
+ * @params: (transfer full) (nullable): A #JsonNode of parameters or %NULL
+ * @cancellable: (nullable): A #GCancellable or %NULL
+ *
+ * Asynchronously calls @method with @params on the remote peer.
+ * This function will not wait or expect a reply from the peer.
+ *
+ * This function is useful when the caller wants to be notified that
+ * the bytes have been delivered to the underlying stream. This does
+ * not indicate that the peer has received them.
+ *
+ * This function takes ownership of @params.
+ */
+void
+jsonrpc_client_notification_async (JsonrpcClient       *self,
+                                   const gchar         *method,
+                                   JsonNode            *params,
+                                   GCancellable        *cancellable,
+                                   GAsyncReadyCallback  callback,
+                                   gpointer             user_data)
+{
+  JsonrpcClientPrivate *priv = jsonrpc_client_get_instance_private (self);
+  g_autoptr(JsonObject) object = NULL;
+  g_autoptr(JsonNode) node = NULL;
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_return_if_fail (JSONRPC_IS_CLIENT (self));
+  g_return_if_fail (method != NULL);
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, jsonrpc_client_notification_async);
+
+  if (!jsonrpc_client_check_ready (self, &error))
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  if (params == NULL)
+    params = json_node_new (JSON_NODE_NULL);
+
+  object = json_object_new ();
+
+  json_object_set_string_member (object, "jsonrpc", "2.0");
+  json_object_set_string_member (object, "method", method);
+  json_object_set_member (object, "params", params);
+
+  node = json_node_new (JSON_NODE_OBJECT);
+  json_node_take_object (node, g_steal_pointer (&object));
+
+  jsonrpc_output_stream_write_message_async (priv->output_stream,
+                                             node,
+                                             cancellable,
+                                             jsonrpc_client_notification_write_cb,
+                                             g_steal_pointer (&task));
+}
+
+/**
+ * jsonrpc_client_notification_finish:
+ * @self: A #JsonrpcClient
+ *
+ * Completes an asynchronous call to jsonrpc_client_notification_async().
+ *
+ * Successful completion of this function only indicates that the request
+ * has been written to the underlying buffer, not that the peer has received
+ * the notification.
+ *
+ * Returns: %TRUE if the bytes have been flushed to the #GIOStream; otherwise
+ *   %FALSE and @error is set.
+ */
+gboolean
+jsonrpc_client_notification_finish (JsonrpcClient  *self,
+                                    GAsyncResult   *result,
+                                    GError        **error)
+{
+  g_return_val_if_fail (JSONRPC_IS_CLIENT (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+/**
+ * jsonrpc_client_close:
+ * @self: A #JsonrpcClient
+ *
+ * Closes the underlying streams and cancels any inflight operations of the
+ * #JsonrpcClient. This is important to call when you are done with the
+ * client so that any outstanding operations that have caused @self to
+ * hold additional references are cancelled.
+ *
+ * Failure to call this method results in a leak of #JsonrpcClient.
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
+ */
+gboolean
+jsonrpc_client_close (JsonrpcClient  *self,
+                      GCancellable   *cancellable,
+                      GError        **error)
+{
+  JsonrpcClientPrivate *priv = jsonrpc_client_get_instance_private (self);
+  g_autoptr(GHashTable) invocations = NULL;
+  g_autoptr(GError) local_error = NULL;
+  GHashTableIter iter;
+  GTask *task;
+
+  g_return_val_if_fail (JSONRPC_IS_CLIENT (self), FALSE);
+  g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+
+  if (!jsonrpc_client_check_ready (self, error))
+    return FALSE;
+
+  priv->in_shutdown = TRUE;
+
+  if (!g_cancellable_is_cancelled (priv->read_loop_cancellable))
+    g_cancellable_cancel (priv->read_loop_cancellable);
+
+  if (!g_output_stream_is_closed (G_OUTPUT_STREAM (priv->output_stream)))
+    {
+      if (!g_output_stream_close (G_OUTPUT_STREAM (priv->output_stream), cancellable, error))
+        return FALSE;
+    }
+
+  if (!g_input_stream_is_closed (G_INPUT_STREAM (priv->input_stream)))
+    {
+      if (!g_input_stream_close (G_INPUT_STREAM (priv->input_stream), cancellable, error))
+        return FALSE;
+    }
+
+  invocations = g_steal_pointer (&priv->invocations);
+  priv->invocations = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
+
+  local_error = g_error_new_literal (G_IO_ERROR,
+                                     G_IO_ERROR_CLOSED,
+                                     "The underlying stream was closed");
+
+  g_hash_table_iter_init (&iter, invocations);
+  while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&task))
+    g_task_return_error (task, g_error_copy (local_error));
+
+  return TRUE;
+}
+
+/**
+ * jsonrpc_client_close_async:
+ * @self: A #JsonrpcClient.
+ *
+ * Asynchronous version of jsonrpc_client_close()
+ *
+ * Currently this operation is implemented synchronously, but in the future may
+ * be converted to using asynchronous operations.
+ */
+void
+jsonrpc_client_close_async (JsonrpcClient       *self,
+                            GCancellable        *cancellable,
+                            GAsyncReadyCallback  callback,
+                            gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (JSONRPC_IS_CLIENT (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, jsonrpc_client_close_async);
+
+  /*
+   * In practice, none of our close operations should block (unless they were
+   * a FUSE fd or something like that. So we'll just perform them synchronously
+   * for now.
+   */
+  jsonrpc_client_close (self, cancellable, NULL);
+
+  g_task_return_boolean (task, TRUE);
+}
+
+/**
+ * jsonrpc_client_close_finish:
+ * @self A #JsonrpcClient.
+ *
+ * Completes an asynchronous request of jsonrpc_client_close_async().
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
+ */
+gboolean
+jsonrpc_client_close_finish (JsonrpcClient  *self,
+                             GAsyncResult   *result,
+                             GError        **error)
+{
+  g_return_val_if_fail (JSONRPC_IS_CLIENT (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
diff --git a/contrib/jsonrpc-glib/jsonrpc-client.h b/contrib/jsonrpc-glib/jsonrpc-client.h
new file mode 100644
index 0000000..3ec0fe9
--- /dev/null
+++ b/contrib/jsonrpc-glib/jsonrpc-client.h
@@ -0,0 +1,95 @@
+/* jsonrpc-client.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef JSONRPC_CLIENT_H
+#define JSONRPC_CLIENT_H
+
+#include <gio/gio.h>
+#include <json-glib/json-glib.h>
+
+G_BEGIN_DECLS
+
+#define JSONRPC_TYPE_CLIENT  (jsonrpc_client_get_type())
+#define JSONRPC_CLIENT_ERROR (jsonrpc_client_error_quark())
+
+G_DECLARE_DERIVABLE_TYPE (JsonrpcClient, jsonrpc_client, JSONRPC, CLIENT, GObject)
+
+struct _JsonrpcClientClass
+{
+  GObjectClass parent_class;
+
+  void (*notification) (JsonrpcClient *self,
+                        const gchar   *method_name,
+                        JsonNode      *params);
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
+};
+
+GQuark         jsonrpc_client_error_quark         (void);
+JsonrpcClient *jsonrpc_client_new                 (GIOStream            *io_stream);
+gboolean       jsonrpc_client_close               (JsonrpcClient        *self,
+                                                   GCancellable         *cancellable,
+                                                   GError              **error);
+void           jsonrpc_client_close_async         (JsonrpcClient        *self,
+                                                   GCancellable         *cancellable,
+                                                   GAsyncReadyCallback   callback,
+                                                   gpointer              user_data);
+gboolean       jsonrpc_client_close_finish        (JsonrpcClient        *self,
+                                                   GAsyncResult         *result,
+                                                   GError              **error);
+gboolean       jsonrpc_client_call                (JsonrpcClient        *self,
+                                                   const gchar          *method,
+                                                   JsonNode             *params,
+                                                   GCancellable         *cancellable,
+                                                   JsonNode            **return_value,
+                                                   GError              **error);
+void           jsonrpc_client_call_async          (JsonrpcClient        *self,
+                                                   const gchar          *method,
+                                                   JsonNode             *params,
+                                                   GCancellable         *cancellable,
+                                                   GAsyncReadyCallback   callback,
+                                                   gpointer              user_data);
+gboolean       jsonrpc_client_call_finish         (JsonrpcClient        *self,
+                                                   GAsyncResult         *result,
+                                                   JsonNode            **return_value,
+                                                   GError              **error);
+gboolean       jsonrpc_client_notification        (JsonrpcClient        *self,
+                                                   const gchar          *method,
+                                                   JsonNode             *params,
+                                                   GCancellable         *cancellable,
+                                                   GError              **error);
+void           jsonrpc_client_notification_async  (JsonrpcClient        *self,
+                                                   const gchar          *method,
+                                                   JsonNode             *params,
+                                                   GCancellable         *cancellable,
+                                                   GAsyncReadyCallback   callback,
+                                                   gpointer              user_data);
+gboolean       jsonrpc_client_notification_finish (JsonrpcClient        *self,
+                                                   GAsyncResult         *result,
+                                                   GError              **error);
+
+G_END_DECLS
+
+#endif /* JSONRPC_CLIENT_H */
diff --git a/contrib/jsonrpc-glib/jsonrpc-glib.h b/contrib/jsonrpc-glib/jsonrpc-glib.h
new file mode 100644
index 0000000..4a23404
--- /dev/null
+++ b/contrib/jsonrpc-glib/jsonrpc-glib.h
@@ -0,0 +1,37 @@
+/* jsonrpc-glib.h
+ *
+ * Copyright (C) 2016 Christian Hergert
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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 JSONRPC_GLIB_H
+#define JSONRPC_GLIB_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define JSONRPC_GLIB_INSIDE
+# include "jsonrpc-client.h"
+# include "jsonrpc-input-stream.h"
+# include "jsonrpc-output-stream.h"
+# include "jsonrpc-version.h"
+# include "jcon.h"
+#undef JSONRPC_GLIB_INSIDE
+
+G_END_DECLS
+
+#endif /* JSONRPC_GLIB_H */
diff --git a/contrib/jsonrpc-glib/jsonrpc-input-stream.c b/contrib/jsonrpc-glib/jsonrpc-input-stream.c
new file mode 100644
index 0000000..02f79cc
--- /dev/null
+++ b/contrib/jsonrpc-glib/jsonrpc-input-stream.c
@@ -0,0 +1,333 @@
+/* jsonrpc-input-stream.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "jsonrpc-input-stream"
+
+#include <errno.h>
+#include <string.h>
+
+#include "jsonrpc-input-stream.h"
+
+typedef struct
+{
+  gssize content_length;
+  gchar *buffer;
+  gint priority;
+} ReadState;
+
+typedef struct
+{
+  gssize max_size_bytes;
+} JsonrpcInputStreamPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (JsonrpcInputStream, jsonrpc_input_stream, G_TYPE_DATA_INPUT_STREAM)
+
+static gboolean jsonrpc_input_stream_debug;
+
+static void
+read_state_free (gpointer data)
+{
+  ReadState *state = data;
+
+  g_free (state->buffer);
+  g_slice_free (ReadState, state);
+}
+
+static void
+jsonrpc_input_stream_class_init (JsonrpcInputStreamClass *klass)
+{
+  jsonrpc_input_stream_debug = !!g_getenv ("JSONRPC_DEBUG");
+}
+
+static void
+jsonrpc_input_stream_init (JsonrpcInputStream *self)
+{
+  JsonrpcInputStreamPrivate *priv = jsonrpc_input_stream_get_instance_private (self);
+
+  /* 16 MB */
+  priv->max_size_bytes = 16 * 1024 * 1024;
+
+  g_data_input_stream_set_newline_type (G_DATA_INPUT_STREAM (self),
+                                        G_DATA_STREAM_NEWLINE_TYPE_ANY);
+}
+
+JsonrpcInputStream *
+jsonrpc_input_stream_new (GInputStream *base_stream)
+{
+  return g_object_new (JSONRPC_TYPE_INPUT_STREAM,
+                       "base-stream", base_stream,
+                       NULL);
+}
+
+static void
+jsonrpc_input_stream_read_body_cb (GObject      *object,
+                                   GAsyncResult *result,
+                                   gpointer      user_data)
+{
+  JsonrpcInputStream *self = (JsonrpcInputStream *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(JsonParser) parser = NULL;
+  g_autoptr(GError) error = NULL;
+  ReadState *state;
+  JsonNode *root;
+  gsize n_read;
+
+  g_assert (JSONRPC_IS_INPUT_STREAM (self));
+  g_assert (G_IS_TASK (task));
+
+  state = g_task_get_task_data (task);
+
+  if (!g_input_stream_read_all_finish (G_INPUT_STREAM (self), result, &n_read, &error))
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  if ((gssize)n_read != state->content_length)
+    {
+      g_task_return_new_error (task,
+                               G_IO_ERROR,
+                               G_IO_ERROR_INVALID_DATA,
+                               "Failed to read %"G_GSSIZE_FORMAT" bytes",
+                               state->content_length);
+      return;
+    }
+
+  state->buffer [state->content_length] = '\0';
+
+  if G_UNLIKELY (jsonrpc_input_stream_debug)
+    g_message ("<<< %s", state->buffer);
+
+  parser = json_parser_new_immutable ();
+
+  if (!json_parser_load_from_data (parser, state->buffer, state->content_length, &error))
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  if (NULL == (root = json_parser_get_root (parser)))
+    {
+      /*
+       * If we get back a NULL root node, that means that we got
+       * a short read (such as a closed stream).
+       */
+      g_task_return_new_error (task,
+                               G_IO_ERROR,
+                               G_IO_ERROR_CLOSED,
+                               "The peer did not send a reply");
+      return;
+    }
+
+  g_task_return_pointer (task, json_node_copy (root), (GDestroyNotify)json_node_unref);
+}
+
+static void
+jsonrpc_input_stream_read_headers_cb (GObject      *object,
+                                      GAsyncResult *result,
+                                      gpointer      user_data)
+{
+  JsonrpcInputStream *self = (JsonrpcInputStream *)object;
+  JsonrpcInputStreamPrivate *priv = jsonrpc_input_stream_get_instance_private (self);
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  g_autofree gchar *line = NULL;
+  GCancellable *cancellable = NULL;
+  ReadState *state;
+  gsize length = 0;
+
+  g_assert (JSONRPC_IS_INPUT_STREAM (self));
+  g_assert (G_IS_TASK (task));
+
+  line = g_data_input_stream_read_line_finish_utf8 (G_DATA_INPUT_STREAM (self), result, &length, &error);
+
+  if (line == NULL)
+    {
+      if (error != NULL)
+        g_task_return_error (task, g_steal_pointer (&error));
+      else
+        g_task_return_new_error (task,
+                                 G_IO_ERROR,
+                                 G_IO_ERROR_CLOSED,
+                                 "The peer has closed the stream");
+      return;
+    }
+
+  state = g_task_get_task_data (task);
+  cancellable = g_task_get_cancellable (task);
+
+  if (strncasecmp ("Content-Length: ", line, 16) == 0)
+    {
+      const gchar *lenptr = line + 16;
+      gint64 content_length;
+
+      content_length = g_ascii_strtoll (lenptr, NULL, 10);
+
+      if (((content_length == G_MININT64 || content_length == G_MAXINT64) && errno == ERANGE) ||
+          (content_length < 0) ||
+          (content_length > G_MAXSSIZE) ||
+          (content_length > priv->max_size_bytes))
+        {
+          g_task_return_new_error (task,
+                                   G_IO_ERROR,
+                                   G_IO_ERROR_INVALID_DATA,
+                                   "Invalid Content-Length received from peer");
+          return;
+        }
+
+      state->content_length = content_length;
+    }
+
+  /*
+   * If we are at the end of the headers, we can make progress towards
+   * parsing the JSON content. Otherwise we need to continue parsing
+   * the next header.
+   */
+
+  if (line[0] == '\0')
+    {
+      if (state->content_length <= 0)
+        {
+          g_task_return_new_error (task,
+                                   G_IO_ERROR,
+                                   G_IO_ERROR_INVALID_DATA,
+                                   "Invalid or missing Content-Length header from peer");
+          return;
+        }
+
+      state->buffer = g_malloc (state->content_length + 1);
+      g_input_stream_read_all_async (G_INPUT_STREAM (self),
+                                     state->buffer,
+                                     state->content_length,
+                                     state->priority,
+                                     cancellable,
+                                     jsonrpc_input_stream_read_body_cb,
+                                     g_steal_pointer (&task));
+      return;
+    }
+
+  g_data_input_stream_read_line_async (G_DATA_INPUT_STREAM (self),
+                                       state->priority,
+                                       cancellable,
+                                       jsonrpc_input_stream_read_headers_cb,
+                                       g_steal_pointer (&task));
+}
+
+void
+jsonrpc_input_stream_read_message_async (JsonrpcInputStream  *self,
+                                         GCancellable        *cancellable,
+                                         GAsyncReadyCallback  callback,
+                                         gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+  ReadState *state;
+
+  g_return_if_fail (JSONRPC_IS_INPUT_STREAM (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  state = g_slice_new0 (ReadState);
+  state->content_length = -1;
+  state->priority = G_PRIORITY_DEFAULT;
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, jsonrpc_input_stream_read_message_async);
+  g_task_set_task_data (task, state, read_state_free);
+
+  g_data_input_stream_read_line_async (G_DATA_INPUT_STREAM (self),
+                                       state->priority,
+                                       cancellable,
+                                       jsonrpc_input_stream_read_headers_cb,
+                                       g_steal_pointer (&task));
+}
+
+gboolean
+jsonrpc_input_stream_read_message_finish (JsonrpcInputStream  *self,
+                                          GAsyncResult        *result,
+                                          JsonNode           **node,
+                                          GError             **error)
+{
+  g_autoptr(JsonNode) local_node = NULL;
+  gboolean ret;
+
+  g_return_val_if_fail (JSONRPC_IS_INPUT_STREAM (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  local_node = g_task_propagate_pointer (G_TASK (result), error);
+  ret = local_node != NULL;
+
+  if (node != NULL)
+    *node = g_steal_pointer (&local_node);
+
+  return ret;
+}
+
+static void
+jsonrpc_input_stream_read_message_sync_cb (GObject      *object,
+                                           GAsyncResult *result,
+                                           gpointer      user_data)
+{
+  JsonrpcInputStream *self = (JsonrpcInputStream *)object;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(JsonNode) node = NULL;
+  GTask *task = user_data;
+
+  g_assert (JSONRPC_IS_INPUT_STREAM (self));
+  g_assert (G_IS_TASK (task));
+
+  if (!jsonrpc_input_stream_read_message_finish (self, result, &node, &error))
+    g_task_return_error (task, g_steal_pointer (&error));
+  else
+    g_task_return_pointer (task, g_steal_pointer (&node), (GDestroyNotify)json_node_unref);
+}
+
+gboolean
+jsonrpc_input_stream_read_message (JsonrpcInputStream  *self,
+                                   GCancellable        *cancellable,
+                                   JsonNode           **node,
+                                   GError             **error)
+{
+  g_autoptr(GMainContext) main_context = NULL;
+  g_autoptr(JsonNode) local_node = NULL;
+  g_autoptr(GTask) task = NULL;
+  gboolean ret;
+
+  g_return_val_if_fail (JSONRPC_IS_INPUT_STREAM (self), FALSE);
+  g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+
+  main_context = g_main_context_ref_thread_default ();
+
+  task = g_task_new (NULL, NULL, NULL, NULL);
+  g_task_set_source_tag (task, jsonrpc_input_stream_read_message);
+
+  jsonrpc_input_stream_read_message_async (self,
+                                           cancellable,
+                                           jsonrpc_input_stream_read_message_sync_cb,
+                                           task);
+
+  while (!g_task_get_completed (task))
+    g_main_context_iteration (main_context, TRUE);
+
+  local_node = g_task_propagate_pointer (task, error);
+  ret = local_node != NULL;
+
+  if (node != NULL)
+    *node = g_steal_pointer (&local_node);
+
+  return ret;
+}
+
diff --git a/contrib/jsonrpc-glib/jsonrpc-input-stream.h b/contrib/jsonrpc-glib/jsonrpc-input-stream.h
new file mode 100644
index 0000000..954118c
--- /dev/null
+++ b/contrib/jsonrpc-glib/jsonrpc-input-stream.h
@@ -0,0 +1,61 @@
+/* jsonrpc-input-stream.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef JSONRPC_INPUT_STREAM_H
+#define JSONRPC_INPUT_STREAM_H
+
+#include <gio/gio.h>
+#include <json-glib/json-glib.h>
+
+G_BEGIN_DECLS
+
+#define JSONRPC_TYPE_INPUT_STREAM (jsonrpc_input_stream_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (JsonrpcInputStream, jsonrpc_input_stream, JSONRPC, INPUT_STREAM, GDataInputStream)
+
+struct _JsonrpcInputStreamClass
+{
+  GDataInputStreamClass parent_class;
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
+};
+
+JsonrpcInputStream *jsonrpc_input_stream_new                 (GInputStream         *base_stream);
+gboolean            jsonrpc_input_stream_read_message        (JsonrpcInputStream   *self,
+                                                              GCancellable         *cancellable,
+                                                              JsonNode            **node,
+                                                              GError              **error);
+void                jsonrpc_input_stream_read_message_async  (JsonrpcInputStream   *self,
+                                                              GCancellable         *cancellable,
+                                                              GAsyncReadyCallback   callback,
+                                                              gpointer              user_data);
+gboolean            jsonrpc_input_stream_read_message_finish (JsonrpcInputStream   *self,
+                                                              GAsyncResult         *result,
+                                                              JsonNode            **node,
+                                                              GError              **error);
+
+G_END_DECLS
+
+#endif /* JSONRPC_INPUT_STREAM_H */
diff --git a/contrib/jsonrpc-glib/jsonrpc-output-stream.c b/contrib/jsonrpc-glib/jsonrpc-output-stream.c
new file mode 100644
index 0000000..e0b3348
--- /dev/null
+++ b/contrib/jsonrpc-glib/jsonrpc-output-stream.c
@@ -0,0 +1,305 @@
+/* jsonrpc-output-stream.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "jsonrpc-output-stream"
+
+#include <string.h>
+
+#include "jsonrpc-output-stream.h"
+#include "jsonrpc-version.h"
+
+typedef struct
+{
+  GQueue queue;
+} JsonrpcOutputStreamPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (JsonrpcOutputStream, jsonrpc_output_stream, G_TYPE_DATA_OUTPUT_STREAM)
+
+static void jsonrpc_output_stream_write_message_async_cb (GObject      *object,
+                                                          GAsyncResult *result,
+                                                          gpointer      user_data);
+
+static gboolean jsonrpc_output_stream_debug;
+
+static void
+jsonrpc_output_stream_finalize (GObject *object)
+{
+  JsonrpcOutputStream *self = (JsonrpcOutputStream *)object;
+  JsonrpcOutputStreamPrivate *priv = jsonrpc_output_stream_get_instance_private (self);
+
+  g_queue_foreach (&priv->queue, (GFunc)g_object_unref, NULL);
+  g_queue_clear (&priv->queue);
+
+  G_OBJECT_CLASS (jsonrpc_output_stream_parent_class)->finalize (object);
+}
+
+static void
+jsonrpc_output_stream_class_init (JsonrpcOutputStreamClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = jsonrpc_output_stream_finalize;
+
+  jsonrpc_output_stream_debug = !!g_getenv ("JSONRPC_DEBUG");
+}
+
+static void
+jsonrpc_output_stream_init (JsonrpcOutputStream *self)
+{
+  JsonrpcOutputStreamPrivate *priv = jsonrpc_output_stream_get_instance_private (self);
+
+  g_queue_init (&priv->queue);
+}
+
+static GBytes *
+jsonrpc_output_stream_create_bytes (JsonrpcOutputStream  *self,
+                                    JsonNode             *node,
+                                    GError              **error)
+{
+  g_autofree gchar *str = NULL;
+  GString *message;
+  gsize len;
+
+  g_assert (JSONRPC_IS_OUTPUT_STREAM (self));
+  g_assert (node != NULL);
+
+  if (!JSON_NODE_HOLDS_OBJECT (node) && !JSON_NODE_HOLDS_ARRAY (node))
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_INVAL,
+                   "node must be an array or object");
+      return FALSE;
+    }
+
+  str = json_to_string (node, FALSE);
+  len = strlen (str);
+
+  if G_UNLIKELY (jsonrpc_output_stream_debug)
+    g_message (">>> %s", str);
+
+  /*
+   * Try to allocate our buffer in a single shot. Sadly we can't serialize
+   * JsonNode directly into a GString or we could remove the double
+   * allocation going on here.
+   */
+  message = g_string_sized_new (len + 32);
+
+  g_string_append_printf (message, "Content-Length: %"G_GSIZE_FORMAT"\r\n\r\n", len);
+  g_string_append_len (message, str, len);
+
+  len = message->len;
+
+  return g_bytes_new_take (g_string_free (message, FALSE), len);
+}
+
+JsonrpcOutputStream *
+jsonrpc_output_stream_new (GOutputStream *base_stream)
+{
+  return g_object_new (JSONRPC_TYPE_OUTPUT_STREAM,
+                       "base-stream", base_stream,
+                       NULL);
+}
+
+static void
+jsonrpc_output_stream_fail_pending (JsonrpcOutputStream *self)
+{
+  JsonrpcOutputStreamPrivate *priv = jsonrpc_output_stream_get_instance_private (self);
+  const GList *iter;
+  GList *list;
+
+  g_assert (JSONRPC_IS_OUTPUT_STREAM (self));
+
+  list = priv->queue.head;
+
+  priv->queue.head = NULL;
+  priv->queue.tail = NULL;
+  priv->queue.length = 0;
+
+  for (iter = list; iter != NULL; iter = iter->next)
+    {
+      g_autoptr(GTask) task = iter->data;
+
+      g_task_return_new_error (task,
+                               G_IO_ERROR,
+                               G_IO_ERROR_FAILED,
+                               "Task failed due to stream failure");
+    }
+
+  g_list_free (list);
+}
+
+static void
+jsonrpc_output_stream_pump (JsonrpcOutputStream *self)
+{
+  JsonrpcOutputStreamPrivate *priv = jsonrpc_output_stream_get_instance_private (self);
+  g_autoptr(GTask) task = NULL;
+  const guint8 *data;
+  GCancellable *cancellable;
+  GBytes *bytes;
+  gsize len;
+
+  g_assert (JSONRPC_IS_OUTPUT_STREAM (self));
+
+  if (priv->queue.length == 0)
+    return;
+
+  task = g_queue_pop_head (&priv->queue);
+  bytes = g_task_get_task_data (task);
+  data = g_bytes_get_data (bytes, &len);
+  cancellable = g_task_get_cancellable (task);
+
+  g_output_stream_write_all_async (G_OUTPUT_STREAM (self),
+                                   data,
+                                   len,
+                                   G_PRIORITY_DEFAULT,
+                                   cancellable,
+                                   jsonrpc_output_stream_write_message_async_cb,
+                                   g_steal_pointer (&task));
+}
+
+static void
+jsonrpc_output_stream_write_message_async_cb (GObject      *object,
+                                              GAsyncResult *result,
+                                              gpointer      user_data)
+{
+  GOutputStream *stream = (GOutputStream *)object;
+  JsonrpcOutputStream *self;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GTask) task = user_data;
+  GBytes *bytes;
+  gsize n_written;
+
+  g_assert (G_IS_OUTPUT_STREAM (stream));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_TASK (task));
+  self = g_task_get_source_object (task);
+  g_assert (JSONRPC_IS_OUTPUT_STREAM (self));
+
+  if (!g_output_stream_write_all_finish (stream, result, &n_written, &error))
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  bytes = g_task_get_task_data (task);
+
+  if (g_bytes_get_size (bytes) != n_written)
+    {
+      g_task_return_new_error (task,
+                               G_IO_ERROR,
+                               G_IO_ERROR_CLOSED,
+                               "Failed to write all bytes to peer");
+      jsonrpc_output_stream_fail_pending (self);
+      return;
+    }
+
+  g_task_return_boolean (task, TRUE);
+
+  jsonrpc_output_stream_pump (self);
+}
+
+void
+jsonrpc_output_stream_write_message_async (JsonrpcOutputStream *self,
+                                           JsonNode            *node,
+                                           GCancellable        *cancellable,
+                                           GAsyncReadyCallback  callback,
+                                           gpointer             user_data)
+{
+  JsonrpcOutputStreamPrivate *priv = jsonrpc_output_stream_get_instance_private (self);
+  g_autoptr(GBytes) bytes = NULL;
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_return_if_fail (JSONRPC_IS_OUTPUT_STREAM (self));
+  g_return_if_fail (node != NULL);
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, jsonrpc_output_stream_write_message_async);
+
+  if (NULL == (bytes = jsonrpc_output_stream_create_bytes (self, node, &error)))
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  g_task_set_task_data (task, g_steal_pointer (&bytes), (GDestroyNotify)g_bytes_unref);
+  g_queue_push_tail (&priv->queue, g_steal_pointer (&task));
+  jsonrpc_output_stream_pump (self);
+}
+
+gboolean
+jsonrpc_output_stream_write_message_finish (JsonrpcOutputStream  *self,
+                                            GAsyncResult         *result,
+                                            GError              **error)
+{
+  g_return_val_if_fail (JSONRPC_IS_OUTPUT_STREAM (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+jsonrpc_output_stream_write_message_sync_cb (GObject      *object,
+                                             GAsyncResult *result,
+                                             gpointer      user_data)
+{
+  JsonrpcOutputStream *self = (JsonrpcOutputStream *)object;
+  GTask *task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (JSONRPC_IS_OUTPUT_STREAM (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_TASK (task));
+
+  if (!jsonrpc_output_stream_write_message_finish (self, result, &error))
+    g_task_return_error (task, g_steal_pointer (&error));
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+gboolean
+jsonrpc_output_stream_write_message (JsonrpcOutputStream  *self,
+                                     JsonNode             *node,
+                                     GCancellable         *cancellable,
+                                     GError              **error)
+{
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(GMainContext) main_context = NULL;
+
+  g_return_val_if_fail (JSONRPC_IS_OUTPUT_STREAM (self), FALSE);
+  g_return_val_if_fail (node != NULL, FALSE);
+  g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
+
+  main_context = g_main_context_ref_thread_default ();
+
+  task = g_task_new (NULL, NULL, NULL, NULL);
+  g_task_set_source_tag (task, jsonrpc_output_stream_write_message);
+
+  jsonrpc_output_stream_write_message_async (self,
+                                             node,
+                                             cancellable,
+                                             jsonrpc_output_stream_write_message_sync_cb,
+                                             task);
+
+  while (!g_task_get_completed (task))
+    g_main_context_iteration (main_context, TRUE);
+
+  return g_task_propagate_boolean (task, error);
+}
diff --git a/contrib/jsonrpc-glib/jsonrpc-output-stream.h b/contrib/jsonrpc-glib/jsonrpc-output-stream.h
new file mode 100644
index 0000000..c4929cd
--- /dev/null
+++ b/contrib/jsonrpc-glib/jsonrpc-output-stream.h
@@ -0,0 +1,65 @@
+/* jsonrpc-output-stream.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 2.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file 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 Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef JSONRPC_OUTPUT_STREAM_H
+#define JSONRPC_OUTPUT_STREAM_H
+
+#include <gio/gio.h>
+#include <json-glib/json-glib.h>
+
+G_BEGIN_DECLS
+
+#define JSONRPC_TYPE_OUTPUT_STREAM (jsonrpc_output_stream_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (JsonrpcOutputStream, jsonrpc_output_stream, JSONRPC, OUTPUT_STREAM, 
GDataOutputStream)
+
+struct _JsonrpcOutputStreamClass
+{
+  GDataOutputStreamClass parent_class;
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
+  gpointer _reserved9;
+  gpointer _reserved10;
+  gpointer _reserved11;
+  gpointer _reserved12;
+};
+
+JsonrpcOutputStream *jsonrpc_output_stream_new                  (GOutputStream        *base_stream);
+gboolean             jsonrpc_output_stream_write_message        (JsonrpcOutputStream  *self,
+                                                                 JsonNode             *node,
+                                                                 GCancellable         *cancellable,
+                                                                 GError              **error);
+void                 jsonrpc_output_stream_write_message_async  (JsonrpcOutputStream  *self,
+                                                                 JsonNode             *node,
+                                                                 GCancellable         *cancellable,
+                                                                 GAsyncReadyCallback   callback,
+                                                                 gpointer              user_data);
+gboolean             jsonrpc_output_stream_write_message_finish (JsonrpcOutputStream  *self,
+                                                                 GAsyncResult         *result,
+                                                                 GError              **error);
+
+G_END_DECLS
+
+#endif /* JSONRPC_OUTPUT_STREAM_H */
diff --git a/contrib/jsonrpc-glib/jsonrpc-version.h.in b/contrib/jsonrpc-glib/jsonrpc-version.h.in
new file mode 100644
index 0000000..1f0768a
--- /dev/null
+++ b/contrib/jsonrpc-glib/jsonrpc-version.h.in
@@ -0,0 +1,98 @@
+/* jsonrpc-version.h.in
+ *
+ * Copyright (C) 2016 Christian Hergert
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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 JSONRPC_GLIB_VERSION_H
+#define JSONRPC_GLIB_VERSION_H
+
+#if !defined(JSONRPC_GLIB_INSIDE) && !defined(JSONRPC_GLIB_COMPILATION)
+# error "Only <jsonrpc-glib.h> can be included directly."
+#endif
+
+/**
+ * SECTION:jsonrpc-version
+ * @short_description: jsonrpc-glib version checking
+ *
+ * jsonrpc-glib provides macros to check the version of the library
+ * at compile-time
+ */
+
+/**
+ * JSONRPC_MAJOR_VERSION:
+ *
+ * jsonrpc-glib major version component (e.g. 1 if %JSONRPC_VERSION is 1.2.3)
+ */
+#define JSONRPC_MAJOR_VERSION (@MAJOR_VERSION@)
+
+/**
+ * JSONRPC_MINOR_VERSION:
+ *
+ * jsonrpc-glib minor version component (e.g. 2 if %JSONRPC_VERSION is 1.2.3)
+ */
+#define JSONRPC_MINOR_VERSION (@MINOR_VERSION@)
+
+/**
+ * JSONRPC_MICRO_VERSION:
+ *
+ * jsonrpc-glib micro version component (e.g. 3 if %JSONRPC_VERSION is 1.2.3)
+ */
+#define JSONRPC_MICRO_VERSION (@MICRO_VERSION@)
+
+/**
+ * JSONRPC_VERSION
+ *
+ * jsonrpc-glib version.
+ */
+#define JSONRPC_VERSION (@VERSION@)
+
+/**
+ * JSONRPC_VERSION_S:
+ *
+ * jsonrpc-glib version, encoded as a string, useful for printing and
+ * concatenation.
+ */
+#define JSONRPC_VERSION_S "@VERSION@"
+
+#define JSONRPC_ENCODE_VERSION(major,minor,micro) \
+        ((major) << 24 | (minor) << 16 | (micro) << 8)
+
+/**
+ * JSONRPC_VERSION_HEX:
+ *
+ * jsonrpc-glib version, encoded as an hexadecimal number, useful for
+ * integer comparisons.
+ */
+#define JSONRPC_VERSION_HEX \
+        (JSONRPC_ENCODE_VERSION (JSONRPC_MAJOR_VERSION, JSONRPC_MINOR_VERSION, JSONRPC_MICRO_VERSION))
+
+/**
+ * JSONRPC_CHECK_VERSION:
+ * @major: required major version
+ * @minor: required minor version
+ * @micro: required micro version
+ *
+ * Compile-time version checking. Evaluates to %TRUE if the version
+ * of jsonrpc-glib is greater than the required one.
+ */
+#define JSONRPC_CHECK_VERSION(major,minor,micro)   \
+        (JSONRPC_MAJOR_VERSION > (major) || \
+         (JSONRPC_MAJOR_VERSION == (major) && JSONRPC_MINOR_VERSION > (minor)) || \
+         (JSONRPC_MAJOR_VERSION == (major) && JSONRPC_MINOR_VERSION == (minor) && \
+          JSONRPC_MICRO_VERSION >= (micro)))
+
+#endif /* JSONRPC_GLIB_VERSION_H */
diff --git a/libide/Makefile.am b/libide/Makefile.am
index acbecca..2e41442 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -492,12 +492,14 @@ libide_1_0_la_includes =                             \
        -DBUILDDIR=\""${abs_top_builddir}"\"         \
        -DSRCDIR=\""${abs_top_srcdir}"\"             \
        -I$(srcdir)                                  \
+       -I$(top_builddir)/contrib/jsonrpc-glib       \
        -I$(top_builddir)/contrib/pnl                \
        -I$(top_builddir)/contrib/tmpl               \
        -I$(top_builddir)/data/icons/hicolor         \
        -I$(top_builddir)/libide                     \
        -I$(top_srcdir)/contrib/egg                  \
        -I$(top_srcdir)/contrib/gd                   \
+       -I$(top_srcdir)/contrib/jsonrpc-glib         \
        -I$(top_srcdir)/contrib/nautilus             \
        -I$(top_srcdir)/contrib/pnl                  \
        -I$(top_srcdir)/contrib/search               \
@@ -532,18 +534,19 @@ libide_1_0_la_LDFLAGS =                         \
        -export-symbols-regex '^(ide_|_ide_).*' \
        $(NULL)
 
-libide_1_0_la_LIBADD =                                       \
-       $(LIBIDE_LIBS)                                       \
-       $(SHM_LIB)                                           \
-       -lm                                                  \
-       $(top_builddir)/data/icons/hicolor/libicons.la       \
-       $(top_builddir)/contrib/egg/libegg-private.la        \
-       $(top_builddir)/contrib/gd/libgd.la                  \
-       $(top_builddir)/contrib/nautilus/libnautilus.la      \
-       $(top_builddir)/contrib/pnl/libpanel-gtk.la          \
-       $(top_builddir)/contrib/search/libsearch.la          \
-       $(top_builddir)/contrib/tmpl/libtemplate-glib-1.0.la \
-       $(top_builddir)/contrib/xml/libxml.la                \
+libide_1_0_la_LIBADD =                                          \
+       $(LIBIDE_LIBS)                                          \
+       $(SHM_LIB)                                              \
+       -lm                                                     \
+       $(top_builddir)/data/icons/hicolor/libicons.la          \
+       $(top_builddir)/contrib/egg/libegg-private.la           \
+       $(top_builddir)/contrib/gd/libgd.la                     \
+       $(top_builddir)/contrib/jsonrpc-glib/libjsonrpc-glib.la \
+       $(top_builddir)/contrib/nautilus/libnautilus.la         \
+       $(top_builddir)/contrib/pnl/libpanel-gtk.la             \
+       $(top_builddir)/contrib/search/libsearch.la             \
+       $(top_builddir)/contrib/tmpl/libtemplate-glib-1.0.la    \
+       $(top_builddir)/contrib/xml/libxml.la                   \
        $(NULL)
 
 if ENABLE_WEBKIT


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