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 void 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 *********************************************************************/
231 parse_smiley(xmlnode
*smiley_node
)
233 const char *shortcut
= NULL
;
234 const char *checksum
= NULL
;
235 const char *filename
= NULL
;
237 shortcut
= xmlnode_get_attrib(smiley_node
, XML_SHORTCUT_ATTRIB_TAG
);
238 checksum
= xmlnode_get_attrib(smiley_node
, XML_CHECKSUM_ATRIB_TAG
);
239 filename
= xmlnode_get_attrib(smiley_node
, XML_FILENAME_ATRIB_TAG
);
241 if ((shortcut
== NULL
) || (checksum
== NULL
) || (filename
== NULL
))
244 purple_smiley_load_file(shortcut
, checksum
, filename
);
248 purple_smileys_load(void)
250 xmlnode
*root_node
, *profile_node
;
251 xmlnode
*smileyset_node
= NULL
;
252 xmlnode
*smiley_node
;
254 smileys_loaded
= TRUE
;
256 root_node
= purple_util_read_xml_from_file(XML_FILE_NAME
,
259 if (root_node
== NULL
)
262 /* See the top comments above to understand why initial tag elements
263 * are not being considered by now. */
264 profile_node
= xmlnode_get_child(root_node
, XML_PROFILE_TAG
);
266 smileyset_node
= xmlnode_get_child(profile_node
, XML_SMILEY_SET_TAG
);
268 if (smileyset_node
) {
269 smiley_node
= xmlnode_get_child(smileyset_node
, XML_SMILEY_TAG
);
270 for (; smiley_node
!= NULL
;
271 smiley_node
= xmlnode_get_next_twin(smiley_node
)) {
272 parse_smiley(smiley_node
);
276 xmlnode_free(root_node
);
279 /*********************************************************************
281 *********************************************************************/
289 #define PROP_SHORTCUT_S "shortcut"
290 #define PROP_IMGSTORE_S "image"
298 static guint signals
[SIG_LAST
];
299 static GObjectClass
*parent_class
;
302 purple_smiley_init(GTypeInstance
*instance
, gpointer klass
)
304 PurpleSmiley
*smiley
= PURPLE_SMILEY(instance
);
305 PURPLE_DBUS_REGISTER_POINTER(smiley
, PurpleSmiley
);
309 purple_smiley_get_property(GObject
*object
, guint param_id
, GValue
*value
,
312 PurpleSmiley
*smiley
= PURPLE_SMILEY(object
);
315 g_value_set_string(value
, smiley
->shortcut
);
318 g_value_set_pointer(value
, smiley
->img
);
321 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, param_id
, spec
);
327 purple_smiley_set_property(GObject
*object
, guint param_id
, const GValue
*value
,
330 PurpleSmiley
*smiley
= PURPLE_SMILEY(object
);
334 const char *shortcut
= g_value_get_string(value
);
335 purple_smiley_set_shortcut(smiley
, shortcut
);
340 PurpleStoredImage
*img
= g_value_get_pointer(value
);
342 purple_imgstore_unref(smiley
->img
);
343 g_free(smiley
->checksum
);
347 smiley
->checksum
= purple_util_get_image_checksum(
348 purple_imgstore_get_data(img
),
349 purple_imgstore_get_size(img
));
350 purple_smiley_data_store(img
);
352 smiley
->checksum
= NULL
;
355 g_object_notify(object
, PROP_IMGSTORE_S
);
359 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, param_id
, spec
);
365 purple_smiley_finalize(GObject
*obj
)
367 PurpleSmiley
*smiley
= PURPLE_SMILEY(obj
);
369 if (g_hash_table_lookup(smiley_shortcut_index
, smiley
->shortcut
)) {
370 g_hash_table_remove(smiley_shortcut_index
, smiley
->shortcut
);
371 g_hash_table_remove(smiley_checksum_index
, smiley
->checksum
);
374 g_free(smiley
->shortcut
);
375 g_free(smiley
->checksum
);
377 purple_smiley_data_unstore(purple_imgstore_get_filename(smiley
->img
));
378 purple_imgstore_unref(smiley
->img
);
380 PURPLE_DBUS_UNREGISTER_POINTER(smiley
);
382 purple_smileys_save();
386 purple_smiley_dispose(GObject
*gobj
)
388 g_signal_emit(gobj
, signals
[SIG_DESTROY
], 0);
389 parent_class
->dispose(gobj
);
393 purple_smiley_class_init(PurpleSmileyClass
*klass
)
395 GObjectClass
*gobj_class
= G_OBJECT_CLASS(klass
);
398 parent_class
= g_type_class_peek_parent(klass
);
400 gobj_class
->get_property
= purple_smiley_get_property
;
401 gobj_class
->set_property
= purple_smiley_set_property
;
402 gobj_class
->finalize
= purple_smiley_finalize
;
403 gobj_class
->dispose
= purple_smiley_dispose
;
406 pspec
= g_param_spec_string(PROP_SHORTCUT_S
, _("Shortcut"),
407 _("The text-shortcut for the smiley"),
410 g_object_class_install_property(gobj_class
, PROP_SHORTCUT
, pspec
);
413 pspec
= g_param_spec_pointer(PROP_IMGSTORE_S
, _("Stored Image"),
414 _("Stored Image. (that'll have to do for now)"),
416 g_object_class_install_property(gobj_class
, PROP_IMGSTORE
, pspec
);
418 signals
[SIG_DESTROY
] = g_signal_new("destroy",
419 G_OBJECT_CLASS_TYPE(klass
),
422 g_cclosure_marshal_VOID__VOID
,
427 purple_smiley_get_type(void)
429 static GType type
= 0;
432 static const GTypeInfo info
= {
433 sizeof(PurpleSmileyClass
),
436 (GClassInitFunc
)purple_smiley_class_init
,
439 sizeof(PurpleSmiley
),
445 type
= g_type_register_static(G_TYPE_OBJECT
,
453 /*********************************************************************
455 *********************************************************************/
457 static char *get_file_full_path(const char *filename
)
461 path
= g_build_filename(purple_smileys_get_storing_dir(), filename
, NULL
);
463 if (!g_file_test(path
, G_FILE_TEST_EXISTS
)) {
472 purple_smiley_load_file(const char *shortcut
, const char *checksum
, const char *filename
)
474 PurpleSmiley
*smiley
= NULL
;
476 size_t smiley_data_len
;
477 char *fullpath
= NULL
;
479 g_return_if_fail(shortcut
!= NULL
);
480 g_return_if_fail(checksum
!= NULL
);
481 g_return_if_fail(filename
!= NULL
);
483 fullpath
= get_file_full_path(filename
);
485 purple_debug_error(SMILEYS_LOG_ID
, "Path for filename %s doesn't exist\n", filename
);
489 smiley
= purple_smiley_create(shortcut
);
495 smiley
->checksum
= g_strdup(checksum
);
497 if (read_smiley_file(fullpath
, &smiley_data
, &smiley_data_len
))
498 purple_smiley_set_data_impl(smiley
, smiley_data
,
501 purple_smiley_delete(smiley
);
508 purple_smiley_data_store(PurpleStoredImage
*stored_img
)
514 g_return_if_fail(stored_img
!= NULL
);
519 dirname
= purple_smileys_get_storing_dir();
520 path
= g_build_filename(dirname
, purple_imgstore_get_filename(stored_img
), NULL
);
522 if (!g_file_test(dirname
, G_FILE_TEST_IS_DIR
)) {
523 purple_debug_info(SMILEYS_LOG_ID
, "Creating smileys directory.\n");
525 if (g_mkdir(dirname
, S_IRUSR
| S_IWUSR
| S_IXUSR
) < 0) {
526 purple_debug_error(SMILEYS_LOG_ID
,
527 "Unable to create directory %s: %s\n",
528 dirname
, g_strerror(errno
));
532 if ((file
= g_fopen(path
, "wb")) != NULL
) {
533 if (!fwrite(purple_imgstore_get_data(stored_img
),
534 purple_imgstore_get_size(stored_img
), 1, file
)) {
535 purple_debug_error(SMILEYS_LOG_ID
, "Error writing %s: %s\n",
536 path
, g_strerror(errno
));
538 purple_debug_info(SMILEYS_LOG_ID
, "Wrote cache file: %s\n", path
);
543 purple_debug_error(SMILEYS_LOG_ID
, "Unable to create file %s: %s\n",
544 path
, g_strerror(errno
));
554 purple_smiley_data_unstore(const char *filename
)
559 g_return_if_fail(filename
!= NULL
);
561 dirname
= purple_smileys_get_storing_dir();
562 path
= g_build_filename(dirname
, filename
, NULL
);
564 if (g_file_test(path
, G_FILE_TEST_EXISTS
)) {
566 purple_debug_error(SMILEYS_LOG_ID
, "Failed to delete %s: %s\n",
567 path
, g_strerror(errno
));
569 purple_debug_info(SMILEYS_LOG_ID
, "Deleted cache file: %s\n", path
);
576 read_smiley_file(const char *path
, guchar
**data
, size_t *len
)
580 if (!g_file_get_contents(path
, (gchar
**)data
, len
, &err
)) {
581 purple_debug_error(SMILEYS_LOG_ID
, "Error reading %s: %s\n",
591 static PurpleStoredImage
*
592 purple_smiley_data_new(guchar
*smiley_data
, size_t smiley_data_len
)
595 PurpleStoredImage
*stored_img
;
597 g_return_val_if_fail(smiley_data
!= NULL
, NULL
);
598 g_return_val_if_fail(smiley_data_len
> 0, NULL
);
600 filename
= purple_util_get_image_filename(smiley_data
, smiley_data_len
);
602 if (filename
== NULL
) {
607 stored_img
= purple_imgstore_add(smiley_data
, smiley_data_len
, filename
);
615 purple_smiley_set_data_impl(PurpleSmiley
*smiley
, guchar
*smiley_data
,
616 size_t smiley_data_len
)
618 PurpleStoredImage
*old_img
, *new_img
;
619 const char *old_filename
= NULL
;
620 const char *new_filename
= NULL
;
622 g_return_if_fail(smiley
!= NULL
);
623 g_return_if_fail(smiley_data
!= NULL
);
624 g_return_if_fail(smiley_data_len
> 0);
626 old_img
= smiley
->img
;
628 new_img
= purple_smiley_data_new(smiley_data
, smiley_data_len
);
630 g_object_set(G_OBJECT(smiley
), PROP_IMGSTORE_S
, new_img
, NULL
);
632 /* If the old and new image files have different names we need
633 * to unstore old image file. */
637 old_filename
= purple_imgstore_get_filename(old_img
);
638 new_filename
= purple_imgstore_get_filename(smiley
->img
);
640 if (g_ascii_strcasecmp(old_filename
, new_filename
))
641 purple_smiley_data_unstore(old_filename
);
642 purple_imgstore_unref(old_img
);
646 /*****************************************************************************
647 * Public API functions *
648 *****************************************************************************/
650 static PurpleSmiley
*
651 purple_smiley_create(const char *shortcut
)
653 PurpleSmiley
*smiley
;
655 smiley
= PURPLE_SMILEY(g_object_new(PURPLE_TYPE_SMILEY
, PROP_SHORTCUT_S
, shortcut
, NULL
));
661 purple_smiley_new(PurpleStoredImage
*img
, const char *shortcut
)
663 PurpleSmiley
*smiley
= NULL
;
665 g_return_val_if_fail(shortcut
!= NULL
, NULL
);
666 g_return_val_if_fail(img
!= NULL
, NULL
);
668 smiley
= purple_smileys_find_by_shortcut(shortcut
);
672 smiley
= purple_smiley_create(shortcut
);
676 g_object_set(G_OBJECT(smiley
), PROP_IMGSTORE_S
, img
, NULL
);
681 static PurpleSmiley
*
682 purple_smiley_new_from_stream(const char *shortcut
, guchar
*smiley_data
,
683 size_t smiley_data_len
)
685 PurpleSmiley
*smiley
;
687 g_return_val_if_fail(shortcut
!= NULL
, NULL
);
688 g_return_val_if_fail(smiley_data
!= NULL
, NULL
);
689 g_return_val_if_fail(smiley_data_len
> 0, NULL
);
691 smiley
= purple_smileys_find_by_shortcut(shortcut
);
695 /* purple_smiley_create() sets shortcut */
696 smiley
= purple_smiley_create(shortcut
);
700 purple_smiley_set_data_impl(smiley
, smiley_data
, smiley_data_len
);
702 purple_smiley_data_store(smiley
->img
);
708 purple_smiley_new_from_file(const char *shortcut
, const char *filepath
)
710 PurpleSmiley
*smiley
= NULL
;
712 size_t smiley_data_len
;
714 g_return_val_if_fail(shortcut
!= NULL
, NULL
);
715 g_return_val_if_fail(filepath
!= NULL
, NULL
);
717 if (read_smiley_file(filepath
, &smiley_data
, &smiley_data_len
)) {
718 smiley
= purple_smiley_new_from_stream(shortcut
, smiley_data
,
726 purple_smiley_delete(PurpleSmiley
*smiley
)
728 g_return_if_fail(smiley
!= NULL
);
730 g_object_unref(smiley
);
734 purple_smiley_set_shortcut(PurpleSmiley
*smiley
, const char *shortcut
)
736 g_return_val_if_fail(smiley
!= NULL
, FALSE
);
737 g_return_val_if_fail(shortcut
!= NULL
, FALSE
);
739 /* Check out whether the new shortcut is already being used. */
740 if (g_hash_table_lookup(smiley_shortcut_index
, shortcut
))
743 /* Remove the old shortcut. */
744 if (smiley
->shortcut
)
745 g_hash_table_remove(smiley_shortcut_index
, smiley
->shortcut
);
747 /* Insert the new shortcut. */
748 g_hash_table_insert(smiley_shortcut_index
, g_strdup(shortcut
), smiley
);
750 g_free(smiley
->shortcut
);
751 smiley
->shortcut
= g_strdup(shortcut
);
753 g_object_notify(G_OBJECT(smiley
), PROP_SHORTCUT_S
);
755 purple_smileys_save();
761 purple_smiley_set_data(PurpleSmiley
*smiley
, guchar
*smiley_data
,
762 size_t smiley_data_len
)
764 g_return_if_fail(smiley
!= NULL
);
765 g_return_if_fail(smiley_data
!= NULL
);
766 g_return_if_fail(smiley_data_len
> 0);
768 /* Remove the previous entry */
769 g_hash_table_remove(smiley_checksum_index
, smiley
->checksum
);
771 /* Update the file data. This also updates the checksum. */
772 purple_smiley_set_data_impl(smiley
, smiley_data
, smiley_data_len
);
774 /* Reinsert the index item. */
775 g_hash_table_insert(smiley_checksum_index
, g_strdup(smiley
->checksum
), smiley
);
777 purple_smileys_save();
781 purple_smiley_get_stored_image(const PurpleSmiley
*smiley
)
783 return purple_imgstore_ref(smiley
->img
);
786 const char *purple_smiley_get_shortcut(const PurpleSmiley
*smiley
)
788 g_return_val_if_fail(smiley
!= NULL
, NULL
);
790 return smiley
->shortcut
;
794 purple_smiley_get_checksum(const PurpleSmiley
*smiley
)
796 g_return_val_if_fail(smiley
!= NULL
, NULL
);
798 return smiley
->checksum
;
802 purple_smiley_get_data(const PurpleSmiley
*smiley
, size_t *len
)
804 g_return_val_if_fail(smiley
!= NULL
, NULL
);
808 *len
= purple_imgstore_get_size(smiley
->img
);
810 return purple_imgstore_get_data(smiley
->img
);
817 purple_smiley_get_extension(const PurpleSmiley
*smiley
)
819 if (smiley
->img
!= NULL
)
820 return purple_imgstore_get_extension(smiley
->img
);
825 char *purple_smiley_get_full_path(PurpleSmiley
*smiley
)
827 g_return_val_if_fail(smiley
!= NULL
, NULL
);
829 if (smiley
->img
== NULL
)
832 return get_file_full_path(purple_imgstore_get_filename(smiley
->img
));
835 static void add_smiley_to_list(gpointer key
, gpointer value
, gpointer user_data
)
837 GList
** returninglist
= (GList
**)user_data
;
839 *returninglist
= g_list_append(*returninglist
, value
);
843 purple_smileys_get_all(void)
845 GList
*returninglist
= NULL
;
847 g_hash_table_foreach(smiley_shortcut_index
, add_smiley_to_list
, &returninglist
);
849 return returninglist
;
853 purple_smileys_find_by_shortcut(const char *shortcut
)
855 g_return_val_if_fail(shortcut
!= NULL
, NULL
);
857 return g_hash_table_lookup(smiley_shortcut_index
, shortcut
);
861 purple_smileys_find_by_checksum(const char *checksum
)
863 g_return_val_if_fail(checksum
!= NULL
, NULL
);
865 return g_hash_table_lookup(smiley_checksum_index
, checksum
);
869 purple_smileys_get_storing_dir(void)
875 purple_smileys_init(void)
877 smiley_shortcut_index
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, NULL
);
878 smiley_checksum_index
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, NULL
);
880 smileys_dir
= g_build_filename(purple_user_dir(), SMILEYS_DEFAULT_FOLDER
, NULL
);
882 purple_smileys_load();
886 purple_smileys_uninit(void)
888 if (save_timer
!= 0) {
889 purple_timeout_remove(save_timer
);
894 g_hash_table_destroy(smiley_shortcut_index
);
895 g_hash_table_destroy(smiley_checksum_index
);