libpurple/image.c

changeset 38277
061e91dd78d4
parent 36049
bffbd724134d
child 38278
f61362f66bbb
equal deleted inserted replaced
38070:4663f9da17aa 38277:061e91dd78d4
28 #define PURPLE_IMAGE_GET_PRIVATE(obj) \ 28 #define PURPLE_IMAGE_GET_PRIVATE(obj) \
29 (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_IMAGE, PurpleImagePrivate)) 29 (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_IMAGE, PurpleImagePrivate))
30 30
31 typedef struct { 31 typedef struct {
32 gchar *path; 32 gchar *path;
33 GString *contents; 33
34 GBytes *contents;
34 35
35 const gchar *extension; 36 const gchar *extension;
36 const gchar *mime; 37 const gchar *mime;
37 gchar *gen_filename; 38 gchar *gen_filename;
38 gchar *friendly_filename; 39 gchar *friendly_filename;
39
40 gboolean is_ready;
41 gboolean has_failed;
42 } PurpleImagePrivate; 40 } PurpleImagePrivate;
43 41
44 enum 42 enum {
45 {
46 PROP_0, 43 PROP_0,
47 PROP_IS_READY,
48 PROP_HAS_FAILED,
49 PROP_LAST 44 PROP_LAST
50 }; 45 };
51 46
52 enum 47 static GObjectClass *parent_class;
48 static GParamSpec *properties[PROP_LAST];
49
50 /******************************************************************************
51 * Object stuff
52 ******************************************************************************/
53 static void
54 purple_image_init(PurpleImage *image) {
55 }
56
57 static void
58 purple_image_finalize(GObject *obj) {
59 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(obj);
60
61 g_bytes_unref(priv->contents);
62
63 g_free(priv->path);
64 g_free(priv->gen_filename);
65 g_free(priv->friendly_filename);
66
67 G_OBJECT_CLASS(parent_class)->finalize(obj);
68 }
69
70 static void
71 purple_image_set_property(GObject *object, guint par_id,
72 const GValue *value, GParamSpec *pspec)
53 { 73 {
54 SIG_READY, 74 switch (par_id) {
55 SIG_FAILED, 75 default:
56 SIG_LAST 76 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, par_id, pspec);
57 }; 77 break;
58 78 }
59 static GObjectClass *parent_class; 79 }
60 static guint signals[SIG_LAST]; 80
61 static GParamSpec *properties[PROP_LAST]; 81 static void
82 purple_image_get_property(GObject *object, guint par_id, GValue *value,
83 GParamSpec *pspec)
84 {
85 switch (par_id) {
86 default:
87 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, par_id, pspec);
88 break;
89 }
90 }
91
92 static void
93 purple_image_class_init(PurpleImageClass *klass) {
94 GObjectClass *gobj_class = G_OBJECT_CLASS(klass);
95
96 parent_class = g_type_class_peek_parent(klass);
97
98 gobj_class->finalize = purple_image_finalize;
99 gobj_class->get_property = purple_image_get_property;
100 gobj_class->set_property = purple_image_set_property;
101
102 g_object_class_install_properties(gobj_class, PROP_LAST, properties);
103 }
104
105 G_DEFINE_TYPE_WITH_PRIVATE(PurpleImage, purple_image, G_TYPE_OBJECT);
62 106
63 /****************************************************************************** 107 /******************************************************************************
64 * Internal logic 108 * API
65 ******************************************************************************/ 109 ******************************************************************************/
66 110 PurpleImage *
67 static void 111 purple_image_new_from_bytes(GBytes *bytes, GError **error) {
68 has_failed(PurpleImage *image) 112 return g_object_new(
69 { 113 PURPLE_TYPE_IMAGE,
70 gboolean ready_changed; 114 NULL
71 PurpleImagePrivate *priv; 115 );
72 priv = PURPLE_IMAGE_GET_PRIVATE(image); 116 }
73 117
74 g_return_if_fail(!priv->has_failed); 118 PurpleImage *
75 119 purple_image_new_from_file(const gchar *path) {
76 priv->has_failed = TRUE; 120 PurpleImage *image = NULL;
77 ready_changed = (priv->is_ready != FALSE); 121 GBytes *bytes = NULL;
78 priv->is_ready = FALSE; 122 gchar *contents = NULL;
79 123 gsize length = 0;
80 if (priv->contents) { 124
81 g_string_free(priv->contents, TRUE); 125 if(!g_file_test(path, G_FILE_TEST_EXISTS)) {
82 priv->contents = NULL; 126 return NULL;
83 } 127 }
84 128
85 if (ready_changed) { 129 if(!g_file_get_contents(path, &contents, &length, NULL)) {
86 g_object_notify_by_pspec(G_OBJECT(image), 130 return NULL;
87 properties[PROP_IS_READY]); 131 }
88 } 132
89 g_object_notify_by_pspec(G_OBJECT(image), properties[PROP_HAS_FAILED]); 133 bytes = g_bytes_new_take(contents, length);
90 g_signal_emit(image, signals[SIG_FAILED], 0); 134
91 } 135 image = purple_image_new_from_bytes(bytes, NULL);
92 136
93 static void 137 g_bytes_unref(bytes);
94 became_ready(PurpleImage *image) 138
95 { 139 return image;
96 PurpleImagePrivate *priv; 140 }
97 priv = PURPLE_IMAGE_GET_PRIVATE(image); 141
98 142 PurpleImage *
99 g_return_if_fail(!priv->has_failed); 143 purple_image_new_from_data(const guint8 *data, gsize length) {
100 g_return_if_fail(!priv->is_ready); 144 PurpleImage *image;
101 145 GBytes *bytes = NULL;
102 priv->is_ready = TRUE; 146
103 147 bytes = g_bytes_new(data, length);
104 g_object_notify_by_pspec(G_OBJECT(image), properties[PROP_IS_READY]); 148
105 g_signal_emit(image, signals[SIG_READY], 0); 149 image = purple_image_new_from_bytes(bytes, NULL);
106 } 150
107 151 g_bytes_unref(bytes);
108 static void 152
109 steal_contents(PurpleImagePrivate *priv, gpointer data, gsize length) 153 return image;
110 { 154 }
111 g_return_if_fail(priv != NULL); 155
112 g_return_if_fail(priv->contents == NULL); 156 PurpleImage *
113 g_return_if_fail(data != NULL); 157 purple_image_new_from_data_take(guint8 *data, gsize length) {
114 g_return_if_fail(length > 0); 158 PurpleImage *image;
115 159 GBytes *bytes = NULL;
116 priv->contents = g_string_new(NULL); 160
117 g_free(priv->contents->str); 161 bytes = g_bytes_new(data, length);
118 priv->contents->str = data; 162
119 priv->contents->len = priv->contents->allocated_len = length; 163 image = purple_image_new_from_bytes(bytes, NULL);
120 } 164
121 165 g_bytes_unref(bytes);
122 static void 166
123 fill_data(PurpleImage *image) 167 return image;
124 {
125 PurpleImagePrivate *priv;
126 GError *error = NULL;
127 gchar *contents;
128 gsize length;
129
130 priv = PURPLE_IMAGE_GET_PRIVATE(image);
131 if (priv->contents)
132 return;
133
134 if (!priv->is_ready)
135 return;
136
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);
143
144 has_failed(image);
145 return;
146 }
147
148 steal_contents(priv, contents, length);
149 }
150
151
152 /******************************************************************************
153 * API implementation
154 ******************************************************************************/
155
156 PurpleImage *
157 purple_image_new_from_file(const gchar *path, gboolean be_eager)
158 {
159 PurpleImagePrivate *priv;
160 PurpleImage *img;
161
162 g_return_val_if_fail(path != NULL, NULL);
163 g_return_val_if_fail(g_file_test(path, G_FILE_TEST_EXISTS), NULL);
164
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);
169
170 if (be_eager) {
171 fill_data(img);
172 if (!priv->contents) {
173 g_object_unref(img);
174 return NULL;
175 }
176
177 g_assert(priv->is_ready && !priv->has_failed);
178 }
179
180 return img;
181 }
182
183 PurpleImage *
184 purple_image_new_from_data(gpointer data, gsize length)
185 {
186 PurpleImage *img;
187 PurpleImagePrivate *priv;
188
189 g_return_val_if_fail(data != NULL, NULL);
190 g_return_val_if_fail(length > 0, NULL);
191
192 img = g_object_new(PURPLE_TYPE_IMAGE, NULL);
193 priv = PURPLE_IMAGE_GET_PRIVATE(img);
194
195 steal_contents(priv, data, length);
196
197 return img;
198 } 168 }
199 169
200 gboolean 170 gboolean
201 purple_image_save(PurpleImage *image, const gchar *path) 171 purple_image_save(PurpleImage *image, const gchar *path) {
202 {
203 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image); 172 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image);
204 gconstpointer data; 173 gconstpointer data;
205 gsize len; 174 gsize len;
206 gboolean succ; 175 gboolean succ;
207 176
221 190
222 return succ; 191 return succ;
223 } 192 }
224 193
225 const gchar * 194 const gchar *
226 purple_image_get_path(PurpleImage *image) 195 purple_image_get_path(PurpleImage *image) {
227 {
228 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image); 196 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image);
229 197
230 g_return_val_if_fail(priv != NULL, NULL); 198 g_return_val_if_fail(priv != NULL, NULL);
231 199
232 return priv->path; 200 return priv->path;
233 } 201 }
234 202
235 gboolean
236 purple_image_is_ready(PurpleImage *image)
237 {
238 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image);
239
240 g_return_val_if_fail(priv != NULL, FALSE);
241
242 return priv->is_ready;
243 }
244
245 gboolean
246 purple_image_has_failed(PurpleImage *image)
247 {
248 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image);
249
250 g_return_val_if_fail(priv != NULL, TRUE);
251
252 return priv->has_failed;
253 }
254
255 gsize 203 gsize
256 purple_image_get_size(PurpleImage *image) 204 purple_image_get_size(PurpleImage *image) {
257 {
258 PurpleImagePrivate *priv; 205 PurpleImagePrivate *priv;
206
207 g_return_val_if_fail(PURPLE_IS_IMAGE(image), 0);
208
259 priv = PURPLE_IMAGE_GET_PRIVATE(image); 209 priv = PURPLE_IMAGE_GET_PRIVATE(image);
260 210
261 g_return_val_if_fail(priv != NULL, 0); 211 return g_bytes_get_size(priv->contents);
262 g_return_val_if_fail(priv->is_ready, 0);
263
264 fill_data(image);
265 g_return_val_if_fail(priv->contents, 0);
266
267 return priv->contents->len;
268 } 212 }
269 213
270 gconstpointer 214 gconstpointer
271 purple_image_get_data(PurpleImage *image) 215 purple_image_get_data(PurpleImage *image) {
272 { 216 PurpleImagePrivate *priv = NULL;
273 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image); 217
274 218 g_return_val_if_fail(PURPLE_IS_IMAGE(image), NULL);
275 g_return_val_if_fail(priv != NULL, NULL); 219
276 g_return_val_if_fail(priv->is_ready, NULL); 220 priv = PURPLE_IMAGE_GET_PRIVATE(image);
277 221
278 fill_data(image); 222 return g_bytes_get_data(priv->contents, NULL);
279 g_return_val_if_fail(priv->contents, NULL); 223 }
280 224
281 return priv->contents->str; 225 const gchar *
282 } 226 purple_image_get_extension(PurpleImage *image) {
283
284 const gchar *
285 purple_image_get_extension(PurpleImage *image)
286 {
287 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image); 227 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image);
288 gconstpointer data; 228 gconstpointer data;
289 229
290 g_return_val_if_fail(priv != NULL, NULL); 230 g_return_val_if_fail(priv != NULL, NULL);
291 231
315 255
316 return NULL; 256 return NULL;
317 } 257 }
318 258
319 const gchar * 259 const gchar *
320 purple_image_get_mimetype(PurpleImage *image) 260 purple_image_get_mimetype(PurpleImage *image) {
321 {
322 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image); 261 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image);
323 const gchar *ext = purple_image_get_extension(image); 262 const gchar *ext = purple_image_get_extension(image);
324 263
325 g_return_val_if_fail(priv != NULL, NULL); 264 g_return_val_if_fail(priv != NULL, NULL);
326 265
344 283
345 return NULL; 284 return NULL;
346 } 285 }
347 286
348 const gchar * 287 const gchar *
349 purple_image_generate_filename(PurpleImage *image) 288 purple_image_generate_filename(PurpleImage *image) {
350 {
351 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image); 289 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image);
352 gconstpointer data; 290 gconstpointer data;
353 gsize len; 291 gsize len;
354 const gchar *ext; 292 const gchar *ext;
355 gchar *checksum; 293 gchar *checksum;
373 311
374 return priv->gen_filename; 312 return priv->gen_filename;
375 } 313 }
376 314
377 void 315 void
378 purple_image_set_friendly_filename(PurpleImage *image, const gchar *filename) 316 purple_image_set_friendly_filename(PurpleImage *image, const gchar *filename) {
379 {
380 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image); 317 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image);
381 gchar *newname; 318 gchar *newname;
382 const gchar *escaped; 319 const gchar *escaped;
383 320
384 g_return_if_fail(priv != NULL); 321 g_return_if_fail(priv != NULL);
398 g_free(priv->friendly_filename); 335 g_free(priv->friendly_filename);
399 priv->friendly_filename = g_strdup(escaped); 336 priv->friendly_filename = g_strdup(escaped);
400 } 337 }
401 338
402 const gchar * 339 const gchar *
403 purple_image_get_friendly_filename(PurpleImage *image) 340 purple_image_get_friendly_filename(PurpleImage *image) {
404 { 341 PurpleImagePrivate *priv = NULL;
405 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image); 342
406 343 g_return_val_if_fail(PURPLE_IS_IMAGE(image), NULL);
407 g_return_val_if_fail(priv != NULL, NULL); 344
408 345 priv = PURPLE_IMAGE_GET_PRIVATE(image);
409 if (G_UNLIKELY(!priv->friendly_filename)) { 346
410 const gchar *newname = purple_image_generate_filename(image); 347 if(priv->friendly_filename) {
411 gsize newname_len = strlen(newname); 348 return priv->friendly_filename;
412 349 }
413 if (newname_len < 10) 350
414 return NULL; 351 return purple_image_get_friendly_filename(image);
415 352 }
416 /* let's use last 6 characters from checksum + 4 characters 353
417 * from file ext */ 354 PurpleImage *
418 newname += newname_len - 10; 355 purple_image_transfer_new(void) {
419 priv->friendly_filename = g_strdup(newname); 356 return NULL;
420 }
421
422 if (G_UNLIKELY(priv->is_ready &&
423 strchr(priv->friendly_filename, '.') == NULL))
424 {
425 const gchar *ext = purple_image_get_extension(image);
426 gchar *tmp;
427 if (!ext)
428 return priv->friendly_filename;
429
430 tmp = g_strdup_printf("%s.%s", priv->friendly_filename, ext);
431 g_free(priv->friendly_filename);
432 priv->friendly_filename = tmp;
433 }
434
435 return priv->friendly_filename;
436 }
437
438 PurpleImage *
439 purple_image_transfer_new(void)
440 {
441 PurpleImage *img;
442 PurpleImagePrivate *priv;
443
444 img = g_object_new(PURPLE_TYPE_IMAGE, NULL);
445 priv = PURPLE_IMAGE_GET_PRIVATE(img);
446
447 priv->is_ready = FALSE;
448 priv->contents = g_string_new(NULL);
449
450 return img;
451 } 357 }
452 358
453 void 359 void
454 purple_image_transfer_write(PurpleImage *image, gconstpointer data, 360 purple_image_transfer_write(PurpleImage *image, gconstpointer data, gsize length) {
455 gsize length)
456 {
457 PurpleImagePrivate *priv =
458 PURPLE_IMAGE_GET_PRIVATE(image);
459
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);
465
466 if (length == 0)
467 return;
468
469 g_string_append_len(priv->contents, (const gchar*)data, length);
470 } 361 }
471 362
472 void 363 void
473 purple_image_transfer_close(PurpleImage *image) 364 purple_image_transfer_close(PurpleImage *image) {
474 {
475 PurpleImagePrivate *priv =
476 PURPLE_IMAGE_GET_PRIVATE(image);
477
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);
482
483 if (priv->contents->len == 0) {
484 purple_debug_error("image", "image is empty");
485 has_failed(image);
486 return;
487 }
488
489 became_ready(image);
490 } 365 }
491 366
492 void 367 void
493 purple_image_transfer_failed(PurpleImage *image) 368 purple_image_transfer_failed(PurpleImage *image) {
494 { 369 }
495 PurpleImagePrivate *priv =
496 PURPLE_IMAGE_GET_PRIVATE(image);
497
498 g_return_if_fail(priv != NULL);
499 g_return_if_fail(!priv->has_failed);
500 g_return_if_fail(!priv->is_ready);
501
502 has_failed(image);
503 }
504
505 /******************************************************************************
506 * Object stuff
507 ******************************************************************************/
508
509 static void
510 purple_image_init(GTypeInstance *instance, gpointer klass)
511 {
512 PurpleImage *image = PURPLE_IMAGE(instance);
513 PurpleImagePrivate *priv =
514 PURPLE_IMAGE_GET_PRIVATE(image);
515
516 priv->contents = NULL;
517 priv->is_ready = TRUE;
518 priv->has_failed = FALSE;
519 }
520
521 static void
522 purple_image_finalize(GObject *obj)
523 {
524 PurpleImage *image = PURPLE_IMAGE(obj);
525 PurpleImagePrivate *priv =
526 PURPLE_IMAGE_GET_PRIVATE(image);
527
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);
533
534 G_OBJECT_CLASS(parent_class)->finalize(obj);
535 }
536
537 static void
538 purple_image_get_property(GObject *object, guint par_id, GValue *value,
539 GParamSpec *pspec)
540 {
541 PurpleImage *image = PURPLE_IMAGE(object);
542 PurpleImagePrivate *priv = PURPLE_IMAGE_GET_PRIVATE(image);
543
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;
554 }
555 }
556
557 static void
558 purple_image_class_init(PurpleImageClass *klass)
559 {
560 GObjectClass *gobj_class = G_OBJECT_CLASS(klass);
561
562 parent_class = g_type_class_peek_parent(klass);
563
564 g_type_class_add_private(klass, sizeof(PurpleImagePrivate));
565
566 gobj_class->finalize = purple_image_finalize;
567 gobj_class->get_property = purple_image_get_property;
568
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);
574
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);
578
579 g_object_class_install_properties(gobj_class, PROP_LAST, properties);
580
581 /**
582 * PurpleImage::failed:
583 * @image: a image that failed to transfer.
584 *
585 * Called when a @image fails to be transferred. It's guaranteed to be
586 * fired at most once for a particular @image.
587 */
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);
591
592 /**
593 * PurpleImage::ready:
594 * @image: a image that became ready.
595 *
596 * Called when a @image becames ready to be displayed. It's guaranteed to be
597 * fired at most once for a particular @image.
598 */
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);
602 }
603
604 GType
605 purple_image_get_type(void)
606 {
607 static GType type = 0;
608
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,
615 };
616
617 type = g_type_register_static(G_TYPE_OBJECT,
618 "PurpleImage", &info, 0);
619 }
620
621 return type;
622 }

mercurial