[gjs] Add new GBytes API and conversions



commit 94cb4e9a2da33e83632f8641e02513b55571cea1
Author: Colin Walters <walters verbum org>
Date:   Mon Nov 5 18:15:08 2012 -0500

    Add new GBytes API and conversions
    
    GLib's annotations for GBytes have been updated so one can now do:
    
    GLib.Bytes.new([1,2,3])
    
    This allows us to construct them natively, which is nice.  But we
    should go further.
    
    Now, we should have the ability to convert them to a JS array.  In
    gjs, we have the byteArray class which "looks like" a JS array, but
    actually points to a native array, since it's a lot more efficient.
    
    So, change the internal byteArray class so that it supports holding
    a GBytes *or* a GByteArray.  It's used as a container as well as
    a bridge from JS -> native conversions for the overrides code.
    
    Add an override method "toArray" that allows converting a GBytes to a
    "gjs byteArray", which in turn has automatic conversions to
    e.g. guint8+len pair as used from C.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=685431

 gi/arg.c                     |   44 ++++++---
 gjs/byteArray.c              |  213 ++++++++++++++++++++++++++++++++++++++----
 gjs/byteArray.h              |   15 +++
 modules/overrides/GLib.js    |    4 +
 test/js/testGIMarshalling.js |   36 +++++++
 5 files changed, 279 insertions(+), 33 deletions(-)
---
diff --git a/gi/arg.c b/gi/arg.c
index 75c1525..ef8ca54 100644
--- a/gi/arg.c
+++ b/gi/arg.c
@@ -1365,9 +1365,12 @@ gjs_value_to_g_argument(JSContext      *context,
                 if ((interface_type == GI_INFO_TYPE_STRUCT || interface_type == GI_INFO_TYPE_BOXED) &&
                     /* We special case Closures later, so skip them here */
                     !g_type_is_a(gtype, G_TYPE_CLOSURE)) {
+                    JSObject *obj = JSVAL_TO_OBJECT(value);
 
-                    /* special case GError too */
-                    if (g_type_is_a(gtype, G_TYPE_ERROR)) {
+                    if (g_type_is_a(gtype, G_TYPE_BYTES)
+                        && gjs_typecheck_bytearray(context, obj, FALSE)) {
+                        arg->v_pointer = gjs_byte_array_get_bytes(context, obj);
+                    } else if (g_type_is_a(gtype, G_TYPE_ERROR)) {
                         if (!gjs_typecheck_gerror(context, JSVAL_TO_OBJECT(value), JS_TRUE)) {
                             arg->v_pointer = NULL;
                             wrong = TRUE;
@@ -1607,21 +1610,36 @@ gjs_value_to_g_argument(JSContext      *context,
         gpointer data;
         gsize length;
         GIArrayType array_type = g_type_info_get_array_type(type_info);
+        GITypeTag element_type;
+        GITypeInfo *param_info;
+        gboolean bytearray_fastpath = FALSE;
 
-        if (array_type == GI_ARRAY_TYPE_BYTE_ARRAY && JSVAL_IS_OBJECT(value)) {
-            GByteArray *byte_array;
+        param_info = g_type_info_get_param_type(type_info, 0);
+        element_type = g_type_info_get_tag(param_info);
 
-            byte_array = gjs_byte_array_get_byte_array(context, JSVAL_TO_OBJECT(value));
-            if (byte_array) {
-                /* extra reference will be removed by gjs_g_argument_release */
-                arg->v_pointer = g_byte_array_ref(byte_array);
-                break;
+        /* First, let's handle the case where we're passed an instance
+         * of our own byteArray class.
+         */
+        if (JSVAL_IS_OBJECT(value) &&
+            gjs_typecheck_bytearray(context,
+                                    JSVAL_TO_OBJECT(value),
+                                    FALSE))
+            {
+                JSObject *bytearray_obj = JSVAL_TO_OBJECT(value);
+                if (array_type == GI_ARRAY_TYPE_BYTE_ARRAY) {
+                    arg->v_pointer = gjs_byte_array_get_byte_array(context, bytearray_obj);
+                    break;
+                } else if (array_type == GI_ARRAY_TYPE_C && 
+                           (element_type == GI_TYPE_TAG_UINT8 || element_type == GI_TYPE_TAG_INT8)) {
+                    gjs_byte_array_peek_data(context, bytearray_obj, data, &length);
+                    bytearray_fastpath = TRUE;
+                } else {
+                    /* Fall through, !handled */
+                }
             }
-            /* otherwise this is not a JS ByteArray, so fall through to extracting
-               all elements from the String or the Array */
-        }
 
-        if (!gjs_array_to_explicit_array_internal(context,
+        if (!bytearray_fastpath &&
+            !gjs_array_to_explicit_array_internal(context,
                                                   value,
                                                   type_info,
                                                   arg_name,
diff --git a/gjs/byteArray.c b/gjs/byteArray.c
index 13c4fcf..e5eb7c4 100644
--- a/gjs/byteArray.c
+++ b/gjs/byteArray.c
@@ -25,12 +25,15 @@
 #include <string.h>
 #include <glib.h>
 #include "byteArray.h"
+#include "../gi/boxed.h"
 #include <gjs/gjs-module.h>
 #include <gjs/compat.h>
+#include <girepository.h>
 #include <util/log.h>
 
 typedef struct {
     GByteArray *array;
+    GBytes     *bytes;
 } ByteArrayInstance;
 
 static struct JSClass gjs_byte_array_class;
@@ -75,6 +78,13 @@ static struct JSClass gjs_byte_array_class = {
     NULL, NULL, NULL, NULL, NULL
 };
 
+JSBool
+gjs_typecheck_bytearray(JSContext     *context,
+                        JSObject      *object,
+                        JSBool         throw)
+{
+    return do_base_typecheck(context, object, throw);
+}
 
 static JSBool
 gjs_value_from_gsize(JSContext         *context,
@@ -89,6 +99,28 @@ gjs_value_from_gsize(JSContext         *context,
     }
 }
 
+static void
+byte_array_ensure_array (ByteArrayInstance  *priv)
+{
+    if (priv->bytes) {
+        priv->array = g_bytes_unref_to_array(priv->bytes);
+        priv->bytes = NULL;
+    } else {
+        g_assert(priv->array);
+    }
+}
+
+static void
+byte_array_ensure_gbytes (ByteArrayInstance  *priv)
+{
+    if (priv->array) {
+        priv->bytes = g_byte_array_free_to_bytes(priv->array);
+        priv->array = NULL;
+    } else {
+        g_assert(priv->bytes);
+    }
+}
+
 static JSBool
 gjs_value_to_gsize(JSContext         *context,
                    jsval              value,
@@ -151,18 +183,20 @@ byte_array_get_index(JSContext         *context,
                      gsize              idx,
                      jsval             *value_p)
 {
-    guint8 v;
+    gsize len;
+    guint8 *data;
+    
+    gjs_byte_array_peek_data(context, obj, &data, &len);
 
-    if (idx >= priv->array->len) {
+    if (idx >= len) {
         gjs_throw(context,
-                  "Index %" G_GSIZE_FORMAT " is out of range for ByteArray length %u",
+                  "Index %" G_GSIZE_FORMAT " is out of range for ByteArray length %lu",
                   idx,
-                  priv->array->len);
+                  (unsigned long)len);
         return JS_FALSE;
     }
 
-    v = g_array_index(priv->array, guint8, idx);
-    *value_p = INT_TO_JSVAL(v);
+    *value_p = INT_TO_JSVAL(data[idx]);
 
     return JS_TRUE;
 }
@@ -183,7 +217,7 @@ byte_array_get_prop(JSContext *context,
 
     if (priv == NULL)
         return JS_FALSE; /* wrong class passed in */
-    if (priv->array == NULL)
+    if (priv->array == NULL && priv->bytes == NULL)
         return JS_TRUE; /* prototype, not an instance. */
 
     if (!JS_IdToValue(context, id, &id_value))
@@ -211,16 +245,20 @@ byte_array_length_getter(JSContext *context,
                          jsval     *value_p)
 {
     ByteArrayInstance *priv;
+    gsize len;
 
     priv = priv_from_js(context, obj);
 
     if (priv == NULL)
         return JS_FALSE; /* wrong class passed in */
-    if (priv->array == NULL)
+    if (priv->array == NULL && priv->bytes == NULL)
         return JS_TRUE; /* prototype, not an instance. */
 
-    return gjs_value_from_gsize(context, priv->array->len,
-                                value_p);
+    if (priv->array != NULL)
+        len = priv->array->len;
+    else if (priv->bytes != NULL)
+        len = g_bytes_get_size (priv->bytes);
+    return gjs_value_from_gsize(context, len, value_p);
 }
 
 static JSBool
@@ -237,9 +275,11 @@ byte_array_length_setter(JSContext *context,
 
     if (priv == NULL)
         return JS_FALSE; /* wrong class passed in */
-    if (priv->array == NULL)
+    if (priv->array == NULL && priv->bytes == NULL)
         return JS_TRUE; /* prototype, not an instance. */
 
+    byte_array_ensure_array(priv);
+
     if (!gjs_value_to_gsize(context, *value_p,
                             &len)) {
         gjs_throw(context,
@@ -264,6 +304,8 @@ byte_array_set_index(JSContext         *context,
         return JS_FALSE;
     }
 
+    byte_array_ensure_array(priv);
+
     /* grow the array if necessary */
     if (idx >= priv->array->len) {
         g_byte_array_set_size(priv->array,
@@ -297,12 +339,14 @@ byte_array_set_prop(JSContext *context,
 
     if (priv == NULL)
         return JS_FALSE; /* wrong class passed in */
-    if (priv->array == NULL)
+    if (priv->array == NULL && priv->bytes == NULL)
         return JS_TRUE; /* prototype, not an instance. */
 
     if (!JS_IdToValue(context, id, &id_value))
         return JS_FALSE;
 
+    byte_array_ensure_array(priv);
+
     /* First handle array indexing */
     if (JSVAL_IS_NUMBER(id_value)) {
         gsize idx;
@@ -352,12 +396,14 @@ byte_array_new_resolve(JSContext *context,
 
     if (priv == NULL)
         return JS_FALSE; /* wrong class passed in */
-    if (priv->array == NULL)
+    if (priv->array == NULL && priv->bytes == NULL)
         return JS_TRUE; /* prototype, not an instance. */
 
     if (!JS_IdToValue(context, id, &id_val))
         return JS_FALSE;
 
+    byte_array_ensure_array(priv);
+
     if (JSVAL_IS_NUMBER(id_val)) {
         gsize idx;
         if (!gjs_value_to_gsize(context, id_val, &idx))
@@ -455,6 +501,8 @@ byte_array_finalize(JSContext *context,
     if (priv->array) {
         g_byte_array_free(priv->array, TRUE);
         priv->array = NULL;
+    } else if (priv->bytes) {
+        g_clear_pointer(&priv->bytes, g_bytes_unref);
     }
 
     g_slice_free(ByteArrayInstance, priv);
@@ -478,6 +526,8 @@ to_string_func(JSContext *context,
     if (priv == NULL)
         return JS_FALSE; /* wrong class passed in */
 
+    byte_array_ensure_array(priv);
+
     encoding_is_utf8 = TRUE;
     if (argc >= 1 &&
         JSVAL_IS_STRING(argv[0])) {
@@ -559,6 +609,30 @@ to_string_func(JSContext *context,
     }
 }
 
+static JSBool
+to_gbytes_func(JSContext *context,
+               uintN      argc,
+               jsval     *vp)
+{
+    JSObject *object = JS_THIS_OBJECT(context, vp);
+    ByteArrayInstance *priv;
+    JSObject *ret_bytes_obj;
+    GIBaseInfo *gbytes_info;
+
+    priv = priv_from_js(context, object);
+    if (priv == NULL)
+        return JS_FALSE; /* wrong class passed in */
+    
+    byte_array_ensure_gbytes(priv);
+
+    gbytes_info = g_irepository_find_by_gtype(NULL, G_TYPE_BYTES);
+    ret_bytes_obj = gjs_boxed_from_c_struct(context, (GIStructInfo*)gbytes_info,
+                                            priv->bytes, GJS_BOXED_CREATION_NONE);
+
+    JS_SET_RVAL(context, vp, OBJECT_TO_JSVAL(ret_bytes_obj));
+    return JS_TRUE;
+}
+
 static JSObject*
 byte_array_new(JSContext *context)
 {
@@ -568,7 +642,6 @@ byte_array_new(JSContext *context)
     array = JS_NewObject(context, &gjs_byte_array_class, gjs_byte_array_prototype, NULL);
 
     priv = g_slice_new0(ByteArrayInstance);
-    priv->array = gjs_g_byte_array_new(0);
 
     g_assert(priv_from_js(context, array) == NULL);
     JS_SetPrivate(context, array, priv);
@@ -600,6 +673,8 @@ from_string_func(JSContext *context,
 
     g_assert(argc > 0); /* because we specified min args 1 */
 
+    priv->array = gjs_g_byte_array_new(0);
+
     if (!JSVAL_IS_STRING(argv[0])) {
         gjs_throw(context,
                   "byteArray.fromString() called with non-string as first arg");
@@ -708,6 +783,8 @@ from_array_func(JSContext *context,
 
     g_assert(argc > 0); /* because we specified min args 1 */
 
+    priv->array = gjs_g_byte_array_new(0);
+
     if (!JS_IsArrayObject(context, JSVAL_TO_OBJECT(argv[0]))) {
         gjs_throw(context,
                   "byteArray.fromArray() called with non-array as first arg");
@@ -750,6 +827,40 @@ from_array_func(JSContext *context,
     return ret;
 }
 
+static JSBool
+from_gbytes_func(JSContext *context,
+                 uintN      argc,
+                 jsval     *vp)
+{
+    jsval *argv = JS_ARGV(context, vp);
+    JSObject *bytes_obj;
+    GBytes *gbytes;
+    ByteArrayInstance *priv;
+    JSObject *obj;
+    JSBool ret = JS_FALSE;
+
+    if (!gjs_parse_args(context, "overrides_gbytes_to_array", "o", argc, argv,
+                        "bytes", &bytes_obj))
+        return JS_FALSE;
+
+    if (!gjs_typecheck_boxed(context, bytes_obj, NULL, G_TYPE_BYTES, TRUE))
+        return JS_FALSE;
+
+    gbytes = gjs_c_struct_from_boxed(context, bytes_obj);
+
+    obj = byte_array_new(context);
+    if (obj == NULL)
+        return JS_FALSE;
+    priv = priv_from_js(context, obj);
+    g_assert (priv != NULL);
+
+    priv->bytes = g_bytes_ref(gbytes);
+
+    ret = JS_TRUE;
+    JS_SET_RVAL(context, vp, OBJECT_TO_JSVAL(obj));
+    return ret;
+}
+
 /* Ensure that the module and class objects exists, and that in turn
  * ensures that JS_InitClass has been called, causing
  * gjs_byte_array_prototype to be valid for the later call to
@@ -798,17 +909,77 @@ gjs_byte_array_from_byte_array (JSContext *context,
     return object;
 }
 
-GByteArray*
-gjs_byte_array_get_byte_array (JSContext  *context,
-                               JSObject   *object)
+JSObject *
+gjs_byte_array_from_bytes (JSContext *context,
+                           GBytes    *bytes)
 {
+    JSObject *object;
     ByteArrayInstance *priv;
 
+    g_return_val_if_fail(context != NULL, NULL);
+    g_return_val_if_fail(bytes != NULL, NULL);
+
+    byte_array_ensure_initialized (context);
+
+    object = JS_NewObject(context, &gjs_byte_array_class,
+                          gjs_byte_array_prototype, NULL);
+    if (!object) {
+        gjs_throw(context, "failed to create byte array");
+        return NULL;
+    }
+
+    priv = g_slice_new0(ByteArrayInstance);
+    g_assert(priv_from_js(context, object) == NULL);
+    JS_SetPrivate(context, object, priv);
+    priv->bytes = g_bytes_ref (bytes);
+
+    return object;
+}
+
+GBytes *
+gjs_byte_array_get_bytes (JSContext  *context,
+                          JSObject   *object)
+{
+    ByteArrayInstance *priv;
     priv = priv_from_js(context, object);
-    if (priv == NULL)
-        return NULL; /* wrong class passed in */
+    g_assert(priv != NULL);
+
+    byte_array_ensure_gbytes(priv);
+
+    return g_bytes_ref (priv->bytes);
+}
+
+GByteArray *
+gjs_byte_array_get_byte_array (JSContext   *context,
+                               JSObject    *obj)
+{
+    ByteArrayInstance *priv;
+    priv = priv_from_js(context, obj);
+    g_assert(priv != NULL);
+
+    byte_array_ensure_array(priv);
+
+    return g_byte_array_ref (priv->array);
+}
 
-    return priv->array;
+void
+gjs_byte_array_peek_data (JSContext  *context,
+                          JSObject   *obj,
+                          guint8    **out_data,
+                          gsize      *out_len)
+{
+    ByteArrayInstance *priv;
+    priv = priv_from_js(context, obj);
+    g_assert(priv != NULL);
+    
+    if (priv->array != NULL) {
+        *out_data = (guint8*)priv->array->data;
+        *out_len = (gsize)priv->array->len;
+    } else if (priv->bytes != NULL) {
+        *out_data = (guint8*)g_bytes_get_data(priv->bytes, out_len);
+    } else {
+        g_assert_not_reached();
+    }
 }
 
 /* no idea what this is used for. examples in
@@ -829,12 +1000,14 @@ static JSPropertySpec gjs_byte_array_proto_props[] = {
 
 static JSFunctionSpec gjs_byte_array_proto_funcs[] = {
     { "toString", (JSNative) to_string_func, 0, 0 },
+    { "toGBytes", (JSNative) to_gbytes_func, 0, 0 },
     { NULL }
 };
 
 static JSFunctionSpec gjs_byte_array_module_funcs[] = {
     { "fromString", (JSNative)from_string_func, 1, 0 },
     { "fromArray", (JSNative)from_array_func, 1, 0 },
+    { "fromGBytes", (JSNative)from_gbytes_func, 1, 0 },
     { NULL }
 };
 
diff --git a/gjs/byteArray.h b/gjs/byteArray.h
index 12c05eb..2d0dadc 100644
--- a/gjs/byteArray.h
+++ b/gjs/byteArray.h
@@ -33,14 +33,29 @@
 
 G_BEGIN_DECLS
 
+JSBool    gjs_typecheck_bytearray        (JSContext     *context,
+                                          JSObject      *obj,
+                                          JSBool         throw);
+
 JSBool        gjs_define_byte_array_stuff    (JSContext  *context,
                                               JSObject   *in_object);
 
 JSObject *    gjs_byte_array_from_byte_array (JSContext  *context,
                                               GByteArray *array);
+JSObject *    gjs_byte_array_from_bytes (JSContext  *context,
+                                         GBytes *bytes);
+
 GByteArray *   gjs_byte_array_get_byte_array (JSContext  *context,
                                               JSObject   *object);
 
+GBytes *      gjs_byte_array_get_bytes (JSContext  *context,
+                                        JSObject   *object);
+
+void          gjs_byte_array_peek_data (JSContext  *context,
+                                        JSObject   *object,
+                                        guint8    **out_data,
+                                        gsize      *out_len);
+
 G_END_DECLS
 
 #endif  /* __GJS_BYTE_ARRAY_H__ */
diff --git a/modules/overrides/GLib.js b/modules/overrides/GLib.js
index 7d69b76..ea3c30d 100644
--- a/modules/overrides/GLib.js
+++ b/modules/overrides/GLib.js
@@ -263,4 +263,8 @@ function _init() {
     this.Variant.prototype.toString = function() {
 	return '[object variant of type "' + this.get_type_string() + '"]';
     }
+
+    this.Bytes.prototype.toArray = function() {
+	return imports.byteArray.fromGBytes(this);
+    }
 }
diff --git a/test/js/testGIMarshalling.js b/test/js/testGIMarshalling.js
index e046e6d..e127c20 100644
--- a/test/js/testGIMarshalling.js
+++ b/test/js/testGIMarshalling.js
@@ -172,6 +172,42 @@ function testByteArray() {
     GIMarshallingTests.bytearray_none_in("\x00\x31\xFF\x33");
 }
 
+function testGBytes() {
+    var i = 0;
+    var refByteArray = new imports.byteArray.ByteArray();
+    refByteArray[i++] = 0;
+    refByteArray[i++] = 49;
+    refByteArray[i++] = 0xFF;
+    refByteArray[i++] = 51;
+    GIMarshallingTests.gbytes_none_in(refByteArray);
+
+    var bytes = GIMarshallingTests.gbytes_full_return();
+    GIMarshallingTests.gbytes_none_in(bytes);
+
+    var array = bytes.toArray();
+    assertEquals(array[0], 0);
+    assertEquals(array[1], 49);
+    assertEquals(array[2], 0xFF);
+    assertEquals(array[3], 51); 
+    
+    bytes = GLib.Bytes.new([0, 49, 0xFF, 51]);
+    GIMarshallingTests.gbytes_none_in(bytes);
+
+    bytes = GLib.Bytes.new("\x00\x31\xFF\x33");
+    GIMarshallingTests.gbytes_none_in(bytes);
+
+    bytes = GIMarshallingTests.gbytes_full_return();    
+    array = bytes.toArray(); // Array should just be holding a ref, not a copy
+    assertEquals(array[1], 49);
+    array[1] = 42;  // Assignment should force to GByteArray
+    assertEquals(array[1], 42);
+    array[1] = 49;  // Flip the value back
+    GIMarshallingTests.gbytes_none_in(array.toGBytes()); // Now convert back to GBytes
+
+    bytes = GLib.Bytes.new([97, 98, 99, 100])
+    GIMarshallingTests.array_uint8_in(bytes.toArray());
+}
+
 function testPtrArray() {
     var array;
 



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