Coverage Report

Created: 2025-11-24 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}