1 /* gdict-source-loader.c - Source loader for Gdict
2 *
3 * Copyright (C) 2005 Emmanuele Bassi <ebassi@gmail.com>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library 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 GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this library. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 /**
20 * SECTION:gdict-source-loader
21 * @short_description: Loader object for a set of dictionary sources
22 *
23 * #GdictSourceLoader allows searching for dictionary source definition
24 * files inside a set of paths and return a #GdictSource using its name.
25 */
26
27 #include "config.h"
28
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #ifdef HAVE_UNISTD_H
33 #include <unistd.h>
34 #endif
35
36 #include <glib.h>
37 #include <glib/gstdio.h>
38 #include <glib/gi18n-lib.h>
39
40 #include "gdict-source-loader.h"
41 #include "gdict-utils.h"
42 #include "gdict-enum-types.h"
43 #include "gdict-marshal.h"
44 #include "gdict-private.h"
45
46 #define GDICT_SOURCE_FILE_SUFFIX ".desktop"
47
48 struct _GdictSourceLoaderPrivate
49 {
50 GSList *paths;
51
52 GSList *sources;
53 GHashTable *sources_by_name;
54
55 guint paths_dirty : 1;
56 };
57
58 enum
59 {
60 PROP_0,
61
62 PROP_PATHS,
63 PROP_SOURCES
64 };
65
66 enum
67 {
68 SOURCE_LOADED,
69
70 LAST_SIGNAL
71 };
72
73 static guint loader_signals[LAST_SIGNAL] = { 0 };
74
G_DEFINE_TYPE_WITH_PRIVATE(GdictSourceLoader,gdict_source_loader,G_TYPE_OBJECT)75 G_DEFINE_TYPE_WITH_PRIVATE (GdictSourceLoader, gdict_source_loader, G_TYPE_OBJECT)
76
77 static void
78 gdict_source_loader_finalize (GObject *object)
79 {
80 GdictSourceLoader *self = GDICT_SOURCE_LOADER (object);
81 GdictSourceLoaderPrivate *priv = gdict_source_loader_get_instance_private (self);
82
83 g_clear_pointer (&priv->sources_by_name, g_hash_table_unref);
84
85 g_slist_free_full (priv->paths, g_free);
86 g_slist_free_full (priv->sources, g_object_unref);
87
88 G_OBJECT_CLASS (gdict_source_loader_parent_class)->finalize (object);
89 }
90
91 static void
gdict_source_loader_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)92 gdict_source_loader_set_property (GObject *object,
93 guint prop_id,
94 const GValue *value,
95 GParamSpec *pspec)
96 {
97 switch (prop_id)
98 {
99 case PROP_PATHS:
100 break;
101 case PROP_SOURCES:
102 break;
103 default:
104 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
105 break;
106 }
107 }
108
109 static void
gdict_source_loader_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)110 gdict_source_loader_get_property (GObject *object,
111 guint prop_id,
112 GValue *value,
113 GParamSpec *pspec)
114 {
115 switch (prop_id)
116 {
117 case PROP_PATHS:
118 break;
119 case PROP_SOURCES:
120 break;
121 default:
122 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
123 break;
124 }
125 }
126
127 static void
gdict_source_loader_class_init(GdictSourceLoaderClass * klass)128 gdict_source_loader_class_init (GdictSourceLoaderClass *klass)
129 {
130 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
131
132 gobject_class->set_property = gdict_source_loader_set_property;
133 gobject_class->get_property = gdict_source_loader_get_property;
134 gobject_class->finalize = gdict_source_loader_finalize;
135
136 /**
137 * GdictSourceLoader:paths
138 *
139 * The search paths used by this object
140 *
141 * Since: 1.0
142 */
143 g_object_class_install_property (gobject_class,
144 PROP_PATHS,
145 g_param_spec_pointer ("paths",
146 "Paths",
147 "Search paths used by this object",
148 G_PARAM_READABLE));
149 /**
150 * GdictSourceLoader:sources
151 *
152 * The #GdictSource objects found by this object
153 *
154 * Since: 1.0
155 */
156 g_object_class_install_property (gobject_class,
157 PROP_SOURCES,
158 g_param_spec_pointer ("sources",
159 "Sources",
160 "Dictionary sources found",
161 G_PARAM_READABLE));
162
163 /**
164 * GdictSourceLoader::source-loaded
165 * @loader: the object which received the signal
166 * @source: the new #GdictSource object found
167 *
168 * This signal is emitted when a new dictionary source has been added
169 * to the list.
170 *
171 * Since: 1.0
172 */
173 loader_signals[SOURCE_LOADED] =
174 g_signal_new ("source-loaded",
175 G_OBJECT_CLASS_TYPE (gobject_class),
176 G_SIGNAL_RUN_LAST,
177 G_STRUCT_OFFSET (GdictSourceLoaderClass, source_loaded),
178 NULL, NULL,
179 gdict_marshal_VOID__OBJECT,
180 G_TYPE_NONE, 1,
181 GDICT_TYPE_SOURCE);
182 }
183
184 static void
gdict_source_loader_init(GdictSourceLoader * loader)185 gdict_source_loader_init (GdictSourceLoader *loader)
186 {
187 GdictSourceLoaderPrivate *priv;
188
189 priv = gdict_source_loader_get_instance_private (loader);
190 loader->priv = priv;
191
192 priv->paths = NULL;
193 /* add the default, system-wide path */
194 priv->paths = g_slist_prepend (priv->paths, g_strdup (GDICTSOURCESDIR));
195
196 priv->sources = NULL;
197 priv->sources_by_name = g_hash_table_new_full (g_str_hash, g_str_equal,
198 (GDestroyNotify) g_free,
199 NULL);
200
201 /* ensure that the sources list will be updated */
202 priv->paths_dirty = TRUE;
203 }
204
205 /**
206 * gdict_source_loader_new:
207 *
208 * Creates a new #GdictSourceLoader object. This object is used to search
209 * into a list of paths for dictionary source files. See #GdictSource for
210 * more informations about the format of dictionary source files.
211 *
212 * Return value: a new #GdictSourceLoader object
213 *
214 * Since: 1.0
215 */
216 GdictSourceLoader *
gdict_source_loader_new(void)217 gdict_source_loader_new (void)
218 {
219 return g_object_new (GDICT_TYPE_SOURCE_LOADER, NULL);
220 }
221
222 /**
223 * gdict_source_loader_update:
224 * @loader: a #GdictSourceLoader
225 *
226 * Queue an update of the sources inside @loader.
227 *
228 * Since: 1.0
229 */
230 void
gdict_source_loader_update(GdictSourceLoader * loader)231 gdict_source_loader_update (GdictSourceLoader *loader)
232 {
233 g_return_if_fail (GDICT_IS_SOURCE_LOADER (loader));
234
235 loader->priv->paths_dirty = TRUE;
236 }
237
238 /**
239 * gdict_source_loader_add_search_path:
240 * @loader: a #GdictSourceLoader
241 * @path: a path to be added to the search path list
242 *
243 * Adds @path to the search paths list of @loader.
244 *
245 * Since: 1.0
246 */
247 void
gdict_source_loader_add_search_path(GdictSourceLoader * loader,const gchar * path)248 gdict_source_loader_add_search_path (GdictSourceLoader *loader,
249 const gchar *path)
250 {
251 GSList *l;
252
253 g_return_if_fail (GDICT_IS_SOURCE_LOADER (loader));
254 g_return_if_fail (path != NULL);
255
256 /* avoid duplications */
257 for (l = loader->priv->paths; l != NULL; l = l->next)
258 if (strcmp (path, (gchar *) l->data) == 0)
259 return;
260
261 loader->priv->paths = g_slist_append (loader->priv->paths, g_strdup (path));
262 loader->priv->paths_dirty = TRUE;
263 }
264
265 /**
266 * gdict_source_loader_get_paths:
267 * @loader: a #GdictSourceLoader
268 *
269 * Gets the list of paths used by @loader to search for dictionary source
270 * files.
271 *
272 * Return value: (transfer none) (element-type utf8): a list containing the paths.
273 * The returned list is owned by the #GdictSourceLoader object and should never
274 * be free or modified.
275 *
276 * Since: 1.0
277 */
278 const GSList *
gdict_source_loader_get_paths(GdictSourceLoader * loader)279 gdict_source_loader_get_paths (GdictSourceLoader *loader)
280 {
281 g_return_val_if_fail (GDICT_IS_SOURCE_LOADER (loader), NULL);
282
283 return loader->priv->paths;
284 }
285
286 /* create the list of dictionary source files, by scanning the search path
287 * directories for .desktop files; we disavow symlinks and sub-directories
288 * for the time being.
289 */
290 static GSList *
build_source_filenames(GdictSourceLoader * loader)291 build_source_filenames (GdictSourceLoader *loader)
292 {
293 GSList *retval, *d;
294
295 g_assert (GDICT_IS_SOURCE_LOADER (loader));
296
297 if (!loader->priv->paths)
298 return NULL;
299
300 retval = NULL;
301 for (d = loader->priv->paths; d != NULL; d = d->next)
302 {
303 gchar *path = (gchar *) d->data;
304 const gchar *filename;
305 GDir *dir;
306
307 dir = g_dir_open (path, 0, NULL);
308 if (!dir)
309 continue;
310
311 do
312 {
313 filename = g_dir_read_name (dir);
314 if (filename)
315 {
316 gchar *full_path;
317
318 if (!g_str_has_suffix (filename, GDICT_SOURCE_FILE_SUFFIX))
319 break;
320
321 full_path = g_build_filename (path, filename, NULL);
322 if (g_file_test (full_path, G_FILE_TEST_IS_REGULAR))
323 {
324 retval = g_slist_prepend (retval, full_path);
325 }
326 }
327 }
328 while (filename != NULL);
329
330 g_dir_close (dir);
331 }
332
333 return g_slist_reverse (retval);
334 }
335
336 static void
gdict_source_loader_update_sources(GdictSourceLoader * loader)337 gdict_source_loader_update_sources (GdictSourceLoader *loader)
338 {
339 GSList *filenames, *f;
340
341 g_assert (GDICT_IS_SOURCE_LOADER (loader));
342
343 g_slist_foreach (loader->priv->sources,
344 (GFunc) g_object_unref,
345 NULL);
346 g_slist_free (loader->priv->sources);
347 loader->priv->sources = NULL;
348
349 filenames = build_source_filenames (loader);
350 for (f = filenames; f != NULL; f = f->next)
351 {
352 GdictSource *source;
353 GError *load_err;
354 gchar *path = (gchar *) f->data;
355
356 g_assert (path != NULL);
357
358 source = gdict_source_new ();
359
360 load_err = NULL;
361 gdict_source_load_from_file (source, path, &load_err);
362 if (load_err)
363 {
364 g_warning ("Unable to load dictionary source at '%s': %s\n",
365 path,
366 load_err->message);
367 g_error_free (load_err);
368
369 continue;
370 }
371
372 loader->priv->sources = g_slist_append (loader->priv->sources,
373 source);
374 g_hash_table_replace (loader->priv->sources_by_name,
375 g_strdup (gdict_source_get_name (source)),
376 source);
377
378 g_signal_emit (loader, loader_signals[SOURCE_LOADED], 0, source);
379 }
380
381 g_slist_foreach (filenames,
382 (GFunc) g_free,
383 NULL);
384 g_slist_free (filenames);
385
386 loader->priv->paths_dirty = FALSE;
387 }
388
389 /**
390 * gdict_source_loader_get_names:
391 * @loader: a #GdictSourceLoader
392 * @length: (out) (optional): return location for the number of
393 * source names, or %NULL
394 *
395 * Retrieves the list of dictionary source names available into the
396 * search paths of @loader.
397 *
398 * Return value: (transfer full): a newly allocated, %NULL terminated
399 * array of strings. You should free the returned string array
400 * with g_strfreev()
401 *
402 * Since: 1.0
403 */
404 gchar **
gdict_source_loader_get_names(GdictSourceLoader * loader,gsize * length)405 gdict_source_loader_get_names (GdictSourceLoader *loader,
406 gsize *length)
407 {
408 GSList *l;
409 gchar **names;
410 gsize i;
411
412 g_return_val_if_fail (GDICT_IS_SOURCE_LOADER (loader), NULL);
413
414 if (loader->priv->paths_dirty)
415 gdict_source_loader_update_sources (loader);
416
417 names = g_new0 (gchar *, g_slist_length (loader->priv->sources) + 1);
418
419 i = 0;
420 for (l = loader->priv->sources; l != NULL; l = l->next)
421 {
422 GdictSource *s = GDICT_SOURCE (l->data);
423
424 g_assert (s != NULL);
425
426 names[i++] = g_strdup (gdict_source_get_name (s));
427 }
428 names[i] = NULL;
429
430 if (length)
431 *length = i;
432
433 return names;
434 }
435
436 /**
437 * gdict_source_loader_get_sources:
438 * @loader: a #GdictSourceLoader
439 *
440 * Retrieves the list of dictionary sources available into the search
441 * paths of @loader, in form of #GdictSource objects.
442 *
443 * Return value: (transfer none) (element-type Gdict.Source): a list of
444 * #GdictSource objects. The returned list is owned by the #GdictSourceLoader
445 * object, and should never be freed or modified.
446 *
447 * Since: 1.0
448 */
449 const GSList *
gdict_source_loader_get_sources(GdictSourceLoader * loader)450 gdict_source_loader_get_sources (GdictSourceLoader *loader)
451 {
452 g_return_val_if_fail (GDICT_IS_SOURCE_LOADER (loader), NULL);
453
454 if (loader->priv->paths_dirty)
455 gdict_source_loader_update_sources (loader);
456
457 return loader->priv->sources;
458 }
459
460 /**
461 * gdict_source_loader_get_source:
462 * @loader: a #GdictSourceLoader
463 * @name: a name of a dictionary source
464 *
465 * Retrieves a dictionary source using @name. You can use the returned
466 * #GdictSource object to create the right #GdictContext for that
467 * dictionary source.
468 *
469 * Return value: (transfer full): a referenced #GdictSource object.
470 *
471 * Since: 1.0
472 */
473 GdictSource *
gdict_source_loader_get_source(GdictSourceLoader * loader,const gchar * name)474 gdict_source_loader_get_source (GdictSourceLoader *loader,
475 const gchar *name)
476 {
477 GdictSource *retval;
478
479 g_return_val_if_fail (GDICT_IS_SOURCE_LOADER (loader), NULL);
480 g_return_val_if_fail (name != NULL, NULL);
481
482 if (loader->priv->paths_dirty)
483 gdict_source_loader_update_sources (loader);
484
485 retval = g_hash_table_lookup (loader->priv->sources_by_name, name);
486 if (retval)
487 return g_object_ref (retval);
488
489 return NULL;
490 }
491
492 /**
493 * gdict_source_loader_remove_source:
494 * @loader: a #GdictSourceLoader
495 * @name: name of a dictionary source
496 *
497 * Removes the dictionary source @name from @loader. This function will
498 * also remove the dictionary source definition file bound to it.
499 *
500 * Return value: %TRUE if the dictionary source was successfully removed
501 *
502 * Since: 1.0
503 */
504 gboolean
gdict_source_loader_remove_source(GdictSourceLoader * loader,const gchar * name)505 gdict_source_loader_remove_source (GdictSourceLoader *loader,
506 const gchar *name)
507 {
508 GdictSourceLoaderPrivate *priv;
509 GSList *l;
510
511 g_return_val_if_fail (GDICT_IS_SOURCE_LOADER (loader), FALSE);
512 g_return_val_if_fail (name != NULL, FALSE);
513
514 priv = loader->priv;
515
516 if (priv->paths_dirty)
517 gdict_source_loader_update_sources (loader);
518
519 for (l = priv->sources; l != NULL; l = l->next)
520 {
521 GdictSource *s = GDICT_SOURCE (l->data);
522
523 if (strcmp (gdict_source_get_name (s), name) == 0)
524 {
525 gchar *filename;
526
527 g_object_get (G_OBJECT (s), "filename", &filename, NULL);
528
529 if (g_unlink (filename) == -1)
530 {
531 g_warning ("Unable to remove filename '%s' for the "
532 "dictionary source '%s'\n",
533 filename,
534 name);
535
536 return FALSE;
537 }
538
539 g_hash_table_remove (priv->sources_by_name, name);
540
541 priv->sources = g_slist_remove_link (priv->sources, l);
542
543 g_object_unref (s);
544 g_slist_free (l);
545
546 return TRUE;
547 }
548 }
549
550 return FALSE;
551 }
552
553 /**
554 * gdict_source_loader_has_source:
555 * @loader: a #GdictSourceLoader
556 * @source_name: the name of a dictionary source
557 *
558 * Checks whether @loader has a dictionary source with name @source_name.
559 *
560 * Return value: %TRUE if the dictionary source is known
561 *
562 * Since: 0.12
563 */
564 gboolean
gdict_source_loader_has_source(GdictSourceLoader * loader,const gchar * source_name)565 gdict_source_loader_has_source (GdictSourceLoader *loader,
566 const gchar *source_name)
567 {
568 g_return_val_if_fail (GDICT_IS_SOURCE_LOADER (loader), FALSE);
569 g_return_val_if_fail (source_name != NULL, FALSE);
570
571 if (loader->priv->paths_dirty)
572 gdict_source_loader_update_sources (loader);
573
574 return (g_hash_table_lookup (loader->priv->sources_by_name, source_name) != NULL);
575 }
576