1 /* dzl-fuzzy-index-builder.c
2  *
3  * Copyright (C) 2016 Christian Hergert <christian@hergert.me>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #define G_LOG_DOMAIN    "dzl-fuzzy-index-builder"
20 #define MAX_KEY_ENTRIES (0x00FFFFFF)
21 
22 #include "config.h"
23 
24 #include <stdlib.h>
25 #include <string.h>
26 
27 #include "search/dzl-fuzzy-index-builder.h"
28 #include "util/dzl-macros.h"
29 #include "util/dzl-variant.h"
30 
31 struct _DzlFuzzyIndexBuilder
32 {
33   GObject       object;
34 
35   guint         case_sensitive : 1;
36 
37   /*
38    * This hash table contains a mapping of GVariants so that we
39    * deduplicate insertions of the same document. This helps when
40    * we have indexes that contain multiple strings to the same
41    * piece of data.
42    */
43   GHashTable *documents_hash;
44 
45   /*
46    * This array contains a pointer to the individual GVariants
47    * while building the index. When writing the index to disk,
48    * we create a fixed array from this array of varians.
49    */
50   GPtrArray *documents;
51 
52   /*
53    * Since we will need to keep a copy of a lot of strings, we
54    * use a GString chunk to reduce the presure on the allocator.
55    * It can certainly leave some gaps that are unused in the
56    * sequence of pages, but it is generally better than using
57    * a GByteArray or some other pow^2 growing array.
58    */
59   GStringChunk *strings;
60 
61   /*
62    * This maps a pointer to a string that is found in the strings
63    * string chunk to a key id (stored as a pointer). The input
64    * string must exist within strings as we use a direct hash from
65    * the input pointer to map to the string to save on the cost
66    * of key equality checks.
67    */
68   GHashTable *key_ids;
69 
70   /*
71    * An array of keys where the index of the key is the "key_id" used
72    * in other structures. The pointer points to a key within the
73    * strings GStringChunk.
74    */
75   GPtrArray *keys;
76 
77   /*
78    * This array maps our document id to a key id. When building the
79    * search index we use this to disambiguate between multiple
80    * documents pointing to the same document.
81    */
82   GArray *kv_pairs;
83 
84   /*
85    * Metadata for the search index, which is stored as the "metadata"
86    * key in the final search index. You can use fuzzy_index_get_metadata()
87    * to retrieve values stored here.
88    *
89    * This might be useful to store things like the mtime of the data
90    * you are indexes so that you know if you need to reindex. You might
91    * also store the version of your indexer here so that when you update
92    * your indexer code, you can force a rebuild of the index.
93    */
94   GHashTable *metadata;
95 };
96 
97 typedef struct
98 {
99   /* The position within the keys array of the key. */
100   guint key_id;
101 
102   /* The position within the documents array of the document */
103   guint document_id;
104 } KVPair;
105 
106 typedef struct
107 {
108   /*
109    * The character position within the string in terms of unicode
110    * characters, not byte-position.
111    */
112   guint position;
113 
114   /* The index into the kvpairs */
115   guint lookaside_id;
116 } IndexItem;
117 
118 G_DEFINE_TYPE (DzlFuzzyIndexBuilder, dzl_fuzzy_index_builder, G_TYPE_OBJECT)
119 
120 enum {
121   PROP_0,
122   PROP_CASE_SENSITIVE,
123   N_PROPS
124 };
125 
126 static GParamSpec *properties [N_PROPS];
127 
128 static guint
mask_priority(guint key_id)129 mask_priority (guint key_id)
130 {
131   return key_id & 0x00FFFFFF;
132 }
133 
134 static void
dzl_fuzzy_index_builder_finalize(GObject * object)135 dzl_fuzzy_index_builder_finalize (GObject *object)
136 {
137   DzlFuzzyIndexBuilder *self = (DzlFuzzyIndexBuilder *)object;
138 
139   g_clear_pointer (&self->documents_hash, g_hash_table_unref);
140   g_clear_pointer (&self->documents, g_ptr_array_unref);
141   g_clear_pointer (&self->strings, g_string_chunk_free);
142   g_clear_pointer (&self->kv_pairs, g_array_unref);
143   g_clear_pointer (&self->metadata, g_hash_table_unref);
144   g_clear_pointer (&self->key_ids, g_hash_table_unref);
145   g_clear_pointer (&self->keys, g_ptr_array_unref);
146 
147   G_OBJECT_CLASS (dzl_fuzzy_index_builder_parent_class)->finalize (object);
148 }
149 
150 static void
dzl_fuzzy_index_builder_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)151 dzl_fuzzy_index_builder_get_property (GObject    *object,
152                                       guint       prop_id,
153                                       GValue     *value,
154                                       GParamSpec *pspec)
155 {
156   DzlFuzzyIndexBuilder *self = DZL_FUZZY_INDEX_BUILDER (object);
157 
158   switch (prop_id)
159     {
160     case PROP_CASE_SENSITIVE:
161       g_value_set_boolean (value, dzl_fuzzy_index_builder_get_case_sensitive (self));
162       break;
163 
164     default:
165       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
166     }
167 }
168 
169 static void
dzl_fuzzy_index_builder_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)170 dzl_fuzzy_index_builder_set_property (GObject      *object,
171                                   guint         prop_id,
172                                   const GValue *value,
173                                   GParamSpec   *pspec)
174 {
175   DzlFuzzyIndexBuilder *self = DZL_FUZZY_INDEX_BUILDER (object);
176 
177   switch (prop_id)
178     {
179     case PROP_CASE_SENSITIVE:
180       dzl_fuzzy_index_builder_set_case_sensitive (self, g_value_get_boolean (value));
181       break;
182 
183     default:
184       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
185     }
186 }
187 
188 static void
dzl_fuzzy_index_builder_class_init(DzlFuzzyIndexBuilderClass * klass)189 dzl_fuzzy_index_builder_class_init (DzlFuzzyIndexBuilderClass *klass)
190 {
191   GObjectClass *object_class = G_OBJECT_CLASS (klass);
192 
193   object_class->finalize = dzl_fuzzy_index_builder_finalize;
194   object_class->get_property = dzl_fuzzy_index_builder_get_property;
195   object_class->set_property = dzl_fuzzy_index_builder_set_property;
196 
197   properties [PROP_CASE_SENSITIVE] =
198     g_param_spec_boolean ("case-sensitive",
199                           "Case Sensitive",
200                           "Case Sensitive",
201                           FALSE,
202                           (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
203 
204   g_object_class_install_properties (object_class, N_PROPS, properties);
205 }
206 
207 static void
dzl_fuzzy_index_builder_init(DzlFuzzyIndexBuilder * self)208 dzl_fuzzy_index_builder_init (DzlFuzzyIndexBuilder *self)
209 {
210   self->documents = g_ptr_array_new_with_free_func ((GDestroyNotify)g_variant_unref);
211   self->documents_hash = g_hash_table_new (dzl_g_variant_hash, g_variant_equal);
212   self->kv_pairs = g_array_new (FALSE, FALSE, sizeof (KVPair));
213   self->strings = g_string_chunk_new (4096);
214   self->key_ids = g_hash_table_new (NULL, NULL);
215   self->keys = g_ptr_array_new ();
216 }
217 
218 DzlFuzzyIndexBuilder *
dzl_fuzzy_index_builder_new(void)219 dzl_fuzzy_index_builder_new (void)
220 {
221   return g_object_new (DZL_TYPE_FUZZY_INDEX_BUILDER, NULL);
222 }
223 
224 /**
225  * dzl_fuzzy_index_builder_insert:
226  * @self: A #DzlFuzzyIndexBuilder
227  * @key: The UTF-8 encoded key for the document
228  * @document: The document to store
229  * @priority: An optional priority for the keyword.
230  *
231  * Inserts @document into the index using @key as the lookup key.
232  *
233  * If a matching document (checked by hashing @document) has already
234  * been inserted, only a single instance of the document will be stored.
235  *
236  * If @document is floating, it will be consumed.
237  *
238  * @priority may be used to group results by priority. Priority must be
239  * less than 256.
240  *
241  * Returns: The document id registered for @document.
242  */
243 guint64
dzl_fuzzy_index_builder_insert(DzlFuzzyIndexBuilder * self,const gchar * key,GVariant * document,guint priority)244 dzl_fuzzy_index_builder_insert (DzlFuzzyIndexBuilder *self,
245                                 const gchar          *key,
246                                 GVariant             *document,
247                                 guint                 priority)
248 {
249   g_autoptr(GVariant) sunk_variant = NULL;
250   GVariant *real_document = NULL;
251   gpointer document_id = NULL;
252   gpointer key_id = NULL;
253   KVPair pair;
254 
255   g_return_val_if_fail (DZL_IS_FUZZY_INDEX_BUILDER (self), 0L);
256   g_return_val_if_fail (key != NULL, 0L);
257   g_return_val_if_fail (document != NULL, 0L);
258   g_return_val_if_fail (priority <= 0xFF, 0L);
259 
260   if (g_variant_is_floating (document))
261     sunk_variant = g_variant_ref_sink (document);
262 
263   /* move the priority bits into the proper area */
264   priority = (priority & 0xFF) << 24;
265 
266   if (self->keys->len > MAX_KEY_ENTRIES)
267     {
268       g_warning ("Index is full, cannot add more entries");
269       return 0L;
270     }
271 
272   key = g_string_chunk_insert_const (self->strings, key);
273 
274   /*
275    * We try to deduplicate document entries here by hashing the document and
276    * looking for another matching it. This way our generated index can stay
277    * relatively small when it comes to documents.
278    */
279   if (!g_hash_table_lookup_extended (self->documents_hash,
280                                      document,
281                                      (gpointer *)&real_document,
282                                      &document_id))
283     {
284       document_id = GUINT_TO_POINTER (self->documents->len);
285       real_document = g_variant_ref (document);
286       g_ptr_array_add (self->documents, real_document);
287       g_hash_table_insert (self->documents_hash, real_document, document_id);
288     }
289 
290   /*
291    * If we already have the key then reuse its key index. If not, then add it.
292    */
293   if (!g_hash_table_lookup_extended (self->key_ids, key, NULL, &key_id))
294     {
295       key_id = GUINT_TO_POINTER (self->keys->len);
296       g_ptr_array_add (self->keys, (gchar *)key);
297       g_hash_table_insert (self->key_ids, (gpointer)key, key_id);
298     }
299 
300   /*
301    * A bit of slight-of-hand here. We share keys between all key<->document
302    * pairs, but steal the high bits for the key priority in the kvpair entry.
303    * This allows for both deduplication and different priorities based on
304    * certain document pairs.
305    */
306   pair.key_id = GPOINTER_TO_UINT (key_id) | priority;
307   pair.document_id = GPOINTER_TO_UINT (document_id);
308 
309   g_array_append_val (self->kv_pairs, pair);
310 
311   return pair.document_id;
312 }
313 
314 static gint
pos_doc_pair_compare(gconstpointer a,gconstpointer b)315 pos_doc_pair_compare (gconstpointer a,
316                       gconstpointer b)
317 {
318   const IndexItem *paira = a;
319   const IndexItem *pairb = b;
320   gint ret;
321 
322   ret = paira->lookaside_id - pairb->lookaside_id;
323 
324   if (ret == 0)
325     ret = paira->position - pairb->position;
326 
327   return ret;
328 }
329 
330 static GVariant *
dzl_fuzzy_index_builder_build_keys(DzlFuzzyIndexBuilder * self)331 dzl_fuzzy_index_builder_build_keys (DzlFuzzyIndexBuilder *self)
332 {
333   g_assert (DZL_IS_FUZZY_INDEX_BUILDER (self));
334 
335   return g_variant_new_strv ((const gchar * const *)self->keys->pdata,
336                              self->keys->len);
337 }
338 
339 static GVariant *
dzl_fuzzy_index_builder_build_lookaside(DzlFuzzyIndexBuilder * self)340 dzl_fuzzy_index_builder_build_lookaside (DzlFuzzyIndexBuilder *self)
341 {
342   g_assert (DZL_IS_FUZZY_INDEX_BUILDER (self));
343 
344   return g_variant_new_fixed_array ((const GVariantType *)"(uu)",
345                                     self->kv_pairs->data,
346                                     self->kv_pairs->len,
347                                     sizeof (KVPair));
348 }
349 
350 static GVariant *
dzl_fuzzy_index_builder_build_index(DzlFuzzyIndexBuilder * self)351 dzl_fuzzy_index_builder_build_index (DzlFuzzyIndexBuilder *self)
352 {
353   g_autoptr(GHashTable) rows = NULL;
354   GVariantDict dict;
355   GHashTableIter iter;
356   gpointer keyptr;
357   GArray *row;
358   guint i;
359 
360   g_assert (DZL_IS_FUZZY_INDEX_BUILDER (self));
361 
362   rows = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)g_array_unref);
363 
364   for (i = 0; i < self->kv_pairs->len; i++)
365     {
366       g_autofree gchar *lower = NULL;
367       const gchar *key;
368       const gchar *tmp;
369       KVPair *kvpair;
370       IndexItem item;
371       guint position = 0;
372 
373       kvpair = &g_array_index (self->kv_pairs, KVPair, i);
374       key = g_ptr_array_index (self->keys, mask_priority (kvpair->key_id));
375 
376       /* the priority for the key is stashed in the high 8 bits of
377        * the kvpair.key_id. So we need to propagate that to the
378        * entry in the index for resolution later.
379        */
380       item.lookaside_id = i | (kvpair->key_id & 0xFF000000);
381 
382       if (!self->case_sensitive)
383         key = lower = g_utf8_casefold (key, -1);
384 
385       for (tmp = key; *tmp; tmp = g_utf8_next_char (tmp))
386         {
387           gunichar ch = g_utf8_get_char (tmp);
388 
389           row = g_hash_table_lookup (rows, GUINT_TO_POINTER (ch));
390 
391           if G_UNLIKELY (row == NULL)
392             {
393               row = g_array_new (FALSE, FALSE, sizeof (IndexItem));
394               g_hash_table_insert (rows, GUINT_TO_POINTER (ch), row);
395             }
396 
397           item.position = position++;
398           g_array_append_val (row, item);
399         }
400     }
401 
402   g_variant_dict_init (&dict, NULL);
403 
404   g_hash_table_iter_init (&iter, rows);
405 
406   while (g_hash_table_iter_next (&iter, &keyptr, (gpointer *)&row))
407     {
408       gchar key[12];
409       GVariant *variant;
410       gunichar ch = GPOINTER_TO_UINT (keyptr);
411 
412       key [g_unichar_to_utf8 (ch, key)] = 0;
413 
414       g_array_sort (row, pos_doc_pair_compare);
415 
416       variant = g_variant_new_fixed_array ((const GVariantType *)"(uu)",
417                                            row->data,
418                                            row->len,
419                                            sizeof (IndexItem));
420       g_variant_dict_insert_value (&dict, key, variant);
421     }
422 
423   return g_variant_dict_end (&dict);
424 }
425 
426 static GVariant *
dzl_fuzzy_index_builder_build_metadata(DzlFuzzyIndexBuilder * self)427 dzl_fuzzy_index_builder_build_metadata (DzlFuzzyIndexBuilder *self)
428 {
429   GVariantDict dict;
430   GHashTableIter iter;
431 
432   g_assert (DZL_IS_FUZZY_INDEX_BUILDER (self));
433 
434   g_variant_dict_init (&dict, NULL);
435 
436   if (self->metadata != NULL)
437     {
438       const gchar *key;
439       GVariant *value;
440 
441       g_hash_table_iter_init (&iter, self->metadata);
442       while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&value))
443         g_variant_dict_insert_value (&dict, key, value);
444     }
445 
446   g_variant_dict_insert (&dict, "case-sensitive", "b", self->case_sensitive);
447 
448   return g_variant_dict_end (&dict);
449 }
450 
451 static void
dzl_fuzzy_index_builder_write_worker(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)452 dzl_fuzzy_index_builder_write_worker (GTask        *task,
453                                       gpointer      source_object,
454                                       gpointer      task_data,
455                                       GCancellable *cancellable)
456 {
457   DzlFuzzyIndexBuilder *self = source_object;
458   g_autoptr(GVariant) variant = NULL;
459   g_autoptr(GVariant) documents = NULL;
460   g_autoptr(GError) error = NULL;
461   GVariantDict dict;
462   GFile *file = task_data;
463 
464   g_assert (G_IS_TASK (task));
465   g_assert (DZL_IS_FUZZY_INDEX_BUILDER (self));
466   g_assert (G_IS_FILE (file));
467   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
468 
469   g_variant_dict_init (&dict, NULL);
470 
471   /* Set our version number for the document */
472   g_variant_dict_insert (&dict, "version", "i", 1);
473 
474   /* Build our dicitionary of metadata */
475   g_variant_dict_insert_value (&dict,
476                                "metadata",
477                                dzl_fuzzy_index_builder_build_metadata (self));
478 
479   /* Keys is an array of string keys where the index is the "key_id" */
480   g_variant_dict_insert_value (&dict,
481                                "keys",
482                                dzl_fuzzy_index_builder_build_keys (self));
483 
484   /* The lookaside is a mapping of kvpair to the repsective keys and
485    * documents. This allows the tables to use the kvpair id as the value
486    * in the index so we can have both document deduplication as well as
487    * the ability to disambiguate the keys which point to the same
488    * document. The contents are "a{uu}".
489    */
490   g_variant_dict_insert_value (&dict,
491                                "lookaside",
492                                dzl_fuzzy_index_builder_build_lookaside (self));
493 
494   /* Build our dicitionary of character → [(pos,lookaside_id),..] tuples.
495    * The position is the utf8 character position within the string.
496    * The lookaside_id is the index within the lookaside buffer to locate
497    * the document_id or key_id.
498    */
499   g_variant_dict_insert_value (&dict,
500                                "tables",
501                                dzl_fuzzy_index_builder_build_index (self));
502 
503   /*
504    * The documents are stored as an array where the document identifier is
505    * their index position. We then use a lookaside buffer to map the insertion
506    * id to the document id. Otherwise, we can't disambiguate between two
507    * keys that insert the same document (as we deduplicate documents inserted
508    * into the index).
509    */
510   documents = g_variant_new_array (NULL,
511                                    (GVariant * const *)self->documents->pdata,
512                                    self->documents->len);
513   g_variant_dict_insert_value (&dict, "documents", g_variant_ref_sink (documents));
514 
515   /* Now write the variant to disk */
516   variant = g_variant_ref_sink (g_variant_dict_end (&dict));
517   if (!g_file_replace_contents (file,
518                                 g_variant_get_data (variant),
519                                 g_variant_get_size (variant),
520                                 NULL,
521                                 FALSE,
522                                 G_FILE_CREATE_NONE,
523                                 NULL,
524                                 cancellable,
525                                 &error))
526     g_task_return_error (task, g_steal_pointer (&error));
527   else
528     g_task_return_boolean (task, TRUE);
529 }
530 
531 /**
532  * dzl_fuzzy_index_builder_write_async:
533  * @self: A #DzlFuzzyIndexBuilder
534  * @file: A #GFile to write the index to
535  * @io_priority: The priority for IO operations
536  * @cancellable: (nullable): An optional #GCancellable or %NULL
537  * @callback: A callback for completion or %NULL
538  * @user_data: User data for @callback
539  *
540  * Builds and writes the index to @file. The file format is a
541  * GVariant on disk and can be loaded and searched using
542  * #FuzzyIndex.
543  */
544 void
dzl_fuzzy_index_builder_write_async(DzlFuzzyIndexBuilder * self,GFile * file,gint io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)545 dzl_fuzzy_index_builder_write_async (DzlFuzzyIndexBuilder *self,
546                                      GFile                *file,
547                                      gint                  io_priority,
548                                      GCancellable         *cancellable,
549                                      GAsyncReadyCallback   callback,
550                                      gpointer              user_data)
551 {
552   g_autoptr(GTask) task = NULL;
553 
554   g_return_if_fail (DZL_IS_FUZZY_INDEX_BUILDER (self));
555   g_return_if_fail (G_IS_FILE (file));
556   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
557 
558   task = g_task_new (self, cancellable, callback, user_data);
559   g_task_set_source_tag (task, dzl_fuzzy_index_builder_write_async);
560   g_task_set_priority (task, io_priority);
561   g_task_set_task_data (task, g_object_ref (file), g_object_unref);
562   g_task_run_in_thread (task, dzl_fuzzy_index_builder_write_worker);
563 }
564 
565 gboolean
dzl_fuzzy_index_builder_write_finish(DzlFuzzyIndexBuilder * self,GAsyncResult * result,GError ** error)566 dzl_fuzzy_index_builder_write_finish (DzlFuzzyIndexBuilder  *self,
567                                       GAsyncResult          *result,
568                                       GError               **error)
569 {
570   g_return_val_if_fail (DZL_IS_FUZZY_INDEX_BUILDER (self), FALSE);
571   g_return_val_if_fail (G_IS_TASK (result), FALSE);
572 
573   return g_task_propagate_boolean (G_TASK (result), error);
574 }
575 
576 gboolean
dzl_fuzzy_index_builder_write(DzlFuzzyIndexBuilder * self,GFile * file,gint io_priority,GCancellable * cancellable,GError ** error)577 dzl_fuzzy_index_builder_write (DzlFuzzyIndexBuilder  *self,
578                                GFile                 *file,
579                                gint                   io_priority,
580                                GCancellable          *cancellable,
581                                GError               **error)
582 {
583   g_autoptr(GTask) task = NULL;
584 
585   g_return_val_if_fail (DZL_IS_FUZZY_INDEX_BUILDER (self), FALSE);
586   g_return_val_if_fail (G_IS_FILE (file), FALSE);
587   g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
588 
589   task = g_task_new (self, cancellable, NULL, NULL);
590   g_task_set_source_tag (task, dzl_fuzzy_index_builder_write);
591   g_task_set_priority (task, io_priority);
592   g_task_set_task_data (task, g_object_ref (file), g_object_unref);
593 
594   dzl_fuzzy_index_builder_write_worker (task, self, file, cancellable);
595 
596   return g_task_propagate_boolean (task, error);
597 }
598 
599 /**
600  * dzl_fuzzy_index_builder_get_document:
601  *
602  * Returns the document that was inserted in a previous call to
603  * dzl_fuzzy_index_builder_insert().
604  *
605  * Returns: (transfer none): A #GVariant
606  */
607 const GVariant *
dzl_fuzzy_index_builder_get_document(DzlFuzzyIndexBuilder * self,guint64 document_id)608 dzl_fuzzy_index_builder_get_document (DzlFuzzyIndexBuilder *self,
609                                       guint64               document_id)
610 {
611   g_return_val_if_fail (DZL_IS_FUZZY_INDEX_BUILDER (self), NULL);
612   g_return_val_if_fail ((guint)document_id < self->documents->len, NULL);
613 
614   return g_ptr_array_index (self->documents, (guint)document_id);
615 }
616 
617 void
dzl_fuzzy_index_builder_set_metadata(DzlFuzzyIndexBuilder * self,const gchar * key,GVariant * value)618 dzl_fuzzy_index_builder_set_metadata (DzlFuzzyIndexBuilder *self,
619                                       const gchar          *key,
620                                       GVariant             *value)
621 {
622   g_return_if_fail (DZL_IS_FUZZY_INDEX_BUILDER (self));
623   g_return_if_fail (key != NULL);
624 
625   if (self->metadata == NULL)
626     self->metadata = g_hash_table_new_full (g_str_hash,
627                                             g_str_equal,
628                                             g_free,
629                                             (GDestroyNotify)g_variant_unref);
630 
631   if (value != NULL)
632     g_hash_table_insert (self->metadata,
633                          g_strdup (key),
634                          g_variant_ref_sink (value));
635   else
636     g_hash_table_remove (self->metadata, key);
637 }
638 
639 void
dzl_fuzzy_index_builder_set_metadata_string(DzlFuzzyIndexBuilder * self,const gchar * key,const gchar * value)640 dzl_fuzzy_index_builder_set_metadata_string (DzlFuzzyIndexBuilder *self,
641                                              const gchar          *key,
642                                              const gchar          *value)
643 {
644   g_return_if_fail (DZL_IS_FUZZY_INDEX_BUILDER (self));
645   g_return_if_fail (key != NULL);
646   g_return_if_fail (value != NULL);
647 
648   dzl_fuzzy_index_builder_set_metadata (self, key, g_variant_new_string (value));
649 }
650 
651 void
dzl_fuzzy_index_builder_set_metadata_uint32(DzlFuzzyIndexBuilder * self,const gchar * key,guint32 value)652 dzl_fuzzy_index_builder_set_metadata_uint32 (DzlFuzzyIndexBuilder *self,
653                                              const gchar          *key,
654                                              guint32               value)
655 {
656   g_return_if_fail (DZL_IS_FUZZY_INDEX_BUILDER (self));
657   g_return_if_fail (key != NULL);
658 
659   dzl_fuzzy_index_builder_set_metadata (self, key, g_variant_new_uint32 (value));
660 }
661 
662 void
dzl_fuzzy_index_builder_set_metadata_uint64(DzlFuzzyIndexBuilder * self,const gchar * key,guint64 value)663 dzl_fuzzy_index_builder_set_metadata_uint64 (DzlFuzzyIndexBuilder *self,
664                                              const gchar          *key,
665                                              guint64               value)
666 {
667   g_return_if_fail (DZL_IS_FUZZY_INDEX_BUILDER (self));
668   g_return_if_fail (key != NULL);
669 
670   dzl_fuzzy_index_builder_set_metadata (self, key, g_variant_new_uint64 (value));
671 }
672 
673 gboolean
dzl_fuzzy_index_builder_get_case_sensitive(DzlFuzzyIndexBuilder * self)674 dzl_fuzzy_index_builder_get_case_sensitive (DzlFuzzyIndexBuilder *self)
675 {
676   g_return_val_if_fail (DZL_IS_FUZZY_INDEX_BUILDER (self), FALSE);
677 
678   return self->case_sensitive;
679 }
680 
681 void
dzl_fuzzy_index_builder_set_case_sensitive(DzlFuzzyIndexBuilder * self,gboolean case_sensitive)682 dzl_fuzzy_index_builder_set_case_sensitive (DzlFuzzyIndexBuilder *self,
683                                             gboolean              case_sensitive)
684 {
685   g_return_if_fail (DZL_IS_FUZZY_INDEX_BUILDER (self));
686 
687   case_sensitive = !!case_sensitive;
688 
689   if (self->case_sensitive != case_sensitive)
690     {
691       self->case_sensitive = case_sensitive;
692       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CASE_SENSITIVE]);
693     }
694 }
695