Coverage Report

Created: 2026-09-14 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libxmlb/src/xb-silo.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
/**
8
 * SECTION:xb-silo
9
 * @title: XbSilo
10
 * @include: xmlb.h
11
 * @stability: Stable
12
 * @short_description: A read-only store of parsed XML data
13
 *
14
 * #XbSilo provides read-only access and querying of a previously parsed blob
15
 * of XML data.
16
 *
17
 * All signal emissions from #XbSilo (currently only #GObject::notify emissions)
18
 * will happen in the #GMainContext which is the thread default when the #XbSilo
19
 * is constructed.
20
 *
21
 * This #GMainContext must be iterated for file monitoring using
22
 * xb_silo_watch_file() to function correctly.
23
 */
24
25
0
#define G_LOG_DOMAIN "XbSilo"
26
27
#include "config.h"
28
29
#include <gio/gio.h>
30
#include <glib-object.h>
31
#include <string.h>
32
33
#ifdef HAVE_LIBSTEMMER
34
#include <libstemmer.h>
35
#endif
36
37
#include "xb-builder.h"
38
#include "xb-common-private.h"
39
#include "xb-machine-private.h"
40
#include "xb-node-private.h"
41
#include "xb-opcode-private.h"
42
#include "xb-silo-node.h"
43
#include "xb-stack-private.h"
44
#include "xb-string-private.h"
45
46
typedef struct {
47
  GMappedFile *mmap;
48
  gchar *guid;
49
  gboolean valid;
50
  GBytes *blob;
51
  const guint8 *data; /* pointers into ->blob */
52
  guint32 datasz;
53
  guint32 strtab;
54
  GHashTable *strtab_tags;
55
  GHashTable *strindex;
56
  GRWLock strindex_mutex;
57
  gboolean enable_node_cache;
58
  GHashTable *nodes; /* (mutex nodes_mutex) */
59
  GMutex nodes_mutex;
60
  GHashTable *file_monitors; /* (element-type GFile XbSiloFileMonitorItem) (mutex
61
              file_monitors_mutex) */
62
  GMutex file_monitors_mutex;
63
  XbMachine *machine;
64
  XbSiloProfileFlags profile_flags;
65
  GString *profile_str;
66
  GRWLock query_cache_mutex;
67
  GHashTable *query_cache;
68
  GMainContext *context; /* (owned) */
69
#ifdef HAVE_LIBSTEMMER
70
  struct sb_stemmer *stemmer_ctx; /* lazy loaded */
71
  GMutex stemmer_mutex;
72
#endif
73
} XbSiloPrivate;
74
75
typedef struct {
76
  GFileMonitor *file_monitor;
77
  gulong file_monitor_id;
78
} XbSiloFileMonitorItem;
79
80
0
G_DEFINE_TYPE_WITH_PRIVATE(XbSilo, xb_silo, G_TYPE_OBJECT)
81
0
#define GET_PRIVATE(o) (xb_silo_get_instance_private(o))
82
83
typedef enum {
84
  PROP_GUID = 1,
85
  PROP_VALID,
86
  PROP_ENABLE_NODE_CACHE,
87
} XbSiloProperty;
88
89
static GParamSpec *obj_props[PROP_ENABLE_NODE_CACHE + 1] = {
90
    NULL,
91
};
92
93
/* private */
94
GTimer *
95
xb_silo_start_profile(XbSilo *self)
96
0
{
97
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
98
99
  /* nothing to do; g_timer_new() does a syscall to clock_gettime() which
100
   * is best avoided if not needed */
101
0
  if (!priv->profile_flags)
102
0
    return NULL;
103
104
0
  return g_timer_new();
105
0
}
106
107
/* private */
108
void
109
xb_silo_add_profile(XbSilo *self, GTimer *timer, const gchar *fmt, ...)
110
0
{
111
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
112
0
  va_list args;
113
0
  g_autoptr(GString) str = NULL;
114
115
  /* nothing to do */
116
0
  if (!priv->profile_flags)
117
0
    return;
118
119
0
  str = g_string_new("");
120
121
  /* add duration */
122
0
  if (timer != NULL) {
123
0
    g_string_append_printf(str, "%.2fms", g_timer_elapsed(timer, NULL) * 1000);
124
0
    for (guint i = str->len; i < 12; i++)
125
0
      g_string_append(str, " ");
126
0
  }
127
128
  /* add varargs */
129
0
  va_start(args, fmt);
130
0
  g_string_append_vprintf(str, fmt, args);
131
0
  va_end(args);
132
133
  /* do the right thing */
134
0
  if (priv->profile_flags & XB_SILO_PROFILE_FLAG_DEBUG)
135
0
    g_debug("%s", str->str);
136
0
  if (priv->profile_flags & XB_SILO_PROFILE_FLAG_APPEND)
137
0
    g_string_append_printf(priv->profile_str, "%s\n", str->str);
138
139
  /* reset automatically */
140
0
  if (timer != NULL)
141
0
    g_timer_reset(timer);
142
0
}
143
144
/* private */
145
static gchar *
146
xb_silo_stem(XbSilo *self, const gchar *value)
147
0
{
148
#ifdef HAVE_LIBSTEMMER
149
  XbSiloPrivate *priv = GET_PRIVATE(self);
150
  const gchar *tmp;
151
  gsize len_dst;
152
  gsize len_src;
153
  g_autofree gchar *value_casefold = NULL;
154
  g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&priv->stemmer_mutex);
155
156
  g_return_val_if_fail(locker != NULL, NULL);
157
158
  /* not enabled */
159
  value_casefold = g_utf8_casefold(value, -1);
160
  if (priv->stemmer_ctx == NULL)
161
    priv->stemmer_ctx = sb_stemmer_new("en", NULL);
162
163
  /* stem */
164
  len_src = strlen(value_casefold);
165
  tmp = (const gchar *)sb_stemmer_stem(priv->stemmer_ctx,
166
               (guchar *)value_casefold,
167
               (gint)len_src);
168
  len_dst = (gsize)sb_stemmer_length(priv->stemmer_ctx);
169
  if (len_src == len_dst)
170
    return g_steal_pointer(&value_casefold);
171
  return g_strndup(tmp, len_dst);
172
#else
173
0
  return g_utf8_casefold(value, -1);
174
0
#endif
175
0
}
176
177
/* private */
178
const gchar *
179
xb_silo_from_strtab(XbSilo *self, guint32 offset, GError **error)
180
0
{
181
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
182
0
  if (G_UNLIKELY(offset == XB_SILO_UNSET)) {
183
0
    g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "offset was unset");
184
0
    return NULL;
185
0
  }
186
0
  if (offset >= priv->datasz - priv->strtab) {
187
0
    g_set_error(error,
188
0
          G_IO_ERROR,
189
0
          G_IO_ERROR_INVALID_DATA,
190
0
          "strtab+offset is outside the data range for %u",
191
0
          offset);
192
0
    return NULL;
193
0
  }
194
0
  return (const gchar *)(priv->data + priv->strtab + offset);
195
0
}
196
197
/* private */
198
gboolean
199
xb_silo_strtab_index_insert(XbSilo *self, guint32 offset, GError **error)
200
0
{
201
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
202
0
  const gchar *tmp;
203
0
  g_autoptr(GRWLockWriterLocker) locker_rw = NULL;
204
205
  /* get the string version */
206
0
  tmp = xb_silo_from_strtab(self, offset, error);
207
0
  if (tmp == NULL)
208
0
    return FALSE;
209
0
  locker_rw = g_rw_lock_writer_locker_new(&priv->strindex_mutex);
210
0
  if (g_hash_table_lookup(priv->strindex, tmp) != NULL)
211
0
    return TRUE;
212
0
  g_hash_table_insert(priv->strindex, (gpointer)tmp, GUINT_TO_POINTER(offset));
213
0
  return TRUE;
214
0
}
215
216
/* private */
217
guint32
218
xb_silo_strtab_index_lookup(XbSilo *self, const gchar *str)
219
0
{
220
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
221
0
  gpointer val = NULL;
222
0
  g_autoptr(GRWLockReaderLocker) locker_ro = NULL;
223
224
0
  locker_ro = g_rw_lock_reader_locker_new(&priv->strindex_mutex);
225
0
  if (!g_hash_table_lookup_extended(priv->strindex, str, NULL, &val))
226
0
    return XB_SILO_UNSET;
227
0
  return GPOINTER_TO_INT(val);
228
0
}
229
230
/* private */
231
XbSiloNode *
232
xb_silo_get_node(XbSilo *self, guint32 off, GError **error)
233
0
{
234
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
235
0
  if (G_UNLIKELY(off + 1 > priv->strtab)) {
236
0
    g_set_error(error,
237
0
          G_IO_ERROR,
238
0
          G_IO_ERROR_INVALID_DATA,
239
0
          "offset %u is outside the expected range",
240
0
          off);
241
0
    return NULL;
242
0
  }
243
0
  return (XbSiloNode *)(priv->data + off);
244
0
}
245
246
/* private */
247
guint32
248
xb_silo_get_offset_for_node(XbSilo *self, XbSiloNode *n)
249
0
{
250
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
251
0
  return ((const guint8 *)n) - priv->data;
252
0
}
253
254
/* private */
255
guint32
256
xb_silo_get_strtab(XbSilo *self)
257
0
{
258
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
259
0
  return priv->strtab;
260
0
}
261
262
/* private */
263
XbSiloNode *
264
xb_silo_get_root_node(XbSilo *self, GError **error)
265
0
{
266
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
267
0
  if (G_UNLIKELY(priv->blob == NULL)) {
268
0
    g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "no blob loaded");
269
0
    return NULL;
270
0
  }
271
0
  if (G_UNLIKELY(g_bytes_get_size(priv->blob) < sizeof(XbSiloHeader))) {
272
0
    g_set_error(error,
273
0
          G_IO_ERROR,
274
0
          G_IO_ERROR_INVALID_DATA,
275
0
          "blob too small: 0x%zx",
276
0
          g_bytes_get_size(priv->blob));
277
0
    return NULL;
278
0
  }
279
0
  if (G_UNLIKELY(g_bytes_get_size(priv->blob) == sizeof(XbSiloHeader))) {
280
0
    g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "no node data");
281
0
    return NULL;
282
0
  }
283
0
  return xb_silo_get_node(self, sizeof(XbSiloHeader), error);
284
0
}
285
286
/* private */
287
XbSiloNode *
288
xb_silo_get_parent_node(XbSilo *self, XbSiloNode *n, GError **error)
289
0
{
290
0
  if (G_UNLIKELY(n->parent == 0x0)) {
291
0
    g_set_error(error,
292
0
          G_IO_ERROR,
293
0
          G_IO_ERROR_INVALID_ARGUMENT,
294
0
          "no parent set for %s",
295
0
          xb_silo_get_node_element(self, n, NULL));
296
0
    return NULL;
297
0
  }
298
0
  return xb_silo_get_node(self, n->parent, error);
299
0
}
300
301
/* private */
302
XbSiloNode *
303
xb_silo_get_next_node(XbSilo *self, XbSiloNode *n, GError **error)
304
0
{
305
0
  if (n->next == 0x0) {
306
0
    g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "no next node");
307
0
    return NULL;
308
0
  }
309
0
  return xb_silo_get_node(self, n->next, error);
310
0
}
311
312
/* private */
313
XbSiloNode *
314
xb_silo_get_child_node(XbSilo *self, XbSiloNode *n, GError **error)
315
0
{
316
0
  XbSiloNode *c;
317
0
  guint32 off = xb_silo_get_offset_for_node(self, n);
318
0
  off += xb_silo_node_get_size(n);
319
320
  /* check for sentinel */
321
0
  c = xb_silo_get_node(self, off, error);
322
0
  if (c == NULL)
323
0
    return NULL;
324
0
  if (!xb_silo_node_has_flag(c, XB_SILO_NODE_FLAG_IS_ELEMENT)) {
325
0
    g_set_error_literal(error,
326
0
            G_IO_ERROR,
327
0
            G_IO_ERROR_INVALID_ARGUMENT,
328
0
            "no child element");
329
0
    return NULL;
330
0
  }
331
0
  return c;
332
0
}
333
334
/**
335
 * xb_silo_get_root:
336
 * @self: a #XbSilo
337
 *
338
 * Gets the root node for the silo. (MIGHT BE MORE).
339
 *
340
 * Returns: (transfer full): A #XbNode, or %NULL for an error
341
 *
342
 * Since: 0.1.0
343
 **/
344
XbNode *
345
xb_silo_get_root(XbSilo *self)
346
0
{
347
0
  XbSiloNode *sn;
348
0
  g_autoptr(GError) error_local = NULL;
349
350
0
  g_return_val_if_fail(XB_IS_SILO(self), NULL);
351
352
0
  sn = xb_silo_get_root_node(self, &error_local);
353
0
  if (sn == NULL) {
354
    /* if there are no XbSiloNodes, still build a root XbNode */
355
0
    if (!g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
356
0
      return NULL;
357
0
    g_debug("ignoring: %s", error_local->message);
358
0
  }
359
0
  return xb_silo_create_node(self, sn, FALSE);
360
0
}
361
362
/* private */
363
guint32
364
xb_silo_get_strtab_idx(XbSilo *self, const gchar *element)
365
0
{
366
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
367
0
  gpointer value = NULL;
368
0
  if (!g_hash_table_lookup_extended(priv->strtab_tags, element, NULL, &value))
369
0
    return XB_SILO_UNSET;
370
0
  return GPOINTER_TO_UINT(value);
371
0
}
372
373
/**
374
 * xb_silo_to_string:
375
 * @self: a #XbSilo
376
 * @error: the #GError, or %NULL
377
 *
378
 * Converts the silo to an internal string representation. This is only
379
 * really useful for debugging #XbSilo itself.
380
 *
381
 * Returns: A string, or %NULL for an error
382
 *
383
 * Since: 0.1.0
384
 **/
385
gchar *
386
xb_silo_to_string(XbSilo *self, GError **error)
387
0
{
388
0
  guint32 off = sizeof(XbSiloHeader);
389
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
390
0
  XbSiloHeader *hdr = (XbSiloHeader *)priv->data;
391
0
  g_autoptr(GString) str = g_string_new(NULL);
392
393
0
  g_return_val_if_fail(XB_IS_SILO(self), NULL);
394
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
395
396
  /* sanity check */
397
0
  if (hdr->strtab > priv->datasz) {
398
0
    g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "strtab invalid");
399
0
    return NULL;
400
0
  }
401
402
0
  g_string_append_printf(str, "magic:        %08x\n", (guint)hdr->magic);
403
0
  g_string_append_printf(str, "guid:         %s\n", priv->guid);
404
0
  g_string_append_printf(str, "filesz:       @%" G_GUINT64_FORMAT "\n", hdr->filesz);
405
0
  g_string_append_printf(str, "strtab:       @%" G_GUINT32_FORMAT "\n", hdr->strtab);
406
0
  g_string_append_printf(str, "strtab_ntags: %" G_GUINT16_FORMAT "\n", hdr->strtab_ntags);
407
0
  while (off < priv->strtab) {
408
0
    XbSiloNode *n = xb_silo_get_node(self, off, error);
409
0
    if (n == NULL)
410
0
      return NULL;
411
0
    if (xb_silo_node_has_flag(n, XB_SILO_NODE_FLAG_IS_ELEMENT)) {
412
0
      guint32 idx;
413
0
      const gchar *element_name;
414
0
      g_string_append_printf(str, "NODE @%" G_GUINT32_FORMAT "\n", off);
415
0
      g_string_append_printf(str,
416
0
                 "size:         %" G_GUINT32_FORMAT "\n",
417
0
                 xb_silo_node_get_size(n));
418
0
      g_string_append_printf(str,
419
0
                 "flags:        %x\n",
420
0
                 xb_silo_node_get_flags(n));
421
0
      element_name = xb_silo_from_strtab(self, n->element_name, error);
422
0
      if (element_name == NULL)
423
0
        return NULL;
424
0
      g_string_append_printf(str,
425
0
                 "element_name: %s [%03u]\n",
426
0
                 element_name,
427
0
                 n->element_name);
428
0
      g_string_append_printf(str,
429
0
                 "next:         %" G_GUINT32_FORMAT "\n",
430
0
                 n->next);
431
0
      g_string_append_printf(str,
432
0
                 "parent:       %" G_GUINT32_FORMAT "\n",
433
0
                 n->parent);
434
0
      idx = xb_silo_node_get_text_idx(n);
435
0
      if (idx != XB_SILO_UNSET) {
436
0
        const gchar *text = xb_silo_from_strtab(self, idx, error);
437
0
        if (text == NULL)
438
0
          return NULL;
439
0
        g_string_append_printf(str, "text:         %s [%03u]\n", text, idx);
440
0
      }
441
0
      idx = xb_silo_node_get_tail_idx(n);
442
0
      if (idx != XB_SILO_UNSET) {
443
0
        const gchar *tail = xb_silo_from_strtab(self, idx, error);
444
0
        if (tail == NULL)
445
0
          return NULL;
446
0
        g_string_append_printf(str, "tail:         %s [%03u]\n", tail, idx);
447
0
      }
448
0
      for (guint8 i = 0; i < xb_silo_node_get_attr_count(n); i++) {
449
0
        XbSiloNodeAttr *a = xb_silo_node_get_attr(n, i);
450
0
        const gchar *attr_name;
451
0
        const gchar *attr_value;
452
453
0
        attr_name = xb_silo_from_strtab(self, a->attr_name, error);
454
0
        if (attr_name == NULL)
455
0
          return NULL;
456
0
        g_string_append_printf(str,
457
0
                   "attr_name:    %s [%03u]\n",
458
0
                   attr_name,
459
0
                   a->attr_name);
460
0
        attr_value = xb_silo_from_strtab(self, a->attr_value, error);
461
0
        if (attr_value == NULL)
462
0
          return NULL;
463
0
        g_string_append_printf(str,
464
0
                   "attr_value:   %s [%03u]\n",
465
0
                   attr_value,
466
0
                   a->attr_value);
467
0
      }
468
0
      for (guint8 i = 0; i < xb_silo_node_get_token_count(n); i++) {
469
0
        guint32 idx_tmp = xb_silo_node_get_token_idx(n, i);
470
0
        const gchar *token = xb_silo_from_strtab(self, idx_tmp, error);
471
0
        if (token == NULL)
472
0
          return NULL;
473
0
        g_string_append_printf(str,
474
0
                   "token:        %s [%03u]\n",
475
0
                   token,
476
0
                   idx_tmp);
477
0
      }
478
0
    } else {
479
0
      g_string_append_printf(str, "SENT @%" G_GUINT32_FORMAT "\n", off);
480
0
    }
481
0
    off += xb_silo_node_get_size(n);
482
0
  }
483
484
  /* add strtab */
485
0
  g_string_append_printf(str, "STRTAB @%" G_GUINT32_FORMAT "\n", hdr->strtab);
486
0
  for (off = 0; off < priv->datasz - hdr->strtab;) {
487
0
    const gchar *tmp = xb_silo_from_strtab(self, off, NULL);
488
0
    if (tmp == NULL)
489
0
      break;
490
0
    g_string_append_printf(str, "[%03u]: %s\n", off, tmp);
491
0
    off += strlen(tmp) + 1;
492
0
  }
493
494
  /* success */
495
0
  return g_string_free(g_steal_pointer(&str), FALSE);
496
0
}
497
498
/* private */
499
const gchar *
500
xb_silo_get_node_element(XbSilo *self, XbSiloNode *n, GError **error)
501
0
{
502
0
  return xb_silo_from_strtab(self, n->element_name, error);
503
0
}
504
505
/* private */
506
XbSiloNodeAttr *
507
xb_silo_get_node_attr_by_str(XbSilo *self, XbSiloNode *n, const gchar *name)
508
0
{
509
0
  guint8 attr_count;
510
511
  /* calculate offset to first attribute */
512
0
  attr_count = xb_silo_node_get_attr_count(n);
513
0
  for (guint8 i = 0; i < attr_count; i++) {
514
0
    XbSiloNodeAttr *a = xb_silo_node_get_attr(n, i);
515
0
    const gchar *name_tmp = xb_silo_from_strtab(self, a->attr_name, NULL);
516
0
    if (name_tmp == NULL)
517
0
      return NULL;
518
0
    if (g_strcmp0(name_tmp, name) == 0)
519
0
      return a;
520
0
  }
521
522
  /* nothing matched */
523
0
  return NULL;
524
0
}
525
526
static XbSiloNodeAttr *
527
xb_silo_node_get_attr_by_val(XbSilo *self, XbSiloNode *n, guint32 name)
528
0
{
529
0
  guint8 attr_count;
530
531
  /* calculate offset to first attribute */
532
0
  attr_count = xb_silo_node_get_attr_count(n);
533
0
  for (guint8 i = 0; i < attr_count; i++) {
534
0
    XbSiloNodeAttr *a = xb_silo_node_get_attr(n, i);
535
0
    if (a->attr_name == name)
536
0
      return a;
537
0
  }
538
539
  /* nothing matched */
540
0
  return NULL;
541
0
}
542
543
/**
544
 * xb_silo_get_size:
545
 * @self: a #XbSilo
546
 *
547
 * Gets the number of nodes in the silo.
548
 *
549
 * Returns: a integer, or 0 is an empty blob
550
 *
551
 * Since: 0.1.0
552
 **/
553
guint
554
xb_silo_get_size(XbSilo *self)
555
0
{
556
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
557
0
  guint32 off = sizeof(XbSiloHeader);
558
0
  guint nodes_cnt = 0;
559
560
0
  g_return_val_if_fail(XB_IS_SILO(self), 0);
561
562
0
  while (off < priv->strtab) {
563
0
    XbSiloNode *n = xb_silo_get_node(self, off, NULL);
564
0
    if (n == NULL)
565
0
      return 0;
566
0
    if (xb_silo_node_has_flag(n, XB_SILO_NODE_FLAG_IS_ELEMENT))
567
0
      nodes_cnt += 1;
568
0
    off += xb_silo_node_get_size(n);
569
0
  }
570
571
  /* success */
572
0
  return nodes_cnt;
573
0
}
574
575
/**
576
 * xb_silo_is_valid:
577
 * @self: a #XbSilo
578
 *
579
 * Checks is the silo is valid. The usual reason the silo is invalidated is
580
 * when the backing mmapped file has changed, or one of the imported files have
581
 * been modified.
582
 *
583
 * Returns: %TRUE if valid
584
 *
585
 * Since: 0.1.0
586
 **/
587
gboolean
588
xb_silo_is_valid(XbSilo *self)
589
0
{
590
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
591
0
  g_return_val_if_fail(XB_IS_SILO(self), FALSE);
592
0
  return priv->valid;
593
0
}
594
595
/* private */
596
gboolean
597
xb_silo_is_empty(XbSilo *self)
598
0
{
599
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
600
0
  g_return_val_if_fail(XB_IS_SILO(self), FALSE);
601
0
  return priv->strtab == sizeof(XbSiloHeader);
602
0
}
603
604
typedef struct {
605
  XbSilo *silo;    /* (owned) */
606
  GParamSpec *pspec; /* (owned) */
607
} SiloNotifyData;
608
609
static void
610
silo_notify_data_free(SiloNotifyData *data)
611
0
{
612
0
  g_clear_object(&data->silo);
613
0
  g_clear_pointer(&data->pspec, g_param_spec_unref);
614
0
  g_free(data);
615
0
}
616
617
G_DEFINE_AUTOPTR_CLEANUP_FUNC(SiloNotifyData, silo_notify_data_free)
618
619
static gboolean
620
silo_notify_cb(gpointer user_data)
621
0
{
622
0
  g_autoptr(SiloNotifyData) data = g_steal_pointer(&user_data);
623
624
0
  g_object_notify_by_pspec(G_OBJECT(data->silo), data->pspec);
625
626
0
  return G_SOURCE_REMOVE;
627
0
}
628
629
/* Like g_object_notify(), but ensure that the signal is emitted in XbSilo.context. */
630
static void
631
silo_notify(XbSilo *self, GParamSpec *pspec)
632
0
{
633
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
634
0
  g_autoptr(SiloNotifyData) data = NULL;
635
636
0
  data = g_new0(SiloNotifyData, 1);
637
0
  data->silo = g_object_ref(self);
638
0
  data->pspec = g_param_spec_ref(pspec);
639
640
0
  g_main_context_invoke(priv->context, silo_notify_cb, g_steal_pointer(&data));
641
0
}
642
643
/**
644
 * xb_silo_invalidate:
645
 * @self: a #XbSilo
646
 *
647
 * Invalidates a silo. Future calls xb_silo_is_valid() will return %FALSE.
648
 *
649
 * Since: 0.1.1
650
 **/
651
void
652
xb_silo_invalidate(XbSilo *self)
653
0
{
654
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
655
0
  if (!priv->valid)
656
0
    return;
657
0
  priv->valid = FALSE;
658
0
  silo_notify(self, obj_props[PROP_VALID]);
659
0
}
660
661
/* private */
662
void
663
xb_silo_uninvalidate(XbSilo *self)
664
0
{
665
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
666
0
  if (priv->valid)
667
0
    return;
668
0
  priv->valid = TRUE;
669
0
  silo_notify(self, obj_props[PROP_VALID]);
670
0
}
671
672
/* private */
673
guint
674
xb_silo_get_node_depth(XbSilo *self, XbSiloNode *n)
675
0
{
676
0
  guint depth = 0;
677
0
  guint32 last_off = xb_silo_get_offset_for_node(self, n);
678
0
  while (n->parent != 0) {
679
0
    if (n->parent >= last_off)
680
0
      break;
681
0
    last_off = n->parent;
682
0
    depth++;
683
0
    n = xb_silo_get_node(self, n->parent, NULL);
684
0
    if (n == NULL)
685
0
      break;
686
0
  }
687
0
  return depth;
688
0
}
689
690
/**
691
 * xb_silo_get_bytes:
692
 * @self: a #XbSilo
693
 *
694
 * Gets the backing object that created the blob.
695
 *
696
 * You should never *ever* modify this data.
697
 *
698
 * Returns: (transfer full): A #GBytes, or %NULL if never set
699
 *
700
 * Since: 0.1.0
701
 **/
702
GBytes *
703
xb_silo_get_bytes(XbSilo *self)
704
0
{
705
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
706
0
  g_return_val_if_fail(XB_IS_SILO(self), NULL);
707
0
  if (priv->blob == NULL)
708
0
    return NULL;
709
0
  return g_bytes_ref(priv->blob);
710
0
}
711
712
/**
713
 * xb_silo_get_guid:
714
 * @self: a #XbSilo
715
 *
716
 * Gets the GUID used to identify this silo.
717
 *
718
 * Returns: a string, otherwise %NULL
719
 *
720
 * Since: 0.1.0
721
 **/
722
const gchar *
723
xb_silo_get_guid(XbSilo *self)
724
0
{
725
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
726
0
  g_return_val_if_fail(XB_IS_SILO(self), NULL);
727
0
  return priv->guid;
728
0
}
729
730
/* private */
731
XbMachine *
732
xb_silo_get_machine(XbSilo *self)
733
0
{
734
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
735
0
  return priv->machine;
736
0
}
737
738
/**
739
 * xb_silo_load_from_bytes:
740
 * @self: a #XbSilo
741
 * @blob: a #GBytes
742
 * @flags: #XbSiloLoadFlags, e.g. %XB_SILO_LOAD_FLAG_NONE
743
 * @error: the #GError, or %NULL
744
 *
745
 * Loads a silo from memory location.
746
 *
747
 * Returns: %TRUE for success, otherwise @error is set.
748
 *
749
 * Since: 0.1.0
750
 **/
751
gboolean
752
xb_silo_load_from_bytes(XbSilo *self, GBytes *blob, XbSiloLoadFlags flags, GError **error)
753
0
{
754
0
  XbGuid guid_tmp;
755
0
  XbSiloHeader *hdr;
756
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
757
0
  gsize sz = 0;
758
0
  guint32 off = 0;
759
0
  g_autoptr(GMutexLocker) locker = NULL;
760
0
  g_autoptr(GTimer) timer = xb_silo_start_profile(self);
761
762
0
  g_return_val_if_fail(XB_IS_SILO(self), FALSE);
763
0
  g_return_val_if_fail(blob != NULL, FALSE);
764
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
765
766
  /* no longer valid */
767
0
  xb_silo_invalidate(self);
768
0
  if (priv->enable_node_cache) {
769
0
    locker = g_mutex_locker_new(&priv->nodes_mutex);
770
0
    if (priv->nodes != NULL)
771
0
      g_hash_table_remove_all(priv->nodes);
772
0
  }
773
774
0
  g_hash_table_remove_all(priv->strtab_tags);
775
776
0
  g_rw_lock_writer_lock(&priv->strindex_mutex);
777
0
  g_hash_table_remove_all(priv->strindex);
778
0
  g_rw_lock_writer_unlock(&priv->strindex_mutex);
779
780
0
  g_clear_pointer(&priv->guid, g_free);
781
782
0
  g_rw_lock_writer_lock(&priv->query_cache_mutex);
783
0
  g_hash_table_remove_all(priv->query_cache);
784
0
  g_rw_lock_writer_unlock(&priv->query_cache_mutex);
785
786
  /* refcount internally */
787
0
  if (priv->blob != NULL)
788
0
    g_bytes_unref(priv->blob);
789
0
  priv->blob = g_bytes_ref(blob);
790
791
  /* update pointers into blob */
792
0
  priv->data = g_bytes_get_data(priv->blob, &sz);
793
794
  /* check size  */
795
0
  if (sz < sizeof(XbSiloHeader)) {
796
0
    g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "blob too small");
797
0
    return FALSE;
798
0
  }
799
0
  if (sz > G_MAXINT32) {
800
0
    g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "blob too large");
801
0
    return FALSE;
802
0
  }
803
804
0
  priv->datasz = (guint32)sz;
805
806
  /* check header magic */
807
0
  hdr = (XbSiloHeader *)priv->data;
808
0
  if ((flags & XB_SILO_LOAD_FLAG_NO_MAGIC) == 0) {
809
0
    if (hdr->magic != XB_SILO_MAGIC_BYTES) {
810
0
      g_set_error_literal(error,
811
0
              G_IO_ERROR,
812
0
              G_IO_ERROR_INVALID_DATA,
813
0
              "magic incorrect");
814
0
      return FALSE;
815
0
    }
816
0
    if (hdr->version != XB_SILO_VERSION) {
817
0
      g_set_error(error,
818
0
            G_IO_ERROR,
819
0
            G_IO_ERROR_INVALID_DATA,
820
0
            "version incorrect, got %u, expected %d",
821
0
            hdr->version,
822
0
            XB_SILO_VERSION);
823
0
      return FALSE;
824
0
    }
825
0
  }
826
827
  /* check size */
828
0
  if (hdr->filesz != sz) {
829
0
    g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "filesz incorrect");
830
0
    return FALSE;
831
0
  }
832
833
  /* get GUID */
834
0
  memcpy(&guid_tmp, &hdr->guid, sizeof(guid_tmp));
835
0
  priv->guid = xb_guid_to_string(&guid_tmp);
836
837
  /* check strtab */
838
0
  priv->strtab = hdr->strtab;
839
0
  if (priv->strtab > priv->datasz) {
840
0
    g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "strtab incorrect");
841
0
    return FALSE;
842
0
  }
843
0
  if (hdr->strtab_ntags > 0 && priv->data[sz - 1] != '\0') {
844
0
    g_set_error_literal(error,
845
0
            G_IO_ERROR,
846
0
            G_IO_ERROR_INVALID_DATA,
847
0
            "strtab invalid, trailing NUL not found");
848
0
    return FALSE;
849
0
  }
850
851
  /* load strtab_tags */
852
0
  for (guint16 i = 0; i < hdr->strtab_ntags; i++) {
853
0
    const gchar *tmp = xb_silo_from_strtab(self, off, error);
854
0
    if (tmp == NULL) {
855
0
      g_prefix_error(error, "strtab_ntags incorrect: ");
856
0
      return FALSE;
857
0
    }
858
0
    g_hash_table_insert(priv->strtab_tags, (gpointer)tmp, GUINT_TO_POINTER(off));
859
0
    off += strlen(tmp) + 1;
860
0
  }
861
862
  /* profile */
863
0
  xb_silo_add_profile(self, timer, "parse blob");
864
865
  /* success */
866
0
  xb_silo_uninvalidate(self);
867
0
  return TRUE;
868
0
}
869
870
/**
871
 * xb_silo_get_profile_string:
872
 * @self: a #XbSilo
873
 *
874
 * Returns the profiling data. This will only return profiling text if
875
 * xb_silo_set_profile_flags() was used with %XB_SILO_PROFILE_FLAG_APPEND.
876
 *
877
 * Returns: text profiling data
878
 *
879
 * Since: 0.1.1
880
 **/
881
const gchar *
882
xb_silo_get_profile_string(XbSilo *self)
883
0
{
884
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
885
0
  g_return_val_if_fail(XB_IS_SILO(self), NULL);
886
0
  return priv->profile_str->str;
887
0
}
888
889
/**
890
 * xb_silo_set_profile_flags:
891
 * @self: a #XbSilo
892
 * @profile_flags: some #XbSiloProfileFlags, e.g. %XB_SILO_PROFILE_FLAG_DEBUG
893
 *
894
 * Enables or disables the collection of profiling data.
895
 *
896
 * Since: 0.1.1
897
 **/
898
void
899
xb_silo_set_profile_flags(XbSilo *self, XbSiloProfileFlags profile_flags)
900
0
{
901
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
902
0
  g_return_if_fail(XB_IS_SILO(self));
903
0
  priv->profile_flags = profile_flags;
904
905
  /* proxy */
906
0
  if (profile_flags & XB_SILO_PROFILE_FLAG_OPTIMIZER) {
907
0
    xb_machine_set_debug_flags(priv->machine,
908
0
             XB_MACHINE_DEBUG_FLAG_SHOW_OPTIMIZER |
909
0
                 XB_MACHINE_DEBUG_FLAG_SHOW_SLOW_PATH);
910
0
  }
911
0
}
912
913
/**
914
 * xb_silo_get_enable_node_cache:
915
 * @self: an #XbSilo
916
 *
917
 * Get #XbSilo:enable-node-cache.
918
 *
919
 * Since: 0.2.0
920
 */
921
gboolean
922
xb_silo_get_enable_node_cache(XbSilo *self)
923
0
{
924
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
925
0
  g_return_val_if_fail(XB_IS_SILO(self), FALSE);
926
0
  return priv->enable_node_cache;
927
0
}
928
929
/**
930
 * xb_silo_set_enable_node_cache:
931
 * @self: an #XbSilo
932
 * @enable_node_cache: %TRUE to enable the node cache, %FALSE otherwise
933
 *
934
 * Set #XbSilo:enable-node-cache.
935
 *
936
 * This is not thread-safe, and can only be called before the #XbSilo is passed
937
 * between threads.
938
 *
939
 * Since: 0.2.0
940
 */
941
void
942
xb_silo_set_enable_node_cache(XbSilo *self, gboolean enable_node_cache)
943
0
{
944
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
945
946
0
  g_return_if_fail(XB_IS_SILO(self));
947
948
0
  if (priv->enable_node_cache == enable_node_cache)
949
0
    return;
950
951
0
  priv->enable_node_cache = enable_node_cache;
952
953
  /* if disabling the cache, destroy any existing data structures;
954
   * if enabling it, create them lazily when the first entry is cached
955
   * (see xb_silo_create_node()) */
956
0
  if (!enable_node_cache) {
957
0
    g_clear_pointer(&priv->nodes, g_hash_table_unref);
958
0
  }
959
960
0
  silo_notify(self, obj_props[PROP_ENABLE_NODE_CACHE]);
961
0
}
962
963
/* private */
964
XbSiloProfileFlags
965
xb_silo_get_profile_flags(XbSilo *self)
966
0
{
967
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
968
0
  return priv->profile_flags;
969
0
}
970
971
/* This will be invoked in silo->context */
972
static void
973
xb_silo_watch_file_cb(GFileMonitor *monitor,
974
          GFile *file,
975
          GFile *other_file,
976
          GFileMonitorEvent event_type,
977
          gpointer user_data)
978
0
{
979
0
  XbSilo *silo = XB_SILO(user_data);
980
0
  g_autofree gchar *fn = g_file_get_path(file);
981
0
  g_autofree gchar *basename = g_file_get_basename(file);
982
0
  if (g_str_has_prefix(basename, "."))
983
0
    return;
984
0
  g_debug("%s changed, invalidating", fn);
985
0
  xb_silo_invalidate(silo);
986
0
}
987
988
typedef struct {
989
  XbSilo *silo; /* (owned) */
990
  GFile *file;  /* (owned) */
991
} WatchFileData;
992
993
static void
994
watch_file_data_free(WatchFileData *data)
995
0
{
996
0
  g_clear_object(&data->silo);
997
0
  g_clear_object(&data->file);
998
0
  g_free(data);
999
0
}
1000
1001
G_DEFINE_AUTOPTR_CLEANUP_FUNC(WatchFileData, watch_file_data_free)
1002
1003
static gboolean
1004
watch_file_cb(gpointer user_data);
1005
1006
/**
1007
 * xb_silo_watch_file:
1008
 * @self: a #XbSilo
1009
 * @file: a #GFile
1010
 * @cancellable: a #GCancellable, or %NULL
1011
 * @error: the #GError, or %NULL
1012
 *
1013
 * Adds a file monitor to the silo. If the file or directory for @file changes
1014
 * then the silo will be invalidated.
1015
 *
1016
 * The monitor will internally use the #GMainContext which was the thread
1017
 * default when the #XbSilo was created, so that #GMainContext must be iterated
1018
 * for monitoring to work.
1019
 *
1020
 * Returns: %TRUE for success, otherwise @error is set.
1021
 *
1022
 * Since: 0.1.0
1023
 **/
1024
gboolean
1025
xb_silo_watch_file(XbSilo *self, GFile *file, GCancellable *cancellable, GError **error)
1026
0
{
1027
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
1028
0
  g_autoptr(WatchFileData) data = NULL;
1029
1030
0
  g_return_val_if_fail(XB_IS_SILO(self), FALSE);
1031
0
  g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE);
1032
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
1033
1034
  /* return if cancelled; this is basically the only failure mode of
1035
   * g_file_monitor() for local files, and this function shouldn’t really
1036
   * be called on non-local files */
1037
0
  if (g_cancellable_set_error_if_cancelled(cancellable, error))
1038
0
    return FALSE;
1039
1040
0
  data = g_new0(WatchFileData, 1);
1041
0
  data->silo = g_object_ref(self);
1042
0
  data->file = g_object_ref(file);
1043
1044
0
  g_main_context_invoke(priv->context, watch_file_cb, g_steal_pointer(&data));
1045
1046
0
  return TRUE;
1047
0
}
1048
1049
static gboolean
1050
watch_file_cb(gpointer user_data)
1051
0
{
1052
0
  g_autoptr(WatchFileData) data = g_steal_pointer(&user_data);
1053
0
  XbSilo *self = data->silo;
1054
0
  GFile *file = data->file;
1055
0
  XbSiloFileMonitorItem *item;
1056
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
1057
0
  g_autoptr(GFileMonitor) file_monitor = NULL;
1058
0
  g_autoptr(GError) error_local = NULL;
1059
0
  g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&priv->file_monitors_mutex);
1060
1061
0
  g_return_val_if_fail(locker != NULL, FALSE);
1062
1063
  /* already exists */
1064
0
  item = g_hash_table_lookup(priv->file_monitors, file);
1065
0
  if (item != NULL)
1066
0
    return G_SOURCE_REMOVE;
1067
1068
  /* try to create */
1069
0
  file_monitor = g_file_monitor(file, G_FILE_MONITOR_NONE, NULL, &error_local);
1070
0
  if (file_monitor == NULL) {
1071
0
    g_warning("Error adding file monitor: %s", error_local->message);
1072
0
    return G_SOURCE_REMOVE;
1073
0
  }
1074
1075
0
  g_file_monitor_set_rate_limit(file_monitor, 20);
1076
1077
  /* add */
1078
0
  item = g_slice_new0(XbSiloFileMonitorItem);
1079
0
  item->file_monitor = g_object_ref(file_monitor);
1080
0
  item->file_monitor_id =
1081
0
      g_signal_connect(file_monitor, "changed", G_CALLBACK(xb_silo_watch_file_cb), self);
1082
0
  g_hash_table_insert(priv->file_monitors, g_object_ref(file), item);
1083
1084
0
  return G_SOURCE_REMOVE;
1085
0
}
1086
1087
/**
1088
 * xb_silo_load_from_file:
1089
 * @self: a #XbSilo
1090
 * @file: a #GFile
1091
 * @flags: #XbSiloLoadFlags, e.g. %XB_SILO_LOAD_FLAG_NONE
1092
 * @cancellable: a #GCancellable, or %NULL
1093
 * @error: the #GError, or %NULL
1094
 *
1095
 * Loads a silo from file.
1096
 *
1097
 * Returns: %TRUE for success, otherwise @error is set.
1098
 *
1099
 * Since: 0.1.0
1100
 **/
1101
gboolean
1102
xb_silo_load_from_file(XbSilo *self,
1103
           GFile *file,
1104
           XbSiloLoadFlags flags,
1105
           GCancellable *cancellable,
1106
           GError **error)
1107
0
{
1108
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
1109
0
  g_autofree gchar *fn = NULL;
1110
0
  g_autoptr(GBytes) blob = NULL;
1111
0
  g_autoptr(GTimer) timer = xb_silo_start_profile(self);
1112
0
  g_autoptr(GMutexLocker) file_monitors_locker =
1113
0
      g_mutex_locker_new(&priv->file_monitors_mutex);
1114
1115
0
  g_return_val_if_fail(XB_IS_SILO(self), FALSE);
1116
0
  g_return_val_if_fail(G_IS_FILE(file), FALSE);
1117
0
  g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE);
1118
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
1119
1120
  /* no longer valid (@nodes is cleared by xb_silo_load_from_bytes()) */
1121
0
  g_hash_table_remove_all(priv->file_monitors);
1122
0
  g_clear_pointer(&file_monitors_locker, g_mutex_locker_free);
1123
1124
0
  g_hash_table_remove_all(priv->strtab_tags);
1125
0
  g_clear_pointer(&priv->guid, g_free);
1126
0
  g_clear_pointer(&priv->mmap, g_mapped_file_unref);
1127
1128
0
  fn = g_file_get_path(file);
1129
0
  priv->mmap = g_mapped_file_new(fn, FALSE, error);
1130
0
  if (priv->mmap == NULL)
1131
0
    return FALSE;
1132
0
  blob = g_mapped_file_get_bytes(priv->mmap);
1133
0
  if (!xb_silo_load_from_bytes(self, blob, flags, error))
1134
0
    return FALSE;
1135
1136
  /* watch file for changes */
1137
0
  if (flags & XB_SILO_LOAD_FLAG_WATCH_BLOB) {
1138
0
    if (!xb_silo_watch_file(self, file, cancellable, error))
1139
0
      return FALSE;
1140
0
  }
1141
1142
  /* success */
1143
0
  xb_silo_add_profile(self, timer, "loaded file");
1144
0
  return TRUE;
1145
0
}
1146
1147
/**
1148
 * xb_silo_save_to_file:
1149
 * @self: a #XbSilo
1150
 * @file: a #GFile
1151
 * @cancellable: a #GCancellable, or %NULL
1152
 * @error: the #GError, or %NULL
1153
 *
1154
 * Saves a silo to a file.
1155
 *
1156
 * Returns: %TRUE for success, otherwise @error is set.
1157
 *
1158
 * Since: 0.1.0
1159
 **/
1160
gboolean
1161
xb_silo_save_to_file(XbSilo *self, GFile *file, GCancellable *cancellable, GError **error)
1162
0
{
1163
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
1164
0
  g_autoptr(GFile) file_parent = NULL;
1165
0
  g_autoptr(GTimer) timer = xb_silo_start_profile(self);
1166
1167
0
  g_return_val_if_fail(XB_IS_SILO(self), FALSE);
1168
0
  g_return_val_if_fail(G_IS_FILE(file), FALSE);
1169
0
  g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE);
1170
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
1171
1172
  /* invalid */
1173
0
  if (priv->data == NULL) {
1174
0
    g_set_error_literal(error,
1175
0
            G_IO_ERROR,
1176
0
            G_IO_ERROR_NOT_INITIALIZED,
1177
0
            "no data to save");
1178
0
    return FALSE;
1179
0
  }
1180
1181
  /* ensure parent directories exist */
1182
0
  file_parent = g_file_get_parent(file);
1183
0
  if (file_parent != NULL && !g_file_query_exists(file_parent, cancellable)) {
1184
0
    if (!g_file_make_directory_with_parents(file_parent, cancellable, error))
1185
0
      return FALSE;
1186
0
  }
1187
1188
  /* save and then rename */
1189
0
  if (!xb_file_set_contents(file, priv->data, (gsize)priv->datasz, cancellable, error))
1190
0
    return FALSE;
1191
1192
0
  xb_silo_add_profile(self, timer, "save file");
1193
0
  return TRUE;
1194
0
}
1195
1196
/**
1197
 * xb_silo_new_from_xml:
1198
 * @xml: XML string
1199
 * @error: the #GError, or %NULL
1200
 *
1201
 * Creates a new silo from an XML string.
1202
 *
1203
 * Returns: a new #XbSilo, or %NULL
1204
 *
1205
 * Since: 0.1.0
1206
 **/
1207
XbSilo *
1208
xb_silo_new_from_xml(const gchar *xml, GError **error)
1209
0
{
1210
0
  g_autoptr(XbBuilder) builder = xb_builder_new();
1211
0
  g_autoptr(XbBuilderSource) source = xb_builder_source_new();
1212
0
  g_return_val_if_fail(xml != NULL, NULL);
1213
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
1214
0
  if (!xb_builder_source_load_xml(source, xml, XB_BUILDER_SOURCE_FLAG_NONE, error))
1215
0
    return NULL;
1216
0
  xb_builder_import_source(builder, source);
1217
0
  return xb_builder_compile(builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error);
1218
0
}
1219
1220
/* private */
1221
XbNode *
1222
xb_silo_create_node(XbSilo *self, XbSiloNode *sn, gboolean force_node_cache)
1223
0
{
1224
0
  XbNode *n;
1225
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
1226
0
  g_autoptr(GMutexLocker) locker = NULL;
1227
1228
  /* the cache should only be enabled/disabled before threads are
1229
   * spawned, so `priv->enable_node_cache` can be accessed unlocked */
1230
0
  if (!priv->enable_node_cache && !force_node_cache)
1231
0
    return xb_node_new(self, sn);
1232
1233
0
  locker = g_mutex_locker_new(&priv->nodes_mutex);
1234
1235
  /* ensure the cache exists */
1236
0
  if (priv->nodes == NULL)
1237
0
    priv->nodes = g_hash_table_new_full(g_direct_hash,
1238
0
                g_direct_equal,
1239
0
                NULL,
1240
0
                (GDestroyNotify)g_object_unref);
1241
1242
  /* does already exist */
1243
0
  n = g_hash_table_lookup(priv->nodes, sn);
1244
0
  if (n != NULL)
1245
0
    return g_object_ref(n);
1246
1247
  /* create and add */
1248
0
  n = xb_node_new(self, sn);
1249
0
  g_hash_table_insert(priv->nodes, sn, g_object_ref(n));
1250
0
  return n;
1251
0
}
1252
1253
/* Push two opcodes onto the stack with appropriate rollback on failure. */
1254
static gboolean
1255
_xb_stack_push_two(XbStack *opcodes, XbOpcode **op1, XbOpcode **op2, GError **error)
1256
0
{
1257
0
  if (!xb_stack_push(opcodes, op1, error))
1258
0
    return FALSE;
1259
0
  if (!xb_stack_push(opcodes, op2, error)) {
1260
0
    xb_stack_pop(opcodes, NULL, NULL);
1261
0
    return FALSE;
1262
0
  }
1263
0
  return TRUE;
1264
0
}
1265
1266
/* convert [2] to position()=2 */
1267
static gboolean
1268
xb_silo_machine_fixup_position_cb(XbMachine *self,
1269
          XbStack *opcodes,
1270
          gpointer user_data,
1271
          GError **error)
1272
0
{
1273
0
  XbOpcode *op1;
1274
0
  XbOpcode *op2;
1275
0
  XbOpcode *tail = xb_stack_peek_tail(opcodes);
1276
1277
0
  if (!_xb_stack_push_two(opcodes, &op1, &op2, error))
1278
0
    return FALSE;
1279
1280
0
  xb_machine_opcode_func_init(self, op1, "position");
1281
0
  xb_machine_opcode_func_init(self, op2, "eq");
1282
1283
  /* always exists, but maybe a @level would be cleaner */
1284
0
  if (tail != NULL) {
1285
0
    xb_opcode_set_level(op1, _xb_opcode_get_level(tail));
1286
0
    xb_opcode_set_level(op2, _xb_opcode_get_level(tail));
1287
0
  }
1288
1289
0
  return TRUE;
1290
0
}
1291
1292
/* convert "'type' attr()" -> "'type' attr() '(null)' ne()" */
1293
static gboolean
1294
xb_silo_machine_fixup_attr_exists_cb(XbMachine *self,
1295
             XbStack *opcodes,
1296
             gpointer user_data,
1297
             GError **error)
1298
0
{
1299
0
  XbOpcode *op1;
1300
0
  XbOpcode *op2;
1301
0
  XbOpcode *tail = xb_stack_peek_tail(opcodes);
1302
1303
0
  if (!_xb_stack_push_two(opcodes, &op1, &op2, error))
1304
0
    return FALSE;
1305
1306
0
  xb_opcode_text_init_static(op1, NULL);
1307
0
  xb_machine_opcode_func_init(self, op2, "ne");
1308
1309
  /* always exists, but maybe a @level would be cleaner */
1310
0
  if (tail != NULL) {
1311
0
    xb_opcode_set_level(op1, _xb_opcode_get_level(tail));
1312
0
    xb_opcode_set_level(op2, _xb_opcode_get_level(tail));
1313
0
  }
1314
1315
0
  return TRUE;
1316
0
}
1317
1318
static gboolean
1319
xb_silo_machine_fixup_attr_search_token_cb(XbMachine *self,
1320
             XbStack *opcodes,
1321
             gpointer user_data,
1322
             GError **error)
1323
0
{
1324
0
  XbOpcode op_func;
1325
0
  XbOpcode op_text;
1326
0
  XbOpcode op_search;
1327
0
  XbOpcode *op_tmp;
1328
1329
  /* text() */
1330
0
  if (!xb_machine_stack_pop(self, opcodes, &op_func, error))
1331
0
    return FALSE;
1332
1333
  /* TEXT */
1334
0
  if (!xb_machine_stack_pop(self, opcodes, &op_text, error))
1335
0
    return FALSE;
1336
0
  xb_machine_opcode_tokenize(self, &op_text);
1337
1338
  /* search() */
1339
0
  if (!xb_machine_stack_pop(self, opcodes, &op_search, error))
1340
0
    return FALSE;
1341
1342
  /* text() */
1343
0
  if (!xb_machine_stack_push(self, opcodes, &op_tmp, error))
1344
0
    return FALSE;
1345
0
  *op_tmp = op_search;
1346
1347
  /* TEXT */
1348
0
  if (!xb_machine_stack_push(self, opcodes, &op_tmp, error))
1349
0
    return FALSE;
1350
0
  *op_tmp = op_text;
1351
1352
  /* search() */
1353
0
  if (!xb_machine_stack_push(self, opcodes, &op_tmp, error))
1354
0
    return FALSE;
1355
0
  *op_tmp = op_func;
1356
0
  return TRUE;
1357
0
}
1358
1359
static gboolean
1360
xb_silo_machine_func_attr_cb(XbMachine *self,
1361
           XbStack *stack,
1362
           gboolean *result,
1363
           gpointer user_data,
1364
           gpointer exec_data,
1365
           GError **error)
1366
0
{
1367
0
  XbOpcode *op2;
1368
0
  XbSiloNodeAttr *a;
1369
0
  XbSilo *silo = XB_SILO(user_data);
1370
0
  XbSiloQueryData *query_data = (XbSiloQueryData *)exec_data;
1371
0
  const gchar *attr_value;
1372
0
  g_auto(XbOpcode) op = XB_OPCODE_INIT();
1373
1374
  /* optimize pass */
1375
0
  if (query_data == NULL) {
1376
0
    g_set_error_literal(error,
1377
0
            G_IO_ERROR,
1378
0
            G_IO_ERROR_FAILED_HANDLED,
1379
0
            "cannot optimize: no silo to query");
1380
0
    return FALSE;
1381
0
  }
1382
1383
0
  if (!xb_machine_stack_pop(self, stack, &op, error))
1384
0
    return FALSE;
1385
1386
  /* indexed string */
1387
0
  if (xb_opcode_get_kind(&op) == XB_OPCODE_KIND_INDEXED_TEXT) {
1388
0
    guint32 val = xb_opcode_get_val(&op);
1389
0
    a = xb_silo_node_get_attr_by_val(silo, query_data->sn, val);
1390
0
  } else {
1391
0
    const gchar *str = xb_opcode_get_str(&op);
1392
0
    a = xb_silo_get_node_attr_by_str(silo, query_data->sn, str);
1393
0
  }
1394
0
  if (a == NULL) {
1395
0
    return xb_machine_stack_push_text_static(self, stack, NULL, error);
1396
0
  }
1397
0
  if (!xb_machine_stack_push(self, stack, &op2, error))
1398
0
    return FALSE;
1399
0
  attr_value = xb_silo_from_strtab(silo, a->attr_value, error);
1400
0
  if (attr_value == NULL)
1401
0
    return FALSE;
1402
0
  xb_opcode_init(op2, XB_OPCODE_KIND_INDEXED_TEXT, attr_value, a->attr_value, NULL);
1403
0
  return TRUE;
1404
0
}
1405
1406
static gboolean
1407
xb_silo_machine_func_stem_cb(XbMachine *self,
1408
           XbStack *stack,
1409
           gboolean *result,
1410
           gpointer user_data,
1411
           gpointer exec_data,
1412
           GError **error)
1413
0
{
1414
0
  XbSilo *silo = XB_SILO(user_data);
1415
0
  XbOpcode *tail;
1416
0
  const gchar *str;
1417
0
  g_auto(XbOpcode) op = XB_OPCODE_INIT();
1418
1419
0
  tail = xb_stack_peek_tail(stack);
1420
0
  if (tail == NULL || !xb_opcode_cmp_str(tail)) {
1421
0
    g_set_error(error,
1422
0
          G_IO_ERROR,
1423
0
          G_IO_ERROR_NOT_SUPPORTED,
1424
0
          "%s type not supported",
1425
0
          (tail != NULL) ? xb_opcode_kind_to_string(xb_opcode_get_kind(tail))
1426
0
             : "(null)");
1427
0
    return FALSE;
1428
0
  }
1429
1430
0
  if (!xb_machine_stack_pop(self, stack, &op, error))
1431
0
    return FALSE;
1432
1433
  /* TEXT */
1434
0
  str = xb_opcode_get_str(&op);
1435
0
  if (str == NULL)
1436
0
    return xb_machine_stack_push_text_static(self, stack, NULL, error);
1437
0
  return xb_machine_stack_push_text_steal(self, stack, xb_silo_stem(silo, str), error);
1438
0
}
1439
1440
static gboolean
1441
xb_silo_machine_func_text_cb(XbMachine *self,
1442
           XbStack *stack,
1443
           gboolean *result,
1444
           gpointer user_data,
1445
           gpointer exec_data,
1446
           GError **error)
1447
0
{
1448
0
  XbSilo *silo = XB_SILO(user_data);
1449
0
  XbSiloQueryData *query_data = (XbSiloQueryData *)exec_data;
1450
0
  XbOpcode *op;
1451
0
  const gchar *text;
1452
0
  guint8 token_count;
1453
1454
  /* optimize pass */
1455
0
  if (query_data == NULL) {
1456
0
    g_set_error_literal(error,
1457
0
            G_IO_ERROR,
1458
0
            G_IO_ERROR_FAILED_HANDLED,
1459
0
            "cannot optimize: no silo to query");
1460
0
    return FALSE;
1461
0
  }
1462
1463
0
  if (xb_silo_node_get_text_idx(query_data->sn) != XB_SILO_UNSET) {
1464
0
    text = xb_silo_from_strtab(silo, xb_silo_node_get_text_idx(query_data->sn), error);
1465
0
    if (text == NULL)
1466
0
      return FALSE;
1467
0
  } else {
1468
0
    text = "";
1469
0
  }
1470
0
  if (!xb_machine_stack_push(self, stack, &op, error))
1471
0
    return FALSE;
1472
0
  xb_opcode_init(op,
1473
0
           XB_OPCODE_KIND_INDEXED_TEXT,
1474
0
           text,
1475
0
           xb_silo_node_get_text_idx(query_data->sn),
1476
0
           NULL);
1477
1478
  /* use the fast token path even if there are no valid tokens */
1479
0
  if (xb_silo_node_has_flag(query_data->sn, XB_SILO_NODE_FLAG_IS_TOKENIZED))
1480
0
    xb_opcode_add_flag(op, XB_OPCODE_FLAG_TOKENIZED);
1481
1482
  /* add tokens */
1483
0
  token_count = xb_silo_node_get_token_count(query_data->sn);
1484
0
  for (guint i = 0; i < token_count; i++) {
1485
0
    guint32 stridx = xb_silo_node_get_token_idx(query_data->sn, i);
1486
0
    const gchar *token = xb_silo_from_strtab(silo, stridx, error);
1487
0
    if (token == NULL)
1488
0
      return FALSE;
1489
0
    xb_opcode_append_token(op, token);
1490
0
  }
1491
1492
0
  return TRUE;
1493
0
}
1494
1495
static gboolean
1496
xb_silo_machine_func_tail_cb(XbMachine *self,
1497
           XbStack *stack,
1498
           gboolean *result,
1499
           gpointer user_data,
1500
           gpointer exec_data,
1501
           GError **error)
1502
0
{
1503
0
  XbSilo *silo = XB_SILO(user_data);
1504
0
  XbSiloQueryData *query_data = (XbSiloQueryData *)exec_data;
1505
0
  const gchar *tail;
1506
0
  XbOpcode *op;
1507
1508
  /* optimize pass */
1509
0
  if (query_data == NULL) {
1510
0
    g_set_error_literal(error,
1511
0
            G_IO_ERROR,
1512
0
            G_IO_ERROR_FAILED_HANDLED,
1513
0
            "cannot optimize: no silo to query");
1514
0
    return FALSE;
1515
0
  }
1516
1517
0
  if (xb_silo_node_get_tail_idx(query_data->sn) != XB_SILO_UNSET) {
1518
0
    tail = xb_silo_from_strtab(silo, xb_silo_node_get_tail_idx(query_data->sn), error);
1519
0
    if (tail == NULL)
1520
0
      return FALSE;
1521
0
  } else {
1522
0
    tail = "";
1523
0
  }
1524
0
  if (!xb_machine_stack_push(self, stack, &op, error))
1525
0
    return FALSE;
1526
0
  xb_opcode_init(op,
1527
0
           XB_OPCODE_KIND_INDEXED_TEXT,
1528
0
           tail,
1529
0
           xb_silo_node_get_tail_idx(query_data->sn),
1530
0
           NULL);
1531
0
  return TRUE;
1532
0
}
1533
1534
static gboolean
1535
xb_silo_machine_func_first_cb(XbMachine *self,
1536
            XbStack *stack,
1537
            gboolean *result,
1538
            gpointer user_data,
1539
            gpointer exec_data,
1540
            GError **error)
1541
0
{
1542
0
  XbSiloQueryData *query_data = (XbSiloQueryData *)exec_data;
1543
1544
  /* optimize pass */
1545
0
  if (query_data == NULL) {
1546
0
    g_set_error_literal(error,
1547
0
            G_IO_ERROR,
1548
0
            G_IO_ERROR_FAILED_HANDLED,
1549
0
            "cannot optimize: no silo to query");
1550
0
    return FALSE;
1551
0
  }
1552
0
  return xb_stack_push_bool(stack, query_data->position == 1, error);
1553
0
}
1554
1555
static gboolean
1556
xb_silo_machine_func_last_cb(XbMachine *self,
1557
           XbStack *stack,
1558
           gboolean *result,
1559
           gpointer user_data,
1560
           gpointer exec_data,
1561
           GError **error)
1562
0
{
1563
0
  XbSiloQueryData *query_data = (XbSiloQueryData *)exec_data;
1564
1565
  /* optimize pass */
1566
0
  if (query_data == NULL) {
1567
0
    g_set_error_literal(error,
1568
0
            G_IO_ERROR,
1569
0
            G_IO_ERROR_FAILED_HANDLED,
1570
0
            "cannot optimize: no silo to query");
1571
0
    return FALSE;
1572
0
  }
1573
0
  return xb_stack_push_bool(stack, query_data->sn->next == 0, error);
1574
0
}
1575
1576
static gboolean
1577
xb_silo_machine_func_position_cb(XbMachine *self,
1578
         XbStack *stack,
1579
         gboolean *result,
1580
         gpointer user_data,
1581
         gpointer exec_data,
1582
         GError **error)
1583
0
{
1584
0
  XbSiloQueryData *query_data = (XbSiloQueryData *)exec_data;
1585
1586
  /* optimize pass */
1587
0
  if (query_data == NULL) {
1588
0
    g_set_error_literal(error,
1589
0
            G_IO_ERROR,
1590
0
            G_IO_ERROR_FAILED_HANDLED,
1591
0
            "cannot optimize: no silo to query");
1592
0
    return FALSE;
1593
0
  }
1594
0
  return xb_machine_stack_push_integer(self, stack, query_data->position, error);
1595
0
}
1596
1597
static gboolean
1598
xb_silo_machine_func_search_cb(XbMachine *self,
1599
             XbStack *stack,
1600
             gboolean *result,
1601
             gpointer user_data,
1602
             gpointer exec_data,
1603
             GError **error)
1604
0
{
1605
0
  XbSilo *silo = XB_SILO(user_data);
1606
0
  XbSiloPrivate *priv = GET_PRIVATE(silo);
1607
0
  const gchar *text;
1608
0
  const gchar *search;
1609
0
  XbOpcode *head1 = NULL;
1610
0
  XbOpcode *head2 = NULL;
1611
0
  g_auto(XbOpcode) op1 = XB_OPCODE_INIT();
1612
0
  g_auto(XbOpcode) op2 = XB_OPCODE_INIT();
1613
1614
0
  if (xb_stack_get_size(stack) >= 2) {
1615
0
    head1 = xb_stack_peek(stack, xb_stack_get_size(stack) - 1);
1616
0
    head2 = xb_stack_peek(stack, xb_stack_get_size(stack) - 2);
1617
0
  }
1618
0
  if (head1 == NULL || !xb_opcode_cmp_str(head1) || head2 == NULL ||
1619
0
      !xb_opcode_cmp_str(head2)) {
1620
0
    g_set_error(error,
1621
0
          G_IO_ERROR,
1622
0
          G_IO_ERROR_NOT_SUPPORTED,
1623
0
          "%s:%s types not supported",
1624
0
          (head1 != NULL) ? xb_opcode_kind_to_string(xb_opcode_get_kind(head1))
1625
0
              : "(null)",
1626
0
          (head2 != NULL) ? xb_opcode_kind_to_string(xb_opcode_get_kind(head2))
1627
0
              : "(null)");
1628
0
    return FALSE;
1629
0
  }
1630
1631
0
  if (!xb_machine_stack_pop_two(self, stack, &op1, &op2, error))
1632
0
    return FALSE;
1633
1634
  /* this cannot be optimized away when constructing the query */
1635
0
  if (!_xb_opcode_has_flag(&op1, XB_OPCODE_FLAG_TOKENIZED) &&
1636
0
      _xb_opcode_get_kind(&op1) == XB_OPCODE_KIND_BOUND_TEXT) {
1637
0
    xb_machine_opcode_tokenize(self, &op1);
1638
0
  }
1639
0
  if (!_xb_opcode_has_flag(&op2, XB_OPCODE_FLAG_TOKENIZED) &&
1640
0
      _xb_opcode_get_kind(&op2) == XB_OPCODE_KIND_BOUND_TEXT) {
1641
0
    xb_machine_opcode_tokenize(self, &op2);
1642
0
  }
1643
1644
  /* TOKN:TOKN */
1645
0
  if (xb_opcode_has_flag(&op1, XB_OPCODE_FLAG_TOKENIZED) &&
1646
0
      xb_opcode_has_flag(&op2, XB_OPCODE_FLAG_TOKENIZED)) {
1647
0
    return xb_stack_push_bool(
1648
0
        stack,
1649
0
        xb_string_searchv(xb_opcode_get_tokens(&op2), xb_opcode_get_tokens(&op1)),
1650
0
        error);
1651
0
  }
1652
1653
  /* this is going to be slow, but correct */
1654
0
  text = xb_opcode_get_str(&op2);
1655
0
  search = xb_opcode_get_str(&op1);
1656
0
  if (text == NULL || search == NULL || text[0] == '\0' || search[0] == '\0')
1657
0
    return xb_stack_push_bool(stack, FALSE, error);
1658
0
  if (!g_str_is_ascii(text) || !g_str_is_ascii(search)) {
1659
0
    if (priv->profile_flags & XB_SILO_PROFILE_FLAG_DEBUG) {
1660
0
      g_debug("tokenization for [%s:%s] may be slow!", text, search);
1661
0
    }
1662
0
    return xb_stack_push_bool(stack, g_str_match_string(search, text, TRUE), error);
1663
0
  }
1664
1665
  /* TEXT:TEXT */
1666
0
  return xb_stack_push_bool(stack, xb_string_search(text, search), error);
1667
0
}
1668
1669
static gboolean
1670
xb_silo_machine_fixup_attr_text_cb(XbMachine *self,
1671
           XbStack *opcodes,
1672
           const gchar *text,
1673
           gboolean *handled,
1674
           gpointer user_data,
1675
           GError **error)
1676
0
{
1677
  /* @foo -> attr(foo) */
1678
0
  if (g_str_has_prefix(text, "@")) {
1679
0
    XbOpcode *op1;
1680
0
    XbOpcode *op2;
1681
1682
0
    if (!_xb_stack_push_two(opcodes, &op1, &op2, error))
1683
0
      return FALSE;
1684
1685
0
    xb_opcode_text_init(op1, text + 1);
1686
0
    if (!xb_machine_opcode_func_init(self, op2, "attr")) {
1687
0
      g_set_error_literal(error,
1688
0
              G_IO_ERROR,
1689
0
              G_IO_ERROR_NOT_SUPPORTED,
1690
0
              "no attr opcode");
1691
0
      xb_stack_pop(opcodes, NULL, NULL);
1692
0
      xb_stack_pop(opcodes, NULL, NULL);
1693
0
      return FALSE;
1694
0
    }
1695
1696
0
    *handled = TRUE;
1697
0
    return TRUE;
1698
0
  }
1699
1700
  /* not us */
1701
0
  return TRUE;
1702
0
}
1703
1704
static void
1705
xb_silo_file_monitor_item_free(XbSiloFileMonitorItem *item)
1706
0
{
1707
0
  g_file_monitor_cancel(item->file_monitor);
1708
0
  g_signal_handler_disconnect(item->file_monitor, item->file_monitor_id);
1709
0
  g_object_unref(item->file_monitor);
1710
0
  g_slice_free(XbSiloFileMonitorItem, item);
1711
0
}
1712
1713
static void
1714
xb_silo_get_property(GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec)
1715
0
{
1716
0
  XbSilo *self = XB_SILO(obj);
1717
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
1718
0
  switch ((XbSiloProperty)prop_id) {
1719
0
  case PROP_GUID:
1720
0
    g_value_set_string(value, priv->guid);
1721
0
    break;
1722
0
  case PROP_VALID:
1723
0
    g_value_set_boolean(value, priv->valid);
1724
0
    break;
1725
0
  case PROP_ENABLE_NODE_CACHE:
1726
0
    g_value_set_boolean(value, priv->enable_node_cache);
1727
0
    break;
1728
0
  default:
1729
0
    G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
1730
0
    break;
1731
0
  }
1732
0
}
1733
1734
static void
1735
xb_silo_set_property(GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec)
1736
0
{
1737
0
  XbSilo *self = XB_SILO(obj);
1738
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
1739
0
  switch ((XbSiloProperty)prop_id) {
1740
0
  case PROP_GUID:
1741
0
    g_free(priv->guid);
1742
0
    priv->guid = g_value_dup_string(value);
1743
0
    silo_notify(self, obj_props[PROP_GUID]);
1744
0
    break;
1745
0
  case PROP_VALID:
1746
    /* Read only */
1747
0
    g_assert_not_reached();
1748
0
    break;
1749
0
  case PROP_ENABLE_NODE_CACHE:
1750
0
    xb_silo_set_enable_node_cache(self, g_value_get_boolean(value));
1751
0
    break;
1752
0
  default:
1753
0
    G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
1754
0
    break;
1755
0
  }
1756
0
}
1757
1758
static void
1759
xb_silo_init(XbSilo *self)
1760
0
{
1761
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
1762
1763
0
  priv->file_monitors = g_hash_table_new_full(g_file_hash,
1764
0
                (GEqualFunc)g_file_equal,
1765
0
                g_object_unref,
1766
0
                (GDestroyNotify)xb_silo_file_monitor_item_free);
1767
0
  g_mutex_init(&priv->file_monitors_mutex);
1768
1769
0
  priv->strtab_tags = g_hash_table_new(g_str_hash, g_str_equal);
1770
0
  priv->strindex = g_hash_table_new(g_str_hash, g_str_equal);
1771
0
  g_rw_lock_init(&priv->strindex_mutex);
1772
0
  priv->profile_str = g_string_new(NULL);
1773
0
  priv->query_cache = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_object_unref);
1774
0
  g_rw_lock_init(&priv->query_cache_mutex);
1775
1776
0
  priv->nodes = NULL; /* initialised when first used */
1777
0
  g_mutex_init(&priv->nodes_mutex);
1778
1779
0
  priv->context = g_main_context_ref_thread_default();
1780
1781
#ifdef HAVE_LIBSTEMMER
1782
  g_mutex_init(&priv->stemmer_mutex);
1783
#endif
1784
1785
0
  priv->machine = xb_machine_new();
1786
0
  xb_machine_add_method(priv->machine, "attr", 1, xb_silo_machine_func_attr_cb, self, NULL);
1787
0
  xb_machine_add_method(priv->machine, "stem", 1, xb_silo_machine_func_stem_cb, self, NULL);
1788
0
  xb_machine_add_method(priv->machine, "text", 0, xb_silo_machine_func_text_cb, self, NULL);
1789
0
  xb_machine_add_method(priv->machine, "tail", 0, xb_silo_machine_func_tail_cb, self, NULL);
1790
0
  xb_machine_add_method(priv->machine, "first", 0, xb_silo_machine_func_first_cb, self, NULL);
1791
0
  xb_machine_add_method(priv->machine, "last", 0, xb_silo_machine_func_last_cb, self, NULL);
1792
0
  xb_machine_add_method(priv->machine,
1793
0
            "position",
1794
0
            0,
1795
0
            xb_silo_machine_func_position_cb,
1796
0
            self,
1797
0
            NULL);
1798
0
  xb_machine_add_method(priv->machine,
1799
0
            "search",
1800
0
            2,
1801
0
            xb_silo_machine_func_search_cb,
1802
0
            self,
1803
0
            NULL);
1804
0
  xb_machine_add_operator(priv->machine, "~=", "search");
1805
0
  xb_machine_add_opcode_fixup(priv->machine,
1806
0
            "INTE",
1807
0
            xb_silo_machine_fixup_position_cb,
1808
0
            self,
1809
0
            NULL);
1810
0
  xb_machine_add_opcode_fixup(priv->machine,
1811
0
            "TEXT,FUNC:attr",
1812
0
            xb_silo_machine_fixup_attr_exists_cb,
1813
0
            self,
1814
0
            NULL);
1815
0
  xb_machine_add_opcode_fixup(priv->machine,
1816
0
            "FUNC:text,TEXT,FUNC:search",
1817
0
            xb_silo_machine_fixup_attr_search_token_cb,
1818
0
            self,
1819
0
            NULL);
1820
0
  xb_machine_add_text_handler(priv->machine, xb_silo_machine_fixup_attr_text_cb, self, NULL);
1821
0
}
1822
1823
static void
1824
xb_silo_finalize(GObject *obj)
1825
0
{
1826
0
  XbSilo *self = XB_SILO(obj);
1827
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
1828
1829
0
  g_clear_pointer(&priv->nodes, g_hash_table_unref);
1830
0
  g_mutex_clear(&priv->nodes_mutex);
1831
1832
#ifdef HAVE_LIBSTEMMER
1833
  if (priv->stemmer_ctx != NULL)
1834
    sb_stemmer_delete(priv->stemmer_ctx);
1835
  g_mutex_clear(&priv->stemmer_mutex);
1836
#endif
1837
1838
0
  g_clear_pointer(&priv->context, g_main_context_unref);
1839
1840
0
  g_free(priv->guid);
1841
0
  g_string_free(priv->profile_str, TRUE);
1842
0
  g_hash_table_unref(priv->query_cache);
1843
0
  g_rw_lock_clear(&priv->query_cache_mutex);
1844
0
  g_object_unref(priv->machine);
1845
0
  g_hash_table_unref(priv->strindex);
1846
0
  g_rw_lock_clear(&priv->strindex_mutex);
1847
0
  g_hash_table_unref(priv->file_monitors);
1848
0
  g_mutex_clear(&priv->file_monitors_mutex);
1849
0
  g_hash_table_unref(priv->strtab_tags);
1850
0
  if (priv->mmap != NULL)
1851
0
    g_mapped_file_unref(priv->mmap);
1852
0
  if (priv->blob != NULL)
1853
0
    g_bytes_unref(priv->blob);
1854
0
  G_OBJECT_CLASS(xb_silo_parent_class)->finalize(obj);
1855
0
}
1856
1857
static void
1858
xb_silo_class_init(XbSiloClass *klass)
1859
0
{
1860
0
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
1861
0
  object_class->finalize = xb_silo_finalize;
1862
0
  object_class->get_property = xb_silo_get_property;
1863
0
  object_class->set_property = xb_silo_set_property;
1864
1865
  /**
1866
   * XbSilo:guid:
1867
   */
1868
0
  obj_props[PROP_GUID] =
1869
0
      g_param_spec_string("guid",
1870
0
        NULL,
1871
0
        NULL,
1872
0
        NULL,
1873
0
        G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS |
1874
0
            G_PARAM_EXPLICIT_NOTIFY);
1875
1876
  /**
1877
   * XbSilo:valid:
1878
   */
1879
0
  obj_props[PROP_VALID] = g_param_spec_boolean("valid",
1880
0
                 NULL,
1881
0
                 NULL,
1882
0
                 TRUE,
1883
0
                 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS |
1884
0
               G_PARAM_EXPLICIT_NOTIFY);
1885
1886
  /**
1887
   * XbSilo:enable-node-cache:
1888
   *
1889
   * Whether to cache all #XbNode instances ever constructed in a single
1890
   * cache in the #XbSilo, so that the same #XbNode instance is always
1891
   * returned in query results for a given XPath. This is a form of
1892
   * memoisation, and allows xb_node_get_data() and xb_node_set_data() to
1893
   * be used.
1894
   *
1895
   * This is enabled by default to preserve compatibility with older
1896
   * versions of libxmlb, but most clients will want to disable it. It
1897
   * adds a large memory overhead (no #XbNode is ever finalised) but
1898
   * achieves moderately low hit rates for typical XML parsing workloads
1899
   * where most nodes are accessed only once or twice as they are
1900
   * processed and then processing moves on to other nodes.
1901
   *
1902
   * This property can only be changed before the #XbSilo is passed
1903
   * between threads. Changing it is not thread-safe.
1904
   *
1905
   * Since: 0.2.0
1906
   */
1907
0
  obj_props[PROP_ENABLE_NODE_CACHE] = g_param_spec_boolean(
1908
0
      "enable-node-cache",
1909
0
      NULL,
1910
0
      NULL,
1911
0
      TRUE,
1912
0
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
1913
1914
0
  g_object_class_install_properties(object_class, G_N_ELEMENTS(obj_props), obj_props);
1915
0
}
1916
1917
/**
1918
 * xb_silo_new:
1919
 *
1920
 * Creates a new silo.
1921
 *
1922
 * Returns: a new #XbSilo
1923
 *
1924
 * Since: 0.1.0
1925
 **/
1926
XbSilo *
1927
xb_silo_new(void)
1928
0
{
1929
0
  return g_object_new(XB_TYPE_SILO, NULL);
1930
0
}
1931
1932
/**
1933
 * xb_silo_lookup_query_full:
1934
 * @self: an #XbSilo
1935
 * @xpath: an XPath query string
1936
 * @error: the #GError, or %NULL
1937
 *
1938
 * Create an #XbQuery from the given @xpath XPath string, or return it from the
1939
 * query cache in the #XbSilo.
1940
 *
1941
 * @xpath must be valid: it is a programmer error if creating the query fails
1942
 * (i.e. if xb_query_new() returns an error).
1943
 *
1944
 * This function is thread-safe.
1945
 *
1946
 * Returns: (transfer full): an #XbQuery representing @xpath
1947
 *
1948
 * Since: 0.3.27
1949
 */
1950
XbQuery *
1951
xb_silo_lookup_query_full(XbSilo *self, const gchar *xpath, GError **error)
1952
0
{
1953
0
  XbSiloPrivate *priv = GET_PRIVATE(self);
1954
0
  XbQuery *query;
1955
0
  g_autoptr(GRWLockReaderLocker) locker_ro = NULL;
1956
0
  g_autoptr(GRWLockWriterLocker) locker_rw = NULL;
1957
1958
0
  g_return_val_if_fail(XB_IS_SILO(self), NULL);
1959
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
1960
1961
  /* read only */
1962
0
  locker_ro = g_rw_lock_reader_locker_new(&priv->query_cache_mutex);
1963
0
  query = g_hash_table_lookup(priv->query_cache, xpath);
1964
0
  if (query != NULL)
1965
0
    return g_object_ref(query);
1966
0
  g_clear_pointer(&locker_ro, g_rw_lock_reader_locker_free);
1967
1968
  /* check again with an exclusive lock */
1969
0
  locker_rw = g_rw_lock_writer_locker_new(&priv->query_cache_mutex);
1970
0
  query = g_hash_table_lookup(priv->query_cache, xpath);
1971
0
  if (query != NULL)
1972
0
    return g_object_ref(query);
1973
1974
  /* add it */
1975
0
  query = xb_query_new(self, xpath, error);
1976
0
  if (query == NULL)
1977
0
    return NULL;
1978
0
  g_hash_table_insert(priv->query_cache, g_strdup(xpath), g_object_ref(query));
1979
0
  return query;
1980
0
}
1981
1982
/**
1983
 * xb_silo_lookup_query:
1984
 * @self: an #XbSilo
1985
 * @xpath: an XPath query string
1986
 *
1987
 * Create an #XbQuery from the given @xpath XPath string, or return it from the
1988
 * query cache in the #XbSilo.
1989
 *
1990
 * @xpath must be valid: it is a programmer error if creating the query fails
1991
 * (i.e. if xb_query_new() returns an error).
1992
 *
1993
 * This function is thread-safe.
1994
 *
1995
 * Returns: (transfer full): an #XbQuery representing @xpath
1996
 *
1997
 * Since: 0.3.0
1998
 */
1999
XbQuery *
2000
xb_silo_lookup_query(XbSilo *self, const gchar *xpath)
2001
0
{
2002
0
  g_autoptr(GError) error = NULL;
2003
0
  g_autoptr(XbQuery) query = NULL;
2004
2005
0
  query = xb_silo_lookup_query_full(self, xpath, &error);
2006
0
  if (query == NULL) {
2007
0
    g_warning("failed: %s", error->message);
2008
0
    return NULL;
2009
0
  }
2010
0
  return g_steal_pointer(&query);
2011
0
}