From acdb4ce03d525a18f6c351a040b8446c7bbd98bd Mon Sep 17 00:00:00 2001 From: Mathieu Duponchelle Date: Thu, 17 Sep 2020 20:44:43 +0200 Subject: [PATCH] gstvalue: expose gst_value_deserialize_with_pspec() Typing hints can only be passed to gst_value_deserialize() through the type of the passed-in value. This means deserialization can only target the desired type for the top-level elements, making it for example impossible to deserialize an array of flags to the expected type. This commit exposes a new function, gst_value_deserialize_full(), that takes an optional pspec as the extra parameter, and updates the deserialization code to pass around that pspec, or the element_spec when recursively parsing the elements of a list-type value. This allows for example passing arrays of flags through the command line or gst_util_set_object_arg, eg: foo="" Part-of: --- gst/gst_private.h | 2 +- gst/gststructure.c | 2 +- gst/gstutils.c | 2 +- gst/gstvalue.c | 138 ++++++++++++++++++++++++++++++++++++--------- gst/gstvalue.h | 33 ++++++++++- gst/parse/grammar.y.in | 4 +- tests/check/gst/gstvalue.c | 76 +++++++++++++++++++++++++ 7 files changed, 224 insertions(+), 33 deletions(-) diff --git a/gst/gst_private.h b/gst/gst_private.h index 3459ecb..b998833 100644 --- a/gst/gst_private.h +++ b/gst/gst_private.h @@ -172,7 +172,7 @@ G_GNUC_INTERNAL const char * _priv_gst_value_gtype_to_abbr (GType type); G_GNUC_INTERNAL gboolean _priv_gst_value_parse_string (gchar * s, gchar ** end, gchar ** next, gboolean unescape); G_GNUC_INTERNAL gboolean _priv_gst_value_parse_simple_string (gchar * str, gchar ** end); -G_GNUC_INTERNAL gboolean _priv_gst_value_parse_value (gchar * str, gchar ** after, GValue * value, GType default_type); +G_GNUC_INTERNAL gboolean _priv_gst_value_parse_value (gchar * str, gchar ** after, GValue * value, GType default_type, GParamSpec *pspec); G_GNUC_INTERNAL gchar * _priv_gst_value_serialize_any_list (const GValue * value, const gchar * begin, const gchar * end, gboolean print_type); /* Used in GstBin for manual state handling */ diff --git a/gst/gststructure.c b/gst/gststructure.c index d9d307f..98a22c4 100644 --- a/gst/gststructure.c +++ b/gst/gststructure.c @@ -2204,7 +2204,7 @@ gst_structure_parse_field (gchar * str, *name_end = c; if (G_UNLIKELY (!_priv_gst_value_parse_value (s, &s, &field->value, - G_TYPE_INVALID))) { + G_TYPE_INVALID, NULL))) { GST_WARNING ("failed to parse value %s", str); return FALSE; } diff --git a/gst/gstutils.c b/gst/gstutils.c index 65d70ec..fb32971 100644 --- a/gst/gstutils.c +++ b/gst/gstutils.c @@ -213,7 +213,7 @@ gst_util_set_object_arg (GObject * object, const gchar * name, goto done; } - if (!gst_value_deserialize (&v, value)) + if (!gst_value_deserialize_with_pspec (&v, value, pspec)) return; done: diff --git a/gst/gstvalue.c b/gst/gstvalue.c index 5502001..adc7d7e 100644 --- a/gst/gstvalue.c +++ b/gst/gstvalue.c @@ -98,9 +98,9 @@ static void gst_value_register_subtract_func (GType minuend_type, GType subtrahend_type, GstValueSubtractFunc func); static gboolean _priv_gst_value_parse_list (gchar * s, gchar ** after, - GValue * value, GType type); + GValue * value, GType type, GParamSpec * pspec); static gboolean _priv_gst_value_parse_array (gchar * s, gchar ** after, - GValue * value, GType type); + GValue * value, GType type, GParamSpec * pspec); typedef struct _GstValueUnionInfo GstValueUnionInfo; struct _GstValueUnionInfo @@ -1244,10 +1244,11 @@ gst_value_serialize_value_list (const GValue * value) } static gboolean -gst_value_deserialize_value_list (GValue * dest, const gchar * s) +gst_value_deserialize_value_list (GValue * dest, const gchar * s, + GParamSpec * pspec) { gchar *s2 = (gchar *) s; - return _priv_gst_value_parse_list (s2, &s2, dest, G_TYPE_INVALID); + return _priv_gst_value_parse_list (s2, &s2, dest, G_TYPE_INVALID, pspec); } static gchar * @@ -1257,10 +1258,11 @@ gst_value_serialize_value_array (const GValue * value) } static gboolean -gst_value_deserialize_value_array (GValue * dest, const gchar * s) +gst_value_deserialize_value_array (GValue * dest, const gchar * s, + GParamSpec * pspec) { gchar *s2 = (gchar *) s; - return _priv_gst_value_parse_array (s2, &s2, dest, G_TYPE_INVALID); + return _priv_gst_value_parse_array (s2, &s2, dest, G_TYPE_INVALID, pspec); } static gchar * @@ -2469,7 +2471,7 @@ _priv_gst_value_parse_range (gchar * s, gchar ** after, GValue * value, return FALSE; s++; - ret = _priv_gst_value_parse_value (s, &s, &value1, type); + ret = _priv_gst_value_parse_value (s, &s, &value1, type, NULL); if (!ret) return FALSE; @@ -2483,7 +2485,7 @@ _priv_gst_value_parse_range (gchar * s, gchar ** after, GValue * value, while (g_ascii_isspace (*s)) s++; - ret = _priv_gst_value_parse_value (s, &s, &value2, type); + ret = _priv_gst_value_parse_value (s, &s, &value2, type, NULL); if (!ret) return FALSE; @@ -2499,7 +2501,7 @@ _priv_gst_value_parse_range (gchar * s, gchar ** after, GValue * value, while (g_ascii_isspace (*s)) s++; - ret = _priv_gst_value_parse_value (s, &s, &value3, type); + ret = _priv_gst_value_parse_value (s, &s, &value3, type, NULL); if (!ret) return FALSE; @@ -2562,11 +2564,15 @@ _priv_gst_value_parse_range (gchar * s, gchar ** after, GValue * value, static gboolean _priv_gst_value_parse_any_list (gchar * s, gchar ** after, GValue * value, - GType type, char begin, char end) + GType type, char begin, char end, GParamSpec * pspec) { GValue list_value = { 0 }; gboolean ret; GstValueList *vlist = VALUE_LIST_ARRAY (value); + GParamSpec *element_spec = NULL; + + if (pspec) + element_spec = GST_PARAM_SPEC_ARRAY_LIST (pspec)->element_spec; if (*s != begin) return FALSE; @@ -2588,7 +2594,8 @@ _priv_gst_value_parse_any_list (gchar * s, gchar ** after, GValue * value, } memset (&list_value, 0, sizeof (list_value)); - ret = _priv_gst_value_parse_value (s, &s, &list_value, type); + + ret = _priv_gst_value_parse_value (s, &s, &list_value, type, element_spec); if (!ret) return FALSE; @@ -2609,16 +2616,18 @@ _priv_gst_value_parse_any_list (gchar * s, gchar ** after, GValue * value, static gboolean _priv_gst_value_parse_list (gchar * s, gchar ** after, GValue * value, - GType type) + GType type, GParamSpec * pspec) { - return _priv_gst_value_parse_any_list (s, after, value, type, '{', '}'); + return _priv_gst_value_parse_any_list (s, after, value, type, '{', '}', + pspec); } static gboolean _priv_gst_value_parse_array (gchar * s, gchar ** after, GValue * value, - GType type) + GType type, GParamSpec * pspec) { - return _priv_gst_value_parse_any_list (s, after, value, type, '<', '>'); + return _priv_gst_value_parse_any_list (s, after, value, type, '<', '>', + pspec); } gboolean @@ -2637,7 +2646,7 @@ _priv_gst_value_parse_simple_string (gchar * str, gchar ** end) gboolean _priv_gst_value_parse_value (gchar * str, - gchar ** after, GValue * value, GType default_type) + gchar ** after, GValue * value, GType default_type, GParamSpec * pspec) { gchar *type_name; gchar *type_end; @@ -2654,7 +2663,10 @@ _priv_gst_value_parse_value (gchar * str, /* check if there's a (type_name) 'cast' */ type_name = NULL; - if (*s == '(') { + + if (pspec) { + type = G_PARAM_SPEC_VALUE_TYPE (pspec); + } else if (*s == '(') { s++; while (g_ascii_isspace (*s)) s++; @@ -2688,10 +2700,10 @@ _priv_gst_value_parse_value (gchar * str, ret = _priv_gst_value_parse_range (s, &s, value, type); } else if (*s == '{') { g_value_init (value, GST_TYPE_LIST); - ret = _priv_gst_value_parse_list (s, &s, value, type); + ret = _priv_gst_value_parse_list (s, &s, value, type, pspec); } else if (*s == '<') { g_value_init (value, GST_TYPE_ARRAY); - ret = _priv_gst_value_parse_array (s, &s, value, type); + ret = _priv_gst_value_parse_array (s, &s, value, type, pspec); } else { value_s = s; @@ -2725,7 +2737,7 @@ _priv_gst_value_parse_value (gchar * str, c = *value_end; *value_end = '\0'; - ret = gst_value_deserialize (value, value_s); + ret = gst_value_deserialize_with_pspec (value, value_s, pspec); if (G_UNLIKELY (!ret)) g_value_unset (value); } @@ -6439,19 +6451,80 @@ gst_value_deserialize (GValue * dest, const gchar * src) type = G_VALUE_TYPE (dest); best = gst_value_hash_lookup_type (type); - if (G_UNLIKELY (!best || !best->deserialize)) { + if (G_UNLIKELY (!best || (!best->deserialize + && !best->deserialize_with_pspec))) { len = gst_value_table->len; best = NULL; for (i = 0; i < len; i++) { table = &g_array_index (gst_value_table, GstValueTable, i); - if (table->deserialize && g_type_is_a (type, table->type)) { + if ((table->deserialize || table->deserialize_with_pspec) && + g_type_is_a (type, table->type)) { if (!best || g_type_is_a (table->type, best->type)) best = table; } } } - if (G_LIKELY (best)) - return best->deserialize (dest, src); + if (G_LIKELY (best)) { + if (best->deserialize_with_pspec) + return best->deserialize_with_pspec (dest, src, NULL); + else + return best->deserialize (dest, src); + } + + return FALSE; +} + +/** + * gst_value_deserialize_with_pspec: + * @dest: (out caller-allocates): #GValue to fill with contents of + * deserialization + * @src: string to deserialize + * @pspec: (nullable): the #GParamSpec describing the expected value + * + * Tries to deserialize a string into the type specified by the given GValue. + * @pspec may be used to guide the deserializing of nested members. + * If the operation succeeds, %TRUE is returned, %FALSE otherwise. + * + * Returns: %TRUE on success + * Since: 1.20 + */ +gboolean +gst_value_deserialize_with_pspec (GValue * dest, const gchar * src, + GParamSpec * pspec) +{ + GstValueTable *table, *best; + guint i, len; + GType type; + + g_return_val_if_fail (src != NULL, FALSE); + g_return_val_if_fail (G_IS_VALUE (dest), FALSE); + + if (pspec) + g_return_val_if_fail (G_VALUE_TYPE (dest) == + G_PARAM_SPEC_VALUE_TYPE (pspec), FALSE); + + type = G_VALUE_TYPE (dest); + + best = gst_value_hash_lookup_type (type); + if (G_UNLIKELY (!best || (!best->deserialize + && !best->deserialize_with_pspec))) { + len = gst_value_table->len; + best = NULL; + for (i = 0; i < len; i++) { + table = &g_array_index (gst_value_table, GstValueTable, i); + if ((table->deserialize || table->deserialize_with_pspec) && + g_type_is_a (type, table->type)) { + if (!best || g_type_is_a (table->type, best->type)) + best = table; + } + } + } + if (G_LIKELY (best)) { + if (best->deserialize_with_pspec) + return best->deserialize_with_pspec (dest, src, pspec); + else + return best->deserialize (dest, src); + } return FALSE; } @@ -7750,7 +7823,8 @@ gst_g_thread_get_type (void) return G_TYPE_THREAD; } -#define SERIAL_VTABLE(t,c,s,d) { t, c, s, d } +#define SERIAL_VTABLE(t,c,s,d) { t, c, s, d, NULL } +#define SERIAL_VTABLE_PSPEC(t,c,s,d) { t, c, s, NULL, d } #define REGISTER_SERIALIZATION_CONST(_gtype, _type) \ G_STMT_START { \ @@ -7769,6 +7843,15 @@ G_STMT_START { \ gst_value_register (&gst_value); \ } G_STMT_END +#define REGISTER_SERIALIZATION_WITH_PSPEC(_gtype, _type) \ +G_STMT_START { \ + static GstValueTable gst_value = \ + SERIAL_VTABLE_PSPEC (0, gst_value_compare_ ## _type, \ + gst_value_serialize_ ## _type, gst_value_deserialize_ ## _type); \ + gst_value.type = _gtype; \ + gst_value_register (&gst_value); \ +} G_STMT_END + #define REGISTER_SERIALIZATION_NO_COMPARE(_gtype, _type) \ G_STMT_START { \ static GstValueTable gst_value = \ @@ -7813,8 +7896,6 @@ _priv_gst_value_initialize (void) REGISTER_SERIALIZATION (gst_int64_range_get_type (), int64_range); REGISTER_SERIALIZATION (gst_double_range_get_type (), double_range); REGISTER_SERIALIZATION (gst_fraction_range_get_type (), fraction_range); - REGISTER_SERIALIZATION (gst_value_list_get_type (), value_list); - REGISTER_SERIALIZATION (gst_value_array_get_type (), value_array); REGISTER_SERIALIZATION (g_value_array_get_type (), g_value_array); REGISTER_SERIALIZATION (gst_buffer_get_type (), buffer); REGISTER_SERIALIZATION (gst_sample_get_type (), sample); @@ -7857,6 +7938,9 @@ _priv_gst_value_initialize (void) REGISTER_SERIALIZATION (G_TYPE_GTYPE, gtype); + REGISTER_SERIALIZATION_WITH_PSPEC (gst_value_list_get_type (), value_list); + REGISTER_SERIALIZATION_WITH_PSPEC (gst_value_array_get_type (), value_array); + g_value_register_transform_func (GST_TYPE_INT_RANGE, G_TYPE_STRING, gst_value_transform_int_range_string); g_value_register_transform_func (GST_TYPE_INT64_RANGE, G_TYPE_STRING, diff --git a/gst/gstvalue.h b/gst/gstvalue.h index 9730d79..af7389d 100644 --- a/gst/gstvalue.h +++ b/gst/gstvalue.h @@ -472,6 +472,22 @@ typedef gchar * (* GstValueSerializeFunc) (const GValue *value1); typedef gboolean (* GstValueDeserializeFunc) (GValue *dest, const gchar *s); +/** + * GstValueDeserializeWithPSpecFunc: + * @dest: a #GValue + * @s: a string + * @pspec: a #GParamSpec describing the expected value + * + * Used by gst_value_deserialize_with_pspec() to parse a non-binary form into the #GValue. + * + * Returns: %TRUE for success + * Since: 1.20 + */ +typedef gboolean (* GstValueDeserializeWithPSpecFunc) (GValue *dest, + const gchar *s, + GParamSpec *pspec); + + typedef struct _GstValueTable GstValueTable; /** * GstValueTable: @@ -479,6 +495,7 @@ typedef struct _GstValueTable GstValueTable; * @compare: a #GstValueCompareFunc * @serialize: a #GstValueSerializeFunc * @deserialize: a #GstValueDeserializeFunc + * @deserialize_with_pspec: a #GstValueDeserializeWithPSpecFunc * * VTable for the #GValue @type. */ @@ -488,8 +505,17 @@ struct _GstValueTable { GstValueSerializeFunc serialize; GstValueDeserializeFunc deserialize; + /** + * GstValueTable.deserialize_with_pspec: + * + * a #GstValueDeserializeWithPSpecFunc + * + * Since: 1.20 + */ + GstValueDeserializeWithPSpecFunc deserialize_with_pspec; + /*< private >*/ - gpointer _gst_reserved [GST_PADDING]; + gpointer _gst_reserved [GST_PADDING - 1]; }; GST_API @@ -538,6 +564,11 @@ GST_API gboolean gst_value_deserialize (GValue *dest, const gchar *src); +GST_API +gboolean gst_value_deserialize_with_pspec (GValue *dest, + const gchar *src, + GParamSpec *pspec); + /* list */ GST_API diff --git a/gst/parse/grammar.y.in b/gst/parse/grammar.y.in index 2ef703d..bc23ea0 100644 --- a/gst/parse/grammar.y.in +++ b/gst/parse/grammar.y.in @@ -328,7 +328,7 @@ static void gst_parse_new_child(GstChildProxy *child_proxy, GObject *object, GST_CAT_LOG_OBJECT (GST_CAT_PIPELINE, child_proxy, "parsing delayed property %s as a %s from %s", pspec->name, g_type_name (value_type), set->value_str); g_value_init (&v, value_type); - if (gst_value_deserialize (&v, set->value_str)) + if (gst_value_deserialize_with_pspec (&v, set->value_str, pspec)) got_value = TRUE; else if (g_type_is_a (value_type, GST_TYPE_ELEMENT)) { GstElement *bin; @@ -424,7 +424,7 @@ static void gst_parse_element_set (gchar *value, GstElement *element, graph_t *g pspec->name, g_type_name (value_type)); g_value_init (&v, value_type); - if (gst_value_deserialize (&v, pos)) + if (gst_value_deserialize_with_pspec (&v, pos, pspec)) got_value = TRUE; else if (g_type_is_a (value_type, GST_TYPE_ELEMENT)) { GstElement *bin; diff --git a/tests/check/gst/gstvalue.c b/tests/check/gst/gstvalue.c index 95882f5..9147f08 100644 --- a/tests/check/gst/gstvalue.c +++ b/tests/check/gst/gstvalue.c @@ -3508,6 +3508,81 @@ GST_START_TEST (test_deserialize_array) GST_END_TEST; +#define TEST_FLAGS_TYPE (test_flags_get_type()) +static GType +test_flags_get_type (void) +{ + static const GFlagsValue values[] = { + {1, "One", "one"}, + {1 << 1, "Two", "two"}, + {1 << 3, "Eight", "eight"}, + {0, NULL, NULL} + }; + static volatile GType id = 0; + + if (g_once_init_enter ((gsize *) & id)) { + GType _id; + + _id = g_flags_register_static ("TestFlags", values); + + g_once_init_leave ((gsize *) & id, _id); + } + + return id; +} + +#define _RESULT(i) result_##i +#define RESULT(i) _RESULT(i) + +GST_START_TEST (test_deserialize_with_pspec) +{ + GValue value = { 0 }; + GParamSpec *pspec; + const gchar *strings[] = { + "< one, 0>", + "< one+eight, two >", + "< 9, 0>", + }; + int i; + + gint results[3][2] = { + {1, 0}, + {9, 2}, + {9, 0} + }; + + pspec = gst_param_spec_array ("flags-array", + "Flags Array", "An array of flags", + g_param_spec_flags ("flags", "Flags", "Flags", TEST_FLAGS_TYPE, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS), + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + for (i = 0; i < G_N_ELEMENTS (strings); ++i) { + int j; + gchar *str = g_strdup (strings[i]); + g_value_init (&value, GST_TYPE_ARRAY); + + fail_unless (gst_value_deserialize_with_pspec (&value, str, pspec), + "could not deserialize %s (%d)", str, i); + + fail_unless (gst_value_array_get_size (&value) == + G_N_ELEMENTS (results[i])); + + for (j = 0; j < G_N_ELEMENTS (results[i]); j++) { + const GValue *elem_value = gst_value_array_get_value (&value, j); + fail_unless (G_VALUE_TYPE (elem_value) == TEST_FLAGS_TYPE); + fail_unless_equals_int (g_value_get_flags (elem_value), results[i][j]); + } + + g_value_unset (&value); + g_free (str); + } + + g_param_spec_unref (pspec); +} + +GST_END_TEST; + static Suite * gst_value_suite (void) { @@ -3562,6 +3637,7 @@ gst_value_suite (void) tcase_add_test (tc_chain, test_transform_array); tcase_add_test (tc_chain, test_transform_list); tcase_add_test (tc_chain, test_serialize_null_aray); + tcase_add_test (tc_chain, test_deserialize_with_pspec); return s; } -- 2.7.4