logdiag/src/ld-library.c

535 lines
12 KiB
C

/*
* ld-library.c
*
* This file is a part of logdiag.
* Copyright Přemysl Janouch 2010 - 2011. All rights reserved.
*
* See the file LICENSE for licensing information.
*
*/
#include <string.h>
#include <gtk/gtk.h>
#include <json-glib/json-glib.h>
#include "config.h"
#include "ld-types.h"
#include "ld-symbol.h"
#include "ld-symbol-category.h"
#include "ld-library.h"
#include "ld-lua.h"
/**
* SECTION:ld-library
* @short_description: A symbol library.
* @see_also: #LdSymbol, #LdSymbolCategory
*
* #LdLibrary is used for loading symbols from their files.
*/
/*
* LdLibraryPrivate:
* @lua: State of the scripting language.
* @children: Child objects of 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);
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:
* @library: The library 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
ld_library_finalize (GObject *gobject)
{
LdLibrary *self;
self = LD_LIBRARY (gobject);
g_object_unref (self->priv->lua);
g_slist_foreach (self->priv->children, (GFunc) g_object_unref, NULL);
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;
/* FIXME: We don't set an error. */
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;
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);
g_free (icon_file);
return cat;
load_category_fail_2:
g_free (icon_file);
load_category_fail_1:
return NULL;
}
/*
* 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)
{
const gchar *name;
LdSymbolCategory *cat;
const GSList *children, *iter;
g_return_if_fail (LD_IS_SYMBOL (symbol));
g_return_if_fail (LD_IS_SYMBOL_CATEGORY (user_data));
cat = LD_SYMBOL_CATEGORY (user_data);
name = ld_symbol_get_name (symbol);
/* Check for name collisions with other symbols. */
children = ld_symbol_category_get_children (cat);
for (iter = children; iter; iter = iter->next)
{
if (!LD_IS_SYMBOL (iter->data))
continue;
if (!strcmp (name, ld_symbol_get_name (LD_SYMBOL (iter->data))))
{
g_warning ("Attempted to insert multiple '%s' symbols into"
" category '%s'.", name, ld_symbol_category_get_name (cat));
return;
}
}
ld_symbol_category_insert_child (cat, G_OBJECT (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_child (data->self, G_OBJECT (cat), -1);
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.
*/
/* XXX: With this level of indentation, this function is really ugly. */
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;
list = ld_library_get_children (self);
for (id_el = id_el_start; id_el[0]; id_el++)
{
LdSymbolCategory *cat;
LdSymbol *symbol;
gboolean found = FALSE;
for (list_el = list; list_el; list_el = g_slist_next (list_el))
{
/* If the current identifier element is a category (not last)
* and this list element is a category.
*/
if (id_el[1] && LD_IS_SYMBOL_CATEGORY (list_el->data))
{
cat = LD_SYMBOL_CATEGORY (list_el->data);
if (strcmp (id_el[0], ld_symbol_category_get_name (cat)))
continue;
list = ld_symbol_category_get_children (cat);
found = TRUE;
break;
}
/* If the current identifier element is a symbol (last)
* and this list element is a symbol.
*/
else if (!id_el[1] && LD_IS_SYMBOL (list_el->data))
{
symbol = LD_SYMBOL (list_el->data);
if (strcmp (id_el[0], ld_symbol_get_name (symbol)))
continue;
g_strfreev (id_el_start);
return symbol;
}
}
if (!found)
break;
}
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) g_object_unref, NULL);
g_slist_free (self->priv->children);
self->priv->children = NULL;
g_signal_emit (self,
LD_LIBRARY_GET_CLASS (self)->changed_signal, 0);
}
/**
* ld_library_insert_child:
* @self: An #LdLibrary object.
* @child: The child to be inserted.
* @pos: The position at which the child will be inserted.
* Negative values will append to the end of list.
*
* Insert a child into the library.
*/
void
ld_library_insert_child (LdLibrary *self, GObject *child, gint pos)
{
g_return_if_fail (LD_IS_LIBRARY (self));
g_return_if_fail (G_IS_OBJECT (child));
g_object_ref (child);
self->priv->children = g_slist_insert (self->priv->children, child, pos);
}
/**
* ld_library_remove_child:
* @self: An #LdLibrary object.
* @child: The child to be removed.
*
* Remove a child from the library.
*/
void
ld_library_remove_child (LdLibrary *self, GObject *child)
{
g_return_if_fail (LD_IS_LIBRARY (self));
g_return_if_fail (G_IS_OBJECT (child));
if (g_slist_find (self->priv->children, child))
{
g_object_unref (child);
self->priv->children = g_slist_remove (self->priv->children, child);
}
}
/**
* ld_library_get_children:
* @self: An #LdLibrary object.
*
* Return value: The internal list of children. Do not modify.
*/
const GSList *
ld_library_get_children (LdLibrary *self)
{
g_return_val_if_fail (LD_IS_LIBRARY (self), NULL);
return self->priv->children;
}