logdiag/liblogdiag/ld-library.c

561 lines
13 KiB
C

/*
* ld-library.c
*
* This file is a part of logdiag.
* Copyright Přemysl Janouch 2010, 2011, 2012. All rights reserved.
*
* See the file LICENSE for licensing information.
*
*/
#include <string.h>
#include "liblogdiag.h"
#include "config.h"
/**
* SECTION:ld-library
* @short_description: A symbol library
* @see_also: #LdSymbol, #LdSymbolCategory
*
* #LdLibrary is used for loading symbols from their files. The library object
* itself is a container for categories, which in turn contain other
* subcategories and the actual symbols.
*/
/*
* LdLibraryPrivate:
* @lua: state of the scripting language.
* @children: categories in the library.
*/
struct _LdLibraryPrivate
{
LdLua *lua;
GSList *children;
};
static void ld_library_finalize (GObject *gobject);
static LdSymbolCategory *load_category (LdLibrary *self,
const gchar *path, const gchar *name);
static gboolean load_category_cb (const gchar *base,
const gchar *filename, gpointer userdata);
static void load_category_symbol_cb (LdSymbol *symbol, gpointer user_data);
static gchar *read_human_name_from_file (const gchar *filename);
static gboolean foreach_dir (const gchar *path,
gboolean (*callback) (const gchar *, const gchar *, gpointer),
gpointer userdata, GError **error);
static gboolean ld_library_load_cb
(const gchar *base, const gchar *filename, gpointer userdata);
static void on_category_notify_name (LdSymbolCategory *category,
GParamSpec *pspec, gpointer user_data);
G_DEFINE_TYPE (LdLibrary, ld_library, G_TYPE_OBJECT);
static void
ld_library_class_init (LdLibraryClass *klass)
{
GObjectClass *object_class;
object_class = G_OBJECT_CLASS (klass);
object_class->finalize = ld_library_finalize;
/**
* LdLibrary::changed:
* @self: an #LdLibrary object.
*
* Contents of the library have changed.
*/
klass->changed_signal = g_signal_new
("changed", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, 0, NULL, NULL,
g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
g_type_class_add_private (klass, sizeof (LdLibraryPrivate));
}
static void
ld_library_init (LdLibrary *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE
(self, LD_TYPE_LIBRARY, LdLibraryPrivate);
self->priv->lua = ld_lua_new ();
self->priv->children = NULL;
}
static void
uninstall_category_cb (LdSymbolCategory *category, LdLibrary *self)
{
g_signal_handlers_disconnect_by_func (category,
on_category_notify_name, self);
g_object_unref (category);
}
static void
ld_library_finalize (GObject *gobject)
{
LdLibrary *self;
self = LD_LIBRARY (gobject);
g_object_unref (self->priv->lua);
g_slist_foreach (self->priv->children, (GFunc) uninstall_category_cb, self);
g_slist_free (self->priv->children);
/* Chain up to the parent class. */
G_OBJECT_CLASS (ld_library_parent_class)->finalize (gobject);
}
/**
* ld_library_new:
*
* Create an instance.
*/
LdLibrary *
ld_library_new (void)
{
return g_object_new (LD_TYPE_LIBRARY, NULL);
}
/*
* foreach_dir:
*
* Call a user-defined function for each file within a directory.
*/
static gboolean
foreach_dir (const gchar *path,
gboolean (*callback) (const gchar *, const gchar *, gpointer),
gpointer userdata, GError **error)
{
GDir *dir;
const gchar *item;
g_return_val_if_fail (path != NULL, FALSE);
g_return_val_if_fail (callback != NULL, FALSE);
dir = g_dir_open (path, 0, error);
if (!dir)
return FALSE;
while ((item = g_dir_read_name (dir)))
{
gchar *filename;
filename = g_build_filename (path, item, NULL);
if (!callback (item, filename, userdata))
break;
g_free (filename);
}
g_dir_close (dir);
return TRUE;
}
/*
* LoadCategoryData:
*
* Data shared between load_category() and load_category_cb().
*/
typedef struct
{
LdLibrary *self;
LdSymbolCategory *cat;
}
LoadCategoryData;
/*
* load_category:
* @self: an #LdLibrary object.
* @path: the path to the category.
* @name: the default name of the category.
*
* Loads a category into the library.
*/
static LdSymbolCategory *
load_category (LdLibrary *self, const gchar *path, const gchar *name)
{
LdSymbolCategory *cat = NULL;
gchar *icon_file, *category_file;
gchar *human_name;
LoadCategoryData data;
g_return_val_if_fail (LD_IS_LIBRARY (self), NULL);
g_return_val_if_fail (path != NULL, NULL);
g_return_val_if_fail (name != NULL, NULL);
if (!g_file_test (path, G_FILE_TEST_IS_DIR))
goto load_category_fail_1;
icon_file = g_build_filename (path, "icon.svg", NULL);
if (!g_file_test (icon_file, G_FILE_TEST_IS_REGULAR))
{
g_warning ("the category in `%s' has no icon", path);
goto load_category_fail_2;
}
category_file = g_build_filename (path, "category.json", NULL);
human_name = read_human_name_from_file (category_file);
if (!human_name)
human_name = g_strdup (name);
cat = ld_symbol_category_new (name, human_name);
ld_symbol_category_set_image_path (cat, icon_file);
data.self = self;
data.cat = cat;
foreach_dir (path, load_category_cb, &data, NULL);
g_free (human_name);
g_free (category_file);
load_category_fail_2:
g_free (icon_file);
load_category_fail_1:
return cat;
}
/*
* load_category_cb:
*
* Load script files from a directory into a symbol category.
*/
static gboolean
load_category_cb (const gchar *base, const gchar *filename, gpointer userdata)
{
LoadCategoryData *data;
g_return_val_if_fail (base != NULL, FALSE);
g_return_val_if_fail (filename != NULL, FALSE);
g_return_val_if_fail (userdata != NULL, FALSE);
data = (LoadCategoryData *) userdata;
if (ld_lua_check_file (data->self->priv->lua, filename))
ld_lua_load_file (data->self->priv->lua, filename,
load_category_symbol_cb, data->cat);
return TRUE;
}
/*
* load_category_symbol_cb:
*
* Insert newly registered symbols into the category.
*/
static void
load_category_symbol_cb (LdSymbol *symbol, gpointer user_data)
{
LdSymbolCategory *cat;
g_return_if_fail (LD_IS_SYMBOL (symbol));
g_return_if_fail (LD_IS_SYMBOL_CATEGORY (user_data));
cat = LD_SYMBOL_CATEGORY (user_data);
ld_symbol_category_insert_symbol (cat, symbol, -1);
}
/*
* read_human_name_from_file:
* @filename: location of the JSON file.
*
* Read the human name of the processed category.
*/
static gchar *
read_human_name_from_file (const gchar *filename)
{
const gchar *const *lang;
JsonParser *parser;
JsonNode *root;
JsonObject *object;
GError *error;
g_return_val_if_fail (filename != NULL, NULL);
parser = json_parser_new ();
error = NULL;
if (!json_parser_load_from_file (parser, filename, &error))
{
g_warning ("%s", error->message);
g_error_free (error);
goto read_human_name_from_file_end;
}
root = json_parser_get_root (parser);
if (!JSON_NODE_HOLDS_OBJECT (root))
{
g_warning ("failed to parse `%s': %s", filename,
"The root node is not an object.");
goto read_human_name_from_file_end;
}
object = json_node_get_object (root);
for (lang = g_get_language_names (); *lang; lang++)
{
const gchar *member;
if (!json_object_has_member (object, *lang))
continue;
member = json_object_get_string_member (object, *lang);
if (member != NULL)
{
gchar *result;
result = g_strdup (member);
g_object_unref (parser);
return result;
}
}
read_human_name_from_file_end:
g_object_unref (parser);
return NULL;
}
/*
* LibraryLoadData:
*
* Data shared between ld_library_load() and ld_library_load_cb().
*/
typedef struct
{
LdLibrary *self;
gboolean changed;
}
LibraryLoadData;
/**
* ld_library_load:
* @self: an #LdLibrary object.
* @directory: a directory to be loaded.
*
* Load the contents of a directory into the library.
*/
gboolean
ld_library_load (LdLibrary *self, const gchar *directory)
{
LibraryLoadData data;
g_return_val_if_fail (LD_IS_LIBRARY (self), FALSE);
g_return_val_if_fail (directory != NULL, FALSE);
data.self = self;
data.changed = FALSE;
foreach_dir (directory, ld_library_load_cb, &data, NULL);
if (data.changed)
g_signal_emit (self, LD_LIBRARY_GET_CLASS (self)->changed_signal, 0);
return TRUE;
}
/*
* ld_library_load_cb:
*
* A callback that's called for each file in the root directory.
*/
static gboolean
ld_library_load_cb (const gchar *base, const gchar *filename, gpointer userdata)
{
LdSymbolCategory *cat;
LibraryLoadData *data;
g_return_val_if_fail (base != NULL, FALSE);
g_return_val_if_fail (filename != NULL, FALSE);
g_return_val_if_fail (userdata != NULL, FALSE);
data = (LibraryLoadData *) userdata;
cat = load_category (data->self, filename, base);
if (cat)
{
ld_library_insert_category (data->self, cat, -1);
g_object_unref (cat);
}
data->changed = TRUE;
return TRUE;
}
/**
* ld_library_find_symbol:
* @self: an #LdLibrary object.
* @identifier: an identifier of the symbol to be searched for.
*
* Search for a symbol in the library.
*
* Return value: a symbol object if found, %NULL otherwise.
*/
LdSymbol *
ld_library_find_symbol (LdLibrary *self, const gchar *identifier)
{
gchar **id_el_start, **id_el;
const GSList *list, *list_el;
g_return_val_if_fail (LD_IS_LIBRARY (self), NULL);
g_return_val_if_fail (identifier != NULL, NULL);
id_el_start = g_strsplit (identifier, LD_LIBRARY_IDENTIFIER_SEPARATOR, 0);
if (!id_el_start)
return NULL;
id_el = id_el_start;
list = self->priv->children;
/* We need at least one category name plus the symbol name. */
if (!id_el[0] || !id_el[1])
goto ld_library_find_symbol_error;
/* Find the category where the symbol is in. */
while (1)
{
gboolean found = FALSE;
LdSymbolCategory *cat;
for (list_el = list; list_el; list_el = g_slist_next (list_el))
{
cat = LD_SYMBOL_CATEGORY (list_el->data);
if (!strcmp (*id_el, ld_symbol_category_get_name (cat)))
{
found = TRUE;
break;
}
}
if (!found)
goto ld_library_find_symbol_error;
if (!(id_el++)[2])
{
list = ld_symbol_category_get_symbols (cat);
break;
}
list = ld_symbol_category_get_subcategories (cat);
}
/* And then the actual symbol. */
for (list_el = list; list_el; list_el = g_slist_next (list_el))
{
LdSymbol *symbol;
symbol = LD_SYMBOL (list_el->data);
if (!strcmp (*id_el, ld_symbol_get_name (symbol)))
{
g_strfreev (id_el_start);
return symbol;
}
}
ld_library_find_symbol_error:
g_strfreev (id_el_start);
return NULL;
}
/**
* ld_library_clear:
* @self: an #LdLibrary object.
*
* Clear all the contents.
*/
void
ld_library_clear (LdLibrary *self)
{
g_return_if_fail (LD_IS_LIBRARY (self));
g_slist_foreach (self->priv->children, (GFunc) uninstall_category_cb, self);
g_slist_free (self->priv->children);
self->priv->children = NULL;
g_signal_emit (self,
LD_LIBRARY_GET_CLASS (self)->changed_signal, 0);
}
static void
on_category_notify_name (LdSymbolCategory *category,
GParamSpec *pspec, gpointer user_data)
{
/* XXX: We could disown the category if a name collision has occured. */
g_warning ("name of a library category has changed");
}
/**
* ld_library_insert_category:
* @self: an #LdLibrary object.
* @category: the category to be inserted.
* @pos: the position at which the category will be inserted.
* Negative values will append to the end of list.
*
* Insert a child category into the library.
*
* Return value: %TRUE if successful (no name collisions).
*/
gboolean
ld_library_insert_category (LdLibrary *self,
LdSymbolCategory *category, gint pos)
{
const gchar *name;
const GSList *iter;
g_return_val_if_fail (LD_IS_LIBRARY (self), FALSE);
g_return_val_if_fail (LD_IS_SYMBOL_CATEGORY (category), FALSE);
/* Check for name collisions. */
name = ld_symbol_category_get_name (category);
for (iter = self->priv->children; iter; iter = iter->next)
{
if (!strcmp (name, ld_symbol_category_get_name (iter->data)))
{
g_warning ("attempted to insert multiple `%s' categories into"
" library", name);
return FALSE;
}
}
g_signal_connect (category, "notify::name",
G_CALLBACK (on_category_notify_name), self);
self->priv->children = g_slist_insert (self->priv->children, category, pos);
g_object_ref (category);
return TRUE;
}
/**
* ld_library_remove_category:
* @self: an #LdLibrary object.
* @category: the category to be removed.
*
* Remove a child category from the library.
*/
void
ld_library_remove_category (LdLibrary *self, LdSymbolCategory *category)
{
g_return_if_fail (LD_IS_LIBRARY (self));
g_return_if_fail (LD_IS_SYMBOL_CATEGORY (category));
if (g_slist_find (self->priv->children, category))
{
g_signal_handlers_disconnect_by_func (category,
on_category_notify_name, self);
self->priv->children = g_slist_remove (self->priv->children, category);
g_object_unref (category);
}
}
/**
* ld_library_get_categories:
* @self: an #LdLibrary object.
*
* Return value: (element-type LdSymbolCategory *):
* a list of child categories. Do not modify.
*/
const GSList *
ld_library_get_categories (LdLibrary *self)
{
g_return_val_if_fail (LD_IS_LIBRARY (self), NULL);
return self->priv->children;
}