/*
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#define G_LOG_DOMAIN "XbSilo"
#include "config.h"
#include <string.h>
#include <glib-object.h>
#include <gio/gio.h>
#ifdef HAVE_LIBSTEMMER
#include <libstemmer.h>
#endif
#include "xb-builder.h"
#include "xb-node-private.h"
#include "xb-opcode-private.h"
#include "xb-silo-private.h"
#include "xb-stack-private.h"
#include "xb-string-private.h"
typedef struct {
GObject parent_instance;
GMappedFile *mmap;
gchar *guid;
gboolean valid;
GBytes *blob;
const guint8 *data; /* pointers into ->blob */
guint32 datasz;
guint32 strtab;
GHashTable *strtab_tags;
GHashTable *strindex;
GHashTable *nodes;
GMutex nodes_mutex;
GHashTable *file_monitors; /* of fn:XbSiloFileMonitorItem */
XbMachine *machine;
XbSiloProfileFlags profile_flags;
GString *profile_str;
#ifdef HAVE_LIBSTEMMER
struct sb_stemmer *stemmer_ctx; /* lazy loaded */
GMutex stemmer_mutex;
#endif
} XbSiloPrivate;
typedef struct {
GFileMonitor *file_monitor;
gulong file_monitor_id;
} XbSiloFileMonitorItem;
G_DEFINE_TYPE_WITH_PRIVATE (XbSilo, xb_silo, G_TYPE_OBJECT)
#define GET_PRIVATE(o) (xb_silo_get_instance_private (o))
enum {
PROP_0,
PROP_GUID,
PROP_VALID,
PROP_LAST
};
/* private */
void
xb_silo_add_profile (XbSilo *self, GTimer *timer, const gchar *fmt, ...)
{
XbSiloPrivate *priv = GET_PRIVATE (self);
va_list args;
g_autoptr(GString) str = g_string_new (NULL);
/* nothing to do */
if (!priv->profile_flags)
return;
/* add duration */
if (timer != NULL) {
g_string_append_printf (str, "%.2fms",
g_timer_elapsed (timer, NULL) * 1000);
for (guint i = str->len; i < 12; i++)
g_string_append (str, " ");
}
/* add varargs */
va_start (args, fmt);
g_string_append_vprintf (str, fmt, args);
va_end (args);
/* do the right thing */
if (priv->profile_flags & XB_SILO_PROFILE_FLAG_DEBUG)
g_debug ("%s", str->str);
if (priv->profile_flags & XB_SILO_PROFILE_FLAG_APPEND)
g_string_append_printf (priv->profile_str, "%s\n", str->str);
/* reset automatically */
if (timer != NULL)
g_timer_reset (timer);
}
/* private */
static gchar *
xb_silo_stem (XbSilo *self, const gchar *value)
{
#ifdef HAVE_LIBSTEMMER
XbSiloPrivate *priv = GET_PRIVATE (self);
const gchar *tmp;
gsize len_dst;
gsize len_src;
g_autofree gchar *value_casefold = NULL;
g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->stemmer_mutex);
/* not enabled */
value_casefold = g_utf8_casefold (value, -1);
if (priv->stemmer_ctx == NULL)
priv->stemmer_ctx = sb_stemmer_new ("en", NULL);
/* stem */
len_src = strlen (value_casefold);
tmp = (const gchar *) sb_stemmer_stem (priv->stemmer_ctx,
(guchar *) value_casefold,
(gint) len_src);
len_dst = (gsize) sb_stemmer_length (priv->stemmer_ctx);
if (len_src == len_dst)
return g_steal_pointer (&value_casefold);
return g_strndup (tmp, len_dst);
#else
return g_utf8_casefold (value, -1);
#endif
}
/* private */
const gchar *
xb_silo_from_strtab (XbSilo *self, guint32 offset)
{
XbSiloPrivate *priv = GET_PRIVATE (self);
if (offset == XB_SILO_UNSET)
return NULL;
if (offset >= priv->datasz - priv->strtab) {
g_critical ("strtab+offset is outside the data range for %u", offset);
return NULL;
}
return (const gchar *) (priv->data + priv->strtab + offset);
}
/* private */
void
xb_silo_strtab_index_insert (XbSilo *self, guint32 offset)
{
XbSiloPrivate *priv = GET_PRIVATE (self);
const gchar *tmp;
/* get the string version */
tmp = xb_silo_from_strtab (self, offset);
if (tmp == NULL)
return;
if (g_hash_table_lookup (priv->strindex, tmp) != NULL)
return;
g_hash_table_insert (priv->strindex,
(gpointer) tmp,
GUINT_TO_POINTER (offset));
}
/* private */
guint32
xb_silo_strtab_index_lookup (XbSilo *self, const gchar *str)
{
XbSiloPrivate *priv = GET_PRIVATE (self);
gpointer val = NULL;
if (!g_hash_table_lookup_extended (priv->strindex, str, NULL, &val))
return XB_SILO_UNSET;
return GPOINTER_TO_INT (val);
}
/* private */
inline XbSiloNode *
xb_silo_get_node (XbSilo *self, guint32 off)
{
XbSiloPrivate *priv = GET_PRIVATE (self);
return (XbSiloNode *) (priv->data + off);
}
/* private */
XbSiloAttr *
xb_silo_get_attr (XbSilo *self, guint32 off, guint8 idx)
{
XbSiloPrivate *priv = GET_PRIVATE (self);
off += sizeof(XbSiloNode);
off += sizeof(XbSiloAttr) * idx;
return (XbSiloAttr *) (priv->data + off);
}
/* private */
guint8
xb_silo_node_get_size (XbSiloNode *n)
{
if (n->is_node) {
guint8 sz = sizeof(XbSiloNode);
sz += n->nr_attrs * sizeof(XbSiloAttr);
return sz;
}
/* sentinel */
return 1;
}
/* private */
guint32
xb_silo_get_offset_for_node (XbSilo *self, XbSiloNode *n)
{
XbSiloPrivate *priv = GET_PRIVATE (self);
return ((const guint8 *) n) - priv->data;
}
/* private */
guint32
xb_silo_get_strtab (XbSilo *self)
{
XbSiloPrivate *priv = GET_PRIVATE (self);
return priv->strtab;
}
/* private */
XbSiloNode *
xb_silo_get_sroot (XbSilo *self)
{
XbSiloPrivate *priv = GET_PRIVATE (self);
if (priv->blob == NULL)
return NULL;
if (g_bytes_get_size (priv->blob) <= sizeof(XbSiloHeader))
return NULL;
return xb_silo_get_node (self, sizeof(XbSiloHeader));
}
/* private */
XbSiloNode *
xb_silo_node_get_parent (XbSilo *self, XbSiloNode *n)
{
if (n->parent == 0x0)
return NULL;
return xb_silo_get_node (self, n->parent);
}
/* private */
XbSiloNode *
xb_silo_node_get_next (XbSilo *self, XbSiloNode *n)
{
if (n->next == 0x0)
return NULL;
return xb_silo_get_node (self, n->next);
}
/* private */
XbSiloNode *
xb_silo_node_get_child (XbSilo *self, XbSiloNode *n)
{
XbSiloNode *c;
guint32 off = xb_silo_get_offset_for_node (self, n);
off += xb_silo_node_get_size (n);
/* check for sentinel */
c = xb_silo_get_node (self, off);
if (!c->is_node)
return NULL;
return c;
}
/**
* xb_silo_get_root:
* @self: a #XbSilo
*
* Gets the root node for the silo. (MIGHT BE MORE).
*
* Returns: (transfer full): A #XbNode, or %NULL for an error
*
* Since: 0.1.0
**/
XbNode *
xb_silo_get_root (XbSilo *self)
{
g_return_val_if_fail (XB_IS_SILO (self), NULL);
return xb_silo_node_create (self, xb_silo_get_sroot (self));
}
/* private */
guint32
xb_silo_get_strtab_idx (XbSilo *self, const gchar *element)
{
XbSiloPrivate *priv = GET_PRIVATE (self);
gpointer value = NULL;
if (!g_hash_table_lookup_extended (priv->strtab_tags, element, NULL, &value))
return XB_SILO_UNSET;
return GPOINTER_TO_UINT (value);
}
/**
* xb_silo_to_string:
* @self: a #XbSilo
* @error: the #GError, or %NULL
*
* Converts the silo to an internal string representation. This is only
* really useful for debugging #XbSilo itself.
*
* Returns: A string, or %NULL for an error
*
* Since: 0.1.0
**/
gchar *
xb_silo_to_string (XbSilo *self, GError **error)
{
guint32 off = sizeof(XbSiloHeader);
XbSiloPrivate *priv = GET_PRIVATE (self);
XbSiloHeader *hdr = (XbSiloHeader *) priv->data;
g_autoptr(GString) str = g_string_new (NULL);
g_return_val_if_fail (XB_IS_SILO (self), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
g_string_append_printf (str, "magic: %08x\n", (guint) hdr->magic);
g_string_append_printf (str, "guid: %s\n", priv->guid);
g_string_append_printf (str, "strtab: @%" G_GUINT32_FORMAT "\n", hdr->strtab);
g_string_append_printf (str, "strtab_ntags: %" G_GUINT16_FORMAT "\n", hdr->strtab_ntags);
while (off < priv->strtab) {
XbSiloNode *n = xb_silo_get_node (self, off);
if (n->is_node) {
g_string_append_printf (str, "NODE @%" G_GUINT32_FORMAT "\n", off);
g_string_append_printf (str, "element_name: %s [%03u]\n",
xb_silo_from_strtab (self, n->element_name),
n->element_name);
g_string_append_printf (str, "next: %" G_GUINT32_FORMAT "\n", n->next);
g_string_append_printf (str, "parent: %" G_GUINT32_FORMAT "\n", n->parent);
if (n->text != XB_SILO_UNSET) {
g_string_append_printf (str, "text: %s\n",
xb_silo_from_strtab (self, n->text));
}
if (n->tail != XB_SILO_UNSET) {
g_string_append_printf (str, "tail: %s\n",
xb_silo_from_strtab (self, n->tail));
}
for (guint8 i = 0; i < n->nr_attrs; i++) {
XbSiloAttr *a = xb_silo_get_attr (self, off, i);
g_string_append_printf (str, "attr_name: %s [%03u]\n",
xb_silo_from_strtab (self, a->attr_name),
a->attr_name);
g_string_append_printf (str, "attr_value: %s [%03u]\n",
xb_silo_from_strtab (self, a->attr_value),
a->attr_value);
}
} else {
g_string_append_printf (str, "SENT @%" G_GUINT32_FORMAT "\n", off);
}
off += xb_silo_node_get_size (n);
}
/* add strtab */
g_string_append_printf (str, "STRTAB @%" G_GUINT32_FORMAT "\n", hdr->strtab);
for (off = 0; off < priv->datasz - hdr->strtab;) {
const gchar *tmp = xb_silo_from_strtab (self, off);
if (tmp == NULL)
break;
g_string_append_printf (str, "[%03u]: %s\n", off, tmp);
off += strlen (tmp) + 1;
}
/* success */
return g_string_free (g_steal_pointer (&str), FALSE);
}
/* private */
const gchar *
xb_silo_node_get_text (XbSilo *self, XbSiloNode *n)
{
if (n->text == XB_SILO_UNSET)
return NULL;
return xb_silo_from_strtab (self, n->text);
}
/* private */
const gchar *
xb_silo_node_get_tail (XbSilo *self, XbSiloNode *n)
{
if (n->tail == XB_SILO_UNSET)
return NULL;
return xb_silo_from_strtab (self, n->tail);
}
/* private */
const gchar *
xb_silo_node_get_element (XbSilo *self, XbSiloNode *n)
{
return xb_silo_from_strtab (self, n->element_name);
}
/* private */
XbSiloAttr *
xb_silo_node_get_attr_by_str (XbSilo *self, XbSiloNode *n, const gchar *name)
{
guint32 off;
/* calculate offset to first attribute */
off = xb_silo_get_offset_for_node (self, n);
for (guint8 i = 0; i < n->nr_attrs; i++) {
XbSiloAttr *a = xb_silo_get_attr (self, off, i);
if (g_strcmp0 (xb_silo_from_strtab (self, a->attr_name), name) == 0)
return a;
}
/* nothing matched */
return NULL;
}
static XbSiloAttr *
xb_silo_node_get_attr_by_val (XbSilo *self, XbSiloNode *n, guint32 name)
{
guint32 off;
/* calculate offset to first attribute */
off = xb_silo_get_offset_for_node (self, n);
for (guint8 i = 0; i < n->nr_attrs; i++) {
XbSiloAttr *a = xb_silo_get_attr (self, off, i);
if (a->attr_name == name)
return a;
}
/* nothing matched */
return NULL;
}
/**
* xb_silo_get_size:
* @self: a #XbSilo
*
* Gets the number of nodes in the silo.
*
* Returns: a integer, or 0 is an empty blob
*
* Since: 0.1.0
**/
guint
xb_silo_get_size (XbSilo *self)
{
XbSiloPrivate *priv = GET_PRIVATE (self);
guint32 off = sizeof(XbSiloHeader);
guint nodes_cnt = 0;
g_return_val_if_fail (XB_IS_SILO (self), 0);
while (off < priv->strtab) {
XbSiloNode *n = xb_silo_get_node (self, off);
if (n->is_node)
nodes_cnt += 1;
off += xb_silo_node_get_size (n);
}
/* success */
return nodes_cnt;
}
/**
* xb_silo_is_valid:
* @self: a #XbSilo
*
* Checks is the silo is valid. The usual reason the silo is invalidated is
* when the backing mmapped file has changed, or one of the imported files have
* been modified.
*
* Returns: %TRUE if valid
*
* Since: 0.1.0
**/
gboolean
xb_silo_is_valid (XbSilo *self)
{
XbSiloPrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (XB_IS_SILO (self), FALSE);
return priv->valid;
}
/* private */
gboolean
xb_silo_is_empty (XbSilo *self)
{
XbSiloPrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (XB_IS_SILO (self), FALSE);
return priv->strtab == sizeof(XbSiloHeader);
}
/**
* xb_silo_invalidate:
* @self: a #XbSilo
*
* Invalidates a silo. Future calls xb_silo_is_valid() will return %FALSE.
*
* Since: 0.1.1
**/
void
xb_silo_invalidate (XbSilo *self)
{
XbSiloPrivate *priv = GET_PRIVATE (self);
if (!priv->valid)
return;
priv->valid = FALSE;
g_object_notify (G_OBJECT (self), "valid");
}
/* private */
void
xb_silo_uninvalidate (XbSilo *self)
{
XbSiloPrivate *priv = GET_PRIVATE (self);
if (priv->valid)
return;
priv->valid = TRUE;
g_object_notify (G_OBJECT (self), "valid");
}
/* private */
guint
xb_silo_node_get_depth (XbSilo *self, XbSiloNode *n)
{
guint depth = 0;
while (n->parent != 0) {
depth++;
n = xb_silo_get_node (self, n->parent);
}
return depth;
}
/**
* xb_silo_get_bytes:
* @self: a #XbSilo
*
* Gets the backing object that created the blob.
*
* You should never *ever* modify this data.
*
* Returns: (transfer full): A #GBytes, or %NULL if never set
*
* Since: 0.1.0
**/
GBytes *
xb_silo_get_bytes (XbSilo *self)
{
XbSiloPrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (XB_IS_SILO (self), NULL);
if (priv->blob == NULL)
return NULL;
return g_bytes_ref (priv->blob);
}
/**
* xb_silo_get_guid:
* @self: a #XbSilo
*
* Gets the GUID used to identify this silo.
*
* Returns: a string, otherwise %NULL
*
* Since: 0.1.0
**/
const gchar *
xb_silo_get_guid (XbSilo *self)
{
XbSiloPrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (XB_IS_SILO (self), NULL);
return priv->guid;
}
/* private */
XbMachine *
xb_silo_get_machine (XbSilo *self)
{
XbSiloPrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (XB_IS_SILO (self), NULL);
return priv->machine;
}
/**
* xb_silo_load_from_bytes:
* @self: a #XbSilo
* @blob: a #GBytes
* @flags: #XbSiloLoadFlags, e.g. %XB_SILO_LOAD_FLAG_NONE
* @error: the #GError, or %NULL
*
* Loads a silo from memory location.
*
* Returns: %TRUE for success, otherwise @error is set.
*
* Since: 0.1.0
**/
gboolean
xb_silo_load_from_bytes (XbSilo *self, GBytes *blob, XbSiloLoadFlags flags, GError **error)
{
XbGuid guid_tmp;
XbSiloHeader *hdr;
XbSiloPrivate *priv = GET_PRIVATE (self);
gsize sz = 0;
guint32 off = 0;
g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->nodes_mutex);
g_autoptr(GTimer) timer = g_timer_new ();
g_return_val_if_fail (XB_IS_SILO (self), FALSE);
g_return_val_if_fail (blob != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
g_return_val_if_fail (locker != NULL, FALSE);
/* no longer valid */
g_hash_table_remove_all (priv->nodes);
g_hash_table_remove_all (priv->strtab_tags);
g_clear_pointer (&priv->guid, g_free);
/* refcount internally */
if (priv->blob != NULL)
g_bytes_unref (priv->blob);
priv->blob = g_bytes_ref (blob);
/* update pointers into blob */
priv->data = g_bytes_get_data (priv->blob, &sz);
priv->datasz = (guint32) sz;
/* check size */
if (sz < sizeof(XbSiloHeader)) {
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"blob too small");
return FALSE;
}
/* check header magic */
hdr = (XbSiloHeader *) priv->data;
if ((flags & XB_SILO_LOAD_FLAG_NO_MAGIC) == 0) {
if (hdr->magic != XB_SILO_MAGIC_BYTES) {
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"magic incorrect");
return FALSE;
}
if (hdr->version != XB_SILO_VERSION) {
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"version incorrect");
return FALSE;
}
}
/* get GUID */
memcpy (&guid_tmp, &hdr->guid, sizeof(guid_tmp));
priv->guid = xb_guid_to_string (&guid_tmp);
/* check strtab */
priv->strtab = hdr->strtab;
if (priv->strtab > priv->datasz) {
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"strtab incorrect");
return FALSE;
}
/* load strtab_tags */
for (guint16 i = 0; i < hdr->strtab_ntags; i++) {
const gchar *tmp = xb_silo_from_strtab (self, off);
if (tmp == NULL) {
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"strtab_ntags incorrect");
return FALSE;
}
g_hash_table_insert (priv->strtab_tags,
(gpointer) tmp,
GUINT_TO_POINTER (off));
off += strlen (tmp) + 1;
}
/* profile */
xb_silo_add_profile (self, timer, "parse blob");
/* success */
priv->valid = TRUE;
return TRUE;
}
/**
* xb_silo_get_profile_string:
* @self: a #XbSilo
*
* Returns the profiling data. This will only return profiling text if
* xb_silo_set_profile_flags() was used with %XB_SILO_PROFILE_FLAG_APPEND.
*
* Returns: text profiling data
*
* Since: 0.1.1
**/
const gchar *
xb_silo_get_profile_string (XbSilo *self)
{
XbSiloPrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (XB_IS_SILO (self), NULL);
return priv->profile_str->str;
}
/**
* xb_silo_set_profile_flags:
* @self: a #XbSilo
* @profile_flags: some #XbSiloProfileFlags, e.g. %XB_SILO_PROFILE_FLAG_DEBUG
*
* Enables or disables the collection of profiling data.
*
* Since: 0.1.1
**/
void
xb_silo_set_profile_flags (XbSilo *self, XbSiloProfileFlags profile_flags)
{
XbSiloPrivate *priv = GET_PRIVATE (self);
g_return_if_fail (XB_IS_SILO (self));
priv->profile_flags = profile_flags;
}
/* private */
XbSiloProfileFlags
xb_silo_get_profile_flags (XbSilo *self)
{
XbSiloPrivate *priv = GET_PRIVATE (self);
return priv->profile_flags;
}
static void
xb_silo_watch_file_cb (GFileMonitor *monitor,
GFile *file,
GFile *other_file,
GFileMonitorEvent event_type,
gpointer user_data)
{
XbSilo *silo = XB_SILO (user_data);
g_autofree gchar *fn = g_file_get_path (file);
g_autofree gchar *basename = g_file_get_basename (file);
if (g_str_has_prefix (basename, ".goutputstream"))
return;
g_debug ("%s changed, invalidating", fn);
xb_silo_invalidate (silo);
}
/**
* xb_silo_watch_file:
* @self: a #XbSilo
* @file: a #GFile
* @cancellable: a #GCancellable, or %NULL
* @error: the #GError, or %NULL
*
* Adds a file monitor to the silo. If the file or directory for @file changes
* then the silo will be invalidated.
*
* Returns: %TRUE for success, otherwise @error is set.
*
* Since: 0.1.0
**/
gboolean
xb_silo_watch_file (XbSilo *self,
GFile *file,
GCancellable *cancellable,
GError **error)
{
XbSiloFileMonitorItem *item;
XbSiloPrivate *priv = GET_PRIVATE (self);
g_autofree gchar *fn = g_file_get_path (file);
g_autoptr(GFileMonitor) file_monitor = NULL;
g_return_val_if_fail (XB_IS_SILO (self), FALSE);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/* already exists */
item = g_hash_table_lookup (priv->file_monitors, fn);
if (item != NULL)
return TRUE;
/* try to create */
file_monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE,
cancellable, error);
if (file_monitor == NULL)
return FALSE;
g_file_monitor_set_rate_limit (file_monitor, 20);
/* add */
item = g_slice_new0 (XbSiloFileMonitorItem);
item->file_monitor = g_object_ref (file_monitor);
item->file_monitor_id = g_signal_connect (file_monitor, "changed",
G_CALLBACK (xb_silo_watch_file_cb), self);
g_hash_table_insert (priv->file_monitors, g_steal_pointer (&fn), item);
return TRUE;
}
/**
* xb_silo_load_from_file:
* @self: a #XbSilo
* @file: a #GFile
* @flags: #XbSiloLoadFlags, e.g. %XB_SILO_LOAD_FLAG_NONE
* @cancellable: a #GCancellable, or %NULL
* @error: the #GError, or %NULL
*
* Loads a silo from file.
*
* Returns: %TRUE for success, otherwise @error is set.
*
* Since: 0.1.0
**/
gboolean
xb_silo_load_from_file (XbSilo *self,
GFile *file,
XbSiloLoadFlags flags,
GCancellable *cancellable,
GError **error)
{
XbSiloPrivate *priv = GET_PRIVATE (self);
g_autofree gchar *fn = NULL;
g_autoptr(GBytes) blob = NULL;
g_autoptr(GTimer) timer = g_timer_new ();
g_return_val_if_fail (XB_IS_SILO (self), FALSE);
g_return_val_if_fail (G_IS_FILE (file), FALSE);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/* no longer valid */
g_hash_table_remove_all (priv->file_monitors);
g_hash_table_remove_all (priv->nodes);
g_hash_table_remove_all (priv->strtab_tags);
g_clear_pointer (&priv->guid, g_free);
if (priv->mmap != NULL)
g_mapped_file_unref (priv->mmap);
fn = g_file_get_path (file);
priv->mmap = g_mapped_file_new (fn, FALSE, error);
if (priv->mmap == NULL)
return FALSE;
blob = g_mapped_file_get_bytes (priv->mmap);
if (!xb_silo_load_from_bytes (self, blob, flags, error))
return FALSE;
/* watch file for changes */
if (flags & XB_SILO_LOAD_FLAG_WATCH_BLOB) {
if (!xb_silo_watch_file (self, file, cancellable, error))
return FALSE;
}
/* success */
xb_silo_add_profile (self, timer, "loaded file");
return TRUE;
}
/**
* xb_silo_save_to_file:
* @self: a #XbSilo
* @file: a #GFile
* @cancellable: a #GCancellable, or %NULL
* @error: the #GError, or %NULL
*
* Saves a silo to a file.
*
* Returns: %TRUE for success, otherwise @error is set.
*
* Since: 0.1.0
**/
gboolean
xb_silo_save_to_file (XbSilo *self,
GFile *file,
GCancellable *cancellable,
GError **error)
{
XbSiloPrivate *priv = GET_PRIVATE (self);
g_autoptr(GFile) file_parent = NULL;
g_autoptr(GTimer) timer = g_timer_new ();
g_return_val_if_fail (XB_IS_SILO (self), FALSE);
g_return_val_if_fail (G_IS_FILE (file), FALSE);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/* invalid */
if (priv->data == NULL) {
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_NOT_INITIALIZED,
"no data to save");
return FALSE;
}
/* ensure parent directories exist */
file_parent = g_file_get_parent (file);
if (file_parent != NULL &&
!g_file_query_exists (file_parent, cancellable)) {
if (!g_file_make_directory_with_parents (file_parent,
cancellable,
error))
return FALSE;
}
/* save and then rename */
if (!g_file_replace_contents (file,
(const gchar *) priv->data,
(gsize) priv->datasz, NULL, FALSE,
G_FILE_CREATE_NONE, NULL,
cancellable, error)) {
return FALSE;
}
xb_silo_add_profile (self, timer, "save file");
return TRUE;
}
/**
* xb_silo_new_from_xml:
* @xml: XML string
* @error: the #GError, or %NULL
*
* Creates a new silo from an XML string.
*
* Returns: a new #XbSilo, or %NULL
*
* Since: 0.1.0
**/
XbSilo *
xb_silo_new_from_xml (const gchar *xml, GError **error)
{
g_autoptr(XbBuilder) builder = xb_builder_new ();
g_autoptr(XbBuilderSource) source = xb_builder_source_new ();
g_return_val_if_fail (xml != NULL, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
if (!xb_builder_source_load_xml (source, xml, XB_BUILDER_SOURCE_FLAG_NONE, error))
return NULL;
xb_builder_import_source (builder, source);
return xb_builder_compile (builder, XB_BUILDER_COMPILE_FLAG_NONE, NULL, error);
}
/* private */
XbNode *
xb_silo_node_create (XbSilo *self, XbSiloNode *sn)
{
XbNode *n;
XbSiloPrivate *priv = GET_PRIVATE (self);
g_autoptr(GMutexLocker) locker = g_mutex_locker_new (&priv->nodes_mutex);
g_return_val_if_fail (locker != NULL, NULL);
/* does already exist */
n = g_hash_table_lookup (priv->nodes, sn);
if (n != NULL)
return g_object_ref (n);
/* create and add */
n = xb_node_new (self, sn);
g_hash_table_insert (priv->nodes, sn, g_object_ref (n));
return n;
}
/* convert [2] to position()=2 */
static gboolean
xb_silo_machine_fixup_position_cb (XbMachine *self,
XbStack *opcodes,
gpointer user_data,
GError **error)
{
xb_stack_push_steal (opcodes, xb_machine_opcode_func_new (self, "position"));
xb_stack_push_steal (opcodes, xb_machine_opcode_func_new (self, "eq"));
return TRUE;
}
/* convert "'type' attr()" -> "'type' attr() '(null)' ne()" */
static gboolean
xb_silo_machine_fixup_attr_exists_cb (XbMachine *self,
XbStack *opcodes,
gpointer user_data,
GError **error)
{
xb_stack_push_steal (opcodes, xb_opcode_text_new_static (NULL));
xb_stack_push_steal (opcodes, xb_machine_opcode_func_new (self, "ne"));
return TRUE;
}
static gboolean
xb_silo_machine_func_attr_cb (XbMachine *self,
XbStack *stack,
gboolean *result,
gpointer user_data,
gpointer exec_data,
GError **error)
{
XbOpcode *op2;
XbSiloAttr *a;
XbSilo *silo = XB_SILO (user_data);
XbSiloQueryData *query_data = (XbSiloQueryData *) exec_data;
g_autoptr(XbOpcode) op = xb_machine_stack_pop (self, stack);
/* optimize pass */
if (query_data == NULL) {
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED,
"cannot optimize: no silo to query");
return FALSE;
}
/* indexed string */
if (xb_opcode_get_kind (op) == XB_OPCODE_KIND_INDEXED_TEXT) {
guint32 val = xb_opcode_get_val (op);
a = xb_silo_node_get_attr_by_val (silo, query_data->sn, val);
} else {
const gchar *str = xb_opcode_get_str (op);
a = xb_silo_node_get_attr_by_str (silo, query_data->sn, str);
}
if (a == NULL) {
xb_machine_stack_push_text_static (self, stack, NULL);
return TRUE;
}
op2 = xb_opcode_new (XB_OPCODE_KIND_INDEXED_TEXT,
xb_silo_from_strtab (silo, a->attr_value),
a->attr_value,
NULL);
xb_machine_stack_push_steal (self, stack, op2);
return TRUE;
}
static gboolean
xb_silo_machine_func_stem_cb (XbMachine *self,
XbStack *stack,
gboolean *result,
gpointer user_data,
gpointer exec_data,
GError **error)
{
XbSilo *silo = XB_SILO (user_data);
g_autoptr(XbOpcode) op = xb_machine_stack_pop (self, stack);
/* TEXT */
if (xb_opcode_cmp_str (op)) {
const gchar *str = xb_opcode_get_str (op);
xb_machine_stack_push_text_steal (self, stack, xb_silo_stem (silo, str));
return TRUE;
}
/* fail */
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"%s type not supported",
xb_opcode_kind_to_string (xb_opcode_get_kind (op)));
return FALSE;
}
static gboolean
xb_silo_machine_func_text_cb (XbMachine *self,
XbStack *stack,
gboolean *result,
gpointer user_data,
gpointer exec_data,
GError **error)
{
XbSilo *silo = XB_SILO (user_data);
XbSiloQueryData *query_data = (XbSiloQueryData *) exec_data;
/* optimize pass */
if (query_data == NULL) {
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED,
"cannot optimize: no silo to query");
return FALSE;
}
xb_machine_stack_push_steal (self, stack,
xb_opcode_new (XB_OPCODE_KIND_INDEXED_TEXT,
xb_silo_node_get_text (silo, query_data->sn),
query_data->sn->text,
NULL));
return TRUE;
}
static gboolean
xb_silo_machine_func_tail_cb (XbMachine *self,
XbStack *stack,
gboolean *result,
gpointer user_data,
gpointer exec_data,
GError **error)
{
XbSilo *silo = XB_SILO (user_data);
XbSiloQueryData *query_data = (XbSiloQueryData *) exec_data;
/* optimize pass */
if (query_data == NULL) {
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED,
"cannot optimize: no silo to query");
return FALSE;
}
xb_machine_stack_push_steal (self, stack,
xb_opcode_new (XB_OPCODE_KIND_INDEXED_TEXT,
xb_silo_node_get_tail (silo, query_data->sn),
query_data->sn->tail,
NULL));
return TRUE;
}
static gboolean
xb_silo_machine_func_first_cb (XbMachine *self,
XbStack *stack,
gboolean *result,
gpointer user_data,
gpointer exec_data,
GError **error)
{
XbSiloQueryData *query_data = (XbSiloQueryData *) exec_data;
/* optimize pass */
if (query_data == NULL) {
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED,
"cannot optimize: no silo to query");
return FALSE;
}
xb_stack_push_bool (stack, query_data->position == 1);
return TRUE;
}
static gboolean
xb_silo_machine_func_last_cb (XbMachine *self,
XbStack *stack,
gboolean *result,
gpointer user_data,
gpointer exec_data,
GError **error)
{
XbSiloQueryData *query_data = (XbSiloQueryData *) exec_data;
/* optimize pass */
if (query_data == NULL) {
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED,
"cannot optimize: no silo to query");
return FALSE;
}
xb_stack_push_bool (stack, query_data->sn->next == 0);
return TRUE;
}
static gboolean
xb_silo_machine_func_position_cb (XbMachine *self,
XbStack *stack,
gboolean *result,
gpointer user_data,
gpointer exec_data,
GError **error)
{
XbSiloQueryData *query_data = (XbSiloQueryData *) exec_data;
/* optimize pass */
if (query_data == NULL) {
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED,
"cannot optimize: no silo to query");
return FALSE;
}
xb_machine_stack_push_integer (self, stack, query_data->position);
return TRUE;
}
static gboolean
xb_silo_machine_func_search_cb (XbMachine *self,
XbStack *stack,
gboolean *result,
gpointer user_data,
gpointer exec_data,
GError **error)
{
g_autoptr(XbOpcode) op1 = xb_machine_stack_pop (self, stack);
g_autoptr(XbOpcode) op2 = xb_machine_stack_pop (self, stack);
/* TEXT:TEXT */
if (xb_opcode_cmp_str (op1) && xb_opcode_cmp_str (op2)) {
xb_stack_push_bool (stack, xb_string_search (xb_opcode_get_str (op2),
xb_opcode_get_str (op1)));
return TRUE;
}
/* fail */
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"%s:%s types not supported",
xb_opcode_kind_to_string (xb_opcode_get_kind (op1)),
xb_opcode_kind_to_string (xb_opcode_get_kind (op2)));
return FALSE;
}
static gboolean
xb_silo_machine_fixup_attr_text_cb (XbMachine *self,
XbStack *opcodes,
const gchar *text,
gboolean *handled,
gpointer user_data,
GError **error)
{
/* @foo -> attr(foo) */
if (g_str_has_prefix (text, "@")) {
XbOpcode *opcode;
opcode = xb_machine_opcode_func_new (self, "attr");
if (opcode == NULL) {
g_set_error_literal (error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"no attr opcode");
return FALSE;
}
xb_stack_push_steal (opcodes, xb_opcode_text_new (text + 1));
xb_stack_push_steal (opcodes, opcode);
*handled = TRUE;
return TRUE;
}
/* not us */
return TRUE;
}
static void
xb_silo_file_monitor_item_free (XbSiloFileMonitorItem *item)
{
g_signal_handler_disconnect (item->file_monitor, item->file_monitor_id);
g_object_unref (item->file_monitor);
g_slice_free (XbSiloFileMonitorItem, item);
}
static void
xb_silo_get_property (GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec)
{
XbSilo *self = XB_SILO (obj);
XbSiloPrivate *priv = GET_PRIVATE (self);
switch (prop_id) {
case PROP_GUID:
g_value_set_string (value, priv->guid);
break;
case PROP_VALID:
g_value_set_boolean (value, priv->valid);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
break;
}
}
static void
xb_silo_set_property (GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec)
{
XbSilo *self = XB_SILO (obj);
XbSiloPrivate *priv = GET_PRIVATE (self);
switch (prop_id) {
case PROP_GUID:
g_free (priv->guid);
priv->guid = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec);
break;
}
}
static void
xb_silo_init (XbSilo *self)
{
XbSiloPrivate *priv = GET_PRIVATE (self);
priv->file_monitors = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, (GDestroyNotify) xb_silo_file_monitor_item_free);
priv->nodes = g_hash_table_new_full (g_direct_hash, g_direct_equal,
NULL, (GDestroyNotify) g_object_unref);
priv->strtab_tags = g_hash_table_new (g_str_hash, g_str_equal);
priv->strindex = g_hash_table_new (g_str_hash, g_str_equal);
priv->profile_str = g_string_new (NULL);
g_mutex_init (&priv->nodes_mutex);
#ifdef HAVE_LIBSTEMMER
g_mutex_init (&priv->stemmer_mutex);
#endif
priv->machine = xb_machine_new ();
xb_machine_add_method (priv->machine, "attr", 1,
xb_silo_machine_func_attr_cb, self, NULL);
xb_machine_add_method (priv->machine, "stem", 1,
xb_silo_machine_func_stem_cb, self, NULL);
xb_machine_add_method (priv->machine, "text", 0,
xb_silo_machine_func_text_cb, self, NULL);
xb_machine_add_method (priv->machine, "tail", 0,
xb_silo_machine_func_tail_cb, self, NULL);
xb_machine_add_method (priv->machine, "first", 0,
xb_silo_machine_func_first_cb, self, NULL);
xb_machine_add_method (priv->machine, "last", 0,
xb_silo_machine_func_last_cb, self, NULL);
xb_machine_add_method (priv->machine, "position", 0,
xb_silo_machine_func_position_cb, self, NULL);
xb_machine_add_method (priv->machine, "search", 2,
xb_silo_machine_func_search_cb, self, NULL);
xb_machine_add_operator (priv->machine, "~=", "search");
xb_machine_add_opcode_fixup (priv->machine, "INTE",
xb_silo_machine_fixup_position_cb, self, NULL);
xb_machine_add_opcode_fixup (priv->machine, "TEXT,FUNC:attr",
xb_silo_machine_fixup_attr_exists_cb, self, NULL);
xb_machine_add_text_handler (priv->machine,
xb_silo_machine_fixup_attr_text_cb, self, NULL);
}
static void
xb_silo_finalize (GObject *obj)
{
XbSilo *self = XB_SILO (obj);
XbSiloPrivate *priv = GET_PRIVATE (self);
g_mutex_clear (&priv->nodes_mutex);
#ifdef HAVE_LIBSTEMMER
if (priv->stemmer_ctx != NULL)
sb_stemmer_delete (priv->stemmer_ctx);
g_mutex_clear (&priv->stemmer_mutex);
#endif
g_free (priv->guid);
g_string_free (priv->profile_str, TRUE);
g_object_unref (priv->machine);
g_hash_table_unref (priv->strindex);
g_hash_table_unref (priv->file_monitors);
g_hash_table_unref (priv->nodes);
g_hash_table_unref (priv->strtab_tags);
if (priv->mmap != NULL)
g_mapped_file_unref (priv->mmap);
if (priv->blob != NULL)
g_bytes_unref (priv->blob);
G_OBJECT_CLASS (xb_silo_parent_class)->finalize (obj);
}
static void
xb_silo_class_init (XbSiloClass *klass)
{
GParamSpec *pspec;
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = xb_silo_finalize;
object_class->get_property = xb_silo_get_property;
object_class->set_property = xb_silo_set_property;
/**
* XbSilo:guid:
*/
pspec = g_param_spec_string ("guid", NULL, NULL, NULL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_NAME);
g_object_class_install_property (object_class, PROP_GUID, pspec);
/**
* XbSilo:allow-cancel:
*/
pspec = g_param_spec_boolean ("valid", NULL, NULL, TRUE,
G_PARAM_READABLE |
G_PARAM_STATIC_NAME);
g_object_class_install_property (object_class, PROP_VALID, pspec);
}
/**
* xb_silo_new:
*
* Creates a new silo.
*
* Returns: a new #XbSilo
*
* Since: 0.1.0
**/
XbSilo *
xb_silo_new (void)
{
return g_object_new (XB_TYPE_SILO, NULL);
}