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