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