diff libpurple/smiley.c @ 23159:25161f5ea347

propagate from branch 'im.pidgin.pidgin' (head 4f00ea617df3c000b6eb237b4139b89e5d8170a3) to branch 'im.pidgin.pidgin.next.minor' (head c2457daceab155be8fb8b11753c4e767c2c1a83c)
author Evan Schoenberg <evan.s@dreskin.net>
date Wed, 21 May 2008 02:09:00 +0000
parents f1df88964b3d
children 013ec6fabd3f
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/smiley.c	Wed May 21 02:09:00 2008 +0000
@@ -0,0 +1,915 @@
+/**
+ * @file smiley.c Simley API
+ * @ingroup core
+ */
+
+/* purple
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
+ */
+
+#include "internal.h"
+#include "dbus-maybe.h"
+#include "debug.h"
+#include "imgstore.h"
+#include "smiley.h"
+#include "util.h"
+#include "xmlnode.h"
+
+/**************************************************************************/
+/* Main structures, members and constants                                 */
+/**************************************************************************/
+
+struct _PurpleSmiley
+{
+	GObject parent;
+	PurpleStoredImage *img;        /**< The id of the stored image with the
+	                                    the smiley data.        */
+	char *shortcut;                /**< Shortcut associated with the custom
+	                                    smiley. This field will work as a
+	                                    unique key by this API. */
+	char *checksum;                /**< The smiley checksum.        */
+};
+
+struct _PurpleSmileyClass
+{
+	GObjectClass parent_class;
+};
+
+static GHashTable *smiley_shortcut_index = NULL; /* shortcut (char *) => smiley (PurpleSmiley*) */
+static GHashTable *smiley_checksum_index = NULL; /* checksum (char *) => smiley (PurpleSmiley*) */
+
+static guint save_timer = 0;
+static gboolean smileys_loaded = FALSE;
+static char *smileys_dir = NULL;
+
+#define SMILEYS_DEFAULT_FOLDER			"custom_smiley"
+#define SMILEYS_LOG_ID				"smileys"
+
+#define XML_FILE_NAME				"smileys.xml"
+
+#define XML_ROOT_TAG				"smileys"
+#define XML_PROFILE_TAG			"profile"
+#define XML_PROFILE_NAME_ATTRIB_TAG		"name"
+#define XML_ACCOUNT_TAG			"account"
+#define XML_ACCOUNT_USERID_ATTRIB_TAG		"userid"
+#define XML_SMILEY_SET_TAG			"smiley_set"
+#define XML_SMILEY_TAG				"smiley"
+#define XML_SHORTCUT_ATTRIB_TAG		"shortcut"
+#define XML_CHECKSUM_ATRIB_TAG			"checksum"
+#define XML_FILENAME_ATRIB_TAG			"filename"
+
+
+/******************************************************************************
+ * XML descriptor file layout                                                 *
+ ******************************************************************************
+ *
+ * Althought we are creating the profile XML structure here, now we
+ * won't handle it.
+ * So, we just add one profile named "default" that has no associated
+ * account elements, and have only the smiley_set that will contain
+ * all existent custom smiley.
+ *
+ * It's our "Highlander Profile" :-)
+ *
+ ******************************************************************************
+ *
+ * <smileys>
+ *   <profile name="john.doe">
+ *     <account userid="john.doe@jabber.org">
+ *     <account userid="john.doe@gmail.com">
+ *     <smiley_set>
+ *       <smiley shortcut="aaa" checksum="xxxxxxxx" filename="file_name1.gif"/>
+ *       <smiley shortcut="bbb" checksum="yyyyyyy" filename="file_name2.gif"/>
+ *     </smiley_set>
+ *   </profile>
+ * </smiley>
+ *
+ *****************************************************************************/
+
+
+/*********************************************************************
+ * Forward declarations                                              *
+ *********************************************************************/
+
+static gboolean read_smiley_file(const char *path, guchar **data, size_t *len);
+
+static char *get_file_full_path(const char *filename);
+
+static PurpleSmiley *purple_smiley_create(const char *shortcut);
+
+static PurpleSmiley *purple_smiley_load_file(const char *shortcut, const char *checksum,
+		const char *filename);
+
+static void
+purple_smiley_set_data_impl(PurpleSmiley *smiley, guchar *smiley_data,
+		size_t smiley_data_len, const char *filename);
+
+static void
+purple_smiley_data_store(PurpleStoredImage *stored_img);
+
+static void
+purple_smiley_data_unstore(const char *filename);
+
+/*********************************************************************
+ * Writing to disk                                                   *
+ *********************************************************************/
+
+static xmlnode *
+smiley_to_xmlnode(PurpleSmiley *smiley)
+{
+	xmlnode *smiley_node = NULL;
+
+	smiley_node = xmlnode_new(XML_SMILEY_TAG);
+
+	if (!smiley_node)
+		return NULL;
+
+	xmlnode_set_attrib(smiley_node, XML_SHORTCUT_ATTRIB_TAG,
+			smiley->shortcut);
+
+	xmlnode_set_attrib(smiley_node, XML_CHECKSUM_ATRIB_TAG,
+			smiley->checksum);
+
+	xmlnode_set_attrib(smiley_node, XML_FILENAME_ATRIB_TAG,
+			purple_imgstore_get_filename(smiley->img));
+
+	return smiley_node;
+}
+
+static void
+add_smiley_to_main_node(gpointer key, gpointer value, gpointer user_data)
+{
+	xmlnode *child_node;
+
+	child_node = smiley_to_xmlnode(value);
+	xmlnode_insert_child((xmlnode*)user_data, child_node);
+}
+
+static xmlnode *
+smileys_to_xmlnode()
+{
+	xmlnode *root_node, *profile_node, *smileyset_node;
+
+	root_node = xmlnode_new(XML_ROOT_TAG);
+	xmlnode_set_attrib(root_node, "version", "1.0");
+
+	/* See the top comment's above to understand why initial tag elements
+	 * are not being considered by now. */
+	profile_node = xmlnode_new(XML_PROFILE_TAG);
+	if (profile_node) {
+		xmlnode_set_attrib(profile_node, XML_PROFILE_NAME_ATTRIB_TAG, "Default");
+		xmlnode_insert_child(root_node, profile_node);
+
+		smileyset_node = xmlnode_new(XML_SMILEY_SET_TAG);
+		if (smileyset_node) {
+			xmlnode_insert_child(profile_node, smileyset_node);
+			g_hash_table_foreach(smiley_shortcut_index, add_smiley_to_main_node, smileyset_node);
+		}
+	}
+
+	return root_node;
+}
+
+static void
+sync_smileys()
+{
+	xmlnode *root_node;
+	char *data;
+
+	if (!smileys_loaded) {
+		purple_debug_error(SMILEYS_LOG_ID, "Attempted to save smileys before it "
+						 "was read!\n");
+		return;
+	}
+
+	root_node = smileys_to_xmlnode();
+	data = xmlnode_to_formatted_str(root_node, NULL);
+	purple_util_write_data_to_file(XML_FILE_NAME, data, -1);
+
+	g_free(data);
+	xmlnode_free(root_node);
+}
+
+static gboolean
+save_smileys_cb(gpointer data)
+{
+	sync_smileys();
+	save_timer = 0;
+	return FALSE;
+}
+
+static void
+purple_smileys_save()
+{
+	if (save_timer == 0)
+		save_timer = purple_timeout_add_seconds(5, save_smileys_cb, NULL);
+}
+
+
+/*********************************************************************
+ * Reading from disk                                                 *
+ *********************************************************************/
+
+static PurpleSmiley *
+parse_smiley(xmlnode *smiley_node)
+{
+	PurpleSmiley *smiley;
+	const char *shortcut = NULL;
+	const char *checksum = NULL;
+	const char *filename = NULL;
+
+	shortcut = xmlnode_get_attrib(smiley_node, XML_SHORTCUT_ATTRIB_TAG);
+	checksum = xmlnode_get_attrib(smiley_node, XML_CHECKSUM_ATRIB_TAG);
+	filename = xmlnode_get_attrib(smiley_node, XML_FILENAME_ATRIB_TAG);
+
+	if ((shortcut == NULL) || (checksum == NULL) || (filename == NULL))
+		return NULL;
+
+	smiley = purple_smiley_load_file(shortcut, checksum, filename);
+
+	return smiley;
+}
+
+static void
+purple_smileys_load()
+{
+	xmlnode *root_node, *profile_node;
+	xmlnode *smileyset_node = NULL;
+	xmlnode *smiley_node;
+
+	smileys_loaded = TRUE;
+
+	root_node = purple_util_read_xml_from_file(XML_FILE_NAME,
+			_(SMILEYS_LOG_ID));
+
+	if (root_node == NULL)
+		return;
+
+	/* See the top comment's above to understand why initial tag elements
+	 * are not being considered by now. */
+	profile_node = xmlnode_get_child(root_node, XML_PROFILE_TAG);
+	if (profile_node)
+		smileyset_node = xmlnode_get_child(profile_node, XML_SMILEY_SET_TAG);
+
+	if (smileyset_node) {
+		smiley_node = xmlnode_get_child(smileyset_node, XML_SMILEY_TAG);
+		for (; smiley_node != NULL;
+				smiley_node = xmlnode_get_next_twin(smiley_node)) {
+			PurpleSmiley *smiley;
+
+			smiley = parse_smiley(smiley_node);
+		}
+	}
+
+	xmlnode_free(root_node);
+}
+
+/*********************************************************************
+ * GObject Stuff                                                     *
+ *********************************************************************/
+enum
+{
+	PROP_0,
+	PROP_SHORTCUT,
+	PROP_IMGSTORE,
+};
+
+#define PROP_SHORTCUT_S "shortcut"
+#define PROP_IMGSTORE_S "image"
+
+enum
+{
+	SIG_DESTROY,
+	SIG_LAST
+};
+
+static guint signals[SIG_LAST];
+static GObjectClass *parent_class;
+
+static void
+purple_smiley_init(GTypeInstance *instance, gpointer klass)
+{
+	PurpleSmiley *smiley = PURPLE_SMILEY(instance);
+	PURPLE_DBUS_REGISTER_POINTER(smiley, PurpleSmiley);
+}
+
+static void
+purple_smiley_get_property(GObject *object, guint param_id, GValue *value,
+		GParamSpec *spec)
+{
+	PurpleSmiley *smiley = PURPLE_SMILEY(object);
+	switch (param_id) {
+		case PROP_SHORTCUT:
+			g_value_set_string(value, smiley->shortcut);
+			break;
+		case PROP_IMGSTORE:
+			g_value_set_pointer(value, smiley->img);
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, spec);
+			break;
+	}
+}
+
+static void
+purple_smiley_set_property(GObject *object, guint param_id, const GValue *value,
+		GParamSpec *spec)
+{
+	PurpleSmiley *smiley = PURPLE_SMILEY(object);
+	switch (param_id) {
+		case PROP_SHORTCUT:
+			{
+				const char *shortcut = g_value_get_string(value);
+				purple_smiley_set_shortcut(smiley, shortcut);
+			}
+			break;
+		case PROP_IMGSTORE:
+			{
+				PurpleStoredImage *img = g_value_get_pointer(value);
+
+				purple_imgstore_unref(smiley->img);
+				g_free(smiley->checksum);
+
+				smiley->img = img;
+				if (img) {
+					smiley->checksum = purple_util_get_image_checksum(
+							purple_imgstore_get_data(img),
+							purple_imgstore_get_size(img));
+					purple_smiley_data_store(img);
+				} else {
+					smiley->checksum = NULL;
+				}
+
+				g_object_notify(object, PROP_IMGSTORE_S);
+			}
+			break;
+		default:
+			G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, spec);
+			break;
+	}
+}
+
+static void
+purple_smiley_finalize(GObject *obj)
+{
+	PurpleSmiley *smiley = PURPLE_SMILEY(obj);
+
+	if (g_hash_table_lookup(smiley_shortcut_index, smiley->shortcut)) {
+		g_hash_table_remove(smiley_shortcut_index, smiley->shortcut);
+		g_hash_table_remove(smiley_checksum_index, smiley->checksum);
+	}
+
+	g_free(smiley->shortcut);
+	g_free(smiley->checksum);
+	if (smiley->img)
+		purple_smiley_data_unstore(purple_imgstore_get_filename(smiley->img));
+	purple_imgstore_unref(smiley->img);
+
+	PURPLE_DBUS_UNREGISTER_POINTER(smiley);
+
+	purple_smileys_save();
+}
+
+static void
+purple_smiley_dispose(GObject *gobj)
+{
+	g_signal_emit(gobj, signals[SIG_DESTROY], 0);
+	parent_class->dispose(gobj);
+}
+
+static void
+purple_smiley_class_init(PurpleSmileyClass *klass)
+{
+	GObjectClass *gobj_class = G_OBJECT_CLASS(klass);
+	GParamSpec *pspec;
+
+	parent_class = g_type_class_peek_parent(klass);
+
+	gobj_class->get_property = purple_smiley_get_property;
+	gobj_class->set_property = purple_smiley_set_property;
+	gobj_class->finalize = purple_smiley_finalize;
+	gobj_class->dispose = purple_smiley_dispose;
+
+	/* Shortcut */
+	pspec = g_param_spec_string(PROP_SHORTCUT_S, _("Shortcut"),
+			_("The text-shortcut for the smiley"),
+			NULL,
+			G_PARAM_READWRITE);
+	g_object_class_install_property(gobj_class, PROP_SHORTCUT, pspec);
+
+	/* Stored Image */
+	pspec = g_param_spec_pointer(PROP_IMGSTORE_S, _("Stored Image"),
+			_("Stored Image. (that'll have to do for now)"),
+			G_PARAM_READWRITE);
+	g_object_class_install_property(gobj_class, PROP_IMGSTORE, pspec);
+
+	signals[SIG_DESTROY] = g_signal_new("destroy",
+			G_OBJECT_CLASS_TYPE(klass),
+			G_SIGNAL_RUN_LAST,
+			0, NULL, NULL,
+			g_cclosure_marshal_VOID__VOID,
+			G_TYPE_NONE, 0);
+}
+
+GType
+purple_smiley_get_type(void)
+{
+	static GType type = 0;
+
+	if(type == 0) {
+		static const GTypeInfo info = {
+			sizeof(PurpleSmileyClass),
+			NULL,
+			NULL,
+			(GClassInitFunc)purple_smiley_class_init,
+			NULL,
+			NULL,
+			sizeof(PurpleSmiley),
+			0,
+			purple_smiley_init,
+			NULL,
+		};
+
+		type = g_type_register_static(G_TYPE_OBJECT,
+				"PurpleSmiley",
+				&info, 0);
+	}
+
+	return type;
+}
+
+/*********************************************************************
+ * Other Stuff                                                             *
+ *********************************************************************/
+
+static char *get_file_full_path(const char *filename)
+{
+	char *path;
+
+	path = g_build_filename(purple_smileys_get_storing_dir(), filename, NULL);
+
+	if (!g_file_test(path, G_FILE_TEST_EXISTS)) {
+		g_free(path);
+		return NULL;
+	}
+
+	return path;
+}
+
+static PurpleSmiley *
+purple_smiley_load_file(const char *shortcut, const char *checksum, const char *filename)
+{
+	PurpleSmiley *smiley = NULL;
+	guchar *smiley_data;
+	size_t smiley_data_len;
+	char *fullpath = NULL;
+
+	g_return_val_if_fail(shortcut  != NULL, NULL);
+	g_return_val_if_fail(checksum  != NULL, NULL);
+	g_return_val_if_fail(filename != NULL, NULL);
+
+	fullpath = get_file_full_path(filename);
+	if (!fullpath)
+		return NULL;
+
+	smiley = purple_smiley_create(shortcut);
+	if (!smiley) {
+		g_free(fullpath);
+		return NULL;
+	}
+
+	smiley->checksum = g_strdup(checksum);
+
+	if (read_smiley_file(fullpath, &smiley_data, &smiley_data_len))
+		purple_smiley_set_data_impl(smiley, smiley_data,
+				smiley_data_len, filename);
+	else
+		purple_smiley_delete(smiley);
+
+	g_free(fullpath);
+
+	return smiley;
+}
+
+static void
+purple_smiley_data_store(PurpleStoredImage *stored_img)
+{
+	const char *dirname;
+	char *path;
+	FILE *file = NULL;
+
+	g_return_if_fail(stored_img != NULL);
+
+	if (!smileys_loaded)
+		return;
+
+	dirname  = purple_smileys_get_storing_dir();
+	path = g_build_filename(dirname, purple_imgstore_get_filename(stored_img), NULL);
+
+	if (!g_file_test(dirname, G_FILE_TEST_IS_DIR)) {
+		purple_debug_info(SMILEYS_LOG_ID, "Creating smileys directory.\n");
+
+		if (g_mkdir(dirname, S_IRUSR | S_IWUSR | S_IXUSR) < 0) {
+			purple_debug_error(SMILEYS_LOG_ID,
+			                   "Unable to create directory %s: %s\n",
+			                   dirname, g_strerror(errno));
+		}
+	}
+
+	if ((file = g_fopen(path, "wb")) != NULL) {
+		if (!fwrite(purple_imgstore_get_data(stored_img),
+				purple_imgstore_get_size(stored_img), 1, file)) {
+			purple_debug_error(SMILEYS_LOG_ID, "Error writing %s: %s\n",
+			                   path, g_strerror(errno));
+		} else {
+			purple_debug_info(SMILEYS_LOG_ID, "Wrote cache file: %s\n", path);
+		}
+
+		fclose(file);
+	} else {
+		purple_debug_error(SMILEYS_LOG_ID, "Unable to create file %s: %s\n",
+		                   path, g_strerror(errno));
+		g_free(path);
+
+		return;
+	}
+
+	g_free(path);
+}
+
+static void
+purple_smiley_data_unstore(const char *filename)
+{
+	const char *dirname;
+	char *path;
+
+	g_return_if_fail(filename != NULL);
+
+	dirname  = purple_smileys_get_storing_dir();
+	path = g_build_filename(dirname, filename, NULL);
+
+	if (g_file_test(path, G_FILE_TEST_EXISTS)) {
+		if (g_unlink(path))
+			purple_debug_error(SMILEYS_LOG_ID, "Failed to delete %s: %s\n",
+			                   path, g_strerror(errno));
+		else
+			purple_debug_info(SMILEYS_LOG_ID, "Deleted cache file: %s\n", path);
+	}
+
+	g_free(path);
+}
+
+static gboolean
+read_smiley_file(const char *path, guchar **data, size_t *len)
+{
+	GError *err = NULL;
+
+	if (!g_file_get_contents(path, (gchar **)data, len, &err)) {
+		purple_debug_error(SMILEYS_LOG_ID, "Error reading %s: %s\n",
+				path, err->message);
+		g_error_free(err);
+
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static PurpleStoredImage *
+purple_smiley_data_new(guchar *smiley_data, size_t smiley_data_len)
+{
+	char *filename;
+	PurpleStoredImage *stored_img;
+
+	g_return_val_if_fail(smiley_data != NULL,   NULL);
+	g_return_val_if_fail(smiley_data_len  > 0,  NULL);
+
+	filename = purple_util_get_image_filename(smiley_data, smiley_data_len);
+
+	if (filename == NULL) {
+		g_free(smiley_data);
+		return NULL;
+	}
+
+	stored_img = purple_imgstore_add(smiley_data, smiley_data_len, filename);
+
+	g_free(filename);
+
+	return stored_img;
+}
+
+static void
+purple_smiley_set_data_impl(PurpleSmiley *smiley, guchar *smiley_data,
+				size_t smiley_data_len, const char *filename)
+{
+	PurpleStoredImage *old_img, *new_img;
+	const char *old_filename = NULL;
+	const char *new_filename = NULL;
+
+	g_return_if_fail(smiley     != NULL);
+	g_return_if_fail(smiley_data != NULL);
+	g_return_if_fail(smiley_data_len > 0);
+
+	old_img = smiley->img;
+
+	if (filename)
+		new_img = purple_imgstore_add(smiley_data, smiley_data_len, filename);
+	else
+		new_img = purple_smiley_data_new(smiley_data, smiley_data_len);
+
+	g_object_set(G_OBJECT(smiley), PROP_IMGSTORE_S, new_img, NULL);
+
+	/* If the old and new image files have different names we need
+	 * to unstore old image file. */
+	if (!old_img)
+		return;
+
+	old_filename = purple_imgstore_get_filename(old_img);
+	new_filename = purple_imgstore_get_filename(smiley->img);
+
+	if (g_ascii_strcasecmp(old_filename, new_filename)) {
+		purple_smiley_data_unstore(old_filename);
+		purple_imgstore_unref(old_img);
+	}
+}
+
+
+/*****************************************************************************
+ * Public API functions                                                      *
+ *****************************************************************************/
+
+static PurpleSmiley *
+purple_smiley_create(const char *shortcut)
+{
+	PurpleSmiley *smiley;
+
+	smiley = PURPLE_SMILEY(g_object_new(PURPLE_TYPE_SMILEY, PROP_SHORTCUT_S, shortcut, NULL));
+
+	return smiley;
+}
+
+PurpleSmiley *
+purple_smiley_new(PurpleStoredImage *img, const char *shortcut)
+{
+	PurpleSmiley *smiley = NULL;
+
+	g_return_val_if_fail(shortcut  != NULL, NULL);
+	g_return_val_if_fail(img       != NULL, NULL);
+
+	smiley = purple_smileys_find_by_shortcut(shortcut);
+	if (smiley)
+		return smiley;
+
+	smiley = purple_smiley_create(shortcut);
+	if (!smiley)
+		return NULL;
+
+	g_object_set(G_OBJECT(smiley), PROP_IMGSTORE_S, img, NULL);
+
+	return smiley;
+}
+
+static PurpleSmiley *
+purple_smiley_new_from_stream(const char *shortcut, guchar *smiley_data,
+			size_t smiley_data_len, const char *filename)
+{
+	PurpleSmiley *smiley;
+
+	g_return_val_if_fail(shortcut  != NULL,    NULL);
+	g_return_val_if_fail(smiley_data != NULL,  NULL);
+	g_return_val_if_fail(smiley_data_len  > 0, NULL);
+
+	smiley = purple_smileys_find_by_shortcut(shortcut);
+	if (smiley)
+		return smiley;
+
+	/* purple_smiley_create() sets shortcut */
+	smiley = purple_smiley_create(shortcut);
+	if (!smiley)
+		return NULL;
+
+	purple_smiley_set_data_impl(smiley, smiley_data, smiley_data_len, filename);
+
+	purple_smiley_data_store(smiley->img);
+
+	return smiley;
+}
+
+PurpleSmiley *
+purple_smiley_new_from_file(const char *shortcut, const char *filepath)
+{
+	PurpleSmiley *smiley = NULL;
+	guchar *smiley_data;
+	size_t smiley_data_len;
+	char *filename;
+
+	g_return_val_if_fail(shortcut  != NULL,  NULL);
+	g_return_val_if_fail(filepath != NULL,  NULL);
+
+	filename = g_path_get_basename(filepath);
+	if (read_smiley_file(filepath, &smiley_data, &smiley_data_len))
+		smiley = purple_smiley_new_from_stream(shortcut, smiley_data,
+				smiley_data_len, filename);
+	g_free(filename);
+
+	return smiley;
+}
+
+void
+purple_smiley_delete(PurpleSmiley *smiley)
+{
+	g_return_if_fail(smiley != NULL);
+
+	g_object_unref(smiley);
+}
+
+gboolean
+purple_smiley_set_shortcut(PurpleSmiley *smiley, const char *shortcut)
+{
+	g_return_val_if_fail(smiley  != NULL, FALSE);
+	g_return_val_if_fail(shortcut != NULL, FALSE);
+
+	/* Check out whether the new shortcut is already being used. */
+	if (g_hash_table_lookup(smiley_shortcut_index, shortcut))
+		return FALSE;
+
+	/* Remove the old shortcut. */
+	if (smiley->shortcut)
+		g_hash_table_remove(smiley_shortcut_index, smiley->shortcut);
+
+	/* Insert the new shortcut. */
+	g_hash_table_insert(smiley_shortcut_index, g_strdup(shortcut), smiley);
+
+	g_free(smiley->shortcut);
+	smiley->shortcut = g_strdup(shortcut);
+
+	g_object_notify(G_OBJECT(smiley), PROP_SHORTCUT_S);
+
+	purple_smileys_save();
+
+	return TRUE;
+}
+
+void
+purple_smiley_set_data(PurpleSmiley *smiley, guchar *smiley_data,
+			   size_t smiley_data_len, gboolean keepfilename)
+{
+	g_return_if_fail(smiley     != NULL);
+	g_return_if_fail(smiley_data != NULL);
+	g_return_if_fail(smiley_data_len > 0);
+
+	/* Remove the previous entry */
+	g_hash_table_remove(smiley_checksum_index, smiley->checksum);
+
+	/* Update the file data. This also updates the checksum. */
+	if ((keepfilename) && (smiley->img) &&
+			(purple_imgstore_get_filename(smiley->img)))
+		purple_smiley_set_data_impl(smiley, smiley_data,
+				smiley_data_len,
+				purple_imgstore_get_filename(smiley->img));
+	else
+		purple_smiley_set_data_impl(smiley, smiley_data,
+				smiley_data_len, NULL);
+
+	/* Reinsert the index item. */
+	g_hash_table_insert(smiley_checksum_index, g_strdup(smiley->checksum), smiley);
+
+	purple_smileys_save();
+}
+
+PurpleStoredImage *
+purple_smiley_get_stored_image(const PurpleSmiley *smiley)
+{
+	return purple_imgstore_ref(smiley->img);
+}
+
+const char *purple_smiley_get_shortcut(const PurpleSmiley *smiley)
+{
+	g_return_val_if_fail(smiley != NULL, NULL);
+
+	return smiley->shortcut;
+}
+
+const char *
+purple_smiley_get_checksum(const PurpleSmiley *smiley)
+{
+	g_return_val_if_fail(smiley != NULL, NULL);
+
+	return smiley->checksum;
+}
+
+gconstpointer
+purple_smiley_get_data(const PurpleSmiley *smiley, size_t *len)
+{
+	g_return_val_if_fail(smiley != NULL, NULL);
+
+	if (smiley->img) {
+		if (len != NULL)
+			*len = purple_imgstore_get_size(smiley->img);
+
+		return purple_imgstore_get_data(smiley->img);
+	}
+
+	return NULL;
+}
+
+const char *
+purple_smiley_get_extension(const PurpleSmiley *smiley)
+{
+	if (smiley->img != NULL)
+		return purple_imgstore_get_extension(smiley->img);
+
+	return NULL;
+}
+
+char *purple_smiley_get_full_path(PurpleSmiley *smiley)
+{
+	g_return_val_if_fail(smiley != NULL, NULL);
+
+	if (smiley->img == NULL)
+		return NULL;
+
+	return get_file_full_path(purple_imgstore_get_filename(smiley->img));
+}
+
+static void add_smiley_to_list(gpointer key, gpointer value, gpointer user_data)
+{
+	GList** returninglist = (GList**)user_data;
+
+	*returninglist = g_list_append(*returninglist, value);
+}
+
+GList *
+purple_smileys_get_all(void)
+{
+	GList *returninglist = NULL;
+
+	g_hash_table_foreach(smiley_shortcut_index, add_smiley_to_list, &returninglist);
+
+	return returninglist;
+}
+
+PurpleSmiley *
+purple_smileys_find_by_shortcut(const char *shortcut)
+{
+	g_return_val_if_fail(shortcut != NULL, NULL);
+
+	return g_hash_table_lookup(smiley_shortcut_index, shortcut);
+}
+
+PurpleSmiley *
+purple_smileys_find_by_checksum(const char *checksum)
+{
+	g_return_val_if_fail(checksum != NULL, NULL);
+
+	return g_hash_table_lookup(smiley_checksum_index, checksum);
+}
+
+const char *
+purple_smileys_get_storing_dir(void)
+{
+	return smileys_dir;
+}
+
+void
+purple_smileys_init()
+{
+	smiley_shortcut_index = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+	smiley_checksum_index = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+
+	smileys_dir = g_build_filename(purple_user_dir(), SMILEYS_DEFAULT_FOLDER, NULL);
+
+	purple_smileys_load();
+}
+
+void
+purple_smileys_uninit()
+{
+	if (save_timer != 0) {
+		purple_timeout_remove(save_timer);
+		save_timer = 0;
+		sync_smileys();
+	}
+
+	g_hash_table_destroy(smiley_shortcut_index);
+	g_hash_table_destroy(smiley_checksum_index);
+	g_free(smileys_dir);
+}
+