/*
* Copyright (C) 2010, 2011 Igalia S.L.
* Copyright (C) 2011 Intel Corporation.
*
* Contact: Iago Toral Quiroga <itoral@igalia.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; version 2.1 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
*/
/**
* SECTION:grl-registry
* @short_description: Grilo plugins loader and manager
* @see_also: #GrlPlugin, #GrlSource
*
* The registry holds the metadata of a set of plugins.
*
* The #GrlRegistry object is a list of plugins and some functions
* for dealing with them. Each #GrlPlugin is matched 1-1 with a file
* on disk, and may or may not be loaded a given time. There only can be
* a single instance of #GrlRegistry (singleton pattern).
*
* A #GrlPlugin can hold several data #GrlSource sources, and #GrlRegistry
* shall register each one of them.
*/
#include "grl-registry.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "grl-registry-priv.h"
#include "grl-plugin-priv.h"
#include "grl-log.h"
#include "grl-error.h"
#include <glib/gi18n-lib.h>
#include <string.h>
#include <gmodule.h>
#include <libxml/parser.h>
#define GRL_LOG_DOMAIN_DEFAULT registry_log_domain
GRL_LOG_DOMAIN(registry_log_domain);
#define XML_ROOT_ELEMENT_NAME "plugin"
#define GRL_PLUGIN_INFO_SUFFIX "xml"
#define GRL_PLUGIN_INFO_MODULE "module"
#define LOCAL_NET_TAG "net:local"
#define INTERNET_NET_TAG "net:internet"
#define SET_INVISIBLE_SOURCE(src, val) \
g_object_set_data(G_OBJECT(src), "invisible", GINT_TO_POINTER(val))
#define SOURCE_IS_INVISIBLE(src) \
GPOINTER_TO_INT(g_object_get_data(G_OBJECT(src), "invisible"))
/* GQuark-like implementation, where we manually assign the first IDs. */
struct KeyIDHandler {
GHashTable *string_to_id;
GArray *id_to_string;
gint last_id;
};
struct _GrlRegistryPrivate {
GHashTable *configs;
GHashTable *plugins;
GHashTable *sources;
GHashTable *related_keys;
GHashTable *system_keys;
GHashTable *ranks;
GSList *plugins_dir;
GSList *allowed_plugins;
gboolean all_plugins_preloaded;
struct KeyIDHandler key_id_handler;
GNetworkMonitor *netmon;
};
static void grl_registry_setup_ranks (GrlRegistry *registry);
static void key_id_handler_init (struct KeyIDHandler *handler);
static void key_id_handler_free (struct KeyIDHandler *handler);
static GrlKeyID key_id_handler_get_key (struct KeyIDHandler *handler,
const gchar *key_name);
static const gchar *key_id_handler_get_name (struct KeyIDHandler *handler,
GrlKeyID key);
static GrlKeyID key_id_handler_add (struct KeyIDHandler *handler,
GrlKeyID key, const gchar *key_name);
static gboolean param_spec_is_equal (GParamSpec *curr, GParamSpec *new);
static void shutdown_plugin (GrlPlugin *plugin);
static void configs_free (GList *configs);
static GrlPlugin *grl_registry_prepare_plugin (GrlRegistry *registry,
const gchar *library_filename,
GError **error);
/* ================ GrlRegistry GObject ================ */
enum {
SIG_SOURCE_ADDED,
SIG_SOURCE_REMOVED,
SIG_METADATA_KEY_ADDED,
SIG_LAST
};
static gint registry_signals[SIG_LAST];
G_DEFINE_TYPE_WITH_PRIVATE (GrlRegistry, grl_registry, G_TYPE_OBJECT);
static void
grl_registry_class_init (GrlRegistryClass *klass)
{
/**
* GrlRegistry::source-added:
* @registry: the registry
* @source: the source that has been added
*
* Signals that a source has been added to the registry.
*
* Since: 0.2.0
*/
registry_signals[SIG_SOURCE_ADDED] =
g_signal_new("source-added",
G_TYPE_FROM_CLASS(klass),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
0,
NULL,
NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1, GRL_TYPE_SOURCE);
/**
* GrlRegistry::source-removed:
* @registry: the registry
* @source: the source that has been removed
*
* Signals that a source has been removed from the registry.
*
* Since: 0.2.0
*/
registry_signals[SIG_SOURCE_REMOVED] =
g_signal_new("source-removed",
G_TYPE_FROM_CLASS(klass),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
0,
NULL,
NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE, 1, GRL_TYPE_SOURCE);
/**
* GrlRegistry::metadata-key-added:
* @registry: the registry
* @key: the name of the new key added
*
* Signals that a new metadata key has been registered.
*
* Since: 0.2.10
*/
registry_signals[SIG_METADATA_KEY_ADDED] =
g_signal_new("metadata-key-added",
G_TYPE_FROM_CLASS(klass),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
0,
NULL,
NULL,
g_cclosure_marshal_VOID__STRING,
G_TYPE_NONE, 1, G_TYPE_STRING);
}
static void
get_connectivity (GrlRegistry *registry,
GNetworkConnectivity *connectivity,
gboolean *network_available)
{
g_assert (connectivity != NULL);
g_assert (network_available != NULL);
if (g_getenv("GRL_NET_MOCKED") != NULL) {
GRL_DEBUG ("Mocked network, assuming network is available and connectivity "
"level is FULL");
*connectivity = G_NETWORK_CONNECTIVITY_FULL;
*network_available = TRUE;
} else {
g_object_get (G_OBJECT (registry->priv->netmon),
"connectivity", connectivity,
"network-available", network_available,
NULL);
GRL_DEBUG ("Connectivity level is %d, Network is %s",
*connectivity, *network_available ? "available" : "unavailable");
}
}
static void
network_changed_cb (GObject *gobject,
GParamSpec *pspec,
GrlRegistry *registry)
{
GNetworkConnectivity connectivity;
gboolean network_available;
GHashTableIter iter;
GrlSource *current_source;
GRL_DEBUG ("Network availability changed");
get_connectivity (registry, &connectivity, &network_available);
if (!network_available) {
g_hash_table_iter_init (&iter, registry->priv->sources);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) ¤t_source)) {
const char **tags = grl_source_get_tags (current_source);
if (!tags)
continue;
if ((g_strv_contains (tags, LOCAL_NET_TAG) ||
g_strv_contains (tags, INTERNET_NET_TAG)) &&
!SOURCE_IS_INVISIBLE(current_source)) {
GRL_DEBUG ("Network isn't available for '%s', hiding",
grl_source_get_id (current_source));
SET_INVISIBLE_SOURCE(current_source, TRUE);
g_signal_emit (registry, registry_signals[SIG_SOURCE_REMOVED], 0, current_source);
}
}
} else {
g_hash_table_iter_init (&iter, registry->priv->sources);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) ¤t_source)) {
const char **tags = grl_source_get_tags (current_source);
if (!tags)
continue;
if (g_strv_contains (tags, LOCAL_NET_TAG) &&
SOURCE_IS_INVISIBLE(current_source)) {
GRL_DEBUG ("Local network became available for '%s', showing",
grl_source_get_id (current_source));
SET_INVISIBLE_SOURCE(current_source, FALSE);
g_signal_emit (registry, registry_signals[SIG_SOURCE_ADDED], 0, current_source);
}
if (g_strv_contains (tags, INTERNET_NET_TAG) &&
connectivity == G_NETWORK_CONNECTIVITY_FULL &&
SOURCE_IS_INVISIBLE(current_source)) {
GRL_DEBUG ("Internet became available for '%s', showing",
grl_source_get_id (current_source));
SET_INVISIBLE_SOURCE(current_source, FALSE);
g_signal_emit (registry, registry_signals[SIG_SOURCE_ADDED], 0, current_source);
}
if (g_strv_contains (tags, INTERNET_NET_TAG) &&
connectivity != G_NETWORK_CONNECTIVITY_FULL &&
!SOURCE_IS_INVISIBLE(current_source)) {
GRL_DEBUG ("Internet became unavailable for '%s', hiding",
grl_source_get_id (current_source));
SET_INVISIBLE_SOURCE(current_source, TRUE);
g_signal_emit (registry, registry_signals[SIG_SOURCE_REMOVED], 0, current_source);
}
}
}
}
static void
grl_registry_init (GrlRegistry *registry)
{
registry->priv = grl_registry_get_instance_private (registry);
registry->priv->configs =
g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) configs_free);
registry->priv->plugins =
g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
registry->priv->sources =
g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
registry->priv->related_keys =
g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
registry->priv->system_keys =
g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) g_param_spec_unref);
registry->priv->netmon = g_network_monitor_get_default ();
g_signal_connect (G_OBJECT (registry->priv->netmon), "notify::connectivity",
G_CALLBACK (network_changed_cb), registry);
g_signal_connect (G_OBJECT (registry->priv->netmon), "notify::network-available",
G_CALLBACK (network_changed_cb), registry);
key_id_handler_init (®istry->priv->key_id_handler);
grl_registry_setup_ranks (registry);
}
/* ================ Utitilies ================ */
static void
configs_free (GList *configs)
{
g_list_free_full (configs, g_object_unref);
}
static void
update_source_visibility (GrlRegistry *registry,
GrlSource *source)
{
GNetworkConnectivity connectivity;
gboolean network_available;
const char **tags;
gboolean needs_local, needs_inet;
tags = grl_source_get_tags (source);
if (!tags)
return;
needs_local = g_strv_contains (tags, LOCAL_NET_TAG);
needs_inet = g_strv_contains (tags, INTERNET_NET_TAG);
if (!needs_local &&
!needs_inet)
return;
get_connectivity (registry, &connectivity, &network_available);
GRL_DEBUG ("Source %s needs %s %s%s",
grl_source_get_id (source),
needs_local ? "local network" : "",
needs_inet && needs_local ? " and " : "",
needs_inet ? "Internet" : "");
if (!network_available) {
if (needs_local || needs_inet) {
GRL_DEBUG ("Network isn't available for '%s', hiding",
grl_source_get_id (source));
SET_INVISIBLE_SOURCE(source, TRUE);
}
} else {
if (connectivity != G_NETWORK_CONNECTIVITY_FULL) {
if (needs_inet) {
GRL_DEBUG ("Internet isn't available for '%s', hiding",
grl_source_get_id (source));
SET_INVISIBLE_SOURCE(source, TRUE);
}
}
}
}
static void
config_source_rank (GrlRegistry *registry,
const gchar *source_id,
gint rank)
{
GRL_DEBUG ("Rank configuration, '%s:%d'", source_id, rank);
g_hash_table_insert (registry->priv->ranks,
g_strdup (source_id),
GINT_TO_POINTER (rank));
}
static void
set_source_rank (GrlRegistry *registry, GrlSource *source)
{
gint rank;
rank =
GPOINTER_TO_INT (g_hash_table_lookup (registry->priv->ranks,
grl_source_get_id (source)));
if (!rank) {
rank = GRL_RANK_DEFAULT;
}
g_object_set (source, "rank", rank, NULL);
GRL_DEBUG ("Source rank '%s' : %d", grl_source_get_id (source), rank);
}
static void
grl_registry_setup_ranks (GrlRegistry *registry)
{
const gchar *ranks_env;
gchar **rank_specs;
gchar **iter;
registry->priv->ranks = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, NULL);
ranks_env = g_getenv (GRL_PLUGIN_RANKS_VAR);
if (!ranks_env) {
GRL_DEBUG ("$%s is not set, using default ranks.", GRL_PLUGIN_RANKS_VAR);
return;
}
rank_specs = g_strsplit (ranks_env, ",", 0);
iter = rank_specs;
while (*iter) {
gchar **rank_info = g_strsplit (*iter, ":", 2);
if (rank_info[0] && rank_info[1]) {
gchar *tmp;
gchar *id = rank_info[0];
gchar *srank = rank_info[1];
gint rank = (gint) g_ascii_strtoll (srank, &tmp, 10);
if (*tmp != '\0') {
GRL_WARNING ("Incorrect ranking definition: '%s'. Skipping...", *iter);
} else {
config_source_rank (registry, id, rank);
}
} else {
GRL_WARNING ("Incorrect ranking definition: '%s'. Skipping...", *iter);
}
g_strfreev (rank_info);
iter++;
}
g_strfreev (rank_specs);
}
static gint
compare_by_rank (gconstpointer a,
gconstpointer b) {
gint rank_a;
gint rank_b;
rank_a = grl_source_get_rank (GRL_SOURCE (a));
rank_b = grl_source_get_rank (GRL_SOURCE (b));
return (rank_a < rank_b) - (rank_a > rank_b);
}
static gboolean
register_keys_plugin (GrlRegistry *registry,
GrlPlugin *plugin,
GError **error)
{
gboolean is_loaded;
/* Check if plugin is already loaded */
g_object_get (plugin, "loaded", &is_loaded, NULL);
if (is_loaded) {
GRL_WARNING ("Plugin is already loaded: '%s'", grl_plugin_get_id (plugin));
g_set_error (error,
GRL_CORE_ERROR,
GRL_CORE_ERROR_LOAD_PLUGIN_FAILED,
_("Plugin “%s” is already loaded"), grl_plugin_get_id (plugin));
return FALSE;
}
grl_plugin_register_keys (plugin);
return TRUE;
}
static gboolean
activate_plugin (GrlRegistry *registry,
GrlPlugin *plugin,
GError **error)
{
GList *plugin_configs;
plugin_configs = g_hash_table_lookup (registry->priv->configs,
grl_plugin_get_id (plugin));
if (!grl_plugin_load (plugin, plugin_configs)) {
GRL_DEBUG ("Failed to initialize plugin from %s. Check if plugin is well configured", grl_plugin_get_filename (plugin));
g_set_error (error,
GRL_CORE_ERROR,
GRL_CORE_ERROR_LOAD_PLUGIN_FAILED,
_("Failed to initialize plugin from %s"), grl_plugin_get_filename (plugin));
shutdown_plugin (plugin);
return FALSE;
}
GRL_DEBUG ("Loaded plugin '%s' from '%s'",
grl_plugin_get_id (plugin),
grl_plugin_get_filename (plugin));
return TRUE;
}
static GrlKeyID
grl_registry_register_metadata_key_full (GrlRegistry *registry,
GParamSpec *param_spec,
GrlKeyID key,
GrlKeyID bind_key,
GError **error)
{
GList *bound_partners;
GList *partner;
const gchar *key_name;
g_return_val_if_fail (GRL_IS_REGISTRY (registry), 0);
g_return_val_if_fail (G_IS_PARAM_SPEC (param_spec), 0);
GrlKeyID registered_key;
key_name = g_param_spec_get_name (param_spec);
registered_key = key_id_handler_get_key (®istry->priv->key_id_handler, key_name);
if (registered_key != GRL_METADATA_KEY_INVALID) {
GParamSpec *key_spec = g_hash_table_lookup (registry->priv->system_keys,
(gpointer) key_name);
if (param_spec_is_equal (key_spec, param_spec)) {
/* Key registered */
GRL_DEBUG ("metadata key '%s' already registered with same spec", key_name);
g_param_spec_unref (param_spec);
return registered_key;
} else {
GRL_WARNING ("metadata key '%s' already exists", key_name);
g_set_error (error,
GRL_CORE_ERROR,
GRL_CORE_ERROR_REGISTER_METADATA_KEY_FAILED,
_("Metadata key “%s” already registered in different format"),
key_name);
return GRL_METADATA_KEY_INVALID;
}
}
registered_key = key_id_handler_add (®istry->priv->key_id_handler, key, key_name);
if (registered_key == GRL_METADATA_KEY_INVALID) {
GRL_WARNING ("metadata key '%s' cannot be registered", key_name);
g_set_error (error,
GRL_CORE_ERROR,
GRL_CORE_ERROR_REGISTER_METADATA_KEY_FAILED,
_("Metadata key “%s” cannot be registered"),
key_name);
return GRL_METADATA_KEY_INVALID;
}
g_hash_table_insert (registry->priv->system_keys,
(gpointer) key_name,
param_spec);
if (bind_key == GRL_METADATA_KEY_INVALID) {
/* Key is only related to itself */
g_hash_table_insert (registry->priv->related_keys,
GRLKEYID_TO_POINTER (registered_key),
g_list_prepend (NULL,
GRLKEYID_TO_POINTER (registered_key)));
} else {
/* Add the new key to the partners */
bound_partners = g_hash_table_lookup (registry->priv->related_keys, GRLKEYID_TO_POINTER (bind_key));
bound_partners = g_list_append (bound_partners, GRLKEYID_TO_POINTER (registered_key));
for (partner = bound_partners;
partner;
partner = g_list_next (partner)) {
g_hash_table_insert (registry->priv->related_keys,
partner->data,
bound_partners);
}
}
return registered_key;
}
G_GNUC_INTERNAL GrlKeyID
grl_registry_register_metadata_key_for_type (GrlRegistry *registry,
const gchar *key_name,
GType type)
{
GParamSpec *spec;
switch (type) {
case G_TYPE_INT:
spec = g_param_spec_int (key_name,
key_name,
key_name,
0, G_MAXINT,
0,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
break;
case G_TYPE_INT64:
spec = g_param_spec_int64 (key_name,
key_name,
key_name,
-1, G_MAXINT64,
-1,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
break;
case G_TYPE_STRING:
spec = g_param_spec_string (key_name,
key_name,
key_name,
NULL,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
break;
case G_TYPE_BOOLEAN:
spec = g_param_spec_boolean (key_name,
key_name,
key_name,
FALSE,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
break;
case G_TYPE_FLOAT:
spec = g_param_spec_float (key_name,
key_name,
key_name,
0, G_MAXFLOAT,
0,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
break;
default:
if (type == G_TYPE_DATE_TIME) {
spec = g_param_spec_boxed (key_name,
key_name,
key_name,
G_TYPE_DATE_TIME,
G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
} else {
GRL_WARNING ("'%s' is being ignored as G_TYPE '%s' is not being handled",
key_name, G_VALUE_TYPE_NAME (type));
return GRL_METADATA_KEY_INVALID;
}
}
return grl_registry_register_metadata_key (registry, spec, GRL_METADATA_KEY_INVALID, NULL);
}
static void
key_id_handler_init (struct KeyIDHandler *handler)
{
const gchar *null_string = NULL;
handler->string_to_id = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
handler->id_to_string = g_array_new (FALSE, /* zero terminated */
TRUE, /* zero-initialised */
sizeof (const gchar *));
/* We want indices in ->id_to_string to start from 1, so we add a NULL entry
* for GRL_METADATA_KEY_INVALID (i.e. 0) */
g_array_insert_val (handler->id_to_string,
GRL_METADATA_KEY_INVALID,
null_string);
}
static void
key_id_handler_free (struct KeyIDHandler *handler)
{
g_hash_table_unref (handler->string_to_id);
g_array_unref (handler->id_to_string);
}
static
GrlKeyID key_id_handler_get_key (struct KeyIDHandler *handler, const gchar *key_name)
{
gpointer val = g_hash_table_lookup (handler->string_to_id, key_name);
if (val == NULL)
return GRL_METADATA_KEY_INVALID;
return GRLPOINTER_TO_KEYID (val);
}
static const gchar *
key_id_handler_get_name (struct KeyIDHandler *handler, GrlKeyID key)
{
if (key < handler->id_to_string->len)
return g_array_index (handler->id_to_string, const gchar *, key);
return NULL;
}
/*
* key_id_handler_add:
* @handler: the handler
* @key: a specific key for system keys, or GRL_METADATA_KEY_INVALID for it to
* be assigned
* @name: the name of the key.
*
* Add a new key<->name correspondence.
*
* Returns: the new key number, or GRL_METADATA_KEY_INVALID if the key could
* not be created (typically if @key or @name is already registered).
*/
static GrlKeyID
key_id_handler_add (struct KeyIDHandler *handler, GrlKeyID key, const gchar *name)
{
GrlKeyID _key = key;
if (_key == GRL_METADATA_KEY_INVALID) {
/* existing keys go from 1 to (id_to_string->len - 1), so the next
* available key is id_to_string->len, which will be incremented by
* g_array_insert_val() */
_key = handler->id_to_string->len;
}
if (NULL != key_id_handler_get_name (handler, _key)) {
GRL_WARNING ("Cannot register %d:%s because key is already defined as %s",
_key, name, key_id_handler_get_name (handler, _key));
return GRL_METADATA_KEY_INVALID;
} else if ( GRL_METADATA_KEY_INVALID != key_id_handler_get_key (handler, name)) {
/* _key or name is already in use! */
GRL_WARNING ("Cannot register %d:%s because name is already registered with key %d",
_key, name, key_id_handler_get_key (handler, name));
return GRL_METADATA_KEY_INVALID;
} else {
/* name_copy is shared between handler->id_to_string and
* handler->string_to_id */
gchar *name_copy = g_strdup (name);
if (_key >= handler->id_to_string->len)
g_array_set_size (handler->id_to_string, _key + 1);
/* yes, g_array_index() is a macro that give you an lvalue */
g_array_index (handler->id_to_string, const gchar *, _key) = name_copy;
g_hash_table_insert (handler->string_to_id,
name_copy, GRLKEYID_TO_POINTER (_key));
}
return _key;
}
static GList *
key_id_handler_get_all_keys (struct KeyIDHandler *handler)
{
return g_hash_table_get_values (handler->string_to_id);
}
#define CHECK_NUMERIC_PARAM_SPEC_LIMITS(is_type, cast_type, a, b) { \
if (is_type) { \
if ((cast_type(a))->maximum != (cast_type(b))->maximum || \
(cast_type(a))->minimum != (cast_type(b))->minimum || \
(cast_type(a))->default_value != (cast_type(b))->default_value) \
return FALSE; \
return TRUE; \
} \
}
/* @curr: The current spec we have
* @new: The spec to match
*
* Returns: true if specs are the same, false otherwise.
*/
static gboolean
param_spec_is_equal (GParamSpec *cur,
GParamSpec *new)
{
GType ctype = G_PARAM_SPEC_TYPE (cur);
if (ctype != G_PARAM_SPEC_TYPE (new))
return FALSE;
CHECK_NUMERIC_PARAM_SPEC_LIMITS ((ctype == G_TYPE_PARAM_INT),
G_PARAM_SPEC_INT, cur, new);
CHECK_NUMERIC_PARAM_SPEC_LIMITS ((ctype == G_TYPE_PARAM_LONG),
G_PARAM_SPEC_LONG, cur, new);
CHECK_NUMERIC_PARAM_SPEC_LIMITS ((ctype == G_TYPE_PARAM_INT64),
G_PARAM_SPEC_INT64, cur, new);
CHECK_NUMERIC_PARAM_SPEC_LIMITS ((ctype == G_TYPE_PARAM_CHAR),
G_PARAM_SPEC_CHAR, cur, new);
CHECK_NUMERIC_PARAM_SPEC_LIMITS ((ctype == G_TYPE_PARAM_UINT),
G_PARAM_SPEC_UINT, cur, new);
CHECK_NUMERIC_PARAM_SPEC_LIMITS ((ctype == G_TYPE_PARAM_ULONG),
G_PARAM_SPEC_ULONG, cur, new);
CHECK_NUMERIC_PARAM_SPEC_LIMITS ((ctype == G_TYPE_PARAM_UINT64),
G_PARAM_SPEC_UINT64, cur, new);
CHECK_NUMERIC_PARAM_SPEC_LIMITS ((ctype == G_TYPE_PARAM_UCHAR),
G_PARAM_SPEC_UCHAR, cur, new);
CHECK_NUMERIC_PARAM_SPEC_LIMITS ((ctype == G_TYPE_PARAM_FLOAT),
G_PARAM_SPEC_FLOAT, cur, new);
CHECK_NUMERIC_PARAM_SPEC_LIMITS ((ctype == G_TYPE_PARAM_DOUBLE),
G_PARAM_SPEC_DOUBLE, cur, new);
if (ctype == G_TYPE_PARAM_STRING) {
GParamSpecString *c = G_PARAM_SPEC_STRING (cur);
GParamSpecString *n = G_PARAM_SPEC_STRING (new);
return (g_strcmp0 (c->default_value, n->default_value) == 0);
} else if (ctype == G_TYPE_PARAM_ENUM) {
GParamSpecEnum *c = G_PARAM_SPEC_ENUM (cur);
GParamSpecEnum *n = G_PARAM_SPEC_ENUM (new);
if (c->default_value != n->default_value ||
cur->value_type != new->value_type) {
GRL_DEBUG ("%s differ (values: %d and %d) (types: %s and %s)",
g_type_name (ctype), c->default_value, n->default_value,
g_type_name (cur->value_type), g_type_name (new->value_type));
return FALSE;
}
} else if (ctype == G_TYPE_PARAM_FLAGS) {
GParamSpecFlags *c = G_PARAM_SPEC_FLAGS (cur);
GParamSpecFlags *n = G_PARAM_SPEC_FLAGS (new);
if (c->default_value != n->default_value ||
cur->value_type != new->value_type) {
GRL_DEBUG ("%s differ (values: %d and %d) (types: %s and %s)",
g_type_name (ctype), c->default_value, n->default_value,
g_type_name (cur->value_type), g_type_name (new->value_type));
return FALSE;
}
} else if (ctype == G_TYPE_PARAM_BOOLEAN) {
GParamSpecBoolean *c = G_PARAM_SPEC_BOOLEAN (cur);
GParamSpecBoolean *n = G_PARAM_SPEC_BOOLEAN (new);
if (c->default_value != n->default_value) {
GRL_DEBUG ("%s type differ: %s != %s", g_type_name (ctype),
g_type_name (cur->value_type), g_type_name (new->value_type));
return FALSE;
}
} else if (ctype == G_TYPE_PARAM_BOXED || ctype == G_TYPE_PARAM_OBJECT) {
if (cur->value_type != new->value_type) {
GRL_DEBUG ("%s type differ: %s != %s", g_type_name (ctype),
g_type_name (cur->value_type), g_type_name (new->value_type));
return FALSE;
}
} else {
g_warn_if_reached();
return FALSE;
}
return TRUE;
}
#undef CHECK_NUMERIC_PARAM_SPEC_LIMITS
static void
shutdown_plugin (GrlPlugin *plugin)
{
GRL_DEBUG ("Unloading plugin '%s'", grl_plugin_get_id (plugin));
grl_plugin_unload (plugin);
if (grl_plugin_get_module (plugin)) {
g_module_close (grl_plugin_get_module (plugin));
grl_plugin_set_module (plugin, NULL);
}
}
/* ================ PRIVATE API ================ */
/*
* grl_registry_restrict_plugins:
* @registry: the registry instance
* @plugins: a @NULL-terminated array of plugins identifiers
*
* Restrict the plugins that application sees to this list.
*
* Other plugins will not be available for the application, unless it uses
* directly #grl_registry_load_plugin() function.
**/
void
grl_registry_restrict_plugins (GrlRegistry *registry,
gchar **plugins)
{
g_return_if_fail (GRL_IS_REGISTRY (registry));
g_return_if_fail (plugins);
/* Free previous list */
if (registry->priv->allowed_plugins) {
g_slist_free_full (registry->priv->allowed_plugins, g_free);
registry->priv->allowed_plugins = NULL;
}
while (*plugins) {
registry->priv->allowed_plugins = g_slist_prepend (registry->priv->allowed_plugins,
g_strdup (*plugins));
plugins++;
}
}
/*
* grl_registry_shutdown:
* @registry: the registry instance
*
* Frees all the resources in the registry and the registry itself.
**/
void
grl_registry_shutdown (GrlRegistry *registry)
{
GHashTableIter iter;
GList *each_key;
GList *related_keys = NULL;
GrlPlugin *plugin = NULL;
GrlSource *source = NULL;
if (registry->priv->plugins) {
g_hash_table_iter_init (&iter, registry->priv->plugins);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &plugin)) {
shutdown_plugin (plugin);
}
g_clear_pointer (®istry->priv->plugins, g_hash_table_unref);
}
if (registry->priv->sources) {
g_hash_table_iter_init (&iter, registry->priv->sources);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &source)) {
g_object_unref (source);
}
g_clear_pointer (®istry->priv->sources, g_hash_table_unref);
}
g_clear_pointer (®istry->priv->ranks, g_hash_table_unref);
g_clear_pointer (®istry->priv->configs, g_hash_table_unref);
/* We need to free this table with care. Several keys can be pointing to the
same value, so we need to ensure that we only free the value once */
if (registry->priv->related_keys) {
while (TRUE) {
g_hash_table_iter_init (&iter, registry->priv->related_keys);
if (!g_hash_table_iter_next (&iter, NULL, (gpointer *) &related_keys)) {
break;
}
/* This will invalidate the iterator */
for (each_key = related_keys; each_key; each_key = g_list_next (each_key)) {
g_hash_table_remove (registry->priv->related_keys, GRLKEYID_TO_POINTER (each_key->data));
}
g_list_free (related_keys);
}
g_clear_pointer (®istry->priv->related_keys, g_hash_table_unref);
}
g_slist_free_full (registry->priv->plugins_dir, (GDestroyNotify) g_free);
g_slist_free_full (registry->priv->allowed_plugins, (GDestroyNotify) g_free);
key_id_handler_free (®istry->priv->key_id_handler);
g_clear_pointer (®istry->priv->system_keys, g_hash_table_unref);
g_object_unref (registry);
}
/* ================ PUBLIC API ================ */
/**
* grl_registry_get_default:
*
* As the registry is designed to work as a singleton, this
* method is in charge of creating the only instance or
* returned it if it is already in memory.
*
* Returns: (transfer none): a new or an already created instance of the registry.
*
* It is NOT MT-safe
*
* Since: 0.2.0
*/
GrlRegistry *
grl_registry_get_default (void)
{
static GrlRegistry *registry = NULL;
if (!registry) {
registry = g_object_new (GRL_TYPE_REGISTRY, NULL);
g_object_add_weak_pointer (G_OBJECT (registry), (gpointer *) ®istry);
}
return registry;
}
/**
* grl_registry_register_source:
* @registry: the registry instance
* @plugin: the plugin which owns the source
* @source: (transfer full): the source to register
* @error: error return location or @NULL to ignore
*
* Register a @source in the @registry with the given @plugin information
*
* Returns: %TRUE if success, %FALSE% otherwise.
*
* Since: 0.2.0
*/
gboolean
grl_registry_register_source (GrlRegistry *registry,
GrlPlugin *plugin,
GrlSource *source,
GError **error)
{
gchar *id;
g_return_val_if_fail (GRL_IS_REGISTRY (registry), FALSE);
g_return_val_if_fail (GRL_IS_PLUGIN (plugin), FALSE);
g_return_val_if_fail (GRL_IS_SOURCE (source), FALSE);
g_object_get (source, "source-id", &id, NULL);
GRL_DEBUG ("New source available: '%s'", id);
/* Take ownership of the source */
g_object_ref_sink (source);
g_object_unref (source);
/* Do not free id, since g_hash_table_insert does not copy,
it will be freed when removed from the hash table */
g_hash_table_insert (registry->priv->sources, id, source);
/* Set the plugin as owner of source */
g_object_set (source, "plugin", plugin, NULL);
/* Set source rank */
set_source_rank (registry, source);
/* Update whether it should be invisible */
update_source_visibility (registry, source);
if (!SOURCE_IS_INVISIBLE(source))
g_signal_emit (registry, registry_signals[SIG_SOURCE_ADDED], 0, source);
return TRUE;
}
/**
* grl_registry_unregister_source:
* @registry: the registry instance
* @source: the source to unregister
* @error: error return location or @NULL to ignore
*
* Removes the @source from the @registry hash table
*
* Returns: %TRUE if success, %FALSE% otherwise.
*
* Since: 0.2.0
*/
gboolean
grl_registry_unregister_source (GrlRegistry *registry,
GrlSource *source,
GError **error)
{
gchar *id;
gboolean ret = TRUE;
g_return_val_if_fail (GRL_IS_REGISTRY (registry), FALSE);
g_return_val_if_fail (GRL_IS_SOURCE (source), FALSE);
g_object_get (source, "source-id", &id, NULL);
GRL_DEBUG ("Unregistering source '%s'", id);
if (g_hash_table_remove (registry->priv->sources, id)) {
GRL_DEBUG ("source '%s' is no longer available", id);
g_signal_emit (registry, registry_signals[SIG_SOURCE_REMOVED], 0, source);
g_object_unref (source);
} else {
GRL_WARNING ("source '%s' not found", id);
g_set_error (error,
GRL_CORE_ERROR,
GRL_CORE_ERROR_UNREGISTER_SOURCE_FAILED,
_("Source with id “%s” was not found"), id);
ret = FALSE;
}
g_free (id);
return ret;
}
/**
* grl_registry_add_directory:
* @registry: the registry instance
* @path: a path with plugins
*
* Set this path as part of default paths to load plugins.
*
* Since: 0.2.0
**/
void
grl_registry_add_directory (GrlRegistry *registry,
const gchar *path)
{
g_return_if_fail (GRL_IS_REGISTRY (registry));
g_return_if_fail (path);
/* Use append instead of prepend so plugins are loaded in the same order as
they were added */
registry->priv->plugins_dir = g_slist_append (registry->priv->plugins_dir,
g_strdup (path));
registry->priv->all_plugins_preloaded = FALSE;
}
static GrlPlugin *
grl_registry_prepare_plugin_from_desc (GrlRegistry *registry,
GrlPluginDescriptor *plugin_desc)
{
GrlPlugin *plugin;
if (!plugin_desc->init ||
!plugin_desc->id) {
GRL_WARNING ("Plugin descriptor is not valid");
return NULL;
}
plugin = g_object_new (GRL_TYPE_PLUGIN, NULL);
grl_plugin_set_id (plugin, plugin_desc->id);
grl_plugin_set_filename (plugin, plugin_desc->id);
grl_plugin_set_load_func (plugin, plugin_desc->init);
grl_plugin_set_unload_func (plugin, plugin_desc->deinit);
grl_plugin_set_register_keys_func (plugin, plugin_desc->register_keys);
/* Insert plugin ID as part of plugin information */
grl_plugin_set_module_name (plugin, plugin_desc->id);
return plugin;
}
static GrlPlugin *
grl_registry_prepare_plugin (GrlRegistry *registry,
const gchar *library_filename,
GError **error)
{
GModule *module;
GrlPluginDescriptor *plugin_desc;
GrlPlugin *plugin;
g_return_val_if_fail (GRL_IS_REGISTRY (registry), FALSE);
module = g_module_open (library_filename, G_MODULE_BIND_LOCAL);
if (!module) {
GRL_WARNING ("Failed to open module: %s", g_module_error ());
g_set_error (error,
GRL_CORE_ERROR,
GRL_CORE_ERROR_LOAD_PLUGIN_FAILED,
_("Failed to load plugin from %s"), library_filename);
return NULL;
}
if (!g_module_symbol (module, "GRL_PLUGIN_DESCRIPTOR", (gpointer) &plugin_desc)) {
GRL_WARNING ("Plugin descriptor not found in '%s'", library_filename);
g_set_error (error,
GRL_CORE_ERROR,
GRL_CORE_ERROR_LOAD_PLUGIN_FAILED,
_("Invalid plugin file %s"), library_filename);
g_module_close (module);
return NULL;
}
if (!plugin_desc->init ||
!plugin_desc->id) {
GRL_WARNING ("Plugin descriptor is not valid: '%s'", library_filename);
g_set_error (error,
GRL_CORE_ERROR,
GRL_CORE_ERROR_LOAD_PLUGIN_FAILED,
_("“%s” is not a valid plugin file"), library_filename);
g_module_close (module);
return NULL;
}
/* Check if plugin is preloaded; if not, then create one */
plugin = g_hash_table_lookup (registry->priv->plugins,
plugin_desc->id);
if (plugin) {
g_module_close (module);
/* Check if the existent plugin is precisely this same plugin */
if (g_strcmp0 (grl_plugin_get_filename (plugin), library_filename) == 0) {
return plugin;
} else {
GRL_WARNING ("Plugin '%s' already exists", library_filename);
g_set_error (error,
GRL_CORE_ERROR,
GRL_CORE_ERROR_LOAD_PLUGIN_FAILED,
_("Plugin “%s” already exists"), library_filename);
return NULL;
}
}
/* Check if plugin is allowed */
if (registry->priv->allowed_plugins &&
!g_slist_find_custom (registry->priv->allowed_plugins,
plugin_desc->id,
(GCompareFunc) g_strcmp0)) {
GRL_DEBUG ("Plugin '%s' not allowed; skipping", plugin_desc->id);
g_module_close (module);
return NULL;
}
plugin = g_object_new (GRL_TYPE_PLUGIN, NULL);
grl_plugin_set_desc (plugin, plugin_desc);
grl_plugin_set_module (plugin, module);
grl_plugin_set_filename (plugin, library_filename);
/* Make plugin resident */
g_module_make_resident (module);
g_hash_table_insert (registry->priv->plugins, g_strdup (plugin_desc->id), plugin);
/* Register custom keys */
grl_plugin_register_keys (plugin);
return plugin;
}
/**
* grl_registry_activate_all_plugins:
* @registry: the registry instace
*
* Activate all the plugins loaded.
*
* Returns: %TRUE if some plugin has been activated
*
* Since: 0.3.0
**/
gboolean
grl_registry_activate_all_plugins (GrlRegistry *registry)
{
GList *all_plugins;
GList *l;
gboolean plugin_activated = FALSE;
g_return_val_if_fail (GRL_IS_REGISTRY (registry), FALSE);
all_plugins = g_hash_table_get_values (registry->priv->plugins);
for (l = all_plugins; l; l = l->next) {
GrlPlugin *plugin = l->data;
plugin_activated |= activate_plugin (registry, plugin, NULL);
}
g_list_free (all_plugins);
return plugin_activated;
}
/**
* grl_registry_load_plugin:
* @registry: the registry instance
* @library_filename: the path to the so file
* @error: error return location or @NULL to ignore
*
* Loads a module from shared object file stored in @path
*
* Returns: %TRUE if the module is loaded correctly
*
* Since: 0.2.0
*/
gboolean
grl_registry_load_plugin (GrlRegistry *registry,
const gchar *library_filename,
GError **error)
{
GrlPlugin *plugin;
plugin = grl_registry_prepare_plugin (registry, library_filename, error);
if (!plugin)
return FALSE;
return register_keys_plugin (registry, plugin, error);
}
/**
* grl_registry_load_plugin_from_desc: (skip)
* @registry: the registry instance
* @plugin_desc: the #GrlPluginDescriptor for the plugin
* @error: error return location or @NULL to ignore
*
* Loads the grilo plugin defined by @plugin_desc. This is
* used to load plugins that aren't shared libraries, and are
* built into applications.
*
* <example>
* Minimal example for loading a builtin plugin, in C.
* <programlisting>
* static GrlPluginDescriptor descriptor = {
* .plugin_id = "grl-example",
* .plugin_init = grl_example_plugin_init,
* };
*
* grl_registry_load_plugin_from_desc (registry, &descriptor, &error);
* </programlisting>
* </example>
*
* Returns: %TRUE if the plugin is initialised correctly
*
* Since: 0.3.0
*/
gboolean
grl_registry_load_plugin_from_desc (GrlRegistry *registry,
GrlPluginDescriptor *plugin_desc,
GError **error)
{
GrlPlugin *plugin;
plugin = grl_registry_prepare_plugin_from_desc (registry, plugin_desc);
if (!plugin)
return FALSE;
return register_keys_plugin (registry, plugin, error) &&
activate_plugin (registry, plugin, error);
}
/**
* grl_registry_load_plugin_directory:
* @registry: the registry instance
* @path: the path to the directory
* @error: error return location or @NULL to ignore
*
* Loads a set of modules from directory in @path which contains
* a group shared object files.
*
* Returns: %TRUE if the directory is valid.
*
* Since: 0.2.0
*/
gboolean
grl_registry_load_plugin_directory (GrlRegistry *registry,
const gchar *path,
GError **error)
{
GDir *dir;
GError *dir_error = NULL;
GrlPlugin *plugin;
const gchar *entry;
gboolean plugin_loaded = FALSE;
gchar *filename;
g_return_val_if_fail (GRL_IS_REGISTRY (registry), FALSE);
g_return_val_if_fail (path, FALSE);
dir = g_dir_open (path, 0, &dir_error);
if (!dir) {
GRL_WARNING ("Could not open directory '%s': %s",
path,
dir_error->message);
g_set_error (error,
GRL_CORE_ERROR,
GRL_CORE_ERROR_LOAD_PLUGIN_FAILED,
_("Invalid path %s"), path);
g_error_free (dir_error);
return FALSE;
}
while ((entry = g_dir_read_name (dir)) != NULL) {
filename = g_build_filename (path, entry, NULL);
if (g_strrstr (filename, "." G_MODULE_SUFFIX) == NULL) {
g_free (filename);
continue;
}
plugin = grl_registry_prepare_plugin (registry, filename, NULL);
plugin_loaded |= (plugin != NULL);
g_free (filename);
}
g_dir_close (dir);
return plugin_loaded;
}
/**
* grl_registry_load_all_plugins:
* @registry: the registry instance
* @activate: %TRUE if plugins must be activated after loading
* @error: error return location or @NULL to ignore
*
* Load all the modules available in the default directory path.
*
* The default directory path can be changed through the environment
* variable %GRL_PLUGIN_PATH and it can contain several paths separated
* by ":"
*
* Returns: %FALSE% is all the configured plugin paths are invalid,
* %TRUE% otherwise.
*
* Since: 0.2.0
*/
gboolean
grl_registry_load_all_plugins (GrlRegistry *registry,
gboolean activate,
GError **error)
{
GSList *plugin_dir;
gboolean loaded_one;
g_return_val_if_fail (GRL_IS_REGISTRY (registry), TRUE);
/* Preload all plugins */
if (!registry->priv->all_plugins_preloaded) {
for (plugin_dir = registry->priv->plugins_dir;
plugin_dir;
plugin_dir = g_slist_next (plugin_dir)) {
grl_registry_load_plugin_directory (registry,
plugin_dir->data,
NULL);
}
registry->priv->all_plugins_preloaded = TRUE;
}
if (activate) {
loaded_one = grl_registry_activate_all_plugins (registry);
if (!loaded_one) {
g_set_error (error,
GRL_CORE_ERROR,
GRL_CORE_ERROR_LOAD_PLUGIN_FAILED,
_("All configured plugin paths are invalid"));
}
return loaded_one;
} else {
return TRUE;
}
}
/**
* grl_registry_activate_plugin_by_id:
* @registry: the registry instance
* @plugin_id: plugin identifier
* @error: error return location or @NULL to ignore
*
* Activates plugin identified by @plugin_id.
*
* Returns: %TRUE if the plugin is loaded correctly
*
* Since: 0.3.0
**/
gboolean
grl_registry_activate_plugin_by_id (GrlRegistry *registry,
const gchar *plugin_id,
GError **error)
{
GrlPlugin *plugin;
gboolean is_loaded;
g_return_val_if_fail (GRL_IS_REGISTRY (registry), FALSE);
g_return_val_if_fail (plugin_id, FALSE);
/* Check if plugin is available */
plugin = g_hash_table_lookup (registry->priv->plugins, plugin_id);
if (!plugin) {
GRL_WARNING ("Plugin '%s' not available", plugin_id);
g_set_error (error,
GRL_CORE_ERROR,
GRL_CORE_ERROR_LOAD_PLUGIN_FAILED,
_("Plugin “%s” not available"), plugin_id);
return FALSE;
}
/* Check if plugin is already loaded */
g_object_get (plugin, "loaded", &is_loaded, NULL);
if (is_loaded) {
GRL_WARNING ("Plugin '%s' is already loaded", plugin_id);
g_set_error (error,
GRL_CORE_ERROR,
GRL_CORE_ERROR_LOAD_PLUGIN_FAILED,
_("Plugin “%s” is already loaded"), plugin_id);
return FALSE;
}
/* activate plugin */
return activate_plugin (registry, plugin, error);
}
/**
* grl_registry_lookup_source:
* @registry: the registry instance
* @source_id: the id of a source
*
* This function will search and retrieve a source given its identifier.
*
* Returns: (transfer none): The source found.
*
* Since: 0.2.0
*/
GrlSource *
grl_registry_lookup_source (GrlRegistry *registry,
const gchar *source_id)
{
GrlSource *source;
g_return_val_if_fail (GRL_IS_REGISTRY (registry), NULL);
g_return_val_if_fail (source_id != NULL, NULL);
source = (GrlSource *) g_hash_table_lookup (registry->priv->sources,
source_id);
if (source && !SOURCE_IS_INVISIBLE(source))
return source;
return NULL;
}
/**
* grl_registry_get_sources:
* @registry: the registry instance
* @ranked: whether the returned list shall be returned ordered by rank
*
* This function will return all the available sources in the @registry.
*
* If @ranked is %TRUE, the source list will be ordered by rank.
*
* Returns: (element-type GrlSource) (transfer container): a #GList of
* available #GrlSource<!-- -->s. The content of the list should not be
* modified or freed. Use g_list_free() when done using the list.
*
* Since: 0.2.0
*/
GList *
grl_registry_get_sources (GrlRegistry *registry,
gboolean ranked)
{
GHashTableIter iter;
GList *source_list = NULL;
GrlSource *current_source;
g_return_val_if_fail (GRL_IS_REGISTRY (registry), NULL);
g_hash_table_iter_init (&iter, registry->priv->sources);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) ¤t_source)) {
if (!SOURCE_IS_INVISIBLE(current_source))
source_list = g_list_prepend (source_list, current_source);
}
if (ranked) {
source_list = g_list_sort (source_list, (GCompareFunc) compare_by_rank);
}
return source_list;
}
/**
* grl_registry_get_sources_by_operations:
* @registry: the registry instance
* @ops: a bitwise mangle of the requested operations.
* @ranked: whether the returned list shall be returned ordered by rank
*
* Give an array of all the available sources in the @registry capable of
* perform the operations requested in @ops.
*
* If @ranked is %TRUE, the source list will be ordered by rank.
*
* Returns: (element-type GrlSource) (transfer container): a #GList of
* available #GrlSource<!-- -->s. The content of the list should not be
* modified or freed. Use g_list_free() when done using the list.
*
* Since: 0.2.0
*/
GList *
grl_registry_get_sources_by_operations (GrlRegistry *registry,
GrlSupportedOps ops,
gboolean ranked)
{
GHashTableIter iter;
GList *source_list = NULL;
GrlSource *source;
g_return_val_if_fail (GRL_IS_REGISTRY (registry), NULL);
g_hash_table_iter_init (&iter, registry->priv->sources);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &source)) {
GrlSupportedOps source_ops;
source_ops =
grl_source_supported_operations (source);
if ((source_ops & ops) == ops &&
!SOURCE_IS_INVISIBLE(source)) {
source_list = g_list_prepend (source_list, source);
}
}
if (ranked) {
source_list = g_list_sort (source_list, compare_by_rank);
}
return source_list;
}
/**
* grl_registry_lookup_plugin:
* @registry: the registry instance
* @plugin_id: the id of a plugin
*
* This function will search and retrieve a plugin given its identifier.
*
* Returns: (transfer none): The plugin found
*
* Since: 0.2.0
**/
GrlPlugin *
grl_registry_lookup_plugin (GrlRegistry *registry,
const gchar *plugin_id)
{
g_return_val_if_fail (GRL_IS_REGISTRY (registry), NULL);
g_return_val_if_fail (plugin_id, NULL);
return (GrlPlugin *) g_hash_table_lookup (registry->priv->plugins,
plugin_id);
}
/**
* grl_registry_get_plugins:
* @registry: the registry instance
* @only_loaded: whether the returned list shall include only loaded plugins
*
* This function will return all the available plugins in the @registry.
*
* If @only_loaded is %TRUE, the plugin list will contain only plugins that are
* loaded.
*
* Returns: (element-type GrlPlugin) (transfer container): a #GList of
* available #GrlPlugin<!-- -->s. The content of the list should not be modified
* or freed. Use g_list_free() when done using the list.
*
* Since: 0.2.0
**/
GList *
grl_registry_get_plugins (GrlRegistry *registry,
gboolean only_loaded)
{
GList *plugin_list = NULL;
GHashTableIter iter;
GrlPlugin *current_plugin;
gboolean is_loaded;
g_return_val_if_fail (GRL_IS_REGISTRY (registry), NULL);
if (only_loaded) {
g_hash_table_iter_init (&iter, registry->priv->plugins);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) ¤t_plugin)) {
g_object_get (current_plugin, "loaded", &is_loaded, NULL);
if (is_loaded) {
plugin_list = g_list_prepend (plugin_list, current_plugin);
}
}
} else {
plugin_list = g_hash_table_get_keys (registry->priv->plugins);
}
return plugin_list;
}
/**
* grl_registry_unload_plugin:
* @registry: the registry instance
* @plugin_id: the identifier of the plugin
* @error: error return location or @NULL to ignore
*
* Unload from memory a module identified by @plugin_id. This means call the
* module's deinit function.
*
* Returns: %TRUE% on success.
*
* Since: 0.2.0
*/
gboolean
grl_registry_unload_plugin (GrlRegistry *registry,
const gchar *plugin_id,
GError **error)
{
GrlPlugin *plugin;
GList *sources = NULL;
GList *sources_iter;
GRL_DEBUG ("%s: %s", __FUNCTION__, plugin_id);
g_return_val_if_fail (GRL_IS_REGISTRY (registry), FALSE);
g_return_val_if_fail (plugin_id != NULL, FALSE);
/* First check the plugin is valid */
plugin = g_hash_table_lookup (registry->priv->plugins, plugin_id);
if (!plugin) {
GRL_WARNING ("Could not deinit plugin '%s'. Plugin not found.", plugin_id);
g_set_error (error,
GRL_CORE_ERROR,
GRL_CORE_ERROR_UNLOAD_PLUGIN_FAILED,
_("Plugin not found: “%s”"), plugin_id);
return FALSE;
}
/* Second, shut down any sources spawned by this plugin */
GRL_DEBUG ("Shutting down sources spawned by '%s'", plugin_id);
sources = grl_registry_get_sources (registry, FALSE);
for (sources_iter = sources; sources_iter;
sources_iter = g_list_next (sources_iter)) {
GrlSource *source = GRL_SOURCE (sources_iter->data);
if (grl_source_get_plugin (source) == plugin) {
grl_registry_unregister_source (registry, source, NULL);
}
}
g_list_free (sources);
/* Third, shut down the plugin */
shutdown_plugin (plugin);
return TRUE;
}
/**
* grl_registry_register_metadata_key:
* @registry: The plugin registry
* @param_spec: (transfer full): The definition of the key to register
* @bind_key: The key the new key is bind to, or #GRL_METADATA_KEY_INVALID if it is not bound.
* @error: error return location or @NULL to ignore
*
* Registers a new metadata key, creating a relation between the new key and
* @bind_key.
*
* Two keys are related when the values of both keys are somehow related.
*
* One example of a relation would be the one between the URI of a media
* resource and its mime-type: they are both tied together and one does not make
* sense without the other.
*
* Relations between keys allow the framework to provide all the data that is
* somehow related when any of the related keys are requested.
* Returns: The #GrlKeyID registered.
*
* Since: 0.3.0
*/
GrlKeyID
grl_registry_register_metadata_key (GrlRegistry *registry,
GParamSpec *param_spec,
GrlKeyID bind_key,
GError **error)
{
GrlKeyID key;
key = grl_registry_register_metadata_key_full (registry,
param_spec,
GRL_METADATA_KEY_INVALID,
bind_key,
error);
if (key != GRL_METADATA_KEY_INVALID) {
g_signal_emit (registry, registry_signals[SIG_METADATA_KEY_ADDED],
0,
grl_metadata_key_get_name (key));
}
return key;
}
/*
* grl_registry_register_metadata_key_system:
*
* This is an internal method only meant to be used to register core
* keys.
*
* For internal use. Plugin developers should use
* grl_registry_register_metadata_key().
*/
GrlKeyID
grl_registry_register_metadata_key_system (GrlRegistry *registry,
GParamSpec *param_spec,
GrlKeyID key,
GrlKeyID bind_key,
GError **error)
{
GrlKeyID registered_key;
registered_key = grl_registry_register_metadata_key_full (registry,
param_spec,
key,
bind_key,
error);
return registered_key;
}
/**
* grl_registry_lookup_metadata_key:
* @registry: the registry instance
* @key_name: the key name
*
* Look up for the metadata key with name @key_name.
*
* Returns: The metadata key, or GRL_METADATA_KEY_INVALID if not found
*
* Since: 0.2.0
*/
GrlKeyID
grl_registry_lookup_metadata_key (GrlRegistry *registry,
const gchar *key_name)
{
g_return_val_if_fail (GRL_IS_REGISTRY (registry), 0);
g_return_val_if_fail (key_name, 0);
return key_id_handler_get_key (®istry->priv->key_id_handler, key_name);
}
/**
* grl_registry_lookup_metadata_key_name:
* @registry: the registry instance
* @key: a metadata key
*
* Returns @key name.
*
* Returns: metadata key name, or @NULL if not found
*
* Since: 0.2.0
*/
const gchar *
grl_registry_lookup_metadata_key_name (GrlRegistry *registry,
GrlKeyID key)
{
g_return_val_if_fail (GRL_IS_REGISTRY (registry), 0);
return key_id_handler_get_name (®istry->priv->key_id_handler, key);
}
/**
* grl_registry_lookup_metadata_key_desc:
* @registry: the registry instance
* @key: a metadata key
*
* Returns @key description.
*
* Returns: metadata key description, or @NULL if not found
*
* Since: 0.2.0
*/
const gchar *
grl_registry_lookup_metadata_key_desc (GrlRegistry *registry,
GrlKeyID key)
{
const gchar *key_name;
GParamSpec *key_pspec;
g_return_val_if_fail (GRL_IS_REGISTRY (registry), 0);
key_name = key_id_handler_get_name (®istry->priv->key_id_handler, key);
if (!key_name) {
return NULL;
}
key_pspec = g_hash_table_lookup (registry->priv->system_keys, key_name);
if (key_pspec) {
return g_param_spec_get_blurb (key_pspec);
} else {
return NULL;
}
}
/**
* grl_registry_lookup_metadata_key_type:
* @registry: the registry instance
* @key: a metadata key
*
* Returns @key expected value type.
*
* Returns: metadata key type, or @G_TYPE_INVALID if not found
*
* Since: 0.2.0
*/
GType
grl_registry_lookup_metadata_key_type (GrlRegistry *registry,
GrlKeyID key)
{
const gchar *key_name;
GParamSpec *key_pspec;
g_return_val_if_fail (GRL_IS_REGISTRY (registry), 0);
key_name = key_id_handler_get_name (®istry->priv->key_id_handler, key);
if (!key_name) {
return G_TYPE_INVALID;
}
key_pspec = g_hash_table_lookup (registry->priv->system_keys, key_name);
if (key_pspec) {
return G_PARAM_SPEC_VALUE_TYPE (key_pspec);
} else {
return G_TYPE_INVALID;
}
}
/**
* grl_registry_metadata_key_validate:
* @registry: the registry instance
* @key: a metadata key
* @value: value to be validate
*
* Validates @value content complies with the key specification. That is, it has
* the expected type, and value are within the range specified in key (for
* integer values).
*
* Returns: %TRUE if complies
*
* Since: 0.2.0
**/
gboolean
grl_registry_metadata_key_validate (GrlRegistry *registry,
GrlKeyID key,
GValue *value)
{
const gchar *key_name;
GParamSpec *key_pspec;
g_return_val_if_fail (GRL_IS_REGISTRY (registry), FALSE);
g_return_val_if_fail (G_IS_VALUE (value), FALSE);
key_name = key_id_handler_get_name (®istry->priv->key_id_handler, key);
if (!key_name) {
return FALSE;
}
key_pspec = g_hash_table_lookup (registry->priv->system_keys, key_name);
if (key_pspec) {
return !g_param_value_validate (key_pspec, value);
} else {
return FALSE;
}
}
/**
* grl_registry_lookup_metadata_key_relation:
* @registry: the registry instance
* @key: a metadata key
*
* Look up the list of keys that have a relation with @key.
*
* @key is included in that list.
*
* Returns: (element-type GrlKeyID) (transfer none): a #GList of
* related keys, or @NULL if key is invalid.
*
* Since: 0.2.0
**/
const GList *
grl_registry_lookup_metadata_key_relation (GrlRegistry *registry,
GrlKeyID key)
{
g_return_val_if_fail (GRL_IS_REGISTRY (registry), NULL);
return g_hash_table_lookup (registry->priv->related_keys, GRLKEYID_TO_POINTER (key));
}
/**
* grl_registry_get_metadata_keys:
* @registry: the registry instance
*
* Returns a list with all registered keys in system.
*
* Returns: (transfer container) (element-type GrlKeyID): a #GList with all the available
* #GrlKeyID<!-- -->s. The content of the list should not be modified or freed.
* Use g_list_free() when done using the list.
*
* Since: 0.2.0
**/
GList *
grl_registry_get_metadata_keys (GrlRegistry *registry)
{
return key_id_handler_get_all_keys (®istry->priv->key_id_handler);
}
/**
* grl_registry_add_config:
* @registry: the registry instance
* @config: (transfer full): a configuration set
* @error: error return location or @NULL to ignore
*
* Add a configuration for a plugin/source.
*
* Returns: %TRUE on success
*
* Since: 0.2.0
*/
gboolean
grl_registry_add_config (GrlRegistry *registry,
GrlConfig *config,
GError **error)
{
gchar *plugin_id;
GList *configs = NULL;
g_return_val_if_fail (config != NULL, FALSE);
g_return_val_if_fail (GRL_IS_REGISTRY (registry), FALSE);
plugin_id = grl_config_get_plugin (config);
if (!plugin_id) {
GRL_WARNING ("Plugin configuration missed plugin information, ignoring...");
g_set_error (error,
GRL_CORE_ERROR,
GRL_CORE_ERROR_CONFIG_FAILED,
_("Plugin configuration does not contain “plugin-id” reference"));
return FALSE;
}
configs = g_hash_table_lookup (registry->priv->configs, plugin_id);
if (configs) {
/* Notice that we are using g_list_append on purpose to avoid
having to insert again in the hash table */
configs = g_list_append (configs, config);
g_free (plugin_id);
} else {
configs = g_list_prepend (configs, config);
g_hash_table_insert (registry->priv->configs,
(gpointer) plugin_id,
configs);
}
return TRUE;
}
static void
add_config_from_keyfile (GKeyFile *keyfile,
GrlRegistry *registry)
{
GrlConfig *config;
gchar **key;
gchar **keys;
gchar **plugin;
gchar **plugins;
gchar *value;
/* Look up for defined plugins */
plugins = g_key_file_get_groups (keyfile, NULL);
for (plugin = plugins; *plugin; plugin++) {
config = grl_config_new (*plugin, NULL);
/* Look up configuration keys for this plugin */
keys = g_key_file_get_keys (keyfile, *plugin, NULL, NULL);
for (key = keys; *key; key++) {
value = g_key_file_get_string (keyfile, *plugin, *key, NULL);
if (value) {
grl_config_set_string (config, *key, value);
g_free (value);
}
}
grl_registry_add_config (registry, config, NULL);
g_strfreev (keys);
}
g_strfreev (plugins);
}
/**
* grl_registry_add_config_from_file:
* @registry: the registry instance
* @config_file: a key-value file containing the configuration
* @error: error return location or @NULL to ignore
*
* Load plugin configurations from a .ini-like config file.
*
* Returns: %TRUE on success
*
* Since: 0.2.0
**/
gboolean
grl_registry_add_config_from_file (GrlRegistry *registry,
const gchar *config_file,
GError **error)
{
GError *load_error = NULL;
GKeyFile *keyfile;
g_return_val_if_fail (GRL_IS_REGISTRY (registry), FALSE);
g_return_val_if_fail (config_file, FALSE);
keyfile = g_key_file_new ();
if (g_key_file_load_from_file (keyfile,
config_file,
G_KEY_FILE_NONE,
&load_error)) {
add_config_from_keyfile (keyfile, registry);
g_key_file_free (keyfile);
return TRUE;
} else {
GRL_WARNING ("Unable to load configuration. %s", load_error->message);
g_set_error_literal (error,
GRL_CORE_ERROR,
GRL_CORE_ERROR_CONFIG_LOAD_FAILED,
load_error->message);
g_error_free (load_error);
g_key_file_free (keyfile);
return FALSE;
}
}
/**
* grl_registry_add_config_from_resource:
* @registry: the registry instance
* @resource_path: a key-value file containing the configuration
* @error: error return location or @NULL to ignore
*
* Load plugin configurations from a .ini-like resource file.
*
* Returns: %TRUE on success
*
* Since: 0.2.8
**/
gboolean
grl_registry_add_config_from_resource (GrlRegistry *registry,
const gchar *resource_path,
GError **error)
{
GError *load_error = NULL;
GKeyFile *keyfile = NULL;
GBytes *bytes;
gboolean ret = FALSE;
g_return_val_if_fail (GRL_IS_REGISTRY (registry), FALSE);
g_return_val_if_fail (resource_path, FALSE);
bytes = g_resources_lookup_data (resource_path, 0, error);
if (bytes == NULL)
goto bail;
keyfile = g_key_file_new ();
if (g_key_file_load_from_data (keyfile,
g_bytes_get_data (bytes, NULL),
g_bytes_get_size (bytes),
G_KEY_FILE_NONE,
&load_error)) {
add_config_from_keyfile (keyfile, registry);
ret = TRUE;
} else {
GRL_WARNING ("Unable to load configuration. %s", load_error->message);
g_set_error_literal (error,
GRL_CORE_ERROR,
GRL_CORE_ERROR_CONFIG_LOAD_FAILED,
load_error->message);
g_error_free (load_error);
}
bail:
g_clear_pointer (&keyfile, g_key_file_free);
g_clear_pointer (&bytes, g_bytes_unref);
return ret;
}