Migrate certificates, icons, logs to XDG dirs
[pidgin-git.git] / libpurple / image.c
blob02ee809a9474cfbacbfaf6566cb28bb2bb6af22a
1 /* purple
3 * Purple is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
5 * source distribution.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
22 #include "internal.h"
23 #include "glibcompat.h"
25 #include "debug.h"
26 #include "image.h"
28 #define PURPLE_IMAGE_GET_PRIVATE(obj) \
29 (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_IMAGE, PurpleImagePrivate))
31 typedef struct {
32 gchar *path;
33 GString *contents;
35 const gchar *extension;
36 const gchar *mime;
37 gchar *gen_filename;
38 gchar *friendly_filename;
40 gboolean is_ready;
41 gboolean has_failed;
42 } PurpleImagePrivate;
44 enum
46 PROP_0,
47 PROP_IS_READY,
48 PROP_HAS_FAILED,
49 PROP_LAST
52 enum
54 SIG_READY,
55 SIG_FAILED,
56 SIG_LAST
59 static GObjectClass *parent_class;
60 static guint signals[SIG_LAST];
61 static GParamSpec *properties[PROP_LAST];
63 /******************************************************************************
64 * Internal logic
65 ******************************************************************************/
67 static void
68 has_failed(PurpleImage *image)
70 gboolean ready_changed;
71 PurpleImagePrivate *priv;
72 priv = PURPLE_IMAGE_GET_PRIVATE(image);
74 g_return_if_fail(!priv->has_failed);
76 priv->has_failed = TRUE;
77 ready_changed = (priv->is_ready != FALSE);
78 priv->is_ready = FALSE;
80 if (priv->contents) {
81 g_string_free(priv->contents, TRUE);
82 priv->contents = NULL;
85 if (ready_changed) {
86 g_object_notify_by_pspec(G_OBJECT(image),
87 properties[PROP_IS_READY]);
89 g_object_notify_by_pspec(G_OBJECT(image), properties[PROP_HAS_FAILED]);
90 g_signal_emit(image, signals[SIG_FAILED], 0);
93 static void
94 became_ready(PurpleImage *image)
96 PurpleImagePrivate *priv;
97 priv = PURPLE_IMAGE_GET_PRIVATE(image);
99 g_return_if_fail(!priv->has_failed);
100 g_return_if_fail(!priv->is_ready);
102 priv->is_ready = TRUE;
104 g_object_notify_by_pspec(G_OBJECT(image), properties[PROP_IS_READY]);
105 g_signal_emit(image, signals[SIG_READY], 0);
108 static void
109 steal_contents(PurpleImagePrivate *priv, gpointer data, gsize length)
111 g_return_if_fail(priv != NULL);
112 g_return_if_fail(priv->contents == NULL);
113 g_return_if_fail(data != NULL);
114 g_return_if_fail(length > 0);
116 priv->contents = g_string_new(NULL);
117 g_free(priv->contents->str);
118 priv->contents->str = data;
119 priv->contents->len = priv->contents->allocated_len = length;
122 static void
123 fill_data(PurpleImage *image)
125 PurpleImagePrivate *priv;
126 GError *error = NULL;
127 gchar *contents;
128 gsize length;
130 priv = PURPLE_IMAGE_GET_PRIVATE(image);
131 if (priv->contents)
132 return;
134 if (!priv->is_ready)
135 return;
137 g_return_if_fail(priv->path);
138 (void)g_file_get_contents(priv->path, &contents, &length, &error);
139 if (error) {
140 purple_debug_error("image", "failed to read '%s' image: %s",
141 priv->path, error->message);
142 g_error_free(error);
144 has_failed(image);
145 return;
148 steal_contents(priv, contents, length);
152 /******************************************************************************
153 * API implementation
154 ******************************************************************************/
156 PurpleImage *
157 purple_image_new_from_file(const gchar *path, gboolean be_eager)
159 PurpleImagePrivate *priv;
160 PurpleImage *img;
162 g_return_val_if_fail(path != NULL, NULL);
163 g_return_val_if_fail(g_file_test(path, G_FILE_TEST_EXISTS), NULL);
165 img = g_object_new(PURPLE_TYPE_IMAGE, NULL);
166 purple_image_set_friendly_filename(img, path);
167 priv = PURPLE_IMAGE_GET_PRIVATE(img);
168 priv->path = g_strdup(path);
170 if (be_eager) {
171 fill_data(img);
172 if (!priv->contents) {
173 g_object_unref(img);
174 return NULL;
177 g_assert(priv->is_ready && !priv->has_failed);
180 return img;
183 PurpleImage *
184 purple_image_new_from_data(gpointer data, gsize length)
186 PurpleImage *img;
187 PurpleImagePrivate *priv;
189 g_return_val_if_fail(data != NULL, NULL);
190 g_return_val_if_fail(length > 0, NULL);
192 img = g_object_new(PURPLE_TYPE_IMAGE, NULL);
193 priv = PURPLE_IMAGE_GET_PRIVATE(img);
195 steal_contents(priv, data, length);
197 return img;
200 gboolean
201 purple_image_save(PurpleImage *image, const gchar *path)
203 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image);
204 gconstpointer data;
205 gsize len;
206 gboolean succ;
208 g_return_val_if_fail(priv != NULL, FALSE);
209 g_return_val_if_fail(path != NULL, FALSE);
210 g_return_val_if_fail(path[0] != '\0', FALSE);
212 data = purple_image_get_data(image);
213 len = purple_image_get_size(image);
215 g_return_val_if_fail(data != NULL, FALSE);
216 g_return_val_if_fail(len > 0, FALSE);
218 succ = purple_util_write_data_to_file_absolute(path, data, len);
219 if (succ && priv->path == NULL)
220 priv->path = g_strdup(path);
222 return succ;
225 const gchar *
226 purple_image_get_path(PurpleImage *image)
228 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image);
230 g_return_val_if_fail(priv != NULL, NULL);
232 return priv->path;
235 gboolean
236 purple_image_is_ready(PurpleImage *image)
238 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image);
240 g_return_val_if_fail(priv != NULL, FALSE);
242 return priv->is_ready;
245 gboolean
246 purple_image_has_failed(PurpleImage *image)
248 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image);
250 g_return_val_if_fail(priv != NULL, TRUE);
252 return priv->has_failed;
255 gsize
256 purple_image_get_size(PurpleImage *image)
258 PurpleImagePrivate *priv;
259 priv = PURPLE_IMAGE_GET_PRIVATE(image);
261 g_return_val_if_fail(priv != NULL, 0);
262 g_return_val_if_fail(priv->is_ready, 0);
264 fill_data(image);
265 g_return_val_if_fail(priv->contents, 0);
267 return priv->contents->len;
270 gconstpointer
271 purple_image_get_data(PurpleImage *image)
273 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image);
275 g_return_val_if_fail(priv != NULL, NULL);
276 g_return_val_if_fail(priv->is_ready, NULL);
278 fill_data(image);
279 g_return_val_if_fail(priv->contents, NULL);
281 return priv->contents->str;
284 const gchar *
285 purple_image_get_extension(PurpleImage *image)
287 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image);
288 gconstpointer data;
290 g_return_val_if_fail(priv != NULL, NULL);
292 if (priv->extension)
293 return priv->extension;
295 if (purple_image_get_size(image) < 4)
296 return NULL;
298 data = purple_image_get_data(image);
299 g_assert(data != NULL);
301 if (memcmp(data, "GIF8", 4) == 0)
302 return priv->extension = "gif";
303 if (memcmp(data, "\xff\xd8\xff", 3) == 0) /* 4th may be e0 through ef */
304 return priv->extension = "jpg";
305 if (memcmp(data, "\x89PNG", 4) == 0)
306 return priv->extension = "png";
307 if (memcmp(data, "MM", 2) == 0)
308 return priv->extension = "tif";
309 if (memcmp(data, "II", 2) == 0)
310 return priv->extension = "tif";
311 if (memcmp(data, "BM", 2) == 0)
312 return priv->extension = "bmp";
313 if (memcmp(data, "\x00\x00\x01\x00", 4) == 0)
314 return priv->extension = "ico";
316 return NULL;
319 const gchar *
320 purple_image_get_mimetype(PurpleImage *image)
322 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image);
323 const gchar *ext = purple_image_get_extension(image);
325 g_return_val_if_fail(priv != NULL, NULL);
327 if (priv->mime)
328 return priv->mime;
330 g_return_val_if_fail(ext != NULL, NULL);
332 if (g_strcmp0(ext, "gif") == 0)
333 return priv->mime = "image/gif";
334 if (g_strcmp0(ext, "jpg") == 0)
335 return priv->mime = "image/jpeg";
336 if (g_strcmp0(ext, "png") == 0)
337 return priv->mime = "image/png";
338 if (g_strcmp0(ext, "tif") == 0)
339 return priv->mime = "image/tiff";
340 if (g_strcmp0(ext, "bmp") == 0)
341 return priv->mime = "image/bmp";
342 if (g_strcmp0(ext, "ico") == 0)
343 return priv->mime = "image/vnd.microsoft.icon";
345 return NULL;
348 const gchar *
349 purple_image_generate_filename(PurpleImage *image)
351 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image);
352 gconstpointer data;
353 gsize len;
354 const gchar *ext;
355 gchar *checksum;
357 g_return_val_if_fail(priv != NULL, NULL);
359 if (priv->gen_filename)
360 return priv->gen_filename;
362 ext = purple_image_get_extension(image);
363 data = purple_image_get_data(image);
364 len = purple_image_get_size(image);
366 g_return_val_if_fail(ext != NULL, NULL);
367 g_return_val_if_fail(data != NULL, NULL);
368 g_return_val_if_fail(len > 0, NULL);
370 checksum = g_compute_checksum_for_data(G_CHECKSUM_SHA1, data, len);
371 priv->gen_filename = g_strdup_printf("%s.%s", checksum, ext);
372 g_free(checksum);
374 return priv->gen_filename;
377 void
378 purple_image_set_friendly_filename(PurpleImage *image, const gchar *filename)
380 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image);
381 gchar *newname;
382 const gchar *escaped;
384 g_return_if_fail(priv != NULL);
386 newname = g_path_get_basename(filename);
387 escaped = purple_escape_filename(newname);
388 g_free(newname);
389 newname = NULL;
391 if (g_strcmp0(escaped, "") == 0 || g_strcmp0(escaped, ".") == 0 ||
392 g_strcmp0(escaped, G_DIR_SEPARATOR_S) == 0 ||
393 g_strcmp0(escaped, "/") == 0 || g_strcmp0(escaped, "\\") == 0)
395 escaped = NULL;
398 g_free(priv->friendly_filename);
399 priv->friendly_filename = g_strdup(escaped);
402 const gchar *
403 purple_image_get_friendly_filename(PurpleImage *image)
405 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image);
407 g_return_val_if_fail(priv != NULL, NULL);
409 if (G_UNLIKELY(!priv->friendly_filename)) {
410 const gchar *newname = purple_image_generate_filename(image);
411 gsize newname_len = strlen(newname);
413 if (newname_len < 10)
414 return NULL;
416 /* let's use last 6 characters from checksum + 4 characters
417 * from file ext */
418 newname += newname_len - 10;
419 priv->friendly_filename = g_strdup(newname);
422 if (G_UNLIKELY(priv->is_ready &&
423 strchr(priv->friendly_filename, '.') == NULL))
425 const gchar *ext = purple_image_get_extension(image);
426 gchar *tmp;
427 if (!ext)
428 return priv->friendly_filename;
430 tmp = g_strdup_printf("%s.%s", priv->friendly_filename, ext);
431 g_free(priv->friendly_filename);
432 priv->friendly_filename = tmp;
435 return priv->friendly_filename;
438 PurpleImage *
439 purple_image_transfer_new(void)
441 PurpleImage *img;
442 PurpleImagePrivate *priv;
444 img = g_object_new(PURPLE_TYPE_IMAGE, NULL);
445 priv = PURPLE_IMAGE_GET_PRIVATE(img);
447 priv->is_ready = FALSE;
448 priv->contents = g_string_new(NULL);
450 return img;
453 void
454 purple_image_transfer_write(PurpleImage *image, gconstpointer data,
455 gsize length)
457 PurpleImagePrivate *priv =
458 PURPLE_IMAGE_GET_PRIVATE(image);
460 g_return_if_fail(priv != NULL);
461 g_return_if_fail(!priv->has_failed);
462 g_return_if_fail(!priv->is_ready);
463 g_return_if_fail(priv->contents != NULL);
464 g_return_if_fail(data != NULL || length == 0);
466 if (length == 0)
467 return;
469 g_string_append_len(priv->contents, (const gchar*)data, length);
472 void
473 purple_image_transfer_close(PurpleImage *image)
475 PurpleImagePrivate *priv =
476 PURPLE_IMAGE_GET_PRIVATE(image);
478 g_return_if_fail(priv != NULL);
479 g_return_if_fail(!priv->has_failed);
480 g_return_if_fail(!priv->is_ready);
481 g_return_if_fail(priv->contents != NULL);
483 if (priv->contents->len == 0) {
484 purple_debug_error("image", "image is empty");
485 has_failed(image);
486 return;
489 became_ready(image);
492 void
493 purple_image_transfer_failed(PurpleImage *image)
495 PurpleImagePrivate *priv =
496 PURPLE_IMAGE_GET_PRIVATE(image);
498 g_return_if_fail(priv != NULL);
499 g_return_if_fail(!priv->has_failed);
500 g_return_if_fail(!priv->is_ready);
502 has_failed(image);
505 /******************************************************************************
506 * Object stuff
507 ******************************************************************************/
509 static void
510 purple_image_init(GTypeInstance *instance, gpointer klass)
512 PurpleImage *image = PURPLE_IMAGE(instance);
513 PurpleImagePrivate *priv =
514 PURPLE_IMAGE_GET_PRIVATE(image);
516 priv->contents = NULL;
517 priv->is_ready = TRUE;
518 priv->has_failed = FALSE;
521 static void
522 purple_image_finalize(GObject *obj)
524 PurpleImage *image = PURPLE_IMAGE(obj);
525 PurpleImagePrivate *priv =
526 PURPLE_IMAGE_GET_PRIVATE(image);
528 if (priv->contents)
529 g_string_free(priv->contents, TRUE);
530 g_free(priv->path);
531 g_free(priv->gen_filename);
532 g_free(priv->friendly_filename);
534 G_OBJECT_CLASS(parent_class)->finalize(obj);
537 static void
538 purple_image_get_property(GObject *object, guint par_id, GValue *value,
539 GParamSpec *pspec)
541 PurpleImage *image = PURPLE_IMAGE(object);
542 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image);
544 switch (par_id) {
545 case PROP_IS_READY:
546 g_value_set_boolean(value, priv->is_ready);
547 break;
548 case PROP_HAS_FAILED:
549 g_value_set_boolean(value, priv->has_failed);
550 break;
551 default:
552 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, par_id, pspec);
553 break;
557 static void
558 purple_image_class_init(PurpleImageClass *klass)
560 GObjectClass *gobj_class = G_OBJECT_CLASS(klass);
562 parent_class = g_type_class_peek_parent(klass);
564 g_type_class_add_private(klass, sizeof(PurpleImagePrivate));
566 gobj_class->finalize = purple_image_finalize;
567 gobj_class->get_property = purple_image_get_property;
569 properties[PROP_IS_READY] = g_param_spec_boolean("is-ready",
570 "Is ready", "The image is ready to be displayed. Image may "
571 "change the state to failed in a single case: if it's backed "
572 "by a file and that file fails to load",
573 TRUE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
575 properties[PROP_HAS_FAILED] = g_param_spec_boolean("has-failed",
576 "Has hailed", "The remote host has failed to send the image",
577 FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
579 g_object_class_install_properties(gobj_class, PROP_LAST, properties);
582 * PurpleImage::failed:
583 * @image: a image that failed to transfer.
585 * Called when a @image fails to be transferred. It's guaranteed to be
586 * fired at most once for a particular @image.
588 signals[SIG_FAILED] = g_signal_new("failed", G_OBJECT_CLASS_TYPE(klass),
589 0, 0, NULL, NULL,
590 g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
593 * PurpleImage::ready:
594 * @image: a image that became ready.
596 * Called when a @image becames ready to be displayed. It's guaranteed to be
597 * fired at most once for a particular @image.
599 signals[SIG_READY] = g_signal_new("ready", G_OBJECT_CLASS_TYPE(klass),
600 0, 0, NULL, NULL,
601 g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
604 GType
605 purple_image_get_type(void)
607 static GType type = 0;
609 if (G_UNLIKELY(type == 0)) {
610 static const GTypeInfo info = {
611 .class_size = sizeof(PurpleImageClass),
612 .class_init = (GClassInitFunc)purple_image_class_init,
613 .instance_size = sizeof(PurpleImage),
614 .instance_init = purple_image_init,
617 type = g_type_register_static(G_TYPE_OBJECT,
618 "PurpleImage", &info, 0);
621 return type;