1 /*
2  * Copyright (C) 2010, 2011 Igalia S.L.
3  * Copyright (C) 2011 Intel Corporation.
4  *
5  * Contact: Iago Toral Quiroga <itoral@igalia.com>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public License
9  * as published by the Free Software Foundation; version 2.1 of
10  * the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20  * 02110-1301 USA
21  *
22  */
23 
24 /**
25  * SECTION:grl-registry
26  * @short_description: Grilo plugins loader and manager
27  * @see_also: #GrlPlugin, #GrlSource
28  *
29  * The registry holds the metadata of a set of plugins.
30  *
31  * The #GrlRegistry object is a list of plugins and some functions
32  * for dealing with them. Each #GrlPlugin is matched 1-1 with a file
33  * on disk, and may or may not be loaded a given time. There only can be
34  * a single instance of #GrlRegistry (singleton pattern).
35  *
36  * A #GrlPlugin can hold several data #GrlSource sources, and #GrlRegistry
37  * shall register each one of them.
38  */
39 
40 #include "grl-registry.h"
41 
42 #ifdef HAVE_CONFIG_H
43 #include "config.h"
44 #endif
45 
46 #include "grl-registry-priv.h"
47 #include "grl-plugin-priv.h"
48 #include "grl-log.h"
49 #include "grl-error.h"
50 
51 #include <glib/gi18n-lib.h>
52 #include <string.h>
53 #include <gmodule.h>
54 #include <libxml/parser.h>
55 
56 #define GRL_LOG_DOMAIN_DEFAULT  registry_log_domain
57 GRL_LOG_DOMAIN(registry_log_domain);
58 
59 #define XML_ROOT_ELEMENT_NAME "plugin"
60 
61 #define GRL_PLUGIN_INFO_SUFFIX "xml"
62 
63 #define GRL_PLUGIN_INFO_MODULE "module"
64 
65 #define LOCAL_NET_TAG      "net:local"
66 #define INTERNET_NET_TAG   "net:internet"
67 
68 #define SET_INVISIBLE_SOURCE(src, val)                          \
69   g_object_set_data(G_OBJECT(src), "invisible", GINT_TO_POINTER(val))
70 #define SOURCE_IS_INVISIBLE(src)                                \
71   GPOINTER_TO_INT(g_object_get_data(G_OBJECT(src), "invisible"))
72 
73 /* GQuark-like implementation, where we manually assign the first IDs. */
74 struct KeyIDHandler {
75   GHashTable *string_to_id;
76   GArray *id_to_string;
77   gint last_id;
78 };
79 
80 struct _GrlRegistryPrivate {
81   GHashTable *configs;
82   GHashTable *plugins;
83   GHashTable *sources;
84   GHashTable *related_keys;
85   GHashTable *system_keys;
86   GHashTable *ranks;
87   GSList *plugins_dir;
88   GSList *allowed_plugins;
89   gboolean all_plugins_preloaded;
90   struct KeyIDHandler key_id_handler;
91   GNetworkMonitor *netmon;
92 };
93 
94 static void grl_registry_setup_ranks (GrlRegistry *registry);
95 
96 static void key_id_handler_init (struct KeyIDHandler *handler);
97 
98 static void key_id_handler_free (struct KeyIDHandler *handler);
99 
100 static GrlKeyID key_id_handler_get_key (struct KeyIDHandler *handler,
101                                         const gchar *key_name);
102 
103 static const gchar *key_id_handler_get_name (struct KeyIDHandler *handler,
104                                              GrlKeyID key);
105 
106 static GrlKeyID key_id_handler_add (struct KeyIDHandler *handler,
107                                     GrlKeyID key, const gchar *key_name);
108 
109 static gboolean param_spec_is_equal (GParamSpec *curr, GParamSpec *new);
110 
111 static void shutdown_plugin (GrlPlugin *plugin);
112 
113 static void configs_free (GList *configs);
114 
115 static GrlPlugin *grl_registry_prepare_plugin (GrlRegistry *registry,
116                                                const gchar *library_filename,
117                                                GError **error);
118 
119 /* ================ GrlRegistry GObject ================ */
120 
121 enum {
122   SIG_SOURCE_ADDED,
123   SIG_SOURCE_REMOVED,
124   SIG_METADATA_KEY_ADDED,
125   SIG_LAST
126 };
127 static gint registry_signals[SIG_LAST];
128 
129 G_DEFINE_TYPE_WITH_PRIVATE (GrlRegistry, grl_registry, G_TYPE_OBJECT);
130 
131 static void
grl_registry_class_init(GrlRegistryClass * klass)132 grl_registry_class_init (GrlRegistryClass *klass)
133 {
134   /**
135    * GrlRegistry::source-added:
136    * @registry: the registry
137    * @source: the source that has been added
138    *
139    * Signals that a source has been added to the registry.
140    *
141    * Since: 0.2.0
142    */
143   registry_signals[SIG_SOURCE_ADDED] =
144     g_signal_new("source-added",
145 		 G_TYPE_FROM_CLASS(klass),
146 		 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
147 		 0,
148 		 NULL,
149 		 NULL,
150 		 g_cclosure_marshal_VOID__OBJECT,
151 		 G_TYPE_NONE, 1, GRL_TYPE_SOURCE);
152 
153   /**
154    * GrlRegistry::source-removed:
155    * @registry: the registry
156    * @source: the source that has been removed
157    *
158    * Signals that a source has been removed from the registry.
159    *
160    * Since: 0.2.0
161    */
162   registry_signals[SIG_SOURCE_REMOVED] =
163     g_signal_new("source-removed",
164 		 G_TYPE_FROM_CLASS(klass),
165 		 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
166 		 0,
167 		 NULL,
168 		 NULL,
169 		 g_cclosure_marshal_VOID__OBJECT,
170 		 G_TYPE_NONE, 1, GRL_TYPE_SOURCE);
171 
172   /**
173    * GrlRegistry::metadata-key-added:
174    * @registry: the registry
175    * @key: the name of the new key added
176    *
177    * Signals that a new metadata key has been registered.
178    *
179    * Since: 0.2.10
180    */
181   registry_signals[SIG_METADATA_KEY_ADDED] =
182     g_signal_new("metadata-key-added",
183                  G_TYPE_FROM_CLASS(klass),
184                  G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
185                  0,
186                  NULL,
187                  NULL,
188                  g_cclosure_marshal_VOID__STRING,
189                  G_TYPE_NONE, 1, G_TYPE_STRING);
190 }
191 
192 static void
get_connectivity(GrlRegistry * registry,GNetworkConnectivity * connectivity,gboolean * network_available)193 get_connectivity (GrlRegistry          *registry,
194                   GNetworkConnectivity *connectivity,
195                   gboolean             *network_available)
196 {
197   g_assert (connectivity != NULL);
198   g_assert (network_available != NULL);
199 
200   if (g_getenv("GRL_NET_MOCKED") != NULL) {
201     GRL_DEBUG ("Mocked network, assuming network is available and connectivity "
202                "level is FULL");
203     *connectivity = G_NETWORK_CONNECTIVITY_FULL;
204     *network_available = TRUE;
205   } else {
206     g_object_get (G_OBJECT (registry->priv->netmon),
207                   "connectivity", connectivity,
208                   "network-available", network_available,
209                   NULL);
210 
211     GRL_DEBUG ("Connectivity level is %d, Network is %s",
212                *connectivity, *network_available ? "available" : "unavailable");
213   }
214 }
215 
216 static void
network_changed_cb(GObject * gobject,GParamSpec * pspec,GrlRegistry * registry)217 network_changed_cb (GObject     *gobject,
218                     GParamSpec  *pspec,
219                     GrlRegistry *registry)
220 {
221   GNetworkConnectivity connectivity;
222   gboolean network_available;
223   GrlSource *current_source;
224   GList *sources, *l;
225 
226   GRL_DEBUG ("Network availability changed");
227   get_connectivity (registry, &connectivity, &network_available);
228 
229   sources = g_hash_table_get_values (registry->priv->sources);
230   if (!sources)
231     return;
232 
233   if (!network_available) {
234     for (l = sources; l != NULL; l = l->next) {
235       const char **tags;
236 
237       current_source = l->data;
238       tags = grl_source_get_tags (current_source);
239 
240       if (!tags)
241         continue;
242 
243       if ((g_strv_contains (tags, LOCAL_NET_TAG) ||
244            g_strv_contains (tags, INTERNET_NET_TAG)) &&
245           !SOURCE_IS_INVISIBLE(current_source)) {
246         GRL_DEBUG ("Network isn't available for '%s', hiding",
247                    grl_source_get_id (current_source));
248         SET_INVISIBLE_SOURCE(current_source, TRUE);
249         g_signal_emit (registry, registry_signals[SIG_SOURCE_REMOVED], 0, current_source);
250       }
251     }
252   } else {
253     GList *to_add, *to_remove;
254 
255     to_add = to_remove = NULL;
256 
257     for (l = sources; l != NULL; l = l->next) {
258       const char **tags;
259 
260       current_source = l->data;
261       tags = grl_source_get_tags (current_source);
262 
263       if (!tags)
264         continue;
265 
266       if (g_strv_contains (tags, LOCAL_NET_TAG) &&
267           SOURCE_IS_INVISIBLE(current_source)) {
268         GRL_DEBUG ("Local network became available for '%s', showing",
269                    grl_source_get_id (current_source));
270         to_add = g_list_prepend (to_add, current_source);
271       }
272 
273       if (g_strv_contains (tags, INTERNET_NET_TAG) &&
274           connectivity == G_NETWORK_CONNECTIVITY_FULL &&
275           SOURCE_IS_INVISIBLE(current_source)) {
276         GRL_DEBUG ("Internet became available for '%s', showing",
277                    grl_source_get_id (current_source));
278         to_add = g_list_prepend (to_add, current_source);
279       }
280 
281       if (g_strv_contains (tags, INTERNET_NET_TAG) &&
282           connectivity != G_NETWORK_CONNECTIVITY_FULL &&
283           !SOURCE_IS_INVISIBLE(current_source)) {
284         GRL_DEBUG ("Internet became unavailable for '%s', hiding",
285                    grl_source_get_id (current_source));
286         to_remove = g_list_prepend (to_remove, current_source);
287       }
288     }
289 
290     for (l = to_add; l != NULL; l = l->next) {
291       SET_INVISIBLE_SOURCE(l->data, FALSE);
292       g_signal_emit (registry, registry_signals[SIG_SOURCE_ADDED], 0, l->data);
293     }
294     g_list_free (to_add);
295 
296     for (l = to_remove; l != NULL; l = l->next) {
297         SET_INVISIBLE_SOURCE(l->data, TRUE);
298         g_signal_emit (registry, registry_signals[SIG_SOURCE_REMOVED], 0, l->data);
299     }
300     g_list_free (to_remove);
301   }
302 
303   g_list_free (sources);
304 }
305 
306 static void
grl_registry_init(GrlRegistry * registry)307 grl_registry_init (GrlRegistry *registry)
308 {
309   registry->priv = grl_registry_get_instance_private (registry);
310 
311   registry->priv->configs =
312     g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) configs_free);
313   registry->priv->plugins =
314     g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
315   registry->priv->sources =
316     g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
317   registry->priv->related_keys =
318     g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
319   registry->priv->system_keys =
320     g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) g_param_spec_unref);
321 
322   registry->priv->netmon = g_network_monitor_get_default ();
323   g_signal_connect (G_OBJECT (registry->priv->netmon), "notify::connectivity",
324                     G_CALLBACK (network_changed_cb), registry);
325   g_signal_connect (G_OBJECT (registry->priv->netmon), "notify::network-available",
326                     G_CALLBACK (network_changed_cb), registry);
327 
328   key_id_handler_init (&registry->priv->key_id_handler);
329 
330   grl_registry_setup_ranks (registry);
331 
332   const gchar *config_path = g_getenv (GRL_CONFIG_PATH_VAR);
333   if (config_path != NULL)
334     grl_registry_add_config_from_file (registry, config_path, NULL);
335 }
336 
337 /* ================ Utitilies ================ */
338 
339 static void
configs_free(GList * configs)340 configs_free (GList *configs)
341 {
342   g_list_free_full (configs, g_object_unref);
343 }
344 
345 static void
update_source_visibility(GrlRegistry * registry,GrlSource * source)346 update_source_visibility (GrlRegistry *registry,
347                           GrlSource   *source)
348 {
349   GNetworkConnectivity connectivity;
350   gboolean network_available;
351   const char **tags;
352   gboolean needs_local, needs_inet;
353 
354   tags = grl_source_get_tags (source);
355   if (!tags)
356     return;
357 
358   needs_local = g_strv_contains (tags, LOCAL_NET_TAG);
359   needs_inet = g_strv_contains (tags, INTERNET_NET_TAG);
360 
361   if (!needs_local &&
362       !needs_inet)
363     return;
364 
365   get_connectivity (registry, &connectivity, &network_available);
366 
367   GRL_DEBUG ("Source %s needs %s %s%s",
368              grl_source_get_id (source),
369              needs_local ? "local network" : "",
370              needs_inet && needs_local ? " and " : "",
371              needs_inet ? "Internet" : "");
372 
373   if (!network_available) {
374     if (needs_local || needs_inet) {
375       GRL_DEBUG ("Network isn't available for '%s', hiding",
376                  grl_source_get_id (source));
377       SET_INVISIBLE_SOURCE(source, TRUE);
378     }
379   } else {
380     if (connectivity != G_NETWORK_CONNECTIVITY_FULL) {
381       if (needs_inet) {
382         GRL_DEBUG ("Internet isn't available for '%s', hiding",
383                    grl_source_get_id (source));
384         SET_INVISIBLE_SOURCE(source, TRUE);
385       }
386     }
387   }
388 }
389 
390 static void
config_source_rank(GrlRegistry * registry,const gchar * source_id,gint rank)391 config_source_rank (GrlRegistry *registry,
392                     const gchar *source_id,
393                     gint rank)
394 {
395   GRL_DEBUG ("Rank configuration, '%s:%d'", source_id, rank);
396   g_hash_table_insert (registry->priv->ranks,
397                        g_strdup (source_id),
398                        GINT_TO_POINTER (rank));
399 }
400 
401 static void
set_source_rank(GrlRegistry * registry,GrlSource * source)402 set_source_rank (GrlRegistry *registry, GrlSource *source)
403 {
404   gint rank;
405 
406   rank =
407     GPOINTER_TO_INT (g_hash_table_lookup (registry->priv->ranks,
408                                           grl_source_get_id (source)));
409 
410   if (!rank) {
411     GHashTableIter iter;
412     gpointer key, value;
413 
414     g_hash_table_iter_init(&iter, registry->priv->ranks);
415 
416     while (g_hash_table_iter_next (&iter, &key, &value)) {
417       if (g_pattern_match_simple (key, grl_source_get_id (source))) {
418         rank = GPOINTER_TO_INT (value);
419         break;
420       }
421     }
422   }
423 
424   if (!rank) {
425     rank = GRL_RANK_DEFAULT;
426   }
427 
428   g_object_set (source, "rank", rank, NULL);
429   GRL_DEBUG ("Source rank '%s' : %d", grl_source_get_id (source), rank);
430 }
431 
432 static void
grl_registry_setup_ranks(GrlRegistry * registry)433 grl_registry_setup_ranks (GrlRegistry *registry)
434 {
435   const gchar *ranks_env;
436   gchar **rank_specs;
437   gchar **iter;
438 
439   registry->priv->ranks = g_hash_table_new_full (g_str_hash, g_str_equal,
440 						 g_free, NULL);
441 
442   ranks_env = g_getenv (GRL_PLUGIN_RANKS_VAR);
443   if (!ranks_env) {
444     GRL_DEBUG ("$%s is not set, using default ranks.", GRL_PLUGIN_RANKS_VAR);
445     return;
446   }
447 
448   rank_specs = g_strsplit (ranks_env, ",", 0);
449   iter = rank_specs;
450 
451   while (*iter) {
452     gchar **rank_info = g_strsplit (*iter, ":", 2);
453     if (rank_info[0] && rank_info[1]) {
454       gchar *tmp;
455       gchar *id = rank_info[0];
456       gchar *srank = rank_info[1];
457       gint rank = (gint) g_ascii_strtoll (srank, &tmp, 10);
458       if (*tmp != '\0') {
459         GRL_WARNING ("Incorrect ranking definition: '%s'. Skipping...", *iter);
460       } else {
461         config_source_rank (registry, id, rank);
462       }
463     } else {
464       GRL_WARNING ("Incorrect ranking definition: '%s'. Skipping...", *iter);
465     }
466     g_strfreev (rank_info);
467     iter++;
468   }
469 
470   g_strfreev (rank_specs);
471 }
472 
473 static gint
compare_by_rank(gconstpointer a,gconstpointer b)474 compare_by_rank (gconstpointer a,
475                  gconstpointer b) {
476   gint rank_a;
477   gint rank_b;
478 
479   rank_a = grl_source_get_rank (GRL_SOURCE (a));
480   rank_b = grl_source_get_rank (GRL_SOURCE (b));
481 
482   return (rank_a < rank_b) - (rank_a > rank_b);
483 }
484 
485 static gboolean
register_keys_plugin(GrlRegistry * registry,GrlPlugin * plugin,GError ** error)486 register_keys_plugin (GrlRegistry *registry,
487                       GrlPlugin *plugin,
488                       GError **error)
489 {
490   gboolean is_loaded;
491 
492   /* Check if plugin is already loaded */
493   g_object_get (plugin, "loaded", &is_loaded, NULL);
494   if (is_loaded) {
495     GRL_WARNING ("Plugin is already loaded: '%s'", grl_plugin_get_id (plugin));
496     g_set_error (error,
497                  GRL_CORE_ERROR,
498                  GRL_CORE_ERROR_LOAD_PLUGIN_FAILED,
499                  _("Plugin “%s” is already loaded"), grl_plugin_get_id (plugin));
500     return FALSE;
501   }
502 
503   grl_plugin_register_keys (plugin);
504 
505   return TRUE;
506 }
507 
508 static gboolean
activate_plugin(GrlRegistry * registry,GrlPlugin * plugin,GError ** error)509 activate_plugin (GrlRegistry *registry,
510                  GrlPlugin *plugin,
511                  GError **error)
512 {
513   GList *plugin_configs;
514 
515   plugin_configs = g_hash_table_lookup (registry->priv->configs,
516                                         grl_plugin_get_id (plugin));
517 
518   if (!grl_plugin_load (plugin, plugin_configs)) {
519     GRL_DEBUG ("Failed to initialize plugin from %s. Check if plugin is well configured", grl_plugin_get_filename (plugin));
520     g_set_error (error,
521                  GRL_CORE_ERROR,
522                  GRL_CORE_ERROR_LOAD_PLUGIN_FAILED,
523                  _("Failed to initialize plugin from %s"), grl_plugin_get_filename (plugin));
524     shutdown_plugin (plugin);
525     return FALSE;
526   }
527 
528   GRL_DEBUG ("Loaded plugin '%s' from '%s'",
529              grl_plugin_get_id (plugin),
530              grl_plugin_get_filename (plugin));
531 
532   return TRUE;
533 }
534 
535 static GrlKeyID
grl_registry_register_metadata_key_full(GrlRegistry * registry,GParamSpec * param_spec,GrlKeyID key,GrlKeyID bind_key,GError ** error)536 grl_registry_register_metadata_key_full (GrlRegistry *registry,
537                                          GParamSpec *param_spec,
538                                          GrlKeyID key,
539                                          GrlKeyID bind_key,
540                                          GError **error)
541 {
542   GList *bound_partners;
543   GList *partner;
544   const gchar *key_name;
545 
546   g_return_val_if_fail (GRL_IS_REGISTRY (registry), 0);
547   g_return_val_if_fail (G_IS_PARAM_SPEC (param_spec), 0);
548   GrlKeyID registered_key;
549 
550   key_name = g_param_spec_get_name (param_spec);
551   registered_key = key_id_handler_get_key (&registry->priv->key_id_handler, key_name);
552   if (registered_key != GRL_METADATA_KEY_INVALID) {
553     GParamSpec *key_spec = g_hash_table_lookup (registry->priv->system_keys,
554                                                 (gpointer) key_name);
555     if (param_spec_is_equal (key_spec, param_spec)) {
556       /* Key registered */
557       GRL_DEBUG ("metadata key '%s' already registered with same spec", key_name);
558       g_param_spec_unref (param_spec);
559       return registered_key;
560     } else {
561       GRL_WARNING ("metadata key '%s' already exists", key_name);
562       g_set_error (error,
563                    GRL_CORE_ERROR,
564                    GRL_CORE_ERROR_REGISTER_METADATA_KEY_FAILED,
565                    _("Metadata key “%s” already registered in different format"),
566                    key_name);
567       return GRL_METADATA_KEY_INVALID;
568     }
569   }
570 
571   registered_key = key_id_handler_add (&registry->priv->key_id_handler, key, key_name);
572 
573   if (registered_key == GRL_METADATA_KEY_INVALID) {
574     GRL_WARNING ("metadata key '%s' cannot be registered", key_name);
575     g_set_error (error,
576                  GRL_CORE_ERROR,
577                  GRL_CORE_ERROR_REGISTER_METADATA_KEY_FAILED,
578                  _("Metadata key “%s” cannot be registered"),
579                  key_name);
580 
581     return GRL_METADATA_KEY_INVALID;
582   }
583 
584   g_hash_table_insert (registry->priv->system_keys,
585                        (gpointer) key_name,
586                        param_spec);
587 
588   if (bind_key == GRL_METADATA_KEY_INVALID) {
589   /* Key is only related to itself */
590     g_hash_table_insert (registry->priv->related_keys,
591                          GRLKEYID_TO_POINTER (registered_key),
592                          g_list_prepend (NULL,
593                                          GRLKEYID_TO_POINTER (registered_key)));
594   } else {
595     /* Add the new key to the partners */
596     bound_partners = g_hash_table_lookup (registry->priv->related_keys, GRLKEYID_TO_POINTER (bind_key));
597     bound_partners = g_list_append (bound_partners, GRLKEYID_TO_POINTER (registered_key));
598     for (partner = bound_partners;
599          partner;
600          partner = g_list_next (partner)) {
601       g_hash_table_insert (registry->priv->related_keys,
602                            partner->data,
603                            bound_partners);
604     }
605   }
606 
607   return registered_key;
608 }
609 
610 G_GNUC_INTERNAL GrlKeyID
grl_registry_register_metadata_key_for_type(GrlRegistry * registry,const gchar * key_name,GType type,GrlKeyID bind_key)611 grl_registry_register_metadata_key_for_type (GrlRegistry *registry,
612                                              const gchar *key_name,
613                                              GType type,
614                                              GrlKeyID bind_key)
615 {
616   GParamSpec *spec;
617 
618   switch (type) {
619   case G_TYPE_INT:
620     spec = g_param_spec_int (key_name,
621                              key_name,
622                              key_name,
623                              0, G_MAXINT,
624                              0,
625                              G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
626     break;
627 
628   case G_TYPE_INT64:
629     spec = g_param_spec_int64 (key_name,
630                                key_name,
631                                key_name,
632                                -1, G_MAXINT64,
633                                -1,
634                                G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
635     break;
636 
637   case G_TYPE_STRING:
638     spec = g_param_spec_string (key_name,
639                                 key_name,
640                                 key_name,
641                                 NULL,
642                                 G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
643     break;
644 
645   case G_TYPE_BOOLEAN:
646     spec = g_param_spec_boolean (key_name,
647                                  key_name,
648                                  key_name,
649                                  FALSE,
650                                  G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
651     break;
652 
653   case G_TYPE_FLOAT:
654     spec = g_param_spec_float (key_name,
655                                key_name,
656                                key_name,
657                                0, G_MAXFLOAT,
658                                0,
659                                G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
660     break;
661 
662   default:
663     if (type == G_TYPE_DATE_TIME) {
664         spec = g_param_spec_boxed (key_name,
665                                    key_name,
666                                    key_name,
667                                    G_TYPE_DATE_TIME,
668                                    G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
669     } else {
670       GRL_WARNING ("'%s' is being ignored as G_TYPE '%s' is not being handled",
671                    key_name, g_type_name (type));
672       return GRL_METADATA_KEY_INVALID;
673     }
674   }
675 
676   return grl_registry_register_metadata_key (registry, spec, bind_key, NULL);
677 }
678 
679 /*
680  * Returns whether the string is a canonical one.
681  */
682 static gboolean
is_canonical(const gchar * key)683 is_canonical (const gchar *key)
684 {
685   if (key == NULL) {
686     return FALSE;
687   }
688 
689   for (; *key != '\0'; key++) {
690     if (*key != '-' &&
691         (*key < '0' || *key > '9') &&
692         (*key < 'A' || *key > 'Z') &&
693         (*key < 'a' || *key > 'z'))
694       return FALSE;
695   }
696 
697   return TRUE;
698 }
699 
700 G_GNUC_INTERNAL GrlKeyID
grl_registry_register_or_lookup_metadata_key(GrlRegistry * registry,const gchar * key_name,const GValue * value,GrlKeyID bind_key)701 grl_registry_register_or_lookup_metadata_key (GrlRegistry *registry,
702                                               const gchar *key_name,
703                                               const GValue *value,
704                                               GrlKeyID bind_key)
705 {
706   GrlKeyID key;
707   GType value_type;
708 
709   g_return_val_if_fail (GRL_IS_REGISTRY (registry), GRL_METADATA_KEY_INVALID);
710 
711   if (value == NULL)
712     return GRL_METADATA_KEY_INVALID;
713 
714   key_name = g_intern_string (key_name);
715   g_return_val_if_fail (is_canonical (key_name), GRL_METADATA_KEY_INVALID);
716 
717   key = grl_registry_lookup_metadata_key (registry, key_name);
718   value_type = G_VALUE_TYPE (value);
719 
720   if (key == GRL_METADATA_KEY_INVALID) {
721     GRL_DEBUG ("%s is not a registered metadata-key", key_name);
722     key = grl_registry_register_metadata_key_for_type (registry, key_name, value_type, bind_key);
723   } else {
724     GType key_type = grl_registry_lookup_metadata_key_type (registry, key);
725     if (!g_value_type_transformable (value_type, key_type)) {
726       GRL_WARNING ("Value type %s can't be set to existing metadata-key of type %s",
727                    g_type_name (value_type),
728                    g_type_name (key_type));
729       return GRL_METADATA_KEY_INVALID;
730     }
731   }
732 
733   return key;
734 }
735 
736 static void
key_id_handler_init(struct KeyIDHandler * handler)737 key_id_handler_init (struct KeyIDHandler *handler)
738 {
739   const gchar *null_string = NULL;
740   handler->string_to_id = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
741   handler->id_to_string = g_array_new (FALSE, /* zero terminated */
742                                        TRUE,  /* zero-initialised */
743                                        sizeof (const gchar *));
744   /* We want indices in ->id_to_string to start from 1, so we add a NULL entry
745    * for GRL_METADATA_KEY_INVALID (i.e. 0) */
746   g_array_insert_val (handler->id_to_string,
747                       GRL_METADATA_KEY_INVALID,
748                       null_string);
749 }
750 
751 static void
key_id_handler_free(struct KeyIDHandler * handler)752 key_id_handler_free (struct KeyIDHandler *handler)
753 {
754   g_hash_table_unref (handler->string_to_id);
755   g_array_unref (handler->id_to_string);
756 }
757 
758 static
key_id_handler_get_key(struct KeyIDHandler * handler,const gchar * key_name)759 GrlKeyID key_id_handler_get_key (struct KeyIDHandler *handler, const gchar *key_name)
760 {
761   gpointer val = g_hash_table_lookup (handler->string_to_id, key_name);
762   if (val == NULL)
763     return GRL_METADATA_KEY_INVALID;
764 
765   return GRLPOINTER_TO_KEYID (val);
766 }
767 
768 static const gchar *
key_id_handler_get_name(struct KeyIDHandler * handler,GrlKeyID key)769 key_id_handler_get_name (struct KeyIDHandler *handler, GrlKeyID key)
770 {
771   if (key < handler->id_to_string->len)
772     return g_array_index (handler->id_to_string, const gchar *, key);
773 
774   return NULL;
775 }
776 
777 /*
778  * key_id_handler_add:
779  * @handler: the handler
780  * @key: a specific key for system keys, or GRL_METADATA_KEY_INVALID for it to
781  * be assigned
782  * @name: the name of the key.
783  *
784  * Add a new key<->name correspondence.
785  *
786  * Returns: the new key number, or GRL_METADATA_KEY_INVALID if the key could
787  * not be created (typically if @key or @name is already registered).
788  */
789 static GrlKeyID
key_id_handler_add(struct KeyIDHandler * handler,GrlKeyID key,const gchar * name)790 key_id_handler_add (struct KeyIDHandler *handler, GrlKeyID key, const gchar *name)
791 {
792   GrlKeyID _key = key;
793 
794   if (_key == GRL_METADATA_KEY_INVALID) {
795     /* existing keys go from 1 to (id_to_string->len - 1), so the next
796      * available key is id_to_string->len, which will be incremented by
797      * g_array_insert_val() */
798     _key = handler->id_to_string->len;
799   }
800 
801   if (NULL != key_id_handler_get_name (handler, _key)) {
802     GRL_WARNING ("Cannot register %d:%s because key is already defined as %s",
803                  _key, name, key_id_handler_get_name (handler, _key));
804     return GRL_METADATA_KEY_INVALID;
805   } else if ( GRL_METADATA_KEY_INVALID != key_id_handler_get_key (handler, name)) {
806     /* _key or name is already in use! */
807     GRL_WARNING ("Cannot register %d:%s because name is already registered with key %d",
808                  _key, name, key_id_handler_get_key (handler, name));
809     return GRL_METADATA_KEY_INVALID;
810   } else {
811     /* name_copy is shared between handler->id_to_string and
812      * handler->string_to_id */
813     gchar *name_copy = g_strdup (name);
814 
815     if (_key >= handler->id_to_string->len)
816       g_array_set_size (handler->id_to_string, _key + 1);
817 
818     /* yes, g_array_index() is a macro that give you an lvalue */
819     g_array_index (handler->id_to_string, const gchar *, _key) = name_copy;
820     g_hash_table_insert (handler->string_to_id,
821                          name_copy, GRLKEYID_TO_POINTER (_key));
822   }
823 
824   return _key;
825 
826 }
827 
828 static GList *
key_id_handler_get_all_keys(struct KeyIDHandler * handler)829 key_id_handler_get_all_keys (struct KeyIDHandler *handler)
830 {
831   return g_hash_table_get_values (handler->string_to_id);
832 }
833 
834 #define CHECK_NUMERIC_PARAM_SPEC_LIMITS(is_type, cast_type, a, b) {     \
835   if (is_type) {                                                        \
836     if ((cast_type(a))->maximum != (cast_type(b))->maximum ||           \
837         (cast_type(a))->minimum != (cast_type(b))->minimum ||           \
838         (cast_type(a))->default_value != (cast_type(b))->default_value) \
839       return FALSE;                                                     \
840     return TRUE;                                                        \
841   }                                                                     \
842 }
843 
844 /* @curr: The current spec we have
845  * @new: The spec to match
846  *
847  * Returns: true if specs are the same, false otherwise.
848  */
849 static gboolean
param_spec_is_equal(GParamSpec * cur,GParamSpec * new)850 param_spec_is_equal (GParamSpec *cur,
851                      GParamSpec *new)
852 {
853   GType ctype = G_PARAM_SPEC_TYPE (cur);
854 
855   if (ctype != G_PARAM_SPEC_TYPE (new))
856     return FALSE;
857 
858   CHECK_NUMERIC_PARAM_SPEC_LIMITS ((ctype == G_TYPE_PARAM_INT),
859                                    G_PARAM_SPEC_INT, cur, new);
860   CHECK_NUMERIC_PARAM_SPEC_LIMITS ((ctype == G_TYPE_PARAM_LONG),
861                                    G_PARAM_SPEC_LONG, cur, new);
862   CHECK_NUMERIC_PARAM_SPEC_LIMITS ((ctype == G_TYPE_PARAM_INT64),
863                                    G_PARAM_SPEC_INT64, cur, new);
864   CHECK_NUMERIC_PARAM_SPEC_LIMITS ((ctype == G_TYPE_PARAM_CHAR),
865                                    G_PARAM_SPEC_CHAR, cur, new);
866   CHECK_NUMERIC_PARAM_SPEC_LIMITS ((ctype == G_TYPE_PARAM_UINT),
867                                    G_PARAM_SPEC_UINT, cur, new);
868   CHECK_NUMERIC_PARAM_SPEC_LIMITS ((ctype == G_TYPE_PARAM_ULONG),
869                                    G_PARAM_SPEC_ULONG, cur, new);
870   CHECK_NUMERIC_PARAM_SPEC_LIMITS ((ctype == G_TYPE_PARAM_UINT64),
871                                    G_PARAM_SPEC_UINT64, cur, new);
872   CHECK_NUMERIC_PARAM_SPEC_LIMITS ((ctype == G_TYPE_PARAM_UCHAR),
873                                    G_PARAM_SPEC_UCHAR, cur, new);
874   CHECK_NUMERIC_PARAM_SPEC_LIMITS ((ctype == G_TYPE_PARAM_FLOAT),
875                                    G_PARAM_SPEC_FLOAT, cur, new);
876   CHECK_NUMERIC_PARAM_SPEC_LIMITS ((ctype == G_TYPE_PARAM_DOUBLE),
877                                    G_PARAM_SPEC_DOUBLE, cur, new);
878   if (ctype == G_TYPE_PARAM_STRING) {
879     GParamSpecString *c = G_PARAM_SPEC_STRING (cur);
880     GParamSpecString *n = G_PARAM_SPEC_STRING (new);
881     return (g_strcmp0 (c->default_value, n->default_value) == 0);
882   } else if (ctype == G_TYPE_PARAM_ENUM) {
883     GParamSpecEnum *c = G_PARAM_SPEC_ENUM (cur);
884     GParamSpecEnum *n = G_PARAM_SPEC_ENUM (new);
885     if (c->default_value != n->default_value ||
886         cur->value_type != new->value_type) {
887       GRL_DEBUG ("%s differ (values: %d and %d) (types: %s and %s)",
888                  g_type_name (ctype), c->default_value, n->default_value,
889                  g_type_name (cur->value_type), g_type_name (new->value_type));
890       return FALSE;
891     }
892   } else if (ctype == G_TYPE_PARAM_FLAGS) {
893     GParamSpecFlags *c = G_PARAM_SPEC_FLAGS (cur);
894     GParamSpecFlags *n = G_PARAM_SPEC_FLAGS (new);
895     if (c->default_value != n->default_value ||
896         cur->value_type != new->value_type) {
897       GRL_DEBUG ("%s differ (values: %d and %d) (types: %s and %s)",
898                  g_type_name (ctype), c->default_value, n->default_value,
899                  g_type_name (cur->value_type), g_type_name (new->value_type));
900       return FALSE;
901     }
902   } else if (ctype == G_TYPE_PARAM_BOOLEAN) {
903     GParamSpecBoolean *c = G_PARAM_SPEC_BOOLEAN (cur);
904     GParamSpecBoolean *n = G_PARAM_SPEC_BOOLEAN (new);
905     if (c->default_value != n->default_value) {
906       GRL_DEBUG ("%s type differ: %s != %s", g_type_name (ctype),
907                  g_type_name (cur->value_type), g_type_name (new->value_type));
908       return FALSE;
909     }
910   } else if (ctype == G_TYPE_PARAM_BOXED || ctype == G_TYPE_PARAM_OBJECT) {
911     if (cur->value_type != new->value_type) {
912       GRL_DEBUG ("%s type differ: %s != %s", g_type_name (ctype),
913                  g_type_name (cur->value_type), g_type_name (new->value_type));
914       return FALSE;
915     }
916   } else {
917     g_warn_if_reached();
918     return FALSE;
919   }
920 
921   return TRUE;
922 }
923 
924 #undef CHECK_NUMERIC_PARAM_SPEC_LIMITS
925 
926 static void
shutdown_plugin(GrlPlugin * plugin)927 shutdown_plugin (GrlPlugin *plugin)
928 {
929   GRL_DEBUG ("Unloading plugin '%s'", grl_plugin_get_id (plugin));
930   grl_plugin_unload (plugin);
931 
932   if (grl_plugin_get_module (plugin)) {
933     g_module_close (grl_plugin_get_module (plugin));
934     grl_plugin_set_module (plugin, NULL);
935   }
936 }
937 
938 /* ================ PRIVATE API ================ */
939 
940 /*
941  * grl_registry_restrict_plugins:
942  * @registry: the registry instance
943  * @plugins: a @NULL-terminated array of plugins identifiers
944  *
945  * Restrict the plugins that application sees to this list.
946  *
947  * Other plugins will not be available for the application, unless it uses
948  * directly #grl_registry_load_plugin() function.
949  **/
950 void
grl_registry_restrict_plugins(GrlRegistry * registry,gchar ** plugins)951 grl_registry_restrict_plugins (GrlRegistry *registry,
952                                gchar **plugins)
953 {
954   g_return_if_fail (GRL_IS_REGISTRY (registry));
955   g_return_if_fail (plugins);
956 
957   /* Free previous list */
958   if (registry->priv->allowed_plugins) {
959     g_slist_free_full (registry->priv->allowed_plugins, g_free);
960     registry->priv->allowed_plugins = NULL;
961   }
962 
963   while (*plugins) {
964     registry->priv->allowed_plugins = g_slist_prepend (registry->priv->allowed_plugins,
965                                                        g_strdup (*plugins));
966     plugins++;
967   }
968 }
969 
970 /*
971  * grl_registry_shutdown:
972  * @registry: the registry instance
973  *
974  * Frees all the resources in the registry and the registry itself.
975  **/
976 void
grl_registry_shutdown(GrlRegistry * registry)977 grl_registry_shutdown (GrlRegistry *registry)
978 {
979   GHashTableIter iter;
980   GList *each_key;
981   GList *related_keys = NULL;
982   GrlPlugin *plugin = NULL;
983   GrlSource *source = NULL;
984 
985   if (registry->priv->plugins) {
986     g_hash_table_iter_init (&iter, registry->priv->plugins);
987     while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &plugin)) {
988       shutdown_plugin (plugin);
989     }
990     g_clear_pointer (&registry->priv->plugins, g_hash_table_unref);
991   }
992 
993   if (registry->priv->sources) {
994     g_hash_table_iter_init (&iter, registry->priv->sources);
995     while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &source)) {
996       g_object_unref (source);
997     }
998     g_clear_pointer (&registry->priv->sources, g_hash_table_unref);
999   }
1000 
1001   g_clear_pointer (&registry->priv->ranks, g_hash_table_unref);
1002   g_clear_pointer (&registry->priv->configs, g_hash_table_unref);
1003 
1004   /* We need to free this table with care. Several keys can be pointing to the
1005      same value, so we need to ensure that we only free the value once */
1006   if (registry->priv->related_keys) {
1007     while (TRUE) {
1008       g_hash_table_iter_init (&iter, registry->priv->related_keys);
1009       if (!g_hash_table_iter_next (&iter, NULL, (gpointer *) &related_keys)) {
1010         break;
1011       }
1012       /* This will invalidate the iterator */
1013       for (each_key = related_keys; each_key; each_key = g_list_next (each_key)) {
1014         g_hash_table_remove (registry->priv->related_keys, GRLKEYID_TO_POINTER (each_key->data));
1015       }
1016       g_list_free (related_keys);
1017     }
1018     g_clear_pointer (&registry->priv->related_keys, g_hash_table_unref);
1019   }
1020 
1021   g_slist_free_full (registry->priv->plugins_dir, (GDestroyNotify) g_free);
1022   g_slist_free_full (registry->priv->allowed_plugins, (GDestroyNotify) g_free);
1023 
1024   key_id_handler_free (&registry->priv->key_id_handler);
1025   g_clear_pointer (&registry->priv->system_keys, g_hash_table_unref);
1026 
1027   g_object_unref (registry);
1028 }
1029 
1030 /* ================ PUBLIC API ================ */
1031 
1032 /**
1033  * grl_registry_get_default:
1034  *
1035  * As the registry is designed to work as a singleton, this
1036  * method is in charge of creating the only instance or
1037  * returned it if it is already in memory.
1038  *
1039  * Returns: (transfer none): a new or an already created instance of the registry.
1040  *
1041  * It is NOT MT-safe
1042  *
1043  * Since: 0.2.0
1044  */
1045 GrlRegistry *
grl_registry_get_default(void)1046 grl_registry_get_default (void)
1047 {
1048   static GrlRegistry *registry = NULL;
1049 
1050   if (!registry) {
1051     registry = g_object_new (GRL_TYPE_REGISTRY, NULL);
1052     g_object_add_weak_pointer (G_OBJECT (registry), (gpointer *) &registry);
1053   }
1054 
1055   return registry;
1056 }
1057 
1058 /**
1059  * grl_registry_register_source:
1060  * @registry: the registry instance
1061  * @plugin: the plugin which owns the source
1062  * @source: (transfer full): the source to register
1063  * @error: error return location or @NULL to ignore
1064  *
1065  * Register a @source in the @registry with the given @plugin information
1066  *
1067  * Returns: %TRUE if success, %FALSE% otherwise.
1068  *
1069  * Since: 0.2.0
1070  */
1071 gboolean
grl_registry_register_source(GrlRegistry * registry,GrlPlugin * plugin,GrlSource * source,GError ** error)1072 grl_registry_register_source (GrlRegistry *registry,
1073                               GrlPlugin *plugin,
1074                               GrlSource *source,
1075                               GError **error)
1076 {
1077   gchar *id;
1078 
1079   g_return_val_if_fail (GRL_IS_REGISTRY (registry), FALSE);
1080   g_return_val_if_fail (GRL_IS_PLUGIN (plugin), FALSE);
1081   g_return_val_if_fail (GRL_IS_SOURCE (source), FALSE);
1082 
1083   g_object_get (source, "source-id", &id, NULL);
1084   GRL_DEBUG ("New source available: '%s'", id);
1085 
1086   /* Take ownership of the source */
1087   g_object_ref_sink (source);
1088   g_object_unref (source);
1089 
1090   /* Do not free id, since g_hash_table_insert does not copy,
1091      it will be freed when removed from the hash table */
1092   g_hash_table_insert (registry->priv->sources, id, source);
1093 
1094   /* Set the plugin as owner of source */
1095   g_object_set (source, "plugin", plugin, NULL);
1096 
1097   /* Set source rank */
1098   set_source_rank (registry, source);
1099 
1100   /* Update whether it should be invisible */
1101   update_source_visibility (registry, source);
1102 
1103   if (!SOURCE_IS_INVISIBLE(source))
1104     g_signal_emit (registry, registry_signals[SIG_SOURCE_ADDED], 0, source);
1105 
1106   return TRUE;
1107 }
1108 
1109 /**
1110  * grl_registry_unregister_source:
1111  * @registry: the registry instance
1112  * @source: the source to unregister
1113  * @error: error return location or @NULL to ignore
1114  *
1115  * Removes the @source from the @registry hash table
1116  *
1117  * Returns: %TRUE if success, %FALSE% otherwise.
1118  *
1119  * Since: 0.2.0
1120  */
1121 gboolean
grl_registry_unregister_source(GrlRegistry * registry,GrlSource * source,GError ** error)1122 grl_registry_unregister_source (GrlRegistry *registry,
1123                                 GrlSource *source,
1124                                 GError **error)
1125 {
1126   gchar *id;
1127   gboolean ret = TRUE;
1128 
1129   g_return_val_if_fail (GRL_IS_REGISTRY (registry), FALSE);
1130   g_return_val_if_fail (GRL_IS_SOURCE (source), FALSE);
1131 
1132   g_object_get (source, "source-id", &id, NULL);
1133   GRL_DEBUG ("Unregistering source '%s'", id);
1134 
1135   if (g_hash_table_remove (registry->priv->sources, id)) {
1136     GRL_DEBUG ("source '%s' is no longer available", id);
1137     g_signal_emit (registry, registry_signals[SIG_SOURCE_REMOVED], 0, source);
1138     g_object_unref (source);
1139   } else {
1140     GRL_WARNING ("source '%s' not found", id);
1141     g_set_error (error,
1142                  GRL_CORE_ERROR,
1143                  GRL_CORE_ERROR_UNREGISTER_SOURCE_FAILED,
1144                  _("Source with id “%s” was not found"), id);
1145     ret = FALSE;
1146   }
1147 
1148   g_free (id);
1149   return ret;
1150 }
1151 
1152 /**
1153  * grl_registry_add_directory:
1154  * @registry: the registry instance
1155  * @path: a path with plugins
1156  *
1157  * Set this path as part of default paths to load plugins.
1158  *
1159  * Since: 0.2.0
1160  **/
1161 void
grl_registry_add_directory(GrlRegistry * registry,const gchar * path)1162 grl_registry_add_directory (GrlRegistry *registry,
1163                             const gchar *path)
1164 {
1165   g_return_if_fail (GRL_IS_REGISTRY (registry));
1166   g_return_if_fail (path);
1167 
1168   /* Use append instead of prepend so plugins are loaded in the same order as
1169      they were added */
1170   registry->priv->plugins_dir = g_slist_append (registry->priv->plugins_dir,
1171                                                 g_strdup (path));
1172   registry->priv->all_plugins_preloaded = FALSE;
1173 }
1174 
1175 static GrlPlugin *
grl_registry_prepare_plugin_from_desc(GrlRegistry * registry,GrlPluginDescriptor * plugin_desc)1176 grl_registry_prepare_plugin_from_desc (GrlRegistry *registry,
1177                                        GrlPluginDescriptor *plugin_desc)
1178 {
1179   GrlPlugin *plugin;
1180 
1181   if (!plugin_desc->init ||
1182       !plugin_desc->id) {
1183     GRL_WARNING ("Plugin descriptor is not valid");
1184     return NULL;
1185   }
1186 
1187   plugin = g_object_new (GRL_TYPE_PLUGIN, NULL);
1188   grl_plugin_set_id (plugin, plugin_desc->id);
1189   grl_plugin_set_filename (plugin, plugin_desc->id);
1190 
1191   grl_plugin_set_load_func (plugin, plugin_desc->init);
1192   grl_plugin_set_unload_func (plugin, plugin_desc->deinit);
1193   grl_plugin_set_register_keys_func (plugin, plugin_desc->register_keys);
1194 
1195   /* Insert plugin ID as part of plugin information */
1196   grl_plugin_set_module_name (plugin, plugin_desc->id);
1197 
1198   return plugin;
1199 }
1200 
1201 static GrlPlugin *
grl_registry_prepare_plugin(GrlRegistry * registry,const gchar * library_filename,GError ** error)1202 grl_registry_prepare_plugin (GrlRegistry *registry,
1203                              const gchar *library_filename,
1204                              GError **error)
1205 {
1206   GModule *module;
1207   GrlPluginDescriptor *plugin_desc;
1208   GrlPlugin *plugin;
1209 
1210   g_return_val_if_fail (GRL_IS_REGISTRY (registry), FALSE);
1211 
1212   module = g_module_open (library_filename, G_MODULE_BIND_LOCAL);
1213   if (!module) {
1214     GRL_WARNING ("Failed to open module: %s", g_module_error ());
1215     g_set_error (error,
1216                  GRL_CORE_ERROR,
1217                  GRL_CORE_ERROR_LOAD_PLUGIN_FAILED,
1218                  _("Failed to load plugin from %s"), library_filename);
1219     return NULL;
1220   }
1221 
1222   if (!g_module_symbol (module, "GRL_PLUGIN_DESCRIPTOR", (gpointer) &plugin_desc)) {
1223     GRL_WARNING ("Plugin descriptor not found in '%s'", library_filename);
1224     g_set_error (error,
1225                  GRL_CORE_ERROR,
1226                  GRL_CORE_ERROR_LOAD_PLUGIN_FAILED,
1227                  _("Invalid plugin file %s"), library_filename);
1228     g_module_close (module);
1229     return NULL;
1230   }
1231 
1232   if (!plugin_desc->init ||
1233       !plugin_desc->id) {
1234     GRL_WARNING ("Plugin descriptor is not valid: '%s'", library_filename);
1235     g_set_error (error,
1236                  GRL_CORE_ERROR,
1237                  GRL_CORE_ERROR_LOAD_PLUGIN_FAILED,
1238                  _("“%s” is not a valid plugin file"), library_filename);
1239     g_module_close (module);
1240     return NULL;
1241   }
1242 
1243   /* Check if plugin is preloaded; if not, then create one */
1244   plugin = g_hash_table_lookup (registry->priv->plugins,
1245                                 plugin_desc->id);
1246 
1247   if (plugin) {
1248     g_module_close (module);
1249     /* Check if the existent plugin is precisely this same plugin */
1250     if (g_strcmp0 (grl_plugin_get_filename (plugin), library_filename) == 0) {
1251       return plugin;
1252     } else {
1253       GRL_WARNING ("Plugin '%s' already exists", library_filename);
1254       g_set_error (error,
1255                    GRL_CORE_ERROR,
1256                    GRL_CORE_ERROR_LOAD_PLUGIN_FAILED,
1257                    _("Plugin “%s” already exists"), library_filename);
1258       return NULL;
1259     }
1260   }
1261 
1262   /* Check if plugin is allowed */
1263   if (registry->priv->allowed_plugins &&
1264       !g_slist_find_custom (registry->priv->allowed_plugins,
1265                             plugin_desc->id,
1266                             (GCompareFunc) g_strcmp0)) {
1267     GRL_DEBUG ("Plugin '%s' not allowed; skipping", plugin_desc->id);
1268     g_module_close (module);
1269     return NULL;
1270   }
1271 
1272   plugin = g_object_new (GRL_TYPE_PLUGIN, NULL);
1273   grl_plugin_set_desc (plugin, plugin_desc);
1274   grl_plugin_set_module (plugin, module);
1275   grl_plugin_set_filename (plugin, library_filename);
1276 
1277   /* Make plugin resident */
1278   g_module_make_resident (module);
1279 
1280   g_hash_table_insert (registry->priv->plugins, g_strdup (plugin_desc->id), plugin);
1281 
1282   /* Register custom keys */
1283   grl_plugin_register_keys (plugin);
1284 
1285   return plugin;
1286 }
1287 
1288 /**
1289  * grl_registry_activate_all_plugins:
1290  * @registry: the registry instace
1291  *
1292  * Activate all the plugins loaded.
1293  *
1294  * Returns: %TRUE if some plugin has been activated
1295  *
1296  * Since: 0.3.0
1297  **/
1298 gboolean
grl_registry_activate_all_plugins(GrlRegistry * registry)1299 grl_registry_activate_all_plugins (GrlRegistry *registry)
1300 {
1301   GList *all_plugins;
1302   GList *l;
1303   gboolean plugin_activated = FALSE;
1304 
1305   g_return_val_if_fail (GRL_IS_REGISTRY (registry), FALSE);
1306 
1307   all_plugins = g_hash_table_get_values (registry->priv->plugins);
1308   for (l = all_plugins; l; l = l->next) {
1309     GrlPlugin *plugin = l->data;
1310     plugin_activated |= activate_plugin (registry, plugin, NULL);
1311   }
1312   g_list_free (all_plugins);
1313 
1314   return plugin_activated;
1315 }
1316 
1317 /**
1318  * grl_registry_load_plugin:
1319  * @registry: the registry instance
1320  * @library_filename: the path to the so file
1321  * @error: error return location or @NULL to ignore
1322  *
1323  * Loads a module from shared object file stored in @path
1324  *
1325  * Returns: %TRUE if the module is loaded correctly
1326  *
1327  * Since: 0.2.0
1328  */
1329 gboolean
grl_registry_load_plugin(GrlRegistry * registry,const gchar * library_filename,GError ** error)1330 grl_registry_load_plugin (GrlRegistry *registry,
1331                           const gchar *library_filename,
1332                           GError **error)
1333 {
1334   GrlPlugin *plugin;
1335 
1336   plugin = grl_registry_prepare_plugin (registry, library_filename, error);
1337   if (!plugin)
1338     return FALSE;
1339 
1340   return register_keys_plugin (registry, plugin, error);
1341 }
1342 
1343 /**
1344  * grl_registry_load_plugin_from_desc: (skip)
1345  * @registry: the registry instance
1346  * @plugin_desc: the #GrlPluginDescriptor for the plugin
1347  * @error: error return location or @NULL to ignore
1348  *
1349  * Loads the grilo plugin defined by @plugin_desc. This is
1350  * used to load plugins that aren't shared libraries, and are
1351  * built into applications.
1352  *
1353  * <example>
1354  *   Minimal example for loading a builtin plugin, in C.
1355  *   <programlisting>
1356  *     static GrlPluginDescriptor descriptor = {
1357  *       .plugin_id = "grl-example",
1358  *       .plugin_init = grl_example_plugin_init,
1359  *     };
1360  *
1361  *     grl_registry_load_plugin_from_desc (registry, &descriptor, &error);
1362  *   </programlisting>
1363  * </example>
1364  *
1365  * Returns: %TRUE if the plugin is initialised correctly
1366  *
1367  * Since: 0.3.0
1368  */
1369 gboolean
grl_registry_load_plugin_from_desc(GrlRegistry * registry,GrlPluginDescriptor * plugin_desc,GError ** error)1370 grl_registry_load_plugin_from_desc (GrlRegistry *registry,
1371                                     GrlPluginDescriptor *plugin_desc,
1372                                     GError **error)
1373 {
1374   GrlPlugin *plugin;
1375 
1376   plugin = grl_registry_prepare_plugin_from_desc (registry, plugin_desc);
1377   if (!plugin)
1378     return FALSE;
1379 
1380   return register_keys_plugin (registry, plugin, error) &&
1381          activate_plugin (registry, plugin, error);
1382 }
1383 
1384 /**
1385  * grl_registry_load_plugin_directory:
1386  * @registry: the registry instance
1387  * @path: the path to the directory
1388  * @error: error return location or @NULL to ignore
1389  *
1390  * Loads a set of modules from directory in @path which contains
1391  * a group shared object files.
1392  *
1393  * Returns: %TRUE if the directory is valid.
1394  *
1395  * Since: 0.2.0
1396  */
1397 gboolean
grl_registry_load_plugin_directory(GrlRegistry * registry,const gchar * path,GError ** error)1398 grl_registry_load_plugin_directory (GrlRegistry *registry,
1399                                     const gchar *path,
1400                                     GError **error)
1401 {
1402   GDir *dir;
1403   GError *dir_error = NULL;
1404   GrlPlugin *plugin;
1405   const gchar *entry;
1406   gboolean plugin_loaded = FALSE;
1407   gchar *filename;
1408 
1409   g_return_val_if_fail (GRL_IS_REGISTRY (registry), FALSE);
1410   g_return_val_if_fail (path, FALSE);
1411 
1412   dir = g_dir_open (path, 0, &dir_error);
1413   if (!dir) {
1414     GRL_WARNING ("Could not open directory '%s': %s",
1415                  path,
1416                  dir_error->message);
1417     g_set_error (error,
1418                  GRL_CORE_ERROR,
1419                  GRL_CORE_ERROR_LOAD_PLUGIN_FAILED,
1420                  _("Invalid path %s"), path);
1421     g_error_free (dir_error);
1422     return FALSE;
1423   }
1424 
1425   while ((entry = g_dir_read_name (dir)) != NULL) {
1426     filename = g_build_filename (path, entry, NULL);
1427     if (!g_str_has_suffix (filename, "." G_MODULE_SUFFIX)) {
1428       g_free (filename);
1429       continue;
1430     }
1431 
1432     plugin = grl_registry_prepare_plugin (registry, filename, NULL);
1433     plugin_loaded |= (plugin != NULL);
1434     g_free (filename);
1435   }
1436   g_dir_close (dir);
1437 
1438   return plugin_loaded;
1439 }
1440 
1441 /**
1442  * grl_registry_load_all_plugins:
1443  * @registry: the registry instance
1444  * @activate: %TRUE if plugins must be activated after loading
1445  * @error: error return location or @NULL to ignore
1446  *
1447  * Load all the modules available in the default directory path.
1448  *
1449  * The default directory path can be changed through the environment
1450  * variable %GRL_PLUGIN_PATH and it can contain several paths separated
1451  * by ":"
1452  *
1453  * Returns: %FALSE% is all the configured plugin paths are invalid,
1454  * %TRUE% otherwise.
1455  *
1456  * Since: 0.2.0
1457  */
1458 gboolean
grl_registry_load_all_plugins(GrlRegistry * registry,gboolean activate,GError ** error)1459 grl_registry_load_all_plugins (GrlRegistry *registry,
1460                                gboolean activate,
1461                                GError **error)
1462 {
1463   GSList *plugin_dir;
1464   gboolean loaded_one;
1465 
1466   g_return_val_if_fail (GRL_IS_REGISTRY (registry), TRUE);
1467 
1468   /* Preload all plugins */
1469   if (!registry->priv->all_plugins_preloaded) {
1470     for (plugin_dir = registry->priv->plugins_dir;
1471          plugin_dir;
1472          plugin_dir = g_slist_next (plugin_dir)) {
1473       grl_registry_load_plugin_directory (registry,
1474                                           plugin_dir->data,
1475                                           NULL);
1476     }
1477     registry->priv->all_plugins_preloaded = TRUE;
1478   }
1479 
1480   if (activate) {
1481     loaded_one = grl_registry_activate_all_plugins (registry);
1482 
1483     if (!loaded_one) {
1484       g_set_error (error,
1485                    GRL_CORE_ERROR,
1486                    GRL_CORE_ERROR_LOAD_PLUGIN_FAILED,
1487                    _("All configured plugin paths are invalid"));
1488     }
1489 
1490     return loaded_one;
1491   } else {
1492     return TRUE;
1493   }
1494 }
1495 
1496 /**
1497  * grl_registry_activate_plugin_by_id:
1498  * @registry: the registry instance
1499  * @plugin_id: plugin identifier
1500  * @error: error return location or @NULL to ignore
1501  *
1502  * Activates plugin identified by @plugin_id.
1503  *
1504  * Returns: %TRUE if the plugin is loaded correctly
1505  *
1506  * Since: 0.3.0
1507  **/
1508 gboolean
grl_registry_activate_plugin_by_id(GrlRegistry * registry,const gchar * plugin_id,GError ** error)1509 grl_registry_activate_plugin_by_id (GrlRegistry *registry,
1510                                     const gchar *plugin_id,
1511                                     GError **error)
1512 {
1513   GrlPlugin *plugin;
1514   gboolean is_loaded;
1515 
1516   g_return_val_if_fail (GRL_IS_REGISTRY (registry), FALSE);
1517   g_return_val_if_fail (plugin_id, FALSE);
1518 
1519 
1520   /* Check if plugin is available */
1521   plugin = g_hash_table_lookup (registry->priv->plugins, plugin_id);
1522   if (!plugin) {
1523     GRL_WARNING ("Plugin '%s' not available", plugin_id);
1524     g_set_error (error,
1525                  GRL_CORE_ERROR,
1526                  GRL_CORE_ERROR_LOAD_PLUGIN_FAILED,
1527                  _("Plugin “%s” not available"), plugin_id);
1528     return FALSE;
1529   }
1530 
1531   /* Check if plugin is already loaded */
1532   g_object_get (plugin, "loaded", &is_loaded, NULL);
1533   if (is_loaded) {
1534     GRL_WARNING ("Plugin '%s' is already loaded", plugin_id);
1535     g_set_error (error,
1536                  GRL_CORE_ERROR,
1537                  GRL_CORE_ERROR_LOAD_PLUGIN_FAILED,
1538                  _("Plugin “%s” is already loaded"), plugin_id);
1539     return FALSE;
1540   }
1541 
1542   /* activate plugin */
1543   return activate_plugin (registry, plugin, error);
1544 }
1545 
1546 /**
1547  * grl_registry_lookup_source:
1548  * @registry: the registry instance
1549  * @source_id: the id of a source
1550  *
1551  * This function will search and retrieve a source given its identifier.
1552  *
1553  * Returns: (transfer none): The source found.
1554  *
1555  * Since: 0.2.0
1556  */
1557 GrlSource *
grl_registry_lookup_source(GrlRegistry * registry,const gchar * source_id)1558 grl_registry_lookup_source (GrlRegistry *registry,
1559                             const gchar *source_id)
1560 {
1561   GrlSource *source;
1562 
1563   g_return_val_if_fail (GRL_IS_REGISTRY (registry), NULL);
1564   g_return_val_if_fail (source_id != NULL, NULL);
1565 
1566   source = (GrlSource *) g_hash_table_lookup (registry->priv->sources,
1567                                               source_id);
1568   if (source && !SOURCE_IS_INVISIBLE(source))
1569     return source;
1570   return NULL;
1571 }
1572 
1573 /**
1574  * grl_registry_get_sources:
1575  * @registry: the registry instance
1576  * @ranked: whether the returned list shall be returned ordered by rank
1577  *
1578  * This function will return all the available sources in the @registry.
1579  *
1580  * If @ranked is %TRUE, the source list will be ordered by rank.
1581  *
1582  * Returns: (element-type GrlSource) (transfer container): a #GList of
1583  * available #GrlSource<!-- -->s. The content of the list should not be
1584  * modified or freed. Use g_list_free() when done using the list.
1585  *
1586  * Since: 0.2.0
1587  */
1588 GList *
grl_registry_get_sources(GrlRegistry * registry,gboolean ranked)1589 grl_registry_get_sources (GrlRegistry *registry,
1590                           gboolean ranked)
1591 {
1592   GHashTableIter iter;
1593   GList *source_list = NULL;
1594   GrlSource *current_source;
1595 
1596   g_return_val_if_fail (GRL_IS_REGISTRY (registry), NULL);
1597 
1598   g_hash_table_iter_init (&iter, registry->priv->sources);
1599   while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &current_source)) {
1600     if (!SOURCE_IS_INVISIBLE(current_source))
1601       source_list = g_list_prepend (source_list, current_source);
1602   }
1603 
1604   if (ranked) {
1605     source_list = g_list_sort (source_list, (GCompareFunc) compare_by_rank);
1606   }
1607 
1608   return source_list;
1609 }
1610 
1611 /**
1612  * grl_registry_get_sources_by_operations:
1613  * @registry: the registry instance
1614  * @ops: a bitwise mangle of the requested operations.
1615  * @ranked: whether the returned list shall be returned ordered by rank
1616  *
1617  * Give an array of all the available sources in the @registry capable of
1618  * perform the operations requested in @ops.
1619  *
1620  * If @ranked is %TRUE, the source list will be ordered by rank.
1621  *
1622  * Returns: (element-type GrlSource) (transfer container): a #GList of
1623  * available #GrlSource<!-- -->s. The content of the list should not be
1624  * modified or freed. Use g_list_free() when done using the list.
1625  *
1626  * Since: 0.2.0
1627  */
1628 GList *
grl_registry_get_sources_by_operations(GrlRegistry * registry,GrlSupportedOps ops,gboolean ranked)1629 grl_registry_get_sources_by_operations (GrlRegistry *registry,
1630                                         GrlSupportedOps ops,
1631                                         gboolean ranked)
1632 {
1633   GHashTableIter iter;
1634   GList *source_list = NULL;
1635   GrlSource *source;
1636 
1637   g_return_val_if_fail (GRL_IS_REGISTRY (registry), NULL);
1638 
1639   g_hash_table_iter_init (&iter, registry->priv->sources);
1640   while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &source)) {
1641     GrlSupportedOps source_ops;
1642     source_ops =
1643       grl_source_supported_operations (source);
1644     if ((source_ops & ops) == ops &&
1645         !SOURCE_IS_INVISIBLE(source)) {
1646       source_list = g_list_prepend (source_list, source);
1647     }
1648   }
1649 
1650   if (ranked) {
1651     source_list = g_list_sort (source_list, compare_by_rank);
1652   }
1653 
1654   return source_list;
1655 }
1656 
1657 /**
1658  * grl_registry_lookup_plugin:
1659  * @registry: the registry instance
1660  * @plugin_id: the id of a plugin
1661  *
1662  * This function will search and retrieve a plugin given its identifier.
1663  *
1664  * Returns: (transfer none): The plugin found
1665  *
1666  * Since: 0.2.0
1667  **/
1668 GrlPlugin *
grl_registry_lookup_plugin(GrlRegistry * registry,const gchar * plugin_id)1669 grl_registry_lookup_plugin (GrlRegistry *registry,
1670                             const gchar *plugin_id)
1671 {
1672   g_return_val_if_fail (GRL_IS_REGISTRY (registry), NULL);
1673   g_return_val_if_fail (plugin_id, NULL);
1674 
1675   return (GrlPlugin *) g_hash_table_lookup (registry->priv->plugins,
1676                                             plugin_id);
1677 }
1678 
1679 /**
1680  * grl_registry_get_plugins:
1681  * @registry: the registry instance
1682  * @only_loaded: whether the returned list shall include only loaded plugins
1683  *
1684  * This function will return all the available plugins in the @registry.
1685  *
1686  * If @only_loaded is %TRUE, the plugin list will contain only plugins that are
1687  * loaded.
1688  *
1689  * Returns: (element-type GrlPlugin) (transfer container): a #GList of
1690  * available #GrlPlugin<!-- -->s. The content of the list should not be modified
1691  * or freed. Use g_list_free() when done using the list.
1692  *
1693  * Since: 0.2.0
1694  **/
1695 GList *
grl_registry_get_plugins(GrlRegistry * registry,gboolean only_loaded)1696 grl_registry_get_plugins (GrlRegistry *registry,
1697                           gboolean only_loaded)
1698 {
1699   GList *plugin_list = NULL;
1700   GHashTableIter iter;
1701   GrlPlugin *current_plugin;
1702   gboolean is_loaded;
1703 
1704   g_return_val_if_fail (GRL_IS_REGISTRY (registry), NULL);
1705 
1706   if (only_loaded) {
1707     g_hash_table_iter_init (&iter, registry->priv->plugins);
1708     while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &current_plugin)) {
1709       g_object_get (current_plugin, "loaded", &is_loaded, NULL);
1710       if (is_loaded) {
1711         plugin_list = g_list_prepend (plugin_list, current_plugin);
1712       }
1713     }
1714   } else {
1715     plugin_list = g_hash_table_get_values (registry->priv->plugins);
1716   }
1717 
1718   return plugin_list;
1719 }
1720 
1721 /**
1722  * grl_registry_unload_plugin:
1723  * @registry: the registry instance
1724  * @plugin_id: the identifier of the plugin
1725  * @error: error return location or @NULL to ignore
1726  *
1727  * Unload from memory a module identified by @plugin_id. This means call the
1728  * module's deinit function.
1729  *
1730  * Returns: %TRUE% on success.
1731  *
1732  * Since: 0.2.0
1733  */
1734 gboolean
grl_registry_unload_plugin(GrlRegistry * registry,const gchar * plugin_id,GError ** error)1735 grl_registry_unload_plugin (GrlRegistry *registry,
1736                             const gchar *plugin_id,
1737                             GError **error)
1738 {
1739   GrlPlugin *plugin;
1740   GList *sources = NULL;
1741   GList *sources_iter;
1742 
1743   GRL_DEBUG ("%s: %s", __FUNCTION__, plugin_id);
1744 
1745   g_return_val_if_fail (GRL_IS_REGISTRY (registry), FALSE);
1746   g_return_val_if_fail (plugin_id != NULL, FALSE);
1747 
1748   /* First check the plugin is valid  */
1749   plugin = g_hash_table_lookup (registry->priv->plugins, plugin_id);
1750   if (!plugin) {
1751     GRL_WARNING ("Could not deinit plugin '%s'. Plugin not found.", plugin_id);
1752     g_set_error (error,
1753                  GRL_CORE_ERROR,
1754                  GRL_CORE_ERROR_UNLOAD_PLUGIN_FAILED,
1755                  _("Plugin not found: “%s”"), plugin_id);
1756     return FALSE;
1757   }
1758 
1759   /* Second, shut down any sources spawned by this plugin */
1760   GRL_DEBUG ("Shutting down sources spawned by '%s'", plugin_id);
1761   sources = grl_registry_get_sources (registry, FALSE);
1762 
1763   for (sources_iter = sources; sources_iter;
1764       sources_iter = g_list_next (sources_iter)) {
1765     GrlSource *source = GRL_SOURCE (sources_iter->data);
1766     if (grl_source_get_plugin (source) == plugin) {
1767       grl_registry_unregister_source (registry, source, NULL);
1768     }
1769   }
1770   g_list_free (sources);
1771 
1772   /* Third, shut down the plugin */
1773   shutdown_plugin (plugin);
1774 
1775   return TRUE;
1776 }
1777 
1778 /**
1779  * grl_registry_register_metadata_key:
1780  * @registry: The plugin registry
1781  * @param_spec: (transfer full): The definition of the key to register
1782  * @bind_key: The key the new key is bind to, or #GRL_METADATA_KEY_INVALID if it is not bound.
1783  * @error: error return location or @NULL to ignore
1784  *
1785  * Registers a new metadata key, creating a relation between the new key and
1786  * @bind_key.
1787  *
1788  * Two keys are related when the values of both keys are somehow related.
1789  *
1790  * One example of a relation would be the one between the URI of a media
1791  * resource and its mime-type: they are both tied together and one does not make
1792  * sense without the other.
1793  *
1794  * Relations between keys allow the framework to provide all the data that is
1795  * somehow related when any of the related keys are requested.
1796 
1797  * Returns: The #GrlKeyID registered.
1798  *
1799  * Since: 0.3.0
1800  */
1801 GrlKeyID
grl_registry_register_metadata_key(GrlRegistry * registry,GParamSpec * param_spec,GrlKeyID bind_key,GError ** error)1802 grl_registry_register_metadata_key (GrlRegistry *registry,
1803                                     GParamSpec *param_spec,
1804                                     GrlKeyID bind_key,
1805                                     GError **error)
1806 {
1807   GrlKeyID key;
1808 
1809   key = grl_registry_register_metadata_key_full (registry,
1810                                                  param_spec,
1811                                                  GRL_METADATA_KEY_INVALID,
1812                                                  bind_key,
1813                                                  error);
1814 
1815   if (key != GRL_METADATA_KEY_INVALID) {
1816     g_signal_emit (registry, registry_signals[SIG_METADATA_KEY_ADDED],
1817                    0,
1818                    grl_metadata_key_get_name (key));
1819   }
1820 
1821   return key;
1822 }
1823 
1824 /*
1825  * grl_registry_register_metadata_key_system:
1826  *
1827  * This is an internal method only meant to be used to register core
1828  * keys.
1829  *
1830  * For internal use. Plugin developers should use
1831  * grl_registry_register_metadata_key().
1832  */
1833 GrlKeyID
grl_registry_register_metadata_key_system(GrlRegistry * registry,GParamSpec * param_spec,GrlKeyID key,GrlKeyID bind_key,GError ** error)1834 grl_registry_register_metadata_key_system (GrlRegistry *registry,
1835                                            GParamSpec *param_spec,
1836                                            GrlKeyID key,
1837                                            GrlKeyID bind_key,
1838                                            GError **error)
1839 {
1840   GrlKeyID registered_key;
1841 
1842   registered_key = grl_registry_register_metadata_key_full (registry,
1843                                                             param_spec,
1844                                                             key,
1845                                                             bind_key,
1846                                                             error);
1847 
1848   return registered_key;
1849 }
1850 
1851 /**
1852  * grl_registry_lookup_metadata_key:
1853  * @registry: the registry instance
1854  * @key_name: the key name
1855  *
1856  * Look up for the metadata key with name @key_name.
1857  *
1858  * Returns: The metadata key, or GRL_METADATA_KEY_INVALID if not found
1859  *
1860  * Since: 0.2.0
1861  */
1862 GrlKeyID
grl_registry_lookup_metadata_key(GrlRegistry * registry,const gchar * key_name)1863 grl_registry_lookup_metadata_key (GrlRegistry *registry,
1864                                   const gchar *key_name)
1865 {
1866   g_return_val_if_fail (GRL_IS_REGISTRY (registry), 0);
1867   g_return_val_if_fail (key_name, 0);
1868 
1869   return key_id_handler_get_key (&registry->priv->key_id_handler, key_name);
1870 }
1871 
1872 /**
1873  * grl_registry_lookup_metadata_key_name:
1874  * @registry: the registry instance
1875  * @key: a metadata key
1876  *
1877  * Returns @key name.
1878  *
1879  * Returns: metadata key name, or @NULL if not found
1880  *
1881  * Since: 0.2.0
1882  */
1883 const gchar *
grl_registry_lookup_metadata_key_name(GrlRegistry * registry,GrlKeyID key)1884 grl_registry_lookup_metadata_key_name (GrlRegistry *registry,
1885                                        GrlKeyID key)
1886 {
1887   g_return_val_if_fail (GRL_IS_REGISTRY (registry), 0);
1888 
1889   return key_id_handler_get_name (&registry->priv->key_id_handler, key);
1890 }
1891 
1892 /**
1893  * grl_registry_lookup_metadata_key_desc:
1894  * @registry: the registry instance
1895  * @key: a metadata key
1896  *
1897  * Returns @key description.
1898  *
1899  * Returns: metadata key description, or @NULL if not found
1900  *
1901  * Since: 0.2.0
1902  */
1903 const gchar *
grl_registry_lookup_metadata_key_desc(GrlRegistry * registry,GrlKeyID key)1904 grl_registry_lookup_metadata_key_desc (GrlRegistry *registry,
1905                                        GrlKeyID key)
1906 {
1907   const gchar *key_name;
1908   GParamSpec *key_pspec;
1909 
1910   g_return_val_if_fail (GRL_IS_REGISTRY (registry), 0);
1911 
1912   key_name = key_id_handler_get_name (&registry->priv->key_id_handler, key);
1913   if (!key_name) {
1914     return NULL;
1915   }
1916   key_pspec = g_hash_table_lookup (registry->priv->system_keys, key_name);
1917 
1918   if (key_pspec) {
1919     return g_param_spec_get_blurb (key_pspec);
1920   } else {
1921     return NULL;
1922   }
1923 }
1924 
1925 /**
1926  * grl_registry_lookup_metadata_key_type:
1927  * @registry: the registry instance
1928  * @key: a metadata key
1929  *
1930  * Returns @key expected value type.
1931  *
1932  * Returns: metadata key type, or @G_TYPE_INVALID if not found
1933  *
1934  * Since: 0.2.0
1935  */
1936 GType
grl_registry_lookup_metadata_key_type(GrlRegistry * registry,GrlKeyID key)1937 grl_registry_lookup_metadata_key_type (GrlRegistry *registry,
1938                                        GrlKeyID key)
1939 {
1940   const gchar *key_name;
1941   GParamSpec *key_pspec;
1942 
1943   g_return_val_if_fail (GRL_IS_REGISTRY (registry), 0);
1944 
1945   key_name = key_id_handler_get_name (&registry->priv->key_id_handler, key);
1946   if (!key_name) {
1947     return G_TYPE_INVALID;
1948   }
1949   key_pspec = g_hash_table_lookup (registry->priv->system_keys, key_name);
1950 
1951   if (key_pspec) {
1952     return G_PARAM_SPEC_VALUE_TYPE (key_pspec);
1953   } else {
1954     return G_TYPE_INVALID;
1955   }
1956 }
1957 
1958 /**
1959  * grl_registry_metadata_key_validate:
1960  * @registry: the registry instance
1961  * @key: a metadata key
1962  * @value: value to be validate
1963  *
1964  * Validates @value content complies with the key specification. That is, it has
1965  * the expected type, and value are within the range specified in key (for
1966  * integer values).
1967  *
1968  * Returns: %TRUE if complies
1969  *
1970  * Since: 0.2.0
1971  **/
1972 gboolean
grl_registry_metadata_key_validate(GrlRegistry * registry,GrlKeyID key,GValue * value)1973 grl_registry_metadata_key_validate (GrlRegistry *registry,
1974                                     GrlKeyID key,
1975                                     GValue *value)
1976 {
1977   const gchar *key_name;
1978   GParamSpec *key_pspec;
1979 
1980   g_return_val_if_fail (GRL_IS_REGISTRY (registry), FALSE);
1981   g_return_val_if_fail (G_IS_VALUE (value), FALSE);
1982 
1983   key_name = key_id_handler_get_name (&registry->priv->key_id_handler, key);
1984   if (!key_name) {
1985     return FALSE;
1986   }
1987   key_pspec = g_hash_table_lookup (registry->priv->system_keys, key_name);
1988 
1989   if (key_pspec) {
1990     return !g_param_value_validate (key_pspec, value);
1991   } else {
1992     return FALSE;
1993   }
1994 }
1995 
1996 /**
1997  * grl_registry_lookup_metadata_key_relation:
1998  * @registry: the registry instance
1999  * @key: a metadata key
2000  *
2001  * Look up the list of keys that have a relation with @key.
2002  *
2003  * @key is included in that list.
2004  *
2005  * Returns: (element-type GrlKeyID) (transfer none): a #GList of
2006  * related keys, or @NULL if key is invalid.
2007  *
2008  * Since: 0.2.0
2009  **/
2010 const GList *
grl_registry_lookup_metadata_key_relation(GrlRegistry * registry,GrlKeyID key)2011 grl_registry_lookup_metadata_key_relation (GrlRegistry *registry,
2012                                            GrlKeyID key)
2013 {
2014   g_return_val_if_fail (GRL_IS_REGISTRY (registry), NULL);
2015 
2016   return g_hash_table_lookup (registry->priv->related_keys, GRLKEYID_TO_POINTER (key));
2017 }
2018 
2019 /**
2020  * grl_registry_get_metadata_keys:
2021  * @registry: the registry instance
2022  *
2023  * Returns a list with all registered keys in system.
2024  *
2025  * Returns: (transfer container) (element-type GrlKeyID): a #GList with all the available
2026  * #GrlKeyID<!-- -->s. The content of the list should not be modified or freed.
2027  * Use g_list_free() when done using the list.
2028  *
2029  * Since: 0.2.0
2030  **/
2031 GList *
grl_registry_get_metadata_keys(GrlRegistry * registry)2032 grl_registry_get_metadata_keys (GrlRegistry *registry)
2033 {
2034   return key_id_handler_get_all_keys (&registry->priv->key_id_handler);
2035 }
2036 
2037 /**
2038  * grl_registry_add_config:
2039  * @registry: the registry instance
2040  * @config: (transfer full): a configuration set
2041  * @error: error return location or @NULL to ignore
2042  *
2043  * Add a configuration for a plugin/source.
2044  *
2045  * Returns: %TRUE on success
2046  *
2047  * Since: 0.2.0
2048  */
2049 gboolean
grl_registry_add_config(GrlRegistry * registry,GrlConfig * config,GError ** error)2050 grl_registry_add_config (GrlRegistry *registry,
2051                          GrlConfig *config,
2052                          GError **error)
2053 {
2054   gchar *plugin_id;
2055   GList *configs = NULL;
2056 
2057   g_return_val_if_fail (config != NULL, FALSE);
2058   g_return_val_if_fail (GRL_IS_REGISTRY (registry), FALSE);
2059 
2060   plugin_id = grl_config_get_plugin (config);
2061   if (!plugin_id) {
2062     GRL_WARNING ("Plugin configuration missed plugin information, ignoring...");
2063     g_set_error (error,
2064                  GRL_CORE_ERROR,
2065                  GRL_CORE_ERROR_CONFIG_FAILED,
2066                  _("Plugin configuration does not contain “plugin-id” reference"));
2067     return FALSE;
2068   }
2069 
2070   configs = g_hash_table_lookup (registry->priv->configs, plugin_id);
2071   if (configs) {
2072     /* Notice that we are using g_list_append on purpose to avoid
2073        having to insert again in the hash table */
2074     configs = g_list_append (configs, config);
2075     g_free (plugin_id);
2076   } else {
2077     configs = g_list_prepend (configs, config);
2078     g_hash_table_insert (registry->priv->configs,
2079 			 (gpointer) plugin_id,
2080 			 configs);
2081   }
2082 
2083   return TRUE;
2084 }
2085 
2086 static void
get_plugin_and_source_from_group_name(const gchar * group_name,gchar ** plugin_name,gchar ** source_name)2087 get_plugin_and_source_from_group_name (const gchar  *group_name,
2088                                        gchar       **plugin_name,
2089                                        gchar       **source_name)
2090 {
2091   gchar *plugin = g_strdup (group_name);
2092   gchar **arr;
2093 
2094   *plugin_name = *source_name = NULL;
2095 
2096   plugin = g_strstrip (plugin);
2097   arr = g_strsplit (plugin, " ", 2);
2098   g_free (plugin);
2099 
2100   *plugin_name = g_strstrip (arr[0]);
2101 
2102   if (arr[1] != NULL)
2103     *source_name = g_strstrip (arr[1]);
2104 
2105   g_free (arr);
2106 }
2107 
2108 static void
add_config_from_keyfile(GKeyFile * keyfile,GrlRegistry * registry)2109 add_config_from_keyfile (GKeyFile    *keyfile,
2110 			 GrlRegistry *registry)
2111 {
2112   GrlConfig *config;
2113   gchar **key;
2114   gchar **keys;
2115   gchar **groupname;
2116   gchar **plugins;
2117   gchar *value;
2118 
2119   /* Look up for defined plugins */
2120   plugins = g_key_file_get_groups (keyfile, NULL);
2121   for (groupname = plugins; *groupname; groupname++) {
2122     gchar *plugin_name, *source_name;
2123 
2124     get_plugin_and_source_from_group_name (*groupname, &plugin_name, &source_name);
2125 
2126     config = grl_config_new (plugin_name, source_name);
2127 
2128     /* Look up configuration keys for this plugin */
2129     keys = g_key_file_get_keys (keyfile, *groupname, NULL, NULL);
2130     for (key = keys; *key; key++) {
2131       value = g_key_file_get_string (keyfile, *groupname, *key, NULL);
2132       if (value) {
2133         GRL_DEBUG ("Config found: %s : %s : %s", plugin_name,
2134                    source_name != NULL ? source_name : plugin_name,
2135                    *key);
2136         grl_config_set_string (config, *key, value);
2137         g_free (value);
2138       }
2139     }
2140     grl_registry_add_config (registry, config, NULL);
2141     g_strfreev (keys);
2142     g_free (source_name);
2143     g_free (plugin_name);
2144   }
2145   g_strfreev (plugins);
2146 }
2147 
2148 /**
2149  * grl_registry_add_config_from_file:
2150  * @registry: the registry instance
2151  * @config_file: a key-value file containing the configuration
2152  * @error: error return location or @NULL to ignore
2153  *
2154  * Load plugin configurations from a .ini-like config file.
2155  *
2156  * Returns: %TRUE on success
2157  *
2158  * Since: 0.2.0
2159  **/
2160 gboolean
grl_registry_add_config_from_file(GrlRegistry * registry,const gchar * config_file,GError ** error)2161 grl_registry_add_config_from_file (GrlRegistry *registry,
2162                                    const gchar *config_file,
2163                                    GError **error)
2164 {
2165   GError *load_error = NULL;
2166   GKeyFile *keyfile;
2167 
2168   g_return_val_if_fail (GRL_IS_REGISTRY (registry), FALSE);
2169   g_return_val_if_fail (config_file, FALSE);
2170 
2171   keyfile = g_key_file_new ();
2172 
2173   if (g_key_file_load_from_file (keyfile,
2174                                  config_file,
2175                                  G_KEY_FILE_NONE,
2176                                  &load_error)) {
2177     add_config_from_keyfile (keyfile, registry);
2178     g_key_file_free (keyfile);
2179     return TRUE;
2180   } else {
2181     GRL_WARNING ("Unable to load configuration. %s", load_error->message);
2182     g_set_error_literal (error,
2183                          GRL_CORE_ERROR,
2184                          GRL_CORE_ERROR_CONFIG_LOAD_FAILED,
2185                          load_error->message);
2186     g_error_free (load_error);
2187     g_key_file_free (keyfile);
2188     return FALSE;
2189   }
2190 }
2191 
2192 /**
2193  * grl_registry_add_config_from_resource:
2194  * @registry: the registry instance
2195  * @resource_path: a key-value file containing the configuration
2196  * @error: error return location or @NULL to ignore
2197  *
2198  * Load plugin configurations from a .ini-like resource file.
2199  *
2200  * Returns: %TRUE on success
2201  *
2202  * Since: 0.2.8
2203  **/
2204 gboolean
grl_registry_add_config_from_resource(GrlRegistry * registry,const gchar * resource_path,GError ** error)2205 grl_registry_add_config_from_resource (GrlRegistry *registry,
2206                                        const gchar *resource_path,
2207                                        GError **error)
2208 {
2209   GError *load_error = NULL;
2210   GKeyFile *keyfile = NULL;
2211   GBytes *bytes;
2212   gboolean ret = FALSE;
2213 
2214   g_return_val_if_fail (GRL_IS_REGISTRY (registry), FALSE);
2215   g_return_val_if_fail (resource_path, FALSE);
2216 
2217   bytes = g_resources_lookup_data (resource_path, 0, error);
2218   if (bytes == NULL)
2219     goto bail;
2220 
2221   keyfile = g_key_file_new ();
2222 
2223   if (g_key_file_load_from_data (keyfile,
2224                                  g_bytes_get_data (bytes, NULL),
2225                                  g_bytes_get_size (bytes),
2226                                  G_KEY_FILE_NONE,
2227                                  &load_error)) {
2228     add_config_from_keyfile (keyfile, registry);
2229     ret = TRUE;
2230   } else {
2231     GRL_WARNING ("Unable to load configuration. %s", load_error->message);
2232     g_set_error_literal (error,
2233                          GRL_CORE_ERROR,
2234                          GRL_CORE_ERROR_CONFIG_LOAD_FAILED,
2235                          load_error->message);
2236     g_error_free (load_error);
2237   }
2238 
2239 bail:
2240   g_clear_pointer (&keyfile, g_key_file_free);
2241   g_clear_pointer (&bytes, g_bytes_unref);
2242 
2243   return ret;
2244 }
2245 
2246 
2247 G_GNUC_INTERNAL gboolean
grl_registry_metadata_key_get_limits(GrlRegistry * registry,GrlKeyID key,GValue * min,GValue * max)2248 grl_registry_metadata_key_get_limits(GrlRegistry *registry,
2249                                      GrlKeyID key,
2250                                      GValue *min,
2251                                      GValue *max)
2252 {
2253   GParamSpec *key_pspec;
2254   const gchar *key_name;
2255   GType key_type;
2256 
2257   key_name = key_id_handler_get_name (&registry->priv->key_id_handler, key);
2258   if (!key_name) {
2259     return FALSE;
2260   }
2261 
2262   key_pspec = g_hash_table_lookup (registry->priv->system_keys, key_name);
2263   if (!key_pspec) {
2264     return FALSE;
2265   }
2266 
2267   key_type = G_PARAM_SPEC_VALUE_TYPE (key_pspec);
2268 
2269 #define CHECK_NUMERIC_AND_SET_VALUE_LIMITS(value_type, numeric_type, getter, cast_type) { \
2270   if (value_type == numeric_type) {                                                       \
2271     g_value_init (min, numeric_type);                                                     \
2272     g_value_init (max, numeric_type);                                                     \
2273     g_value_set_##getter (min, (cast_type (key_pspec))->minimum);                         \
2274     g_value_set_##getter (max, (cast_type (key_pspec))->maximum);                         \
2275     return TRUE;                                                                          \
2276   }                                                                                       \
2277 }
2278 
2279   CHECK_NUMERIC_AND_SET_VALUE_LIMITS (key_type, G_TYPE_INT, int, G_PARAM_SPEC_INT);
2280   CHECK_NUMERIC_AND_SET_VALUE_LIMITS (key_type, G_TYPE_LONG, long, G_PARAM_SPEC_LONG);
2281   CHECK_NUMERIC_AND_SET_VALUE_LIMITS (key_type, G_TYPE_INT64, int64, G_PARAM_SPEC_INT64);
2282   CHECK_NUMERIC_AND_SET_VALUE_LIMITS (key_type, G_TYPE_CHAR, schar, G_PARAM_SPEC_CHAR);
2283   CHECK_NUMERIC_AND_SET_VALUE_LIMITS (key_type, G_TYPE_UINT, uint, G_PARAM_SPEC_UINT);
2284   CHECK_NUMERIC_AND_SET_VALUE_LIMITS (key_type, G_TYPE_ULONG, ulong, G_PARAM_SPEC_ULONG);
2285   CHECK_NUMERIC_AND_SET_VALUE_LIMITS (key_type, G_TYPE_UINT64, uint64, G_PARAM_SPEC_UINT64);
2286   CHECK_NUMERIC_AND_SET_VALUE_LIMITS (key_type, G_TYPE_UCHAR, uchar, G_PARAM_SPEC_UCHAR);
2287   CHECK_NUMERIC_AND_SET_VALUE_LIMITS (key_type, G_TYPE_FLOAT, float, G_PARAM_SPEC_FLOAT);
2288   CHECK_NUMERIC_AND_SET_VALUE_LIMITS (key_type, G_TYPE_DOUBLE, double, G_PARAM_SPEC_DOUBLE);
2289   return FALSE;
2290 }
2291 
2292 G_GNUC_INTERNAL gboolean
grl_registry_metadata_key_clamp(GrlRegistry * registry,GrlKeyID key,GValue * min,GValue * value,GValue * max)2293 grl_registry_metadata_key_clamp(GrlRegistry *registry,
2294                                 GrlKeyID key,
2295                                 GValue *min,
2296                                 GValue *value,
2297                                 GValue *max)
2298 {
2299   const gchar *key_name;
2300 
2301   key_name = key_id_handler_get_name (&registry->priv->key_id_handler, key);
2302   if (key_name) {
2303     GParamSpec *key_pspec;
2304 
2305     key_pspec = g_hash_table_lookup (registry->priv->system_keys, key_name);
2306     if (key_pspec) {
2307       if (g_param_values_cmp(key_pspec, value, min) < 0) {
2308         GRL_DEBUG("reset value to min");
2309         g_value_transform(min, value);
2310         return TRUE;
2311       } else if (g_param_values_cmp(key_pspec, value, max) > 0) {
2312         GRL_DEBUG("reset value to max");
2313         g_value_transform(max, value);
2314         return TRUE;
2315       }
2316     }
2317   }
2318   return FALSE;
2319 }
2320