534 lines
12 KiB
C
534 lines
12 KiB
C
/*
|
|
* ld-library.c
|
|
*
|
|
* This file is a part of logdiag.
|
|
* Copyright Přemysl Janouch 2010. 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-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: A symbol library 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: Where the JSON file is located.
|
|
*
|
|
* 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: A symbol library 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: A symbol library 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: A symbol library object.
|
|
*
|
|
* Clears 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.
|
|
*
|
|
* Removes 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;
|
|
}
|
|
|