/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "XbSilo" #include "config.h" #include #include #include #ifdef HAVE_LIBSTEMMER #include #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); }