/src/libxmlb/src/xb-value-bindings.c
Line | Count | Source |
1 | | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- |
2 | | * vi:set noexpandtab tabstop=8 shiftwidth=8: |
3 | | * |
4 | | * Copyright 2020 Endless OS Foundation LLC |
5 | | * |
6 | | * Author: Philip Withnall <withnall@endlessm.com> |
7 | | * |
8 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
9 | | */ |
10 | | |
11 | 0 | #define G_LOG_DOMAIN "XbValueBindings" |
12 | | |
13 | | #include "config.h" |
14 | | |
15 | | #include <glib.h> |
16 | | |
17 | | #include "xb-opcode-private.h" |
18 | | #include "xb-silo-private.h" |
19 | | #include "xb-value-bindings-private.h" |
20 | | |
21 | | typedef struct { |
22 | | guint8 kind; /* XbBoundValueKind */ |
23 | | guint32 val; |
24 | | gpointer ptr; |
25 | | GDestroyNotify destroy_func; |
26 | | } XbBoundValue; |
27 | | |
28 | | typedef enum { |
29 | | XB_BOUND_VALUE_KIND_NONE, |
30 | | XB_BOUND_VALUE_KIND_TEXT, |
31 | | XB_BOUND_VALUE_KIND_INTEGER, |
32 | | XB_BOUND_VALUE_KIND_INDEXED_TEXT, |
33 | | } XbBoundValueKind; |
34 | | |
35 | | typedef struct { |
36 | | /* Currently limited to 4 values since that’s all that any client |
37 | | * uses. This could be expanded to dynamically allow more in future. */ |
38 | | XbBoundValue values[4]; |
39 | | gpointer dummy[3]; |
40 | | } RealValueBindings; |
41 | | |
42 | 0 | G_DEFINE_BOXED_TYPE(XbValueBindings, |
43 | 0 | xb_value_bindings, |
44 | 0 | xb_value_bindings_copy, |
45 | 0 | xb_value_bindings_free) |
46 | 0 |
|
47 | 0 | /** |
48 | 0 | * xb_value_bindings_init: |
49 | 0 | * @self: an uninitialised #XbValueBindings to initialise |
50 | 0 | * |
51 | 0 | * Initialise a stack-allocated #XbValueBindings struct so it can be used. |
52 | 0 | * |
53 | 0 | * Stack-allocated #XbValueBindings instances should be freed once finished |
54 | 0 | * with, using xb_value_bindings_clear() (or `g_auto(XbValueBindings)`, which is |
55 | 0 | * equivalent). |
56 | 0 | * |
57 | 0 | * Since: 0.3.0 |
58 | 0 | */ |
59 | 0 | void |
60 | 0 | xb_value_bindings_init(XbValueBindings *self) |
61 | 0 | { |
62 | 0 | RealValueBindings *_self = (RealValueBindings *)self; |
63 | |
|
64 | 0 | for (gsize i = 0; i < G_N_ELEMENTS(_self->values); i++) |
65 | 0 | _self->values[i].kind = XB_BOUND_VALUE_KIND_NONE; |
66 | 0 | } |
67 | | |
68 | | static void |
69 | | xb_value_bindings_clear_index(XbValueBindings *self, guint idx) |
70 | 0 | { |
71 | 0 | RealValueBindings *_self = (RealValueBindings *)self; |
72 | |
|
73 | 0 | g_return_if_fail(idx < G_N_ELEMENTS(_self->values)); |
74 | | |
75 | 0 | if (_self->values[idx].ptr != NULL && _self->values[idx].destroy_func) |
76 | 0 | _self->values[idx].destroy_func(_self->values[idx].ptr); |
77 | 0 | _self->values[idx].kind = XB_BOUND_VALUE_KIND_NONE; |
78 | 0 | _self->values[idx].ptr = NULL; |
79 | 0 | _self->values[idx].destroy_func = NULL; |
80 | 0 | } |
81 | | |
82 | | /** |
83 | | * xb_value_bindings_clear: |
84 | | * @self: an #XbValueBindings |
85 | | * |
86 | | * Clear an #XbValueBindings, freeing any allocated memory it points to. |
87 | | * |
88 | | * After this function has been called, the contents of the #XbValueBindings are |
89 | | * undefined, and it’s only safe to call xb_value_bindings_init() on it. |
90 | | * |
91 | | * Since: 0.3.0 |
92 | | */ |
93 | | void |
94 | | xb_value_bindings_clear(XbValueBindings *self) |
95 | 0 | { |
96 | 0 | RealValueBindings *_self = (RealValueBindings *)self; |
97 | |
|
98 | 0 | for (gsize i = 0; i < G_N_ELEMENTS(_self->values); i++) |
99 | 0 | xb_value_bindings_clear_index(self, i); |
100 | 0 | } |
101 | | |
102 | | /* private */ |
103 | | gchar * |
104 | | xb_value_bindings_to_string(XbValueBindings *self) |
105 | 0 | { |
106 | 0 | RealValueBindings *_self = (RealValueBindings *)self; |
107 | 0 | g_autoptr(GString) str = g_string_new(""); |
108 | |
|
109 | 0 | for (guint i = 0; i < G_N_ELEMENTS(_self->values); i++) { |
110 | 0 | XbBoundValue *value = &_self->values[i]; |
111 | 0 | if (value->kind == XB_BOUND_VALUE_KIND_NONE) |
112 | 0 | continue; |
113 | 0 | if (str->len > 0) |
114 | 0 | g_string_append(str, ", "); |
115 | 0 | if (value->kind == XB_BOUND_VALUE_KIND_INTEGER) |
116 | 0 | g_string_append_printf(str, "?%u → %u", i, value->val); |
117 | 0 | else if (value->kind == XB_BOUND_VALUE_KIND_TEXT && value->val > 0) |
118 | 0 | g_string_append_printf(str, |
119 | 0 | "?%u → %s [%u]", |
120 | 0 | i, |
121 | 0 | (const gchar *)value->ptr, |
122 | 0 | value->val); |
123 | 0 | else if (value->kind == XB_BOUND_VALUE_KIND_TEXT) |
124 | 0 | g_string_append_printf(str, "?%u → %s", i, (const gchar *)value->ptr); |
125 | 0 | } |
126 | 0 | return g_string_free(g_steal_pointer(&str), FALSE); |
127 | 0 | } |
128 | | |
129 | | /** |
130 | | * xb_value_bindings_copy: |
131 | | * @self: an #XbValueBindings |
132 | | * |
133 | | * Copy @self into a new heap-allocated #XbValueBindings instance. |
134 | | * |
135 | | * Returns: (transfer full): a copy of @self |
136 | | * Since: 0.3.0 |
137 | | */ |
138 | | XbValueBindings * |
139 | | xb_value_bindings_copy(XbValueBindings *self) |
140 | 0 | { |
141 | 0 | RealValueBindings *_self = (RealValueBindings *)self; |
142 | 0 | g_autoptr(XbValueBindings) copy = g_new0(XbValueBindings, 1); |
143 | |
|
144 | 0 | xb_value_bindings_init(copy); |
145 | |
|
146 | 0 | for (gsize i = 0; i < G_N_ELEMENTS(_self->values); i++) { |
147 | 0 | gboolean copied = xb_value_bindings_copy_binding(self, i, copy, i); |
148 | 0 | g_assert(copied); |
149 | 0 | } |
150 | | |
151 | 0 | return g_steal_pointer(©); |
152 | 0 | } |
153 | | |
154 | | /** |
155 | | * xb_value_bindings_free: |
156 | | * @self: a heap-allocated #XbValueBindings |
157 | | * |
158 | | * Free a heap-allocated #XbValueBindings instance. This should be used on |
159 | | * #XbValueBindings instances created with xb_value_bindings_copy(). |
160 | | * |
161 | | * For stack-allocated instances, xb_value_bindings_clear() should be used |
162 | | * instead. |
163 | | * |
164 | | * Since: 0.3.0 |
165 | | */ |
166 | | void |
167 | | xb_value_bindings_free(XbValueBindings *self) |
168 | 0 | { |
169 | 0 | g_return_if_fail(self != NULL); |
170 | | |
171 | 0 | xb_value_bindings_clear(self); |
172 | 0 | g_free(self); |
173 | 0 | } |
174 | | |
175 | | /** |
176 | | * xb_value_bindings_is_bound: |
177 | | * @self: an #XbValueBindings |
178 | | * @idx: 0-based index of the binding to check |
179 | | * |
180 | | * Check whether a value has been bound to the given index using (for example) |
181 | | * xb_value_bindings_bind_str(). |
182 | | * |
183 | | * Returns: %TRUE if a value is bound to @idx, %FALSE otherwise |
184 | | * Since: 0.3.0 |
185 | | */ |
186 | | gboolean |
187 | | xb_value_bindings_is_bound(XbValueBindings *self, guint idx) |
188 | 0 | { |
189 | 0 | RealValueBindings *_self = (RealValueBindings *)self; |
190 | |
|
191 | 0 | return (idx < G_N_ELEMENTS(_self->values) && |
192 | 0 | _self->values[idx].kind != XB_BOUND_VALUE_KIND_NONE); |
193 | 0 | } |
194 | | |
195 | | /** |
196 | | * xb_value_bindings_bind_str: |
197 | | * @self: an #XbValueBindings |
198 | | * @idx: 0-based index to bind to |
199 | | * @str: (transfer full) (not nullable): a string to bind to @idx |
200 | | * @destroy_func: (nullable): function to free @str |
201 | | * |
202 | | * Bind @str to @idx in the value bindings. |
203 | | * |
204 | | * This will overwrite any previous binding at @idx. It will take ownership of |
205 | | * @str, and an appropriate @destroy_func must be provided to free @str once the |
206 | | * binding is no longer needed. @destroy_func will be called exactly once at |
207 | | * some point before the #XbValueBindings is cleared or freed. |
208 | | * |
209 | | * Since: 0.3.0 |
210 | | */ |
211 | | void |
212 | | xb_value_bindings_bind_str(XbValueBindings *self, |
213 | | guint idx, |
214 | | const gchar *str, |
215 | | GDestroyNotify destroy_func) |
216 | 0 | { |
217 | 0 | RealValueBindings *_self = (RealValueBindings *)self; |
218 | |
|
219 | 0 | g_return_if_fail(self != NULL); |
220 | 0 | g_return_if_fail(str != NULL); |
221 | | |
222 | | /* Currently limited to two values, but this restriction could be lifted |
223 | | * in future. */ |
224 | 0 | g_return_if_fail(idx < G_N_ELEMENTS(_self->values)); |
225 | | |
226 | 0 | xb_value_bindings_clear_index(self, idx); |
227 | |
|
228 | 0 | _self->values[idx].kind = XB_BOUND_VALUE_KIND_TEXT; |
229 | 0 | _self->values[idx].ptr = (gpointer)str; |
230 | 0 | _self->values[idx].destroy_func = destroy_func; |
231 | 0 | } |
232 | | |
233 | | /** |
234 | | * xb_value_bindings_bind_val: |
235 | | * @self: an #XbValueBindings |
236 | | * @idx: 0-based index to bind to |
237 | | * @val: an integer to bind to @idx |
238 | | * |
239 | | * Bind @val to @idx in the value bindings. |
240 | | * |
241 | | * This will overwrite any previous binding at @idx. |
242 | | * |
243 | | * Since: 0.3.0 |
244 | | */ |
245 | | void |
246 | | xb_value_bindings_bind_val(XbValueBindings *self, guint idx, guint32 val) |
247 | 0 | { |
248 | 0 | RealValueBindings *_self = (RealValueBindings *)self; |
249 | |
|
250 | 0 | g_return_if_fail(self != NULL); |
251 | | |
252 | | /* Currently limited to two values, but this restriction could be lifted |
253 | | * in future. */ |
254 | 0 | g_return_if_fail(idx < G_N_ELEMENTS(_self->values)); |
255 | | |
256 | 0 | xb_value_bindings_clear_index(self, idx); |
257 | |
|
258 | 0 | _self->values[idx].kind = XB_BOUND_VALUE_KIND_INTEGER; |
259 | 0 | _self->values[idx].val = val; |
260 | 0 | _self->values[idx].destroy_func = NULL; |
261 | 0 | } |
262 | | |
263 | | /** |
264 | | * xb_value_bindings_lookup_opcode: |
265 | | * @self: an #XbValueBindings |
266 | | * @idx: 0-based index to look up the binding from |
267 | | * @opcode_out: (out caller-allocates) (not nullable): pointer to an #XbOpcode |
268 | | * to initialise from the binding |
269 | | * |
270 | | * Initialises an #XbOpcode with the value bound to @idx, if a value is bound. |
271 | | * If no value is bound, @opcode_out is not touched and %FALSE is returned. |
272 | | * |
273 | | * @opcode_out is initialised to point to the data inside the #XbValueBindings, |
274 | | * so must have a shorter lifetime than the #XbValueBindings. It will be of kind |
275 | | * %XB_OPCODE_KIND_BOUND_TEXT or %XB_OPCODE_KIND_BOUND_INTEGER. |
276 | | * |
277 | | * Returns: %TRUE if @idx was bound, %FALSE otherwise |
278 | | * Since: 0.3.0 |
279 | | */ |
280 | | gboolean |
281 | | xb_value_bindings_lookup_opcode(XbValueBindings *self, guint idx, XbOpcode *opcode_out) |
282 | 0 | { |
283 | 0 | RealValueBindings *_self = (RealValueBindings *)self; |
284 | |
|
285 | 0 | if (!xb_value_bindings_is_bound(self, idx)) |
286 | 0 | return FALSE; |
287 | | |
288 | 0 | switch (_self->values[idx].kind) { |
289 | 0 | case XB_BOUND_VALUE_KIND_TEXT: |
290 | 0 | xb_opcode_init(opcode_out, |
291 | 0 | XB_OPCODE_KIND_BOUND_TEXT, |
292 | 0 | _self->values[idx].ptr, |
293 | 0 | 0, |
294 | 0 | NULL); |
295 | 0 | break; |
296 | 0 | case XB_BOUND_VALUE_KIND_INTEGER: |
297 | 0 | xb_opcode_init(opcode_out, |
298 | 0 | XB_OPCODE_KIND_BOUND_INTEGER, |
299 | 0 | NULL, |
300 | 0 | _self->values[idx].val, |
301 | 0 | NULL); |
302 | 0 | break; |
303 | 0 | case XB_BOUND_VALUE_KIND_INDEXED_TEXT: |
304 | 0 | xb_opcode_init(opcode_out, |
305 | 0 | XB_OPCODE_KIND_BOUND_INDEXED_TEXT, |
306 | 0 | _self->values[idx].ptr, |
307 | 0 | _self->values[idx].val, |
308 | 0 | NULL); |
309 | 0 | break; |
310 | 0 | case XB_BOUND_VALUE_KIND_NONE: |
311 | 0 | default: |
312 | 0 | g_assert_not_reached(); |
313 | 0 | } |
314 | | |
315 | 0 | return TRUE; |
316 | 0 | } |
317 | | |
318 | | /** |
319 | | * xb_value_bindings_copy_binding: |
320 | | * @self: an #XbValueBindings to copy from |
321 | | * @idx: 0-based index to look up the binding from in @self |
322 | | * @dest: an #XbValueBindings to copy to |
323 | | * @dest_idx: 0-based index to copy the binding to in @dest |
324 | | * |
325 | | * Copies the value bound at @idx on @self to @dest_idx on @dest. If no value is |
326 | | * bound at @idx, @dest is not modified and %FALSE is returned. |
327 | | * |
328 | | * @dest must be initialised. If a binding already exists at @dest_idx, it will |
329 | | * be overwritten. |
330 | | * |
331 | | * Returns: %TRUE if @idx was bound, %FALSE otherwise |
332 | | * Since: 0.3.0 |
333 | | */ |
334 | | gboolean |
335 | | xb_value_bindings_copy_binding(XbValueBindings *self, |
336 | | guint idx, |
337 | | XbValueBindings *dest, |
338 | | guint dest_idx) |
339 | 0 | { |
340 | 0 | RealValueBindings *_self = (RealValueBindings *)self; |
341 | 0 | RealValueBindings *_dest = (RealValueBindings *)dest; |
342 | |
|
343 | 0 | if (!xb_value_bindings_is_bound(self, idx)) |
344 | 0 | return FALSE; |
345 | | |
346 | 0 | switch (_self->values[idx].kind) { |
347 | 0 | case XB_BOUND_VALUE_KIND_TEXT: |
348 | 0 | xb_value_bindings_bind_str(dest, dest_idx, _self->values[idx].ptr, NULL); |
349 | 0 | break; |
350 | 0 | case XB_BOUND_VALUE_KIND_INTEGER: |
351 | 0 | xb_value_bindings_bind_val(dest, dest_idx, _self->values[idx].val); |
352 | 0 | break; |
353 | 0 | case XB_BOUND_VALUE_KIND_INDEXED_TEXT: |
354 | 0 | xb_value_bindings_bind_str(dest, dest_idx, _self->values[idx].ptr, NULL); |
355 | 0 | _dest->values[idx].kind = XB_BOUND_VALUE_KIND_INDEXED_TEXT; |
356 | 0 | _dest->values[idx].val = _self->values[idx].val; |
357 | 0 | break; |
358 | 0 | case XB_BOUND_VALUE_KIND_NONE: |
359 | 0 | default: |
360 | 0 | g_assert_not_reached(); |
361 | 0 | } |
362 | | |
363 | 0 | return TRUE; |
364 | 0 | } |
365 | | |
366 | | /* private */ |
367 | | gboolean |
368 | | xb_value_bindings_indexed_text_lookup(XbValueBindings *self, XbSilo *silo, GError **error) |
369 | 0 | { |
370 | 0 | RealValueBindings *_self = (RealValueBindings *)self; |
371 | 0 | for (guint i = 0; i < G_N_ELEMENTS(_self->values); i++) { |
372 | 0 | XbBoundValue *value = &_self->values[i]; |
373 | 0 | if (value->kind == XB_BOUND_VALUE_KIND_TEXT) { |
374 | 0 | guint32 val = xb_silo_strtab_index_lookup(silo, (const gchar *)value->ptr); |
375 | 0 | if (val == XB_SILO_UNSET) { |
376 | 0 | g_set_error(error, |
377 | 0 | G_IO_ERROR, |
378 | 0 | G_IO_ERROR_INVALID_ARGUMENT, |
379 | 0 | "indexed string '%s' was unfound", |
380 | 0 | (const gchar *)value->ptr); |
381 | 0 | return FALSE; |
382 | 0 | } |
383 | 0 | value->kind = XB_BOUND_VALUE_KIND_INDEXED_TEXT; |
384 | 0 | value->val = val; |
385 | 0 | } |
386 | 0 | } |
387 | 0 | return TRUE; |
388 | 0 | } |