Coverage Report

Created: 2026-04-28 06:49

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
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
}