/src/libxmlb/src/xb-builder.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2018 Richard Hughes <richard@hughsie.com> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | 0 | #define G_LOG_DOMAIN "XbSilo" |
8 | | |
9 | | #include "config.h" |
10 | | |
11 | | #include <gio/gio.h> |
12 | | #include <string.h> |
13 | | |
14 | | #include "xb-builder-fixup-private.h" |
15 | | #include "xb-builder-node-private.h" |
16 | | #include "xb-builder-source-private.h" |
17 | | #include "xb-builder.h" |
18 | | #include "xb-opcode-private.h" |
19 | | #include "xb-silo-private.h" |
20 | | #include "xb-string-private.h" |
21 | | #include "xb-version.h" |
22 | | |
23 | 0 | #define XB_BUILDER_MAX_DEPTH 100 |
24 | | |
25 | | typedef struct { |
26 | | GPtrArray *sources; /* of XbBuilderSource */ |
27 | | GPtrArray *nodes; /* of XbBuilderNode */ |
28 | | GPtrArray *fixups; /* of XbBuilderFixup */ |
29 | | GPtrArray *locales; /* of str */ |
30 | | XbSiloProfileFlags profile_flags; |
31 | | GString *guid; |
32 | | } XbBuilderPrivate; |
33 | | |
34 | 0 | G_DEFINE_TYPE_WITH_PRIVATE(XbBuilder, xb_builder, G_TYPE_OBJECT) |
35 | 0 | #define GET_PRIVATE(o) (xb_builder_get_instance_private(o)) |
36 | | |
37 | | typedef struct { |
38 | | XbSilo *silo; |
39 | | XbBuilderNode *root; /* transfer full */ |
40 | | XbBuilderNode *current; /* transfer none */ |
41 | | XbBuilderCompileFlags compile_flags; |
42 | | XbBuilderSourceFlags source_flags; |
43 | | GHashTable *strtab_hash; |
44 | | GByteArray *strtab; |
45 | | GPtrArray *locales; |
46 | | gboolean elem_closed; |
47 | | guint depth; |
48 | | GError *error; |
49 | | } XbBuilderCompileHelper; |
50 | | |
51 | | static guint32 |
52 | | xb_builder_compile_add_to_strtab(XbBuilderCompileHelper *helper, const gchar *str) |
53 | 0 | { |
54 | 0 | gpointer val; |
55 | 0 | guint32 idx; |
56 | 0 | gsize str_len; |
57 | | |
58 | | /* already exists */ |
59 | 0 | if (g_hash_table_lookup_extended(helper->strtab_hash, str, NULL, &val)) |
60 | 0 | return GPOINTER_TO_UINT(val); |
61 | | |
62 | | /* check for integer truncation */ |
63 | 0 | if (helper->strtab->len > G_MAXINT32) { |
64 | 0 | g_set_error(&helper->error, |
65 | 0 | G_IO_ERROR, |
66 | 0 | G_IO_ERROR_INVALID_DATA, |
67 | 0 | "string table too large"); |
68 | 0 | return XB_SILO_UNSET; |
69 | 0 | } |
70 | | |
71 | | /* check if adding this string would exceed the limit */ |
72 | 0 | str_len = strlen(str) + 1; |
73 | 0 | if (helper->strtab->len + str_len > G_MAXUINT32) { |
74 | 0 | g_set_error(&helper->error, |
75 | 0 | G_IO_ERROR, |
76 | 0 | G_IO_ERROR_INVALID_DATA, |
77 | 0 | "string table too large"); |
78 | 0 | return XB_SILO_UNSET; |
79 | 0 | } |
80 | | |
81 | | /* new */ |
82 | 0 | idx = helper->strtab->len; |
83 | 0 | g_byte_array_append(helper->strtab, (const guint8 *)str, str_len); |
84 | 0 | g_hash_table_insert(helper->strtab_hash, g_strdup(str), GUINT_TO_POINTER(idx)); |
85 | 0 | return idx; |
86 | 0 | } |
87 | | |
88 | | static gint |
89 | | xb_builder_get_locale_priority(XbBuilderCompileHelper *helper, const gchar *locale) |
90 | 0 | { |
91 | 0 | for (guint i = 0; i < helper->locales->len; i++) { |
92 | 0 | const gchar *locale_tmp = g_ptr_array_index(helper->locales, i); |
93 | 0 | if (g_strcmp0(locale_tmp, locale) == 0) |
94 | 0 | return helper->locales->len - i; |
95 | 0 | } |
96 | 0 | return -1; |
97 | 0 | } |
98 | | |
99 | | static void |
100 | | xb_builder_compile_start_element_cb(GMarkupParseContext *context, |
101 | | const gchar *element_name, |
102 | | const gchar **attr_names, |
103 | | const gchar **attr_values, |
104 | | gpointer user_data, |
105 | | GError **error) |
106 | 0 | { |
107 | 0 | XbBuilderCompileHelper *helper = (XbBuilderCompileHelper *)user_data; |
108 | 0 | g_autoptr(XbBuilderNode) bn = xb_builder_node_new(element_name); |
109 | | |
110 | | /* check recursion depth to prevent stack exhaustion */ |
111 | 0 | if (helper->depth >= XB_BUILDER_MAX_DEPTH) { |
112 | 0 | g_set_error(error, |
113 | 0 | G_IO_ERROR, |
114 | 0 | G_IO_ERROR_INVALID_DATA, |
115 | 0 | "nesting deeper than %u levels not supported", |
116 | 0 | (guint)XB_BUILDER_MAX_DEPTH); |
117 | 0 | return; |
118 | 0 | } |
119 | 0 | helper->depth++; |
120 | | |
121 | | /* parent node is being ignored */ |
122 | 0 | if (helper->current != NULL && |
123 | 0 | xb_builder_node_has_flag(helper->current, XB_BUILDER_NODE_FLAG_IGNORE)) |
124 | 0 | xb_builder_node_add_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE); |
125 | | |
126 | | /* check if we should ignore the locale */ |
127 | 0 | if (!xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE) && |
128 | 0 | helper->compile_flags & XB_BUILDER_COMPILE_FLAG_NATIVE_LANGS) { |
129 | 0 | const gchar *xml_lang = NULL; |
130 | 0 | for (guint i = 0; attr_names[i] != NULL; i++) { |
131 | 0 | if (g_strcmp0(attr_names[i], "xml:lang") == 0) { |
132 | 0 | xml_lang = attr_values[i]; |
133 | 0 | break; |
134 | 0 | } |
135 | 0 | } |
136 | 0 | if (xml_lang == NULL) { |
137 | 0 | if (helper->current != NULL) { |
138 | 0 | gint prio = xb_builder_node_get_priority(helper->current); |
139 | 0 | xb_builder_node_set_priority(bn, prio); |
140 | 0 | } |
141 | 0 | } else { |
142 | 0 | gint prio = xb_builder_get_locale_priority(helper, xml_lang); |
143 | 0 | if (prio < 0) |
144 | 0 | xb_builder_node_add_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE); |
145 | 0 | xb_builder_node_set_priority(bn, prio); |
146 | 0 | } |
147 | 0 | } |
148 | | |
149 | | /* add attributes */ |
150 | 0 | if (!xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE)) { |
151 | 0 | for (guint i = 0; attr_names[i] != NULL; i++) |
152 | 0 | xb_builder_node_set_attr(bn, attr_names[i], attr_values[i]); |
153 | 0 | } |
154 | | |
155 | | /* add to tree */ |
156 | 0 | xb_builder_node_add_child(helper->current, bn); |
157 | 0 | helper->current = bn; |
158 | |
|
159 | 0 | helper->elem_closed = FALSE; |
160 | 0 | } |
161 | | |
162 | | static void |
163 | | xb_builder_compile_end_element_cb(GMarkupParseContext *context, |
164 | | const gchar *element_name, |
165 | | gpointer user_data, |
166 | | GError **error) |
167 | 0 | { |
168 | 0 | XbBuilderCompileHelper *helper = (XbBuilderCompileHelper *)user_data; |
169 | 0 | g_autoptr(XbBuilderNode) parent = xb_builder_node_get_parent(helper->current); |
170 | 0 | if (parent == NULL) { |
171 | 0 | g_set_error_literal(error, |
172 | 0 | G_IO_ERROR, |
173 | 0 | G_IO_ERROR_INVALID_DATA, |
174 | 0 | "Mismatched XML; no parent"); |
175 | 0 | return; |
176 | 0 | } |
177 | 0 | helper->current = parent; |
178 | 0 | helper->depth--; |
179 | 0 | helper->elem_closed = TRUE; |
180 | 0 | } |
181 | | |
182 | | static void |
183 | | xb_builder_compile_text_cb(GMarkupParseContext *context, |
184 | | const gchar *text, |
185 | | gsize text_len, |
186 | | gpointer user_data, |
187 | | GError **error) |
188 | 0 | { |
189 | 0 | XbBuilderCompileHelper *helper = (XbBuilderCompileHelper *)user_data; |
190 | 0 | XbBuilderNode *bn = helper->current; |
191 | 0 | XbBuilderNode *bc = xb_builder_node_get_last_child(bn); |
192 | | |
193 | | /* unimportant */ |
194 | 0 | if (xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE)) |
195 | 0 | return; |
196 | | |
197 | | /* repair text unless we know it's valid */ |
198 | 0 | if (helper->source_flags & XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT) |
199 | 0 | xb_builder_node_add_flag(bn, XB_BUILDER_NODE_FLAG_LITERAL_TEXT); |
200 | | |
201 | | /* text or tail */ |
202 | 0 | if (!helper->elem_closed) { |
203 | 0 | if (xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_HAS_TEXT)) { |
204 | 0 | g_autoptr(GString) str = g_string_new(xb_builder_node_get_text(bn)); |
205 | 0 | g_string_append_len(str, text, text_len); |
206 | 0 | xb_builder_node_set_text(bn, str->str, str->len); |
207 | 0 | } else { |
208 | 0 | xb_builder_node_set_text(bn, text, text_len); |
209 | 0 | } |
210 | 0 | return; |
211 | 0 | } |
212 | | |
213 | | /* does this node have a child */ |
214 | 0 | if (bc != NULL) { |
215 | 0 | xb_builder_node_set_tail(bc, text, text_len); |
216 | 0 | return; |
217 | 0 | } |
218 | | |
219 | | /* always set a tail, even if already set */ |
220 | 0 | xb_builder_node_set_tail(bn, text, text_len); |
221 | 0 | } |
222 | | |
223 | | /** |
224 | | * xb_builder_import_source: |
225 | | * @self: a #XbSilo |
226 | | * @source: a #XbBuilderSource |
227 | | * |
228 | | * Adds a #XbBuilderSource to the #XbBuilder. |
229 | | * |
230 | | * Since: 0.1.0 |
231 | | **/ |
232 | | void |
233 | | xb_builder_import_source(XbBuilder *self, XbBuilderSource *source) |
234 | 0 | { |
235 | 0 | XbBuilderPrivate *priv = GET_PRIVATE(self); |
236 | 0 | g_autofree gchar *guid = NULL; |
237 | |
|
238 | 0 | g_return_if_fail(XB_IS_BUILDER(self)); |
239 | 0 | g_return_if_fail(XB_IS_BUILDER_SOURCE(source)); |
240 | | |
241 | | /* get latest GUID */ |
242 | 0 | guid = xb_builder_source_get_guid(source); |
243 | 0 | xb_builder_append_guid(self, guid); |
244 | 0 | g_ptr_array_add(priv->sources, g_object_ref(source)); |
245 | 0 | } |
246 | | |
247 | | static gboolean |
248 | | xb_builder_compile_source(XbBuilderCompileHelper *helper, |
249 | | XbBuilderSource *source, |
250 | | XbBuilderNode *root, |
251 | | GCancellable *cancellable, |
252 | | GError **error) |
253 | 0 | { |
254 | 0 | GPtrArray *children; |
255 | 0 | XbBuilderNode *info; |
256 | 0 | gsize chunk_size = 32 * 1024; |
257 | 0 | gssize len; |
258 | 0 | g_autofree gchar *data = NULL; |
259 | 0 | g_autofree gchar *guid = xb_builder_source_get_guid(source); |
260 | 0 | g_autoptr(GPtrArray) children_copy = NULL; |
261 | 0 | g_autoptr(GInputStream) istream = NULL; |
262 | 0 | g_autoptr(GMarkupParseContext) ctx = NULL; |
263 | 0 | g_autoptr(GTimer) timer = xb_silo_start_profile(helper->silo); |
264 | 0 | g_autoptr(XbBuilderNode) root_tmp = xb_builder_node_new(NULL); |
265 | 0 | const GMarkupParser parser = {xb_builder_compile_start_element_cb, |
266 | 0 | xb_builder_compile_end_element_cb, |
267 | 0 | xb_builder_compile_text_cb, |
268 | 0 | NULL, |
269 | 0 | NULL}; |
270 | | |
271 | | /* add the source to a fake root in case it fails during processing */ |
272 | 0 | helper->current = root_tmp; |
273 | 0 | helper->source_flags = xb_builder_source_get_flags(source); |
274 | | |
275 | | /* decompress */ |
276 | 0 | istream = xb_builder_source_get_istream(source, cancellable, error); |
277 | 0 | if (istream == NULL) |
278 | 0 | return FALSE; |
279 | | |
280 | | /* parse */ |
281 | 0 | ctx = g_markup_parse_context_new(&parser, |
282 | 0 | G_MARKUP_PREFIX_ERROR_POSITION | |
283 | 0 | G_MARKUP_TREAT_CDATA_AS_TEXT, |
284 | 0 | helper, |
285 | 0 | NULL); |
286 | 0 | data = g_malloc(chunk_size); |
287 | 0 | while ((len = g_input_stream_read(istream, data, chunk_size, cancellable, error)) > 0) { |
288 | 0 | if (!g_markup_parse_context_parse(ctx, data, len, error)) |
289 | 0 | return FALSE; |
290 | 0 | } |
291 | 0 | if (len < 0) |
292 | 0 | return FALSE; |
293 | | |
294 | | /* more opening than closing */ |
295 | 0 | if (root_tmp != helper->current) { |
296 | 0 | g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Mismatched XML"); |
297 | 0 | return FALSE; |
298 | 0 | } |
299 | | |
300 | | /* run any node functions */ |
301 | 0 | if (!xb_builder_source_fixup(source, root_tmp, error)) |
302 | 0 | return FALSE; |
303 | | |
304 | | /* check to see if the root was ignored */ |
305 | 0 | if (xb_builder_node_has_flag(root_tmp, XB_BUILDER_NODE_FLAG_IGNORE)) { |
306 | 0 | g_set_error_literal(error, |
307 | 0 | G_IO_ERROR, |
308 | 0 | G_IO_ERROR_INVALID_DATA, |
309 | 0 | "root node cannot be ignored"); |
310 | 0 | return FALSE; |
311 | 0 | } |
312 | | |
313 | | /* a single root with no siblings was required */ |
314 | 0 | if (helper->compile_flags & XB_BUILDER_COMPILE_FLAG_SINGLE_ROOT) { |
315 | 0 | if (xb_builder_node_get_children(root_tmp)->len > 1) { |
316 | 0 | g_set_error_literal(error, |
317 | 0 | G_IO_ERROR, |
318 | 0 | G_IO_ERROR_INVALID_DATA, |
319 | 0 | "A root node without siblings was required"); |
320 | 0 | return FALSE; |
321 | 0 | } |
322 | 0 | } |
323 | | |
324 | | /* this is something we can query with later */ |
325 | 0 | info = xb_builder_source_get_info(source); |
326 | 0 | if (info != NULL) { |
327 | 0 | children = xb_builder_node_get_children(helper->current); |
328 | 0 | for (guint i = 0; i < children->len; i++) { |
329 | 0 | XbBuilderNode *bn = g_ptr_array_index(children, i); |
330 | 0 | if (!xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE)) |
331 | 0 | xb_builder_node_add_child(bn, info); |
332 | 0 | } |
333 | 0 | } |
334 | | |
335 | | /* add the children to the main document */ |
336 | 0 | children = xb_builder_node_get_children(root_tmp); |
337 | 0 | children_copy = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
338 | 0 | for (guint i = 0; i < children->len; i++) { |
339 | 0 | XbBuilderNode *bn = g_ptr_array_index(children, i); |
340 | 0 | g_ptr_array_add(children_copy, g_object_ref(bn)); |
341 | 0 | } |
342 | 0 | for (guint i = 0; i < children_copy->len; i++) { |
343 | 0 | XbBuilderNode *bn = g_ptr_array_index(children_copy, i); |
344 | 0 | xb_builder_node_unlink(bn); |
345 | 0 | xb_builder_node_add_child(root, bn); |
346 | 0 | } |
347 | | |
348 | | /* success */ |
349 | 0 | xb_silo_add_profile(helper->silo, timer, "compile %s", guid); |
350 | 0 | return TRUE; |
351 | 0 | } |
352 | | |
353 | | static gboolean |
354 | | xb_builder_strtab_element_names_cb(XbBuilderNode *bn, gpointer user_data) |
355 | 0 | { |
356 | 0 | XbBuilderCompileHelper *helper = (XbBuilderCompileHelper *)user_data; |
357 | 0 | const gchar *tmp; |
358 | 0 | guint32 strtab_idx; |
359 | | |
360 | | /* root node */ |
361 | 0 | if (xb_builder_node_get_element(bn) == NULL) |
362 | 0 | return FALSE; |
363 | 0 | if (xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE)) |
364 | 0 | return FALSE; |
365 | 0 | tmp = xb_builder_node_get_element(bn); |
366 | 0 | strtab_idx = xb_builder_compile_add_to_strtab(helper, tmp); |
367 | 0 | if (strtab_idx == XB_SILO_UNSET) |
368 | 0 | return TRUE; |
369 | 0 | xb_builder_node_set_element_idx(bn, strtab_idx); |
370 | 0 | return FALSE; |
371 | 0 | } |
372 | | |
373 | | static gboolean |
374 | | xb_builder_strtab_attr_name_cb(XbBuilderNode *bn, gpointer user_data) |
375 | 0 | { |
376 | 0 | GPtrArray *attrs; |
377 | 0 | XbBuilderCompileHelper *helper = (XbBuilderCompileHelper *)user_data; |
378 | | |
379 | | /* root node */ |
380 | 0 | if (xb_builder_node_get_element(bn) == NULL) |
381 | 0 | return FALSE; |
382 | 0 | if (xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE)) |
383 | 0 | return FALSE; |
384 | 0 | attrs = xb_builder_node_get_attrs(bn); |
385 | 0 | for (guint i = 0; attrs != NULL && i < attrs->len; i++) { |
386 | 0 | XbBuilderNodeAttr *attr = g_ptr_array_index(attrs, i); |
387 | 0 | attr->name_idx = xb_builder_compile_add_to_strtab(helper, attr->name); |
388 | 0 | if (attr->name_idx == XB_SILO_UNSET) |
389 | 0 | return TRUE; |
390 | 0 | } |
391 | 0 | return FALSE; |
392 | 0 | } |
393 | | |
394 | | static gboolean |
395 | | xb_builder_strtab_attr_value_cb(XbBuilderNode *bn, gpointer user_data) |
396 | 0 | { |
397 | 0 | GPtrArray *attrs; |
398 | 0 | XbBuilderCompileHelper *helper = (XbBuilderCompileHelper *)user_data; |
399 | | |
400 | | /* root node */ |
401 | 0 | if (xb_builder_node_get_element(bn) == NULL) |
402 | 0 | return FALSE; |
403 | 0 | if (xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE)) |
404 | 0 | return FALSE; |
405 | 0 | attrs = xb_builder_node_get_attrs(bn); |
406 | 0 | for (guint i = 0; attrs != NULL && i < attrs->len; i++) { |
407 | 0 | XbBuilderNodeAttr *attr = g_ptr_array_index(attrs, i); |
408 | 0 | attr->value_idx = xb_builder_compile_add_to_strtab(helper, attr->value); |
409 | 0 | if (attr->value_idx == XB_SILO_UNSET) |
410 | 0 | return TRUE; |
411 | 0 | } |
412 | 0 | return FALSE; |
413 | 0 | } |
414 | | |
415 | | static gboolean |
416 | | xb_builder_strtab_text_cb(XbBuilderNode *bn, gpointer user_data) |
417 | 0 | { |
418 | 0 | XbBuilderCompileHelper *helper = (XbBuilderCompileHelper *)user_data; |
419 | 0 | const gchar *tmp; |
420 | 0 | guint32 strtab_idx; |
421 | | |
422 | | /* root node */ |
423 | 0 | if (xb_builder_node_get_element(bn) == NULL) |
424 | 0 | return FALSE; |
425 | 0 | if (xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE)) |
426 | 0 | return FALSE; |
427 | 0 | if (xb_builder_node_get_text(bn) != NULL) { |
428 | 0 | tmp = xb_builder_node_get_text(bn); |
429 | 0 | strtab_idx = xb_builder_compile_add_to_strtab(helper, tmp); |
430 | 0 | if (strtab_idx == XB_SILO_UNSET) |
431 | 0 | return TRUE; |
432 | 0 | xb_builder_node_set_text_idx(bn, strtab_idx); |
433 | 0 | } |
434 | 0 | if (xb_builder_node_get_tail(bn) != NULL) { |
435 | 0 | tmp = xb_builder_node_get_tail(bn); |
436 | 0 | strtab_idx = xb_builder_compile_add_to_strtab(helper, tmp); |
437 | 0 | if (strtab_idx == XB_SILO_UNSET) |
438 | 0 | return TRUE; |
439 | 0 | xb_builder_node_set_tail_idx(bn, strtab_idx); |
440 | 0 | } |
441 | 0 | return FALSE; |
442 | 0 | } |
443 | | |
444 | | static gboolean |
445 | | xb_builder_strtab_tokens_cb(XbBuilderNode *bn, gpointer user_data) |
446 | 0 | { |
447 | 0 | XbBuilderCompileHelper *helper = (XbBuilderCompileHelper *)user_data; |
448 | 0 | GPtrArray *tokens = xb_builder_node_get_tokens(bn); |
449 | 0 | guint32 strtab_idx; |
450 | | |
451 | | /* root node */ |
452 | 0 | if (xb_builder_node_get_element(bn) == NULL) |
453 | 0 | return FALSE; |
454 | 0 | if (xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE)) |
455 | 0 | return FALSE; |
456 | 0 | if (tokens == NULL) |
457 | 0 | return FALSE; |
458 | 0 | for (guint i = 0; i < MIN(tokens->len, XB_OPCODE_TOKEN_MAX); i++) { |
459 | 0 | const gchar *tmp = g_ptr_array_index(tokens, i); |
460 | 0 | if (tmp == NULL) |
461 | 0 | continue; |
462 | 0 | strtab_idx = xb_builder_compile_add_to_strtab(helper, tmp); |
463 | 0 | if (strtab_idx == XB_SILO_UNSET) |
464 | 0 | return TRUE; |
465 | 0 | xb_builder_node_add_token_idx(bn, strtab_idx); |
466 | 0 | } |
467 | 0 | return FALSE; |
468 | 0 | } |
469 | | |
470 | | static gboolean |
471 | | xb_builder_xml_lang_prio_cb(XbBuilderNode *bn, gpointer user_data) |
472 | 0 | { |
473 | 0 | GPtrArray *nodes_to_destroy = (GPtrArray *)user_data; |
474 | 0 | gint prio_best = 0; |
475 | 0 | g_autoptr(GPtrArray) nodes = g_ptr_array_new(); |
476 | 0 | GPtrArray *siblings; |
477 | 0 | g_autoptr(XbBuilderNode) parent = xb_builder_node_get_parent(bn); |
478 | | |
479 | | /* root node */ |
480 | 0 | if (xb_builder_node_get_element(bn) == NULL) |
481 | 0 | return FALSE; |
482 | | |
483 | | /* already ignored */ |
484 | 0 | if (xb_builder_node_get_priority(bn) == -2) |
485 | 0 | return FALSE; |
486 | | |
487 | | /* get all the siblings with the same name */ |
488 | 0 | siblings = xb_builder_node_get_children(parent); |
489 | 0 | for (guint i = 0; i < siblings->len; i++) { |
490 | 0 | XbBuilderNode *bn2 = g_ptr_array_index(siblings, i); |
491 | 0 | if (g_strcmp0(xb_builder_node_get_element(bn), xb_builder_node_get_element(bn2)) == |
492 | 0 | 0) |
493 | 0 | g_ptr_array_add(nodes, bn2); |
494 | 0 | } |
495 | | |
496 | | /* only one thing, so bail early */ |
497 | 0 | if (nodes->len == 1) |
498 | 0 | return FALSE; |
499 | | |
500 | | /* find the best locale */ |
501 | 0 | for (guint i = 0; i < nodes->len; i++) { |
502 | 0 | XbBuilderNode *bn2 = g_ptr_array_index(nodes, i); |
503 | 0 | if (xb_builder_node_get_priority(bn2) > prio_best) |
504 | 0 | prio_best = xb_builder_node_get_priority(bn2); |
505 | 0 | } |
506 | | |
507 | | /* add any nodes not as good as the bext locale to the kill list */ |
508 | 0 | for (guint i = 0; i < nodes->len; i++) { |
509 | 0 | XbBuilderNode *bn2 = g_ptr_array_index(nodes, i); |
510 | 0 | if (xb_builder_node_get_priority(bn2) < prio_best) |
511 | 0 | g_ptr_array_add(nodes_to_destroy, g_object_ref(bn2)); |
512 | | |
513 | | /* never visit this node again */ |
514 | 0 | xb_builder_node_set_priority(bn2, -2); |
515 | 0 | } |
516 | |
|
517 | 0 | return FALSE; |
518 | 0 | } |
519 | | |
520 | | static gboolean |
521 | | xb_builder_nodetab_size_cb(XbBuilderNode *bn, gpointer user_data) |
522 | 0 | { |
523 | 0 | guint32 *sz = (guint32 *)user_data; |
524 | | |
525 | | /* root node */ |
526 | 0 | if (xb_builder_node_get_element(bn) == NULL) |
527 | 0 | return FALSE; |
528 | 0 | if (xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE)) |
529 | 0 | return FALSE; |
530 | 0 | *sz += xb_builder_node_size(bn) + 1; /* +1 for the sentinel */ |
531 | 0 | return FALSE; |
532 | 0 | } |
533 | | |
534 | | typedef struct { |
535 | | GByteArray *buf; |
536 | | } XbBuilderNodetabHelper; |
537 | | |
538 | | static void |
539 | | xb_builder_nodetab_write_sentinel(XbBuilderNodetabHelper *helper) |
540 | 0 | { |
541 | 0 | XbSiloNode sn = { |
542 | 0 | .flags = XB_SILO_NODE_FLAG_NONE, |
543 | 0 | .attr_count = 0, |
544 | 0 | }; |
545 | | // g_debug ("SENT @%u", (guint) helper->buf->len); |
546 | 0 | g_byte_array_append(helper->buf, (const guint8 *)&sn, xb_silo_node_get_size(&sn)); |
547 | 0 | } |
548 | | |
549 | 0 | #define XB_BUILDER_ATTR_MAX ((1 << 6) - 1) |
550 | | |
551 | | static gboolean |
552 | | xb_builder_nodetab_write_node(XbBuilderNodetabHelper *helper, XbBuilderNode *bn, GError **error) |
553 | 0 | { |
554 | 0 | GPtrArray *attrs = xb_builder_node_get_attrs(bn); |
555 | 0 | GArray *token_idxs = xb_builder_node_get_token_idxs(bn); |
556 | 0 | XbSiloNode sn = { |
557 | 0 | .flags = XB_SILO_NODE_FLAG_IS_ELEMENT, |
558 | 0 | .attr_count = (attrs != NULL) ? attrs->len : 0, |
559 | 0 | .element_name = xb_builder_node_get_element_idx(bn), |
560 | 0 | .next = 0x0, |
561 | 0 | .parent = 0x0, |
562 | 0 | .text = xb_builder_node_get_text_idx(bn), |
563 | 0 | .tail = xb_builder_node_get_tail_idx(bn), |
564 | 0 | .token_count = 0, |
565 | 0 | }; |
566 | | |
567 | | /* sanity check */ |
568 | 0 | if (attrs != NULL && attrs->len > XB_BUILDER_ATTR_MAX) { |
569 | 0 | g_set_error(error, |
570 | 0 | G_IO_ERROR, |
571 | 0 | G_IO_ERROR_NOT_SUPPORTED, |
572 | 0 | "too many attributes: %u", |
573 | 0 | attrs->len); |
574 | 0 | return FALSE; |
575 | 0 | } |
576 | | |
577 | | /* add tokens */ |
578 | 0 | if (token_idxs != NULL) |
579 | 0 | sn.flags |= XB_SILO_NODE_FLAG_IS_TOKENIZED; |
580 | | |
581 | | /* if the node had no children and the text is just whitespace then |
582 | | * remove it even in literal mode */ |
583 | 0 | if (xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_LITERAL_TEXT)) { |
584 | 0 | if (xb_string_isspace(xb_builder_node_get_text(bn), -1)) |
585 | 0 | sn.text = XB_SILO_UNSET; |
586 | 0 | if (xb_string_isspace(xb_builder_node_get_tail(bn), -1)) |
587 | 0 | sn.tail = XB_SILO_UNSET; |
588 | 0 | } |
589 | | |
590 | | /* save this so we can set up the ->next pointers correctly */ |
591 | 0 | xb_builder_node_set_offset(bn, helper->buf->len); |
592 | | |
593 | | // g_debug ("NODE @%u (%s)", (guint) helper->buf->len, xb_builder_node_get_element |
594 | | //(bn)); |
595 | | |
596 | | /* there is no point adding more tokens than we can match */ |
597 | 0 | if (token_idxs != NULL) |
598 | 0 | sn.token_count = MIN(token_idxs->len, XB_OPCODE_TOKEN_MAX); |
599 | | |
600 | | /* add to the buf */ |
601 | 0 | g_byte_array_append(helper->buf, (const guint8 *)&sn, sizeof(sn)); |
602 | | |
603 | | /* add to the buf */ |
604 | 0 | for (guint i = 0; attrs != NULL && i < attrs->len; i++) { |
605 | 0 | XbBuilderNodeAttr *ba = g_ptr_array_index(attrs, i); |
606 | 0 | XbSiloNodeAttr attr = { |
607 | 0 | .attr_name = ba->name_idx, |
608 | 0 | .attr_value = ba->value_idx, |
609 | 0 | }; |
610 | 0 | g_byte_array_append(helper->buf, (const guint8 *)&attr, sizeof(attr)); |
611 | 0 | } |
612 | | |
613 | | /* add tokens */ |
614 | 0 | for (guint i = 0; i < sn.token_count; i++) { |
615 | 0 | guint32 idx = g_array_index(token_idxs, guint32, i); |
616 | 0 | g_byte_array_append(helper->buf, (const guint8 *)&idx, sizeof(idx)); |
617 | 0 | } |
618 | | |
619 | | /* success */ |
620 | 0 | return TRUE; |
621 | 0 | } |
622 | | |
623 | | static gboolean |
624 | | xb_builder_nodetab_write(XbBuilderNodetabHelper *helper, XbBuilderNode *bn, GError **error) |
625 | 0 | { |
626 | 0 | GPtrArray *children; |
627 | | |
628 | | /* ignore this */ |
629 | 0 | if (xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE)) |
630 | 0 | return TRUE; |
631 | | |
632 | | /* element */ |
633 | 0 | if (xb_builder_node_get_element(bn) != NULL) { |
634 | 0 | if (!xb_builder_nodetab_write_node(helper, bn, error)) |
635 | 0 | return FALSE; |
636 | 0 | } |
637 | | |
638 | | /* children */ |
639 | 0 | children = xb_builder_node_get_children(bn); |
640 | 0 | for (guint i = 0; i < children->len; i++) { |
641 | 0 | XbBuilderNode *bc = g_ptr_array_index(children, i); |
642 | 0 | if (!xb_builder_nodetab_write(helper, bc, error)) |
643 | 0 | return FALSE; |
644 | 0 | } |
645 | | |
646 | | /* sentinel */ |
647 | 0 | if (xb_builder_node_get_element(bn) != NULL) |
648 | 0 | xb_builder_nodetab_write_sentinel(helper); |
649 | | |
650 | | /* success */ |
651 | 0 | return TRUE; |
652 | 0 | } |
653 | | |
654 | | static XbSiloNode * |
655 | | xb_builder_get_node(GByteArray *str, guint32 off) |
656 | 0 | { |
657 | 0 | return (XbSiloNode *)(str->data + off); |
658 | 0 | } |
659 | | |
660 | | static gboolean |
661 | | xb_builder_nodetab_fix_cb(XbBuilderNode *bn, gpointer user_data) |
662 | 0 | { |
663 | 0 | GPtrArray *siblings; |
664 | 0 | XbBuilderNodetabHelper *helper = (XbBuilderNodetabHelper *)user_data; |
665 | 0 | XbSiloNode *sn; |
666 | 0 | gboolean found = FALSE; |
667 | 0 | g_autoptr(XbBuilderNode) parent = xb_builder_node_get_parent(bn); |
668 | | |
669 | | /* root node */ |
670 | 0 | if (xb_builder_node_get_element(bn) == NULL) |
671 | 0 | return FALSE; |
672 | 0 | if (xb_builder_node_has_flag(bn, XB_BUILDER_NODE_FLAG_IGNORE)) |
673 | 0 | return FALSE; |
674 | | |
675 | | /* get the position in the buffer */ |
676 | 0 | sn = xb_builder_get_node(helper->buf, xb_builder_node_get_offset(bn)); |
677 | 0 | if (sn == NULL) |
678 | 0 | return FALSE; |
679 | | |
680 | | /* set the parent if the node has one */ |
681 | 0 | if (xb_builder_node_get_element(parent) != NULL) |
682 | 0 | sn->parent = xb_builder_node_get_offset(parent); |
683 | | |
684 | | /* set ->next if the node has one */ |
685 | 0 | siblings = xb_builder_node_get_children(parent); |
686 | 0 | for (guint i = 0; i < siblings->len; i++) { |
687 | 0 | XbBuilderNode *bn2 = g_ptr_array_index(siblings, i); |
688 | 0 | if (bn2 == bn) { |
689 | 0 | found = TRUE; |
690 | 0 | continue; |
691 | 0 | } |
692 | 0 | if (!found) |
693 | 0 | continue; |
694 | 0 | if (!xb_builder_node_has_flag(bn2, XB_BUILDER_NODE_FLAG_IGNORE)) { |
695 | 0 | sn->next = xb_builder_node_get_offset(bn2); |
696 | 0 | break; |
697 | 0 | } |
698 | 0 | } |
699 | |
|
700 | 0 | return FALSE; |
701 | 0 | } |
702 | | |
703 | | static void |
704 | | xb_builder_compile_helper_free(XbBuilderCompileHelper *helper) |
705 | 0 | { |
706 | 0 | g_hash_table_unref(helper->strtab_hash); |
707 | 0 | g_byte_array_unref(helper->strtab); |
708 | 0 | g_clear_error(&helper->error); |
709 | 0 | g_clear_object(&helper->silo); |
710 | 0 | g_object_unref(helper->root); |
711 | 0 | g_free(helper); |
712 | 0 | } |
713 | | |
714 | | G_DEFINE_AUTOPTR_CLEANUP_FUNC(XbBuilderCompileHelper, xb_builder_compile_helper_free) |
715 | | |
716 | | static gchar * |
717 | | xb_builder_generate_guid(XbBuilder *self) |
718 | 0 | { |
719 | 0 | XbBuilderPrivate *priv = GET_PRIVATE(self); |
720 | 0 | XbGuid guid = {0x0}; |
721 | 0 | if (priv->guid->len > 0) { |
722 | 0 | xb_guid_compute_for_data(&guid, (const guint8 *)priv->guid->str, priv->guid->len); |
723 | 0 | } |
724 | 0 | return xb_guid_to_string(&guid); |
725 | 0 | } |
726 | | |
727 | | /** |
728 | | * xb_builder_import_node: |
729 | | * @self: a #XbSilo |
730 | | * @bn: a #XbBuilderNode |
731 | | * |
732 | | * Adds a node tree to the builder. |
733 | | * |
734 | | * If you are manually adding dynamic data sourced from a non-static source then you MUST use |
735 | | * xb_builder_append_guid() with the appropriate GUID value, e.g. the file name and mtime. |
736 | | * |
737 | | * If no appropriate value is available, the caller can use something like: |
738 | | * |
739 | | * g_autofree gchar *tmp = xb_builder_node_export(bn, XB_NODE_EXPORT_FLAG_NONE, NULL); |
740 | | * xb_builder_append_guid(builder, tmp); |
741 | | * |
742 | | * Failure to include an appropriate GUID value would allow an out-of-data silo to be used. |
743 | | * |
744 | | * Since: 0.1.0 |
745 | | **/ |
746 | | void |
747 | | xb_builder_import_node(XbBuilder *self, XbBuilderNode *bn) |
748 | 0 | { |
749 | 0 | XbBuilderPrivate *priv = GET_PRIVATE(self); |
750 | 0 | g_return_if_fail(XB_IS_BUILDER(self)); |
751 | 0 | g_return_if_fail(XB_IS_BUILDER_NODE(bn)); |
752 | 0 | g_ptr_array_add(priv->nodes, g_object_ref(bn)); |
753 | 0 | } |
754 | | |
755 | | /** |
756 | | * xb_builder_add_locale: |
757 | | * @self: a #XbSilo |
758 | | * @locale: a locale, e.g. "en_US" |
759 | | * |
760 | | * Adds a locale to the builder. Locales added first will be prioritised over |
761 | | * locales added later. |
762 | | * |
763 | | * Since: 0.1.0 |
764 | | **/ |
765 | | void |
766 | | xb_builder_add_locale(XbBuilder *self, const gchar *locale) |
767 | 0 | { |
768 | 0 | XbBuilderPrivate *priv = GET_PRIVATE(self); |
769 | |
|
770 | 0 | g_return_if_fail(XB_IS_BUILDER(self)); |
771 | 0 | g_return_if_fail(locale != NULL); |
772 | | |
773 | 0 | if (g_str_has_suffix(locale, ".UTF-8")) |
774 | 0 | return; |
775 | 0 | for (guint i = 0; i < priv->locales->len; i++) { |
776 | 0 | const gchar *locale_tmp = g_ptr_array_index(priv->locales, i); |
777 | 0 | if (g_strcmp0(locale_tmp, locale) == 0) |
778 | 0 | return; |
779 | 0 | } |
780 | 0 | g_ptr_array_add(priv->locales, g_strdup(locale)); |
781 | | |
782 | | /* if the user changes LANG, the blob is no longer valid */ |
783 | 0 | xb_builder_append_guid(self, locale); |
784 | 0 | } |
785 | | |
786 | | static gboolean |
787 | | xb_builder_watch_source(XbBuilder *self, |
788 | | XbBuilderSource *source, |
789 | | XbSilo *silo, |
790 | | GCancellable *cancellable, |
791 | | GError **error) |
792 | 0 | { |
793 | 0 | GFile *file = xb_builder_source_get_file(source); |
794 | 0 | g_autoptr(GFile) watched_file = NULL; |
795 | 0 | if (file == NULL) |
796 | 0 | return TRUE; |
797 | 0 | if ((xb_builder_source_get_flags(source) & |
798 | 0 | (XB_BUILDER_SOURCE_FLAG_WATCH_FILE | XB_BUILDER_SOURCE_FLAG_WATCH_DIRECTORY)) == 0) |
799 | 0 | return TRUE; |
800 | | |
801 | 0 | if (xb_builder_source_get_flags(source) & XB_BUILDER_SOURCE_FLAG_WATCH_DIRECTORY) |
802 | 0 | watched_file = g_file_get_parent(file); |
803 | 0 | else |
804 | 0 | watched_file = g_object_ref(file); |
805 | |
|
806 | 0 | return xb_silo_watch_file(silo, watched_file, cancellable, error); |
807 | 0 | } |
808 | | |
809 | | static gboolean |
810 | | xb_builder_watch_sources(XbBuilder *self, XbSilo *silo, GCancellable *cancellable, GError **error) |
811 | 0 | { |
812 | 0 | XbBuilderPrivate *priv = GET_PRIVATE(self); |
813 | 0 | for (guint i = 0; i < priv->sources->len; i++) { |
814 | 0 | XbBuilderSource *source = g_ptr_array_index(priv->sources, i); |
815 | 0 | if (!xb_builder_watch_source(self, source, silo, cancellable, error)) |
816 | 0 | return FALSE; |
817 | 0 | } |
818 | 0 | return TRUE; |
819 | 0 | } |
820 | | |
821 | | /** |
822 | | * xb_builder_compile: |
823 | | * @self: a #XbSilo |
824 | | * @flags: some #XbBuilderCompileFlags, e.g. %XB_BUILDER_SOURCE_FLAG_LITERAL_TEXT |
825 | | * @cancellable: a #GCancellable, or %NULL |
826 | | * @error: the #GError, or %NULL |
827 | | * |
828 | | * Compiles a #XbSilo. |
829 | | * |
830 | | * Returns: (transfer full): a #XbSilo, or %NULL for error |
831 | | * |
832 | | * Since: 0.1.0 |
833 | | **/ |
834 | | XbSilo * |
835 | | xb_builder_compile(XbBuilder *self, |
836 | | XbBuilderCompileFlags flags, |
837 | | GCancellable *cancellable, |
838 | | GError **error) |
839 | 0 | { |
840 | 0 | XbBuilderPrivate *priv = GET_PRIVATE(self); |
841 | 0 | guint32 nodetabsz = sizeof(XbSiloHeader); |
842 | 0 | g_autoptr(GByteArray) buf = NULL; |
843 | 0 | g_autoptr(GBytes) blob = NULL; |
844 | 0 | XbSiloHeader *hdrptr; |
845 | 0 | XbSiloHeader hdr = { |
846 | 0 | .magic = XB_SILO_MAGIC_BYTES, |
847 | 0 | .version = XB_SILO_VERSION, |
848 | 0 | .strtab = 0, |
849 | 0 | .strtab_ntags = 0, |
850 | 0 | .padding = {0x0}, |
851 | 0 | .guid = {0x0}, |
852 | 0 | .filesz = 0x0, |
853 | 0 | }; |
854 | 0 | XbBuilderNodetabHelper nodetab_helper = { |
855 | 0 | .buf = NULL, |
856 | 0 | }; |
857 | 0 | g_autoptr(GPtrArray) nodes_to_destroy = |
858 | 0 | g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
859 | 0 | g_autoptr(GTimer) timer = NULL; |
860 | 0 | g_autoptr(XbBuilderCompileHelper) helper = NULL; |
861 | |
|
862 | 0 | g_return_val_if_fail(XB_IS_BUILDER(self), NULL); |
863 | 0 | g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); |
864 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
865 | | |
866 | | /* this is inferred */ |
867 | 0 | if (flags & XB_BUILDER_COMPILE_FLAG_SINGLE_LANG) |
868 | 0 | flags |= XB_BUILDER_COMPILE_FLAG_NATIVE_LANGS; |
869 | | |
870 | | /* the builder needs to know the locales */ |
871 | 0 | if (priv->locales->len == 0 && (flags & XB_BUILDER_COMPILE_FLAG_NATIVE_LANGS)) { |
872 | 0 | g_set_error_literal(error, |
873 | 0 | G_IO_ERROR, |
874 | 0 | G_IO_ERROR_INVALID_DATA, |
875 | 0 | "No locales set and using NATIVE_LANGS"); |
876 | 0 | return NULL; |
877 | 0 | } |
878 | | |
879 | | /* create helper used for compiling */ |
880 | 0 | helper = g_new0(XbBuilderCompileHelper, 1); |
881 | 0 | helper->compile_flags = flags; |
882 | 0 | helper->root = xb_builder_node_new(NULL); |
883 | 0 | helper->silo = xb_silo_new(); |
884 | 0 | helper->locales = priv->locales; |
885 | 0 | helper->strtab = g_byte_array_new(); |
886 | 0 | helper->strtab_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); |
887 | 0 | helper->elem_closed = FALSE; |
888 | | |
889 | | /* for profiling */ |
890 | 0 | xb_silo_set_profile_flags(helper->silo, priv->profile_flags); |
891 | 0 | timer = xb_silo_start_profile(helper->silo); |
892 | | |
893 | | /* build node tree */ |
894 | 0 | for (guint i = 0; i < priv->sources->len; i++) { |
895 | 0 | XbBuilderSource *source = g_ptr_array_index(priv->sources, i); |
896 | 0 | const gchar *prefix = xb_builder_source_get_prefix(source); |
897 | 0 | g_autofree gchar *source_guid = xb_builder_source_get_guid(source); |
898 | 0 | g_autoptr(XbBuilderNode) root = NULL; |
899 | 0 | g_autoptr(GError) error_local = NULL; |
900 | | |
901 | | /* find, or create the prefix */ |
902 | 0 | if (prefix != NULL) { |
903 | 0 | root = xb_builder_node_get_child(helper->root, prefix, NULL); |
904 | 0 | if (root == NULL) |
905 | 0 | root = xb_builder_node_insert(helper->root, prefix, NULL); |
906 | 0 | } else { |
907 | | /* don't allow damaged XML files to ruin all the next ones */ |
908 | 0 | root = g_object_ref(helper->root); |
909 | 0 | } |
910 | | |
911 | | /* watch the source */ |
912 | 0 | if (!xb_builder_watch_source(self, source, helper->silo, cancellable, error)) |
913 | 0 | return NULL; |
914 | | |
915 | 0 | if (priv->profile_flags & XB_SILO_PROFILE_FLAG_DEBUG) |
916 | 0 | g_debug("compiling %s…", source_guid); |
917 | 0 | if (!xb_builder_compile_source(helper, source, root, cancellable, &error_local)) { |
918 | 0 | if (flags & XB_BUILDER_COMPILE_FLAG_IGNORE_INVALID) { |
919 | 0 | g_debug("ignoring invalid file %s: %s", |
920 | 0 | source_guid, |
921 | 0 | error_local->message); |
922 | 0 | continue; |
923 | 0 | } |
924 | 0 | g_propagate_prefixed_error(error, |
925 | 0 | g_steal_pointer(&error_local), |
926 | 0 | "failed to compile %s: ", |
927 | 0 | source_guid); |
928 | 0 | return NULL; |
929 | 0 | } |
930 | 0 | } |
931 | | |
932 | | /* run any node functions */ |
933 | 0 | for (guint i = 0; i < priv->fixups->len; i++) { |
934 | 0 | XbBuilderFixup *fixup = g_ptr_array_index(priv->fixups, i); |
935 | 0 | if (!xb_builder_fixup_node(fixup, helper->root, error)) |
936 | 0 | return NULL; |
937 | 0 | } |
938 | | |
939 | | /* only include the highest priority translation */ |
940 | 0 | if (flags & XB_BUILDER_COMPILE_FLAG_SINGLE_LANG) { |
941 | 0 | xb_builder_node_traverse(helper->root, |
942 | 0 | G_PRE_ORDER, |
943 | 0 | G_TRAVERSE_ALL, |
944 | 0 | -1, |
945 | 0 | xb_builder_xml_lang_prio_cb, |
946 | 0 | nodes_to_destroy); |
947 | 0 | for (guint i = 0; i < nodes_to_destroy->len; i++) { |
948 | 0 | XbBuilderNode *bn = g_ptr_array_index(nodes_to_destroy, i); |
949 | 0 | xb_builder_node_unlink(bn); |
950 | 0 | } |
951 | 0 | xb_silo_add_profile(helper->silo, timer, "filter single-lang"); |
952 | 0 | } |
953 | | |
954 | | /* add any manually build nodes */ |
955 | 0 | for (guint i = 0; i < priv->nodes->len; i++) { |
956 | 0 | XbBuilderNode *bn = g_ptr_array_index(priv->nodes, i); |
957 | 0 | xb_builder_node_add_child(helper->root, bn); |
958 | 0 | } |
959 | | |
960 | | /* get the size of the nodetab */ |
961 | 0 | xb_builder_node_traverse(helper->root, |
962 | 0 | G_PRE_ORDER, |
963 | 0 | G_TRAVERSE_ALL, |
964 | 0 | -1, |
965 | 0 | xb_builder_nodetab_size_cb, |
966 | 0 | &nodetabsz); |
967 | 0 | buf = g_byte_array_sized_new(nodetabsz); |
968 | 0 | xb_silo_add_profile(helper->silo, timer, "get size nodetab"); |
969 | | |
970 | | /* add everything to the strtab */ |
971 | 0 | xb_builder_node_traverse(helper->root, |
972 | 0 | G_PRE_ORDER, |
973 | 0 | G_TRAVERSE_ALL, |
974 | 0 | -1, |
975 | 0 | xb_builder_strtab_element_names_cb, |
976 | 0 | helper); |
977 | 0 | if (helper->error != NULL) { |
978 | 0 | g_propagate_error(error, g_steal_pointer(&helper->error)); |
979 | 0 | return NULL; |
980 | 0 | } |
981 | 0 | hdr.strtab_ntags = g_hash_table_size(helper->strtab_hash); |
982 | 0 | xb_silo_add_profile(helper->silo, timer, "adding strtab element"); |
983 | 0 | xb_builder_node_traverse(helper->root, |
984 | 0 | G_PRE_ORDER, |
985 | 0 | G_TRAVERSE_ALL, |
986 | 0 | -1, |
987 | 0 | xb_builder_strtab_attr_name_cb, |
988 | 0 | helper); |
989 | 0 | if (helper->error != NULL) { |
990 | 0 | g_propagate_error(error, g_steal_pointer(&helper->error)); |
991 | 0 | return NULL; |
992 | 0 | } |
993 | 0 | xb_silo_add_profile(helper->silo, timer, "adding strtab attr name"); |
994 | 0 | xb_builder_node_traverse(helper->root, |
995 | 0 | G_PRE_ORDER, |
996 | 0 | G_TRAVERSE_ALL, |
997 | 0 | -1, |
998 | 0 | xb_builder_strtab_attr_value_cb, |
999 | 0 | helper); |
1000 | 0 | if (helper->error != NULL) { |
1001 | 0 | g_propagate_error(error, g_steal_pointer(&helper->error)); |
1002 | 0 | return NULL; |
1003 | 0 | } |
1004 | 0 | xb_silo_add_profile(helper->silo, timer, "adding strtab attr value"); |
1005 | 0 | xb_builder_node_traverse(helper->root, |
1006 | 0 | G_PRE_ORDER, |
1007 | 0 | G_TRAVERSE_ALL, |
1008 | 0 | -1, |
1009 | 0 | xb_builder_strtab_text_cb, |
1010 | 0 | helper); |
1011 | 0 | if (helper->error != NULL) { |
1012 | 0 | g_propagate_error(error, g_steal_pointer(&helper->error)); |
1013 | 0 | return NULL; |
1014 | 0 | } |
1015 | 0 | xb_silo_add_profile(helper->silo, timer, "adding strtab text"); |
1016 | 0 | xb_builder_node_traverse(helper->root, |
1017 | 0 | G_PRE_ORDER, |
1018 | 0 | G_TRAVERSE_ALL, |
1019 | 0 | -1, |
1020 | 0 | xb_builder_strtab_tokens_cb, |
1021 | 0 | helper); |
1022 | 0 | if (helper->error != NULL) { |
1023 | 0 | g_propagate_error(error, g_steal_pointer(&helper->error)); |
1024 | 0 | return NULL; |
1025 | 0 | } |
1026 | 0 | xb_silo_add_profile(helper->silo, timer, "adding strtab tokens"); |
1027 | | |
1028 | | /* add the initial header */ |
1029 | 0 | hdr.strtab = nodetabsz; |
1030 | 0 | if (priv->guid->len > 0) { |
1031 | 0 | XbGuid guid_tmp; |
1032 | 0 | xb_guid_compute_for_data(&guid_tmp, |
1033 | 0 | (const guint8 *)priv->guid->str, |
1034 | 0 | priv->guid->len); |
1035 | 0 | memcpy(&hdr.guid, &guid_tmp, sizeof(guid_tmp)); |
1036 | 0 | } |
1037 | 0 | g_byte_array_append(buf, (const guint8 *)&hdr, sizeof(hdr)); |
1038 | | |
1039 | | /* write nodes to the nodetab */ |
1040 | 0 | nodetab_helper.buf = buf; |
1041 | 0 | if (!xb_builder_nodetab_write(&nodetab_helper, helper->root, error)) |
1042 | 0 | return NULL; |
1043 | 0 | xb_silo_add_profile(helper->silo, timer, "writing nodetab"); |
1044 | | |
1045 | | /* set all the ->next and ->parent offsets */ |
1046 | 0 | xb_builder_node_traverse(helper->root, |
1047 | 0 | G_PRE_ORDER, |
1048 | 0 | G_TRAVERSE_ALL, |
1049 | 0 | -1, |
1050 | 0 | xb_builder_nodetab_fix_cb, |
1051 | 0 | &nodetab_helper); |
1052 | 0 | xb_silo_add_profile(helper->silo, timer, "fixing ->parent and ->next"); |
1053 | | |
1054 | | /* append the string table */ |
1055 | 0 | g_byte_array_append(buf, (const guint8 *)helper->strtab->data, helper->strtab->len); |
1056 | 0 | xb_silo_add_profile(helper->silo, timer, "appending strtab"); |
1057 | | |
1058 | | /* update the file size */ |
1059 | 0 | hdrptr = (XbSiloHeader *)buf->data; |
1060 | 0 | hdrptr->filesz = buf->len; |
1061 | | |
1062 | | /* create data */ |
1063 | 0 | blob = g_byte_array_free_to_bytes(g_steal_pointer(&buf)); |
1064 | 0 | if (!xb_silo_load_from_bytes(helper->silo, blob, XB_SILO_LOAD_FLAG_NONE, error)) |
1065 | 0 | return NULL; |
1066 | | |
1067 | | /* success */ |
1068 | 0 | return g_steal_pointer(&helper->silo); |
1069 | 0 | } |
1070 | | |
1071 | | /** |
1072 | | * xb_builder_ensure: |
1073 | | * @self: a #XbSilo |
1074 | | * @file: a #GFile |
1075 | | * @flags: some #XbBuilderCompileFlags, e.g. %XB_BUILDER_COMPILE_FLAG_IGNORE_INVALID |
1076 | | * @cancellable: a #GCancellable, or %NULL |
1077 | | * @error: the #GError, or %NULL |
1078 | | * |
1079 | | * Ensures @file is up to date, and returns a compiled #XbSilo. |
1080 | | * |
1081 | | * If @silo is being used by a query (e.g. in another thread) then all node |
1082 | | * data is immediately invalid. |
1083 | | * |
1084 | | * The returned #XbSilo will use the thread-default main context at the time of |
1085 | | * calling this function for its future signal emissions. |
1086 | | * |
1087 | | * Returns: (transfer full): a #XbSilo, or %NULL for error |
1088 | | * |
1089 | | * Since: 0.1.0 |
1090 | | **/ |
1091 | | XbSilo * |
1092 | | xb_builder_ensure(XbBuilder *self, |
1093 | | GFile *file, |
1094 | | XbBuilderCompileFlags flags, |
1095 | | GCancellable *cancellable, |
1096 | | GError **error) |
1097 | 0 | { |
1098 | 0 | XbBuilderPrivate *priv = GET_PRIVATE(self); |
1099 | 0 | XbSiloLoadFlags load_flags = XB_SILO_LOAD_FLAG_NONE; |
1100 | 0 | g_autofree gchar *fn = NULL; |
1101 | 0 | g_autoptr(XbSilo) silo_tmp = xb_silo_new(); |
1102 | 0 | g_autoptr(XbSilo) silo = NULL; |
1103 | 0 | g_autoptr(GError) error_local = NULL; |
1104 | |
|
1105 | 0 | g_return_val_if_fail(XB_IS_BUILDER(self), NULL); |
1106 | 0 | g_return_val_if_fail(G_IS_FILE(file), NULL); |
1107 | 0 | g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL); |
1108 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
1109 | | |
1110 | | /* watch the blob, so propagate flags */ |
1111 | 0 | if (flags & XB_BUILDER_COMPILE_FLAG_WATCH_BLOB) { |
1112 | 0 | load_flags |= XB_SILO_LOAD_FLAG_WATCH_BLOB; |
1113 | 0 | if (!xb_silo_watch_file(silo_tmp, file, cancellable, error)) |
1114 | 0 | return NULL; |
1115 | 0 | } |
1116 | | |
1117 | | /* ensure all the sources are watched */ |
1118 | 0 | if (!xb_builder_watch_sources(self, silo_tmp, cancellable, error)) |
1119 | 0 | return NULL; |
1120 | | |
1121 | | /* profile new silo if needed */ |
1122 | 0 | xb_silo_set_profile_flags(silo_tmp, priv->profile_flags); |
1123 | | |
1124 | | /* load the file and peek at the GUIDs */ |
1125 | 0 | fn = g_file_get_path(file); |
1126 | 0 | g_debug("attempting to load %s", fn); |
1127 | 0 | if (!xb_silo_load_from_file(silo_tmp, |
1128 | 0 | file, |
1129 | 0 | XB_SILO_LOAD_FLAG_NONE, |
1130 | 0 | cancellable, |
1131 | 0 | &error_local)) { |
1132 | 0 | g_debug("failed to load silo: %s", error_local->message); |
1133 | 0 | } else { |
1134 | 0 | g_autofree gchar *guid = xb_builder_generate_guid(self); |
1135 | 0 | if (priv->profile_flags & XB_SILO_PROFILE_FLAG_DEBUG) |
1136 | 0 | g_debug("GUID string: %s", priv->guid->str); |
1137 | 0 | g_debug("file: %s, current:%s", xb_silo_get_guid(silo_tmp), guid); |
1138 | | |
1139 | | /* no compile required */ |
1140 | 0 | if (g_strcmp0(xb_silo_get_guid(silo_tmp), guid) == 0 || |
1141 | 0 | (flags & XB_BUILDER_COMPILE_FLAG_IGNORE_GUID) > 0) { |
1142 | 0 | g_debug("loading silo with existing file contents"); |
1143 | 0 | return g_steal_pointer(&silo_tmp); |
1144 | 0 | } |
1145 | 0 | } |
1146 | | |
1147 | | /* fallback to just creating a new file */ |
1148 | 0 | silo = xb_builder_compile(self, flags, cancellable, error); |
1149 | 0 | if (silo == NULL) |
1150 | 0 | return NULL; |
1151 | | |
1152 | | /* this might seem unnecessary, but windows cannot do _chsize() (introduced in GLib commit |
1153 | | * https://gitlab.gnome.org/GNOME/glib/-/commit/3f705ffa1230757b910a06a705104d4b0fee2c05) |
1154 | | * on a mmap'd file -- so manually tear that down before writing the new file */ |
1155 | 0 | g_clear_object(&silo_tmp); |
1156 | 0 | if (!xb_silo_save_to_file(silo, file, NULL, error)) |
1157 | 0 | return NULL; |
1158 | | |
1159 | | /* load from a file to re-mmap it */ |
1160 | 0 | if (!xb_silo_load_from_file(silo, file, load_flags, cancellable, error)) |
1161 | 0 | return NULL; |
1162 | | |
1163 | | /* ensure all the sources are watched on the reloaded silo */ |
1164 | 0 | if (!xb_builder_watch_sources(self, silo, cancellable, error)) |
1165 | 0 | return NULL; |
1166 | | |
1167 | | /* success */ |
1168 | 0 | return g_steal_pointer(&silo); |
1169 | 0 | } |
1170 | | |
1171 | | /** |
1172 | | * xb_builder_append_guid: |
1173 | | * @self: a #XbSilo |
1174 | | * @guid: any text, typcically a filename or GUID |
1175 | | * |
1176 | | * Adds the GUID to the internal correctness hash. |
1177 | | * |
1178 | | * Since: 0.1.0 |
1179 | | **/ |
1180 | | void |
1181 | | xb_builder_append_guid(XbBuilder *self, const gchar *guid) |
1182 | 0 | { |
1183 | 0 | XbBuilderPrivate *priv = GET_PRIVATE(self); |
1184 | 0 | if (priv->guid->len > 0) |
1185 | 0 | g_string_append(priv->guid, "&"); |
1186 | 0 | g_string_append(priv->guid, guid); |
1187 | 0 | } |
1188 | | |
1189 | | /** |
1190 | | * xb_builder_set_profile_flags: |
1191 | | * @self: a #XbBuilder |
1192 | | * @profile_flags: some #XbSiloProfileFlags, e.g. %XB_SILO_PROFILE_FLAG_DEBUG |
1193 | | * |
1194 | | * Enables or disables the collection of profiling data. |
1195 | | * |
1196 | | * Since: 0.1.1 |
1197 | | **/ |
1198 | | void |
1199 | | xb_builder_set_profile_flags(XbBuilder *self, XbSiloProfileFlags profile_flags) |
1200 | 0 | { |
1201 | 0 | XbBuilderPrivate *priv = GET_PRIVATE(self); |
1202 | 0 | g_return_if_fail(XB_IS_BUILDER(self)); |
1203 | 0 | priv->profile_flags = profile_flags; |
1204 | 0 | } |
1205 | | |
1206 | | /** |
1207 | | * xb_builder_add_fixup: |
1208 | | * @self: a #XbBuilder |
1209 | | * @fixup: a #XbBuilderFixup |
1210 | | * |
1211 | | * Adds a function that will get run on every #XbBuilderNode compile creates |
1212 | | * for the silo. This is run after all the #XbBuilderSource fixups have been |
1213 | | * run. |
1214 | | * |
1215 | | * Since: 0.1.3 |
1216 | | **/ |
1217 | | void |
1218 | | xb_builder_add_fixup(XbBuilder *self, XbBuilderFixup *fixup) |
1219 | 0 | { |
1220 | 0 | XbBuilderPrivate *priv = GET_PRIVATE(self); |
1221 | 0 | g_autofree gchar *guid = NULL; |
1222 | |
|
1223 | 0 | g_return_if_fail(XB_IS_BUILDER(self)); |
1224 | 0 | g_return_if_fail(XB_IS_BUILDER_FIXUP(fixup)); |
1225 | | |
1226 | | /* append function IDs */ |
1227 | 0 | guid = xb_builder_fixup_get_guid(fixup); |
1228 | 0 | xb_builder_append_guid(self, guid); |
1229 | 0 | g_ptr_array_add(priv->fixups, g_object_ref(fixup)); |
1230 | 0 | } |
1231 | | |
1232 | | static void |
1233 | | xb_builder_finalize(GObject *obj) |
1234 | 0 | { |
1235 | 0 | XbBuilder *self = XB_BUILDER(obj); |
1236 | 0 | XbBuilderPrivate *priv = GET_PRIVATE(self); |
1237 | |
|
1238 | 0 | g_ptr_array_unref(priv->sources); |
1239 | 0 | g_ptr_array_unref(priv->nodes); |
1240 | 0 | g_ptr_array_unref(priv->locales); |
1241 | 0 | g_ptr_array_unref(priv->fixups); |
1242 | 0 | g_string_free(priv->guid, TRUE); |
1243 | |
|
1244 | 0 | G_OBJECT_CLASS(xb_builder_parent_class)->finalize(obj); |
1245 | 0 | } |
1246 | | |
1247 | | static void |
1248 | | xb_builder_class_init(XbBuilderClass *klass) |
1249 | 0 | { |
1250 | 0 | GObjectClass *object_class = G_OBJECT_CLASS(klass); |
1251 | 0 | object_class->finalize = xb_builder_finalize; |
1252 | 0 | } |
1253 | | |
1254 | | static void |
1255 | | xb_builder_init(XbBuilder *self) |
1256 | 0 | { |
1257 | 0 | XbBuilderPrivate *priv = GET_PRIVATE(self); |
1258 | 0 | priv->sources = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
1259 | 0 | priv->nodes = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
1260 | 0 | priv->fixups = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); |
1261 | 0 | priv->locales = g_ptr_array_new_with_free_func(g_free); |
1262 | 0 | priv->guid = g_string_new(xb_version_string()); |
1263 | 0 | } |
1264 | | |
1265 | | /** |
1266 | | * xb_builder_new: |
1267 | | * |
1268 | | * Creates a new builder. |
1269 | | * |
1270 | | * The #XbSilo returned by the methods of this #XbBuilder will use the |
1271 | | * thread-default main context at the time of calling this function for its |
1272 | | * future signal emissions. |
1273 | | * |
1274 | | * Returns: a new #XbBuilder |
1275 | | * |
1276 | | * Since: 0.1.0 |
1277 | | **/ |
1278 | | XbBuilder * |
1279 | | xb_builder_new(void) |
1280 | 0 | { |
1281 | 0 | return g_object_new(XB_TYPE_BUILDER, NULL); |
1282 | 0 | } |