1 /* ide-persistent-map.c
2 *
3 * Copyright 2017 Anoop Chandu <anoopchandu96@gmail.com>
4 * Copyright 2017-2019 Christian Hergert <chergert@redhat.com>
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 * SPDX-License-Identifier: GPL-3.0-or-later
20 */
21
22 #define G_LOG_DOMAIN "ide-persistent-map"
23
24 #include "config.h"
25
26 #include <libide-threading.h>
27
28 #include "ide-persistent-map.h"
29
30 typedef struct
31 {
32 guint32 key;
33 guint32 value;
34 } KVPair;
35
36 struct _IdePersistentMap
37 {
38 GObject parent;
39
40 GMappedFile *mapped_file;
41
42 GVariant *data;
43
44 GVariant *keys_var;
45 const gchar *keys;
46
47 GVariant *values;
48
49 GVariant *kvpairs_var;
50 const KVPair *kvpairs;
51
52 GVariantDict *metadata;
53
54 gsize n_kvpairs;
55
56 gint32 byte_order;
57
58 guint load_called : 1;
59 guint loaded : 1;
60 };
61
62 G_STATIC_ASSERT (sizeof (KVPair) == 8);
63
G_DEFINE_FINAL_TYPE(IdePersistentMap,ide_persistent_map,G_TYPE_OBJECT)64 G_DEFINE_FINAL_TYPE (IdePersistentMap, ide_persistent_map, G_TYPE_OBJECT)
65
66 static void
67 ide_persistent_map_load_file_worker (IdeTask *task,
68 gpointer source_object,
69 gpointer task_data,
70 GCancellable *cancellable)
71 {
72 IdePersistentMap *self = source_object;
73 GFile *file = task_data;
74 g_autofree gchar *path = NULL;
75 g_autoptr(GMappedFile) mapped_file = NULL;
76 g_autoptr(GVariant) data = NULL;
77 g_autoptr(GVariant) keys = NULL;
78 g_autoptr(GVariant) values = NULL;
79 g_autoptr(GVariant) metadata = NULL;
80 g_autoptr(GVariant) kvpairs = NULL;
81 g_autoptr(GError) error = NULL;
82 g_autoptr(GVariantDict) dict = NULL;
83 gint32 version;
84 gsize n_elements;
85
86 g_assert (IDE_IS_TASK (task));
87 g_assert (IDE_IS_PERSISTENT_MAP (self));
88 g_assert (G_IS_FILE (file));
89 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
90 g_assert (self->loaded == FALSE);
91
92 self->loaded = TRUE;
93
94 if (!g_file_is_native (file) || NULL == (path = g_file_get_path (file)))
95 {
96 ide_task_return_new_error (task,
97 G_IO_ERROR,
98 G_IO_ERROR_INVALID_FILENAME,
99 "Index must be a local file");
100 return;
101 }
102
103 mapped_file = g_mapped_file_new (path, FALSE, &error);
104
105 if (mapped_file == NULL)
106 {
107 ide_task_return_error (task, g_steal_pointer (&error));
108 return;
109 }
110
111 data = g_variant_new_from_data (G_VARIANT_TYPE_VARDICT,
112 g_mapped_file_get_contents (mapped_file),
113 g_mapped_file_get_length (mapped_file),
114 FALSE, NULL, NULL);
115
116 if (data == NULL)
117 {
118 ide_task_return_new_error (task,
119 G_IO_ERROR,
120 G_IO_ERROR_INVAL,
121 "Failed to parse GVariant");
122 return;
123 }
124
125 g_variant_take_ref (data);
126
127 dict = g_variant_dict_new (data);
128
129 if (!g_variant_dict_lookup (dict, "version", "i", &version) || version != 2)
130 {
131 ide_task_return_new_error (task,
132 G_IO_ERROR,
133 G_IO_ERROR_INVAL,
134 "Version mismatch in gvariant. Got %d, expected 1",
135 version);
136 return;
137 }
138
139 keys = g_variant_dict_lookup_value (dict, "keys", G_VARIANT_TYPE_ARRAY);
140 values = g_variant_dict_lookup_value (dict, "values", G_VARIANT_TYPE_ARRAY);
141 kvpairs = g_variant_dict_lookup_value (dict, "kvpairs", G_VARIANT_TYPE_ARRAY);
142 metadata = g_variant_dict_lookup_value (dict, "metadata", G_VARIANT_TYPE_VARDICT);
143
144 if (!g_variant_dict_lookup (dict, "byte-order", "i", &self->byte_order))
145 self->byte_order = G_BYTE_ORDER;
146
147 if (keys == NULL || values == NULL || kvpairs == NULL || metadata == NULL || !self->byte_order)
148 {
149 ide_task_return_new_error (task,
150 G_IO_ERROR,
151 G_IO_ERROR_INVAL,
152 "Invalid GVariant index");
153 return;
154 }
155
156 self->keys = g_variant_get_fixed_array (keys, &n_elements, sizeof (guint8));
157 self->kvpairs = g_variant_get_fixed_array (kvpairs, &self->n_kvpairs, sizeof (KVPair));
158
159 self->mapped_file = g_steal_pointer (&mapped_file);
160 self->data = g_steal_pointer (&data);
161 self->keys_var = g_steal_pointer (&keys);
162 self->values = g_steal_pointer (&values);
163 self->kvpairs_var = g_steal_pointer (&kvpairs);
164 self->metadata = g_variant_dict_new (metadata);
165
166 g_assert (!g_variant_is_floating (self->data));
167 g_assert (!g_variant_is_floating (self->keys_var));
168 g_assert (!g_variant_is_floating (self->values));
169 g_assert (!g_variant_is_floating (self->kvpairs_var));
170 g_assert (self->keys != NULL);
171 g_assert (self->kvpairs != NULL);
172 g_assert (self->metadata != NULL);
173
174 ide_task_return_boolean (task, TRUE);
175 }
176
177 gboolean
ide_persistent_map_load_file(IdePersistentMap * self,GFile * file,GCancellable * cancellable,GError ** error)178 ide_persistent_map_load_file (IdePersistentMap *self,
179 GFile *file,
180 GCancellable *cancellable,
181 GError **error)
182 {
183 g_autoptr(IdeTask) task = NULL;
184
185 g_return_val_if_fail (IDE_IS_PERSISTENT_MAP (self), FALSE);
186 g_return_val_if_fail (self->load_called == FALSE, FALSE);
187 g_return_val_if_fail (G_IS_FILE (file), FALSE);
188 g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);
189
190 self->load_called = TRUE;
191
192 task = ide_task_new (self, cancellable, NULL, NULL);
193 ide_task_set_source_tag (task, ide_persistent_map_load_file);
194 ide_task_set_priority (task, G_PRIORITY_LOW);
195 ide_task_set_kind (task, IDE_TASK_KIND_INDEXER);
196 ide_persistent_map_load_file_worker (task, self, file, cancellable);
197
198 return ide_task_propagate_boolean (task, error);
199 }
200
201 void
ide_persistent_map_load_file_async(IdePersistentMap * self,GFile * file,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)202 ide_persistent_map_load_file_async (IdePersistentMap *self,
203 GFile *file,
204 GCancellable *cancellable,
205 GAsyncReadyCallback callback,
206 gpointer user_data)
207 {
208 g_autoptr(IdeTask) task = NULL;
209
210 g_return_if_fail (IDE_IS_PERSISTENT_MAP (self));
211 g_return_if_fail (self->load_called == FALSE);
212 g_return_if_fail (G_IS_FILE (file));
213 g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
214
215 self->load_called = TRUE;
216
217 task = ide_task_new (self, cancellable, callback, user_data);
218 ide_task_set_source_tag (task, ide_persistent_map_load_file_async);
219 ide_task_set_priority (task, G_PRIORITY_LOW);
220 ide_task_set_kind (task, IDE_TASK_KIND_INDEXER);
221 ide_task_set_task_data (task, g_object_ref (file), g_object_unref);
222 ide_task_run_in_thread (task, ide_persistent_map_load_file_worker);
223 }
224
225 /**
226 * ide_persistent_map_load_file_finish:
227 * @self: an #IdePersistentMap
228 * @result: a #GAsyncResult provided to callback
229 * @error: a location for a #GError, or %NULL
230 *
231 * Returns: Whether file is loaded or not.
232 *
233 * Since: 3.32
234 */
235 gboolean
ide_persistent_map_load_file_finish(IdePersistentMap * self,GAsyncResult * result,GError ** error)236 ide_persistent_map_load_file_finish (IdePersistentMap *self,
237 GAsyncResult *result,
238 GError **error)
239 {
240 g_return_val_if_fail (IDE_IS_PERSISTENT_MAP (self), FALSE);
241 g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
242
243 return ide_task_propagate_boolean (IDE_TASK (result), error);
244 }
245
246 /**
247 * ide_persistent_map_lookup_value:
248 * @self: An #IdePersistentMap instance.
249 * @key: key to lookup value
250 *
251 * Returns: (transfer full) : value associalted with @key.
252 *
253 * Since: 3.32
254 */
255 GVariant *
ide_persistent_map_lookup_value(IdePersistentMap * self,const gchar * key)256 ide_persistent_map_lookup_value (IdePersistentMap *self,
257 const gchar *key)
258 {
259 g_autoptr(GVariant) value = NULL;
260 gint64 l;
261 gint64 r;
262
263 g_return_val_if_fail (IDE_IS_PERSISTENT_MAP (self), NULL);
264 g_return_val_if_fail (self->loaded, NULL);
265 g_return_val_if_fail (self != NULL, NULL);
266 g_return_val_if_fail (self->kvpairs != NULL, NULL);
267 g_return_val_if_fail (self->keys != NULL, NULL);
268 g_return_val_if_fail (self->values != NULL, NULL);
269 g_return_val_if_fail (self->n_kvpairs < G_MAXINT64, NULL);
270
271 if (self->n_kvpairs == 0)
272 return NULL;
273
274 /* unsigned long to signed long */
275 r = (gint64)self->n_kvpairs - 1;
276 l = 0;
277
278 while (l <= r)
279 {
280 gint64 m;
281 gint32 k;
282 gint cmp;
283
284 m = (l + r) / 2;
285 g_assert (m >= 0);
286
287 k = self->kvpairs [m].key;
288 g_assert (k >= 0);
289
290 cmp = g_strcmp0 (key, &self->keys [k]);
291
292 if (cmp < 0)
293 r = m - 1;
294 else if (cmp > 0)
295 l = m + 1;
296 else
297 {
298 value = g_variant_get_child_value (self->values, self->kvpairs [m].value);
299 break;
300 }
301 }
302
303 if (value != NULL && self->byte_order != G_BYTE_ORDER)
304 return g_variant_byteswap (value);
305
306 return g_steal_pointer (&value);
307 }
308
309 gint64
ide_persistent_map_builder_get_metadata_int64(IdePersistentMap * self,const gchar * key)310 ide_persistent_map_builder_get_metadata_int64 (IdePersistentMap *self,
311 const gchar *key)
312 {
313 guint64 value = 0;
314
315 g_return_val_if_fail (IDE_IS_PERSISTENT_MAP (self), 0);
316 g_return_val_if_fail (key != NULL, 0);
317 g_return_val_if_fail (self->metadata != NULL, 0);
318
319 if (!g_variant_dict_lookup (self->metadata, key, "x", &value))
320 return 0;
321
322 return value;
323 }
324
325 static void
ide_persistent_map_finalize(GObject * object)326 ide_persistent_map_finalize (GObject *object)
327 {
328 IdePersistentMap *self = (IdePersistentMap *)object;
329
330 self->keys = NULL;
331 self->kvpairs = NULL;
332
333 g_clear_pointer (&self->data, g_variant_unref);
334 g_clear_pointer (&self->keys_var, g_variant_unref);
335 g_clear_pointer (&self->values, g_variant_unref);
336 g_clear_pointer (&self->kvpairs_var, g_variant_unref);
337 g_clear_pointer (&self->metadata, g_variant_dict_unref);
338 g_clear_pointer (&self->mapped_file, g_mapped_file_unref);
339
340 G_OBJECT_CLASS (ide_persistent_map_parent_class)->finalize (object);
341 }
342
343 static void
ide_persistent_map_init(IdePersistentMap * self)344 ide_persistent_map_init (IdePersistentMap *self)
345 {
346 }
347
348 static void
ide_persistent_map_class_init(IdePersistentMapClass * self)349 ide_persistent_map_class_init (IdePersistentMapClass *self)
350 {
351 GObjectClass *object_class = G_OBJECT_CLASS (self);
352
353 object_class->finalize = ide_persistent_map_finalize;
354 }
355
356 IdePersistentMap *
ide_persistent_map_new(void)357 ide_persistent_map_new (void)
358 {
359 return g_object_new (IDE_TYPE_PERSISTENT_MAP, NULL);
360 }
361