As well as shorter, strangely. Symbols may technically exist directly in the root category now.
		
			
				
	
	
		
			421 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			421 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * ld-library.c
 | 
						|
 *
 | 
						|
 * This file is a part of logdiag.
 | 
						|
 * Copyright 2010, 2011, 2012 Přemysl Eric Janouch
 | 
						|
 *
 | 
						|
 * 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, #LdCategory
 | 
						|
 *
 | 
						|
 * #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;
 | 
						|
	LdCategory *root;
 | 
						|
};
 | 
						|
 | 
						|
static void ld_library_finalize (GObject *gobject);
 | 
						|
 | 
						|
static LdCategory *load_category (LdLibrary *self,
 | 
						|
	const gchar *path, const gchar *name);
 | 
						|
static gboolean load_category_cb (const gchar *base,
 | 
						|
	const gchar *path, 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 LdSymbol *traverse_path (LdCategory *category, gchar **path);
 | 
						|
 | 
						|
 | 
						|
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->root = ld_category_new (LD_LIBRARY_IDENTIFIER_SEPARATOR, "/");
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
ld_library_finalize (GObject *gobject)
 | 
						|
{
 | 
						|
	LdLibrary *self;
 | 
						|
 | 
						|
	self = LD_LIBRARY (gobject);
 | 
						|
 | 
						|
	g_object_unref (self->priv->lua);
 | 
						|
	g_object_unref (self->priv->root);
 | 
						|
 | 
						|
	/* 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;
 | 
						|
	LdCategory *cat;
 | 
						|
	guint changed : 1;
 | 
						|
	guint load_symbols : 1;
 | 
						|
}
 | 
						|
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 LdCategory *
 | 
						|
load_category (LdLibrary *self, const gchar *path, const gchar *name)
 | 
						|
{
 | 
						|
	gchar *category_file, *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))
 | 
						|
		return NULL;
 | 
						|
 | 
						|
	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);
 | 
						|
 | 
						|
	data.self = self;
 | 
						|
	data.cat = ld_category_new (name, human_name);
 | 
						|
	data.load_symbols = TRUE;
 | 
						|
	data.changed = FALSE;
 | 
						|
	foreach_dir (path, load_category_cb, &data, NULL);
 | 
						|
 | 
						|
	g_free (human_name);
 | 
						|
	g_free (category_file);
 | 
						|
	return data.cat;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * load_category_cb:
 | 
						|
 *
 | 
						|
 * Load contents of a directory into a symbol category.
 | 
						|
 */
 | 
						|
static gboolean
 | 
						|
load_category_cb (const gchar *base, const gchar *path, gpointer userdata)
 | 
						|
{
 | 
						|
	LoadCategoryData *data;
 | 
						|
 | 
						|
	g_return_val_if_fail (base != NULL, FALSE);
 | 
						|
	g_return_val_if_fail (path != NULL, FALSE);
 | 
						|
	g_return_val_if_fail (userdata != NULL, FALSE);
 | 
						|
 | 
						|
	data = (LoadCategoryData *) userdata;
 | 
						|
 | 
						|
	if (g_file_test (path, G_FILE_TEST_IS_DIR))
 | 
						|
	{
 | 
						|
		LdCategory *cat;
 | 
						|
 | 
						|
		cat = load_category (data->self, path, base);
 | 
						|
		if (cat)
 | 
						|
		{
 | 
						|
			ld_category_add_child (data->cat, cat);
 | 
						|
			g_object_unref (cat);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	else if (data->load_symbols
 | 
						|
		&& ld_lua_check_file (data->self->priv->lua, path))
 | 
						|
	{
 | 
						|
		ld_lua_load_file (data->self->priv->lua, path,
 | 
						|
			load_category_symbol_cb, data->cat);
 | 
						|
	}
 | 
						|
 | 
						|
	data->changed = TRUE;
 | 
						|
	return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * load_category_symbol_cb:
 | 
						|
 *
 | 
						|
 * Insert newly registered symbols into the category.
 | 
						|
 */
 | 
						|
static void
 | 
						|
load_category_symbol_cb (LdSymbol *symbol, gpointer user_data)
 | 
						|
{
 | 
						|
	LdCategory *cat;
 | 
						|
 | 
						|
	g_return_if_fail (LD_IS_SYMBOL (symbol));
 | 
						|
	g_return_if_fail (LD_IS_CATEGORY (user_data));
 | 
						|
 | 
						|
	cat = LD_CATEGORY (user_data);
 | 
						|
	ld_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;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * 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)
 | 
						|
{
 | 
						|
	LoadCategoryData data;
 | 
						|
 | 
						|
	g_return_val_if_fail (LD_IS_LIBRARY (self), FALSE);
 | 
						|
	g_return_val_if_fail (directory != NULL, FALSE);
 | 
						|
 | 
						|
	/* Almost like load_category(). */
 | 
						|
	data.self = self;
 | 
						|
	data.cat = self->priv->root;
 | 
						|
	data.load_symbols = FALSE;
 | 
						|
	data.changed = FALSE;
 | 
						|
	foreach_dir (directory, load_category_cb, &data, NULL);
 | 
						|
 | 
						|
	/* XXX: It might also make sense to just forward the "children-changed"
 | 
						|
	 *      signal of the root category but we'd have to block it here anyway,
 | 
						|
	 *      so that we don't unnecessarily fire events for every single change.
 | 
						|
	 *
 | 
						|
	 *      The user code isn't supposed to make changes to / and it's its own
 | 
						|
	 *      problem if it keeps reloading something a hundred times in a row.
 | 
						|
	 *
 | 
						|
	 *      That said, it'd be possible to add change grouping methods to
 | 
						|
	 *      LdCategory and so delay the signal emission until an `unblock'.
 | 
						|
	 */
 | 
						|
	if (data.changed)
 | 
						|
		g_signal_emit (self, LD_LIBRARY_GET_CLASS (self)->changed_signal, 0);
 | 
						|
 | 
						|
	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 **path;
 | 
						|
	LdSymbol *symbol = NULL;
 | 
						|
 | 
						|
	g_return_val_if_fail (LD_IS_LIBRARY (self), NULL);
 | 
						|
	g_return_val_if_fail (identifier != NULL, NULL);
 | 
						|
 | 
						|
	path = g_strsplit (identifier, LD_LIBRARY_IDENTIFIER_SEPARATOR, 0);
 | 
						|
	if (path)
 | 
						|
	{
 | 
						|
		symbol = traverse_path (self->priv->root, path);
 | 
						|
		g_strfreev (path);
 | 
						|
	}
 | 
						|
	return symbol;
 | 
						|
}
 | 
						|
 | 
						|
static LdSymbol *
 | 
						|
traverse_path (LdCategory *category, gchar **path)
 | 
						|
{
 | 
						|
	const GSList *list, *iter;
 | 
						|
	LdSymbol *symbol;
 | 
						|
 | 
						|
	g_return_val_if_fail (*path != NULL, NULL);
 | 
						|
 | 
						|
	/* Walk the category tree to where the symbol is supposed to be. */
 | 
						|
	for (; path[1]; path++)
 | 
						|
	{
 | 
						|
		list = ld_category_get_children (category);
 | 
						|
		for (iter = list; iter; iter = g_slist_next (iter))
 | 
						|
		{
 | 
						|
			category = LD_CATEGORY (iter->data);
 | 
						|
			if (!strcmp (*path, ld_category_get_name (category)))
 | 
						|
				break;
 | 
						|
		}
 | 
						|
		if (!iter)
 | 
						|
			return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	/* And look up the actual symbol at the leaf. */
 | 
						|
	list = ld_category_get_symbols (category);
 | 
						|
	for (iter = list; iter; iter = g_slist_next (iter))
 | 
						|
	{
 | 
						|
		symbol = LD_SYMBOL (iter->data);
 | 
						|
		if (!strcmp (*path, ld_symbol_get_name (symbol)))
 | 
						|
			return symbol;
 | 
						|
	}
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * ld_library_get_root:
 | 
						|
 * @self: an #LdLibrary object.
 | 
						|
 *
 | 
						|
 * Return value: (transfer none): the root category. Do not modify.
 | 
						|
 */
 | 
						|
LdCategory *
 | 
						|
ld_library_get_root (LdLibrary *self)
 | 
						|
{
 | 
						|
	g_return_val_if_fail (LD_IS_LIBRARY (self), NULL);
 | 
						|
	return self->priv->root;
 | 
						|
}
 |