Coverage Report

Created: 2025-07-18 06:26

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