2 * @file smiley.c Simley API
8 * Purple is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
28 #include "dbus-maybe.h"
35 /**************************************************************************/
36 /* Main structures, members and constants */
37 /**************************************************************************/
42 PurpleStoredImage
*img
; /**< The id of the stored image with the
44 char *shortcut
; /**< Shortcut associated with the custom
45 smiley. This field will work as a
46 unique key by this API. */
47 char *checksum
; /**< The smiley checksum. */
50 struct _PurpleSmileyClass
52 GObjectClass parent_class
;
55 static GHashTable
*smiley_shortcut_index
= NULL
; /* shortcut (char *) => smiley (PurpleSmiley*) */
56 static GHashTable
*smiley_checksum_index
= NULL
; /* checksum (char *) => smiley (PurpleSmiley*) */
58 static guint save_timer
= 0;
59 static gboolean smileys_loaded
= FALSE
;
60 static char *smileys_dir
= NULL
;
62 #define SMILEYS_DEFAULT_FOLDER "custom_smiley"
63 #define SMILEYS_LOG_ID "smileys"
65 #define XML_FILE_NAME "smileys.xml"
67 #define XML_ROOT_TAG "smileys"
68 #define XML_PROFILE_TAG "profile"
69 #define XML_PROFILE_NAME_ATTRIB_TAG "name"
70 #define XML_ACCOUNT_TAG "account"
71 #define XML_ACCOUNT_USERID_ATTRIB_TAG "userid"
72 #define XML_SMILEY_SET_TAG "smiley_set"
73 #define XML_SMILEY_TAG "smiley"
74 #define XML_SHORTCUT_ATTRIB_TAG "shortcut"
75 #define XML_CHECKSUM_ATRIB_TAG "checksum"
76 #define XML_FILENAME_ATRIB_TAG "filename"
79 /******************************************************************************
80 * XML descriptor file layout *
81 ******************************************************************************
83 * Although we are creating the profile XML structure here, now we
85 * So, we just add one profile named "default" that has no associated
86 * account elements, and have only the smiley_set that will contain
87 * all existent custom smiley.
89 * It's our "Highlander Profile" :-)
91 ******************************************************************************
94 * <profile name="john.doe">
95 * <account userid="john.doe@jabber.org">
96 * <account userid="john.doe@gmail.com">
98 * <smiley shortcut="aaa" checksum="xxxxxxxx" filename="file_name1.gif"/>
99 * <smiley shortcut="bbb" checksum="yyyyyyy" filename="file_name2.gif"/>
104 *****************************************************************************/
107 /*********************************************************************
108 * Forward declarations *
109 *********************************************************************/
111 static gboolean
read_smiley_file(const char *path
, guchar
**data
, size_t *len
);
113 static char *get_file_full_path(const char *filename
);
115 static PurpleSmiley
*purple_smiley_create(const char *shortcut
);
117 static PurpleSmiley
*purple_smiley_load_file(const char *shortcut
, const char *checksum
,
118 const char *filename
);
121 purple_smiley_set_data_impl(PurpleSmiley
*smiley
, guchar
*smiley_data
,
122 size_t smiley_data_len
);
125 purple_smiley_data_store(PurpleStoredImage
*stored_img
);
128 purple_smiley_data_unstore(const char *filename
);
130 /*********************************************************************
132 *********************************************************************/
135 smiley_to_xmlnode(PurpleSmiley
*smiley
)
137 xmlnode
*smiley_node
= NULL
;
139 smiley_node
= xmlnode_new(XML_SMILEY_TAG
);
144 xmlnode_set_attrib(smiley_node
, XML_SHORTCUT_ATTRIB_TAG
,
147 xmlnode_set_attrib(smiley_node
, XML_CHECKSUM_ATRIB_TAG
,
150 xmlnode_set_attrib(smiley_node
, XML_FILENAME_ATRIB_TAG
,
151 purple_imgstore_get_filename(smiley
->img
));
157 add_smiley_to_main_node(gpointer key
, gpointer value
, gpointer user_data
)
161 child_node
= smiley_to_xmlnode(value
);
162 xmlnode_insert_child((xmlnode
*)user_data
, child_node
);
166 smileys_to_xmlnode(void)
168 xmlnode
*root_node
, *profile_node
, *smileyset_node
;
170 root_node
= xmlnode_new(XML_ROOT_TAG
);
171 xmlnode_set_attrib(root_node
, "version", "1.0");
173 /* See the top comments above to understand why initial tag elements
174 * are not being considered by now. */
175 profile_node
= xmlnode_new(XML_PROFILE_TAG
);
177 xmlnode_set_attrib(profile_node
, XML_PROFILE_NAME_ATTRIB_TAG
, "Default");
178 xmlnode_insert_child(root_node
, profile_node
);
180 smileyset_node
= xmlnode_new(XML_SMILEY_SET_TAG
);
181 if (smileyset_node
) {
182 xmlnode_insert_child(profile_node
, smileyset_node
);
183 g_hash_table_foreach(smiley_shortcut_index
, add_smiley_to_main_node
, smileyset_node
);
196 if (!smileys_loaded
) {
197 purple_debug_error(SMILEYS_LOG_ID
, "Attempted to save smileys before it "
202 root_node
= smileys_to_xmlnode();
203 data
= xmlnode_to_formatted_str(root_node
, NULL
);
204 purple_util_write_data_to_file(XML_FILE_NAME
, data
, -1);
207 xmlnode_free(root_node
);
211 save_smileys_cb(gpointer data
)
219 purple_smileys_save(void)
222 save_timer
= purple_timeout_add_seconds(5, save_smileys_cb
, NULL
);
226 /*********************************************************************
227 * Reading from disk *
228 *********************************************************************/
230 static PurpleSmiley
*
231 parse_smiley(xmlnode
*smiley_node
)
233 PurpleSmiley
*smiley
;
234 const char *shortcut
= NULL
;
235 const char *checksum
= NULL
;
236 const char *filename
= NULL
;
238 shortcut
= xmlnode_get_attrib(smiley_node
, XML_SHORTCUT_ATTRIB_TAG
);
239 checksum
= xmlnode_get_attrib(smiley_node
, XML_CHECKSUM_ATRIB_TAG
);
240 filename
= xmlnode_get_attrib(smiley_node
, XML_FILENAME_ATRIB_TAG
);
242 if ((shortcut
== NULL
) || (checksum
== NULL
) || (filename
== NULL
))
245 smiley
= purple_smiley_load_file(shortcut
, checksum
, filename
);
251 purple_smileys_load(void)
253 xmlnode
*root_node
, *profile_node
;
254 xmlnode
*smileyset_node
= NULL
;
255 xmlnode
*smiley_node
;
257 smileys_loaded
= TRUE
;
259 root_node
= purple_util_read_xml_from_file(XML_FILE_NAME
,
262 if (root_node
== NULL
)
265 /* See the top comments above to understand why initial tag elements
266 * are not being considered by now. */
267 profile_node
= xmlnode_get_child(root_node
, XML_PROFILE_TAG
);
269 smileyset_node
= xmlnode_get_child(profile_node
, XML_SMILEY_SET_TAG
);
271 if (smileyset_node
) {
272 smiley_node
= xmlnode_get_child(smileyset_node
, XML_SMILEY_TAG
);
273 for (; smiley_node
!= NULL
;
274 smiley_node
= xmlnode_get_next_twin(smiley_node
)) {
275 PurpleSmiley
*smiley
;
277 smiley
= parse_smiley(smiley_node
);
281 xmlnode_free(root_node
);
284 /*********************************************************************
286 *********************************************************************/
294 #define PROP_SHORTCUT_S "shortcut"
295 #define PROP_IMGSTORE_S "image"
303 static guint signals
[SIG_LAST
];
304 static GObjectClass
*parent_class
;
307 purple_smiley_init(GTypeInstance
*instance
, gpointer klass
)
309 PurpleSmiley
*smiley
= PURPLE_SMILEY(instance
);
310 PURPLE_DBUS_REGISTER_POINTER(smiley
, PurpleSmiley
);
314 purple_smiley_get_property(GObject
*object
, guint param_id
, GValue
*value
,
317 PurpleSmiley
*smiley
= PURPLE_SMILEY(object
);
320 g_value_set_string(value
, smiley
->shortcut
);
323 g_value_set_pointer(value
, smiley
->img
);
326 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, param_id
, spec
);
332 purple_smiley_set_property(GObject
*object
, guint param_id
, const GValue
*value
,
335 PurpleSmiley
*smiley
= PURPLE_SMILEY(object
);
339 const char *shortcut
= g_value_get_string(value
);
340 purple_smiley_set_shortcut(smiley
, shortcut
);
345 PurpleStoredImage
*img
= g_value_get_pointer(value
);
347 purple_imgstore_unref(smiley
->img
);
348 g_free(smiley
->checksum
);
352 smiley
->checksum
= purple_util_get_image_checksum(
353 purple_imgstore_get_data(img
),
354 purple_imgstore_get_size(img
));
355 purple_smiley_data_store(img
);
357 smiley
->checksum
= NULL
;
360 g_object_notify(object
, PROP_IMGSTORE_S
);
364 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, param_id
, spec
);
370 purple_smiley_finalize(GObject
*obj
)
372 PurpleSmiley
*smiley
= PURPLE_SMILEY(obj
);
374 if (g_hash_table_lookup(smiley_shortcut_index
, smiley
->shortcut
)) {
375 g_hash_table_remove(smiley_shortcut_index
, smiley
->shortcut
);
376 g_hash_table_remove(smiley_checksum_index
, smiley
->checksum
);
379 g_free(smiley
->shortcut
);
380 g_free(smiley
->checksum
);
382 purple_smiley_data_unstore(purple_imgstore_get_filename(smiley
->img
));
383 purple_imgstore_unref(smiley
->img
);
385 PURPLE_DBUS_UNREGISTER_POINTER(smiley
);
387 purple_smileys_save();
391 purple_smiley_dispose(GObject
*gobj
)
393 g_signal_emit(gobj
, signals
[SIG_DESTROY
], 0);
394 parent_class
->dispose(gobj
);
398 purple_smiley_class_init(PurpleSmileyClass
*klass
)
400 GObjectClass
*gobj_class
= G_OBJECT_CLASS(klass
);
403 parent_class
= g_type_class_peek_parent(klass
);
405 gobj_class
->get_property
= purple_smiley_get_property
;
406 gobj_class
->set_property
= purple_smiley_set_property
;
407 gobj_class
->finalize
= purple_smiley_finalize
;
408 gobj_class
->dispose
= purple_smiley_dispose
;
411 pspec
= g_param_spec_string(PROP_SHORTCUT_S
, _("Shortcut"),
412 _("The text-shortcut for the smiley"),
415 g_object_class_install_property(gobj_class
, PROP_SHORTCUT
, pspec
);
418 pspec
= g_param_spec_pointer(PROP_IMGSTORE_S
, _("Stored Image"),
419 _("Stored Image. (that'll have to do for now)"),
421 g_object_class_install_property(gobj_class
, PROP_IMGSTORE
, pspec
);
423 signals
[SIG_DESTROY
] = g_signal_new("destroy",
424 G_OBJECT_CLASS_TYPE(klass
),
427 g_cclosure_marshal_VOID__VOID
,
432 purple_smiley_get_type(void)
434 static GType type
= 0;
437 static const GTypeInfo info
= {
438 sizeof(PurpleSmileyClass
),
441 (GClassInitFunc
)purple_smiley_class_init
,
444 sizeof(PurpleSmiley
),
450 type
= g_type_register_static(G_TYPE_OBJECT
,
458 /*********************************************************************
460 *********************************************************************/
462 static char *get_file_full_path(const char *filename
)
466 path
= g_build_filename(purple_smileys_get_storing_dir(), filename
, NULL
);
468 if (!g_file_test(path
, G_FILE_TEST_EXISTS
)) {
476 static PurpleSmiley
*
477 purple_smiley_load_file(const char *shortcut
, const char *checksum
, const char *filename
)
479 PurpleSmiley
*smiley
= NULL
;
481 size_t smiley_data_len
;
482 char *fullpath
= NULL
;
484 g_return_val_if_fail(shortcut
!= NULL
, NULL
);
485 g_return_val_if_fail(checksum
!= NULL
, NULL
);
486 g_return_val_if_fail(filename
!= NULL
, NULL
);
488 fullpath
= get_file_full_path(filename
);
492 smiley
= purple_smiley_create(shortcut
);
498 smiley
->checksum
= g_strdup(checksum
);
500 if (read_smiley_file(fullpath
, &smiley_data
, &smiley_data_len
))
501 purple_smiley_set_data_impl(smiley
, smiley_data
,
504 purple_smiley_delete(smiley
);
512 purple_smiley_data_store(PurpleStoredImage
*stored_img
)
518 g_return_if_fail(stored_img
!= NULL
);
523 dirname
= purple_smileys_get_storing_dir();
524 path
= g_build_filename(dirname
, purple_imgstore_get_filename(stored_img
), NULL
);
526 if (!g_file_test(dirname
, G_FILE_TEST_IS_DIR
)) {
527 purple_debug_info(SMILEYS_LOG_ID
, "Creating smileys directory.\n");
529 if (g_mkdir(dirname
, S_IRUSR
| S_IWUSR
| S_IXUSR
) < 0) {
530 purple_debug_error(SMILEYS_LOG_ID
,
531 "Unable to create directory %s: %s\n",
532 dirname
, g_strerror(errno
));
536 if ((file
= g_fopen(path
, "wb")) != NULL
) {
537 if (!fwrite(purple_imgstore_get_data(stored_img
),
538 purple_imgstore_get_size(stored_img
), 1, file
)) {
539 purple_debug_error(SMILEYS_LOG_ID
, "Error writing %s: %s\n",
540 path
, g_strerror(errno
));
542 purple_debug_info(SMILEYS_LOG_ID
, "Wrote cache file: %s\n", path
);
547 purple_debug_error(SMILEYS_LOG_ID
, "Unable to create file %s: %s\n",
548 path
, g_strerror(errno
));
558 purple_smiley_data_unstore(const char *filename
)
563 g_return_if_fail(filename
!= NULL
);
565 dirname
= purple_smileys_get_storing_dir();
566 path
= g_build_filename(dirname
, filename
, NULL
);
568 if (g_file_test(path
, G_FILE_TEST_EXISTS
)) {
570 purple_debug_error(SMILEYS_LOG_ID
, "Failed to delete %s: %s\n",
571 path
, g_strerror(errno
));
573 purple_debug_info(SMILEYS_LOG_ID
, "Deleted cache file: %s\n", path
);
580 read_smiley_file(const char *path
, guchar
**data
, size_t *len
)
584 if (!g_file_get_contents(path
, (gchar
**)data
, len
, &err
)) {
585 purple_debug_error(SMILEYS_LOG_ID
, "Error reading %s: %s\n",
595 static PurpleStoredImage
*
596 purple_smiley_data_new(guchar
*smiley_data
, size_t smiley_data_len
)
599 PurpleStoredImage
*stored_img
;
601 g_return_val_if_fail(smiley_data
!= NULL
, NULL
);
602 g_return_val_if_fail(smiley_data_len
> 0, NULL
);
604 filename
= purple_util_get_image_filename(smiley_data
, smiley_data_len
);
606 if (filename
== NULL
) {
611 stored_img
= purple_imgstore_add(smiley_data
, smiley_data_len
, filename
);
619 purple_smiley_set_data_impl(PurpleSmiley
*smiley
, guchar
*smiley_data
,
620 size_t smiley_data_len
)
622 PurpleStoredImage
*old_img
, *new_img
;
623 const char *old_filename
= NULL
;
624 const char *new_filename
= NULL
;
626 g_return_if_fail(smiley
!= NULL
);
627 g_return_if_fail(smiley_data
!= NULL
);
628 g_return_if_fail(smiley_data_len
> 0);
630 old_img
= smiley
->img
;
632 new_img
= purple_smiley_data_new(smiley_data
, smiley_data_len
);
634 g_object_set(G_OBJECT(smiley
), PROP_IMGSTORE_S
, new_img
, NULL
);
636 /* If the old and new image files have different names we need
637 * to unstore old image file. */
641 old_filename
= purple_imgstore_get_filename(old_img
);
642 new_filename
= purple_imgstore_get_filename(smiley
->img
);
644 if (g_ascii_strcasecmp(old_filename
, new_filename
))
645 purple_smiley_data_unstore(old_filename
);
646 purple_imgstore_unref(old_img
);
650 /*****************************************************************************
651 * Public API functions *
652 *****************************************************************************/
654 static PurpleSmiley
*
655 purple_smiley_create(const char *shortcut
)
657 PurpleSmiley
*smiley
;
659 smiley
= PURPLE_SMILEY(g_object_new(PURPLE_TYPE_SMILEY
, PROP_SHORTCUT_S
, shortcut
, NULL
));
665 purple_smiley_new(PurpleStoredImage
*img
, const char *shortcut
)
667 PurpleSmiley
*smiley
= NULL
;
669 g_return_val_if_fail(shortcut
!= NULL
, NULL
);
670 g_return_val_if_fail(img
!= NULL
, NULL
);
672 smiley
= purple_smileys_find_by_shortcut(shortcut
);
676 smiley
= purple_smiley_create(shortcut
);
680 g_object_set(G_OBJECT(smiley
), PROP_IMGSTORE_S
, img
, NULL
);
685 static PurpleSmiley
*
686 purple_smiley_new_from_stream(const char *shortcut
, guchar
*smiley_data
,
687 size_t smiley_data_len
)
689 PurpleSmiley
*smiley
;
691 g_return_val_if_fail(shortcut
!= NULL
, NULL
);
692 g_return_val_if_fail(smiley_data
!= NULL
, NULL
);
693 g_return_val_if_fail(smiley_data_len
> 0, NULL
);
695 smiley
= purple_smileys_find_by_shortcut(shortcut
);
699 /* purple_smiley_create() sets shortcut */
700 smiley
= purple_smiley_create(shortcut
);
704 purple_smiley_set_data_impl(smiley
, smiley_data
, smiley_data_len
);
706 purple_smiley_data_store(smiley
->img
);
712 purple_smiley_new_from_file(const char *shortcut
, const char *filepath
)
714 PurpleSmiley
*smiley
= NULL
;
716 size_t smiley_data_len
;
718 g_return_val_if_fail(shortcut
!= NULL
, NULL
);
719 g_return_val_if_fail(filepath
!= NULL
, NULL
);
721 if (read_smiley_file(filepath
, &smiley_data
, &smiley_data_len
)) {
722 smiley
= purple_smiley_new_from_stream(shortcut
, smiley_data
,
730 purple_smiley_delete(PurpleSmiley
*smiley
)
732 g_return_if_fail(smiley
!= NULL
);
734 g_object_unref(smiley
);
738 purple_smiley_set_shortcut(PurpleSmiley
*smiley
, const char *shortcut
)
740 g_return_val_if_fail(smiley
!= NULL
, FALSE
);
741 g_return_val_if_fail(shortcut
!= NULL
, FALSE
);
743 /* Check out whether the new shortcut is already being used. */
744 if (g_hash_table_lookup(smiley_shortcut_index
, shortcut
))
747 /* Remove the old shortcut. */
748 if (smiley
->shortcut
)
749 g_hash_table_remove(smiley_shortcut_index
, smiley
->shortcut
);
751 /* Insert the new shortcut. */
752 g_hash_table_insert(smiley_shortcut_index
, g_strdup(shortcut
), smiley
);
754 g_free(smiley
->shortcut
);
755 smiley
->shortcut
= g_strdup(shortcut
);
757 g_object_notify(G_OBJECT(smiley
), PROP_SHORTCUT_S
);
759 purple_smileys_save();
765 purple_smiley_set_data(PurpleSmiley
*smiley
, guchar
*smiley_data
,
766 size_t smiley_data_len
)
768 g_return_if_fail(smiley
!= NULL
);
769 g_return_if_fail(smiley_data
!= NULL
);
770 g_return_if_fail(smiley_data_len
> 0);
772 /* Remove the previous entry */
773 g_hash_table_remove(smiley_checksum_index
, smiley
->checksum
);
775 /* Update the file data. This also updates the checksum. */
776 purple_smiley_set_data_impl(smiley
, smiley_data
, smiley_data_len
);
778 /* Reinsert the index item. */
779 g_hash_table_insert(smiley_checksum_index
, g_strdup(smiley
->checksum
), smiley
);
781 purple_smileys_save();
785 purple_smiley_get_stored_image(const PurpleSmiley
*smiley
)
787 return purple_imgstore_ref(smiley
->img
);
790 const char *purple_smiley_get_shortcut(const PurpleSmiley
*smiley
)
792 g_return_val_if_fail(smiley
!= NULL
, NULL
);
794 return smiley
->shortcut
;
798 purple_smiley_get_checksum(const PurpleSmiley
*smiley
)
800 g_return_val_if_fail(smiley
!= NULL
, NULL
);
802 return smiley
->checksum
;
806 purple_smiley_get_data(const PurpleSmiley
*smiley
, size_t *len
)
808 g_return_val_if_fail(smiley
!= NULL
, NULL
);
812 *len
= purple_imgstore_get_size(smiley
->img
);
814 return purple_imgstore_get_data(smiley
->img
);
821 purple_smiley_get_extension(const PurpleSmiley
*smiley
)
823 if (smiley
->img
!= NULL
)
824 return purple_imgstore_get_extension(smiley
->img
);
829 char *purple_smiley_get_full_path(PurpleSmiley
*smiley
)
831 g_return_val_if_fail(smiley
!= NULL
, NULL
);
833 if (smiley
->img
== NULL
)
836 return get_file_full_path(purple_imgstore_get_filename(smiley
->img
));
839 static void add_smiley_to_list(gpointer key
, gpointer value
, gpointer user_data
)
841 GList
** returninglist
= (GList
**)user_data
;
843 *returninglist
= g_list_append(*returninglist
, value
);
847 purple_smileys_get_all(void)
849 GList
*returninglist
= NULL
;
851 g_hash_table_foreach(smiley_shortcut_index
, add_smiley_to_list
, &returninglist
);
853 return returninglist
;
857 purple_smileys_find_by_shortcut(const char *shortcut
)
859 g_return_val_if_fail(shortcut
!= NULL
, NULL
);
861 return g_hash_table_lookup(smiley_shortcut_index
, shortcut
);
865 purple_smileys_find_by_checksum(const char *checksum
)
867 g_return_val_if_fail(checksum
!= NULL
, NULL
);
869 return g_hash_table_lookup(smiley_checksum_index
, checksum
);
873 purple_smileys_get_storing_dir(void)
879 purple_smileys_init(void)
881 smiley_shortcut_index
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, NULL
);
882 smiley_checksum_index
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, NULL
);
884 smileys_dir
= g_build_filename(purple_user_dir(), SMILEYS_DEFAULT_FOLDER
, NULL
);
886 purple_smileys_load();
890 purple_smileys_uninit(void)
892 if (save_timer
!= 0) {
893 purple_timeout_remove(save_timer
);
898 g_hash_table_destroy(smiley_shortcut_index
);
899 g_hash_table_destroy(smiley_checksum_index
);