Location | Tool | Test ID | Function | Issue |
---|---|---|---|---|
e-attachment.c:2045:38 | clang-analyzer | Access to field 'message' results in a dereference of a null pointer (loaded from variable 'error') | ||
e-attachment.c:2045:38 | clang-analyzer | Access to field 'message' results in a dereference of a null pointer (loaded from variable 'error') | ||
e-attachment.c:2344:38 | clang-analyzer | Access to field 'message' results in a dereference of a null pointer (loaded from variable 'error') | ||
e-attachment.c:2344:38 | clang-analyzer | Access to field 'message' results in a dereference of a null pointer (loaded from variable 'error') |
1 /*
2 * e-attachment.c
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with the program; if not, see <http://www.gnu.org/licenses/>
16 *
17 *
18 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
19 *
20 */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include "e-attachment.h"
27
28 #include <errno.h>
29 #include <glib/gi18n.h>
30 #include <glib/gstdio.h>
31
32 #include <libedataserver/libedataserver.h>
33
34 #include "e-util/e-icon-factory.h"
35 #include "e-util/e-util.h"
36 #include "e-util/e-mktemp.h"
37 #include "e-attachment-store.h"
38
39 #define E_ATTACHMENT_GET_PRIVATE(obj) \
40 (G_TYPE_INSTANCE_GET_PRIVATE \
41 ((obj), E_TYPE_ATTACHMENT, EAttachmentPrivate))
42
43 /* Fallback Icon */
44 #define DEFAULT_ICON_NAME "mail-attachment"
45
46 /* Emblems */
47 #define EMBLEM_CANCELLED "gtk-cancel"
48 #define EMBLEM_LOADING "emblem-downloads"
49 #define EMBLEM_SAVING "document-save"
50 #define EMBLEM_ENCRYPT_WEAK "security-low"
51 #define EMBLEM_ENCRYPT_STRONG "security-high"
52 #define EMBLEM_ENCRYPT_UNKNOWN "security-medium"
53 #define EMBLEM_SIGN_BAD "stock_signature-bad"
54 #define EMBLEM_SIGN_GOOD "stock_signature-ok"
55 #define EMBLEM_SIGN_UNKNOWN "stock_signature"
56
57 /* Attributes needed for EAttachmentStore columns. */
58 #define ATTACHMENT_QUERY "standard::*,preview::*,thumbnail::*"
59
60 struct _EAttachmentPrivate {
61 GFile *file;
62 GIcon *icon;
63 GFileInfo *file_info;
64 GCancellable *cancellable;
65 CamelMimePart *mime_part;
66 guint emblem_timeout_id;
67 gchar *disposition;
68 gint percent;
69 gint64 last_percent_notify; /* to avoid excessive notifications */
70
71 guint can_show : 1;
72 guint loading : 1;
73 guint saving : 1;
74 guint shown : 1;
75
76 camel_cipher_validity_encrypt_t encrypted;
77 camel_cipher_validity_sign_t signed_;
78
79 /* This is a reference to our row in an EAttachmentStore,
80 * serving as a means of broadcasting "row-changed" signals.
81 * If we are removed from the store, we lazily free the
82 * reference when it is found to be to be invalid. */
83 GtkTreeRowReference *reference;
84 };
85
86 enum {
87 PROP_0,
88 PROP_CAN_SHOW,
89 PROP_DISPOSITION,
90 PROP_ENCRYPTED,
91 PROP_FILE,
92 PROP_FILE_INFO,
93 PROP_ICON,
94 PROP_LOADING,
95 PROP_MIME_PART,
96 PROP_PERCENT,
97 PROP_REFERENCE,
98 PROP_SAVING,
99 PROP_SHOWN,
100 PROP_SIGNED
101 };
102
103 G_DEFINE_TYPE (
104 EAttachment,
105 e_attachment,
106 G_TYPE_OBJECT)
107
108 static gboolean
109 create_system_thumbnail (EAttachment *attachment,
110 GIcon **icon)
111 {
112 GFile *file;
113 GFile *icon_file;
114 gchar *thumbnail = NULL;
115
116 g_return_val_if_fail (attachment != NULL, FALSE);
117 g_return_val_if_fail (icon != NULL, FALSE);
118
119 file = e_attachment_get_file (attachment);
120
121 if (file && g_file_has_uri_scheme (file, "file")) {
122 gchar *path = g_file_get_path (file);
123 if (path) {
124 thumbnail = e_icon_factory_create_thumbnail (path);
125 g_free (path);
126 }
127 }
128
129 if (thumbnail == NULL)
130 return FALSE;
131
132 icon_file = g_file_new_for_path (thumbnail);
133
134 if (*icon)
135 g_object_unref (*icon);
136
137 *icon = g_file_icon_new (icon_file);
138
139 g_object_unref (icon_file);
140
141 if (file) {
142 GFileInfo *file_info;
143 const gchar *attribute;
144
145 file_info = e_attachment_get_file_info (attachment);
146 attribute = G_FILE_ATTRIBUTE_THUMBNAIL_PATH;
147
148 if (file_info != NULL)
149 g_file_info_set_attribute_byte_string (
150 file_info, attribute, thumbnail);
151 }
152
153 g_free (thumbnail);
154
155 return TRUE;
156 }
157
158 static gchar *
159 attachment_get_default_charset (void)
160 {
161 GSettings *settings;
162 gchar *charset;
163
164 /* XXX This doesn't really belong here. */
165
166 settings = g_settings_new ("org.gnome.evolution.mail");
167 charset = g_settings_get_string (settings, "composer-charset");
168 if (charset == NULL || *charset == '\0') {
169 g_free (charset);
170 /* FIXME This was "/apps/evolution/mail/format/charset",
171 * not sure it relates to "charset" */
172 charset = g_settings_get_string (settings, "charset");
173 if (charset == NULL || *charset == '\0') {
174 g_free (charset);
175 charset = NULL;
176 }
177 }
178 g_object_unref (settings);
179
180 if (charset == NULL)
181 charset = g_strdup (camel_iconv_locale_charset ());
182
183 if (charset == NULL)
184 charset = g_strdup ("us-ascii");
185
186 return charset;
187 }
188
189 static void
190 attachment_update_file_info_columns (EAttachment *attachment)
191 {
192 GtkTreeRowReference *reference;
193 GtkTreeModel *model;
194 GtkTreePath *path;
195 GtkTreeIter iter;
196 GFileInfo *file_info;
197 const gchar *content_type;
198 const gchar *description;
199 const gchar *display_name;
200 gchar *content_desc;
201 gchar *display_size;
202 gchar *caption;
203 goffset size;
204
205 reference = e_attachment_get_reference (attachment);
206 if (!gtk_tree_row_reference_valid (reference))
207 return;
208
209 file_info = e_attachment_get_file_info (attachment);
210 if (file_info == NULL)
211 return;
212
213 model = gtk_tree_row_reference_get_model (reference);
214 path = gtk_tree_row_reference_get_path (reference);
215 gtk_tree_model_get_iter (model, &iter, path);
216 gtk_tree_path_free (path);
217
218 content_type = g_file_info_get_content_type (file_info);
219 display_name = g_file_info_get_display_name (file_info);
220 size = g_file_info_get_size (file_info);
221
222 content_desc = g_content_type_get_description (content_type);
223 display_size = g_format_size_for_display (size);
224
225 description = e_attachment_get_description (attachment);
226 if (description == NULL || *description == '\0')
227 description = display_name;
228
229 if (size > 0)
230 caption = g_strdup_printf (
231 "%s\n(%s)", description, display_size);
232 else
233 caption = g_strdup (description);
234
235 gtk_list_store_set (
236 GTK_LIST_STORE (model), &iter,
237 E_ATTACHMENT_STORE_COLUMN_CAPTION, caption,
238 E_ATTACHMENT_STORE_COLUMN_CONTENT_TYPE, content_desc,
239 E_ATTACHMENT_STORE_COLUMN_DESCRIPTION, description,
240 E_ATTACHMENT_STORE_COLUMN_SIZE, size,
241 -1);
242
243 g_free (content_desc);
244 g_free (display_size);
245 g_free (caption);
246 }
247
248 static void
249 attachment_update_icon_column (EAttachment *attachment)
250 {
251 GtkTreeRowReference *reference;
252 GtkTreeModel *model;
253 GtkTreePath *path;
254 GtkTreeIter iter;
255 GFileInfo *file_info;
256 GCancellable *cancellable;
257 GIcon *icon = NULL;
258 const gchar *emblem_name = NULL;
259 const gchar *thumbnail_path = NULL;
260
261 reference = e_attachment_get_reference (attachment);
262 if (!gtk_tree_row_reference_valid (reference))
263 return;
264
265 model = gtk_tree_row_reference_get_model (reference);
266 path = gtk_tree_row_reference_get_path (reference);
267 gtk_tree_model_get_iter (model, &iter, path);
268 gtk_tree_path_free (path);
269
270 cancellable = attachment->priv->cancellable;
271 file_info = e_attachment_get_file_info (attachment);
272
273 if (file_info != NULL) {
274 icon = g_file_info_get_icon (file_info);
275 thumbnail_path = g_file_info_get_attribute_byte_string (
276 file_info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH);
277 }
278
279 /* Prefer the thumbnail if we have one. */
280 if (thumbnail_path != NULL && *thumbnail_path != '\0') {
281 GFile *file;
282
283 file = g_file_new_for_path (thumbnail_path);
284 icon = g_file_icon_new (file);
285 g_object_unref (file);
286
287 /* Try the system thumbnailer. */
288 } else if (create_system_thumbnail (attachment, &icon)) {
289 /* Nothing to do, just use the icon. */
290
291 /* Else use the standard icon for the content type. */
292 } else if (icon != NULL)
293 g_object_ref (icon);
294
295 /* Last ditch fallback. (GFileInfo not yet loaded?) */
296 else
297 icon = g_themed_icon_new (DEFAULT_ICON_NAME);
298
299 /* Pick an emblem, limit one. Choices listed by priority. */
300
301 if (g_cancellable_is_cancelled (cancellable))
302 emblem_name = EMBLEM_CANCELLED;
303
304 else if (e_attachment_get_loading (attachment))
305 emblem_name = EMBLEM_LOADING;
306
307 else if (e_attachment_get_saving (attachment))
308 emblem_name = EMBLEM_SAVING;
309
310 else if (e_attachment_get_encrypted (attachment))
311 switch (e_attachment_get_encrypted (attachment)) {
312 case CAMEL_CIPHER_VALIDITY_ENCRYPT_WEAK:
313 emblem_name = EMBLEM_ENCRYPT_WEAK;
314 break;
315
316 case CAMEL_CIPHER_VALIDITY_ENCRYPT_ENCRYPTED:
317 emblem_name = EMBLEM_ENCRYPT_UNKNOWN;
318 break;
319
320 case CAMEL_CIPHER_VALIDITY_ENCRYPT_STRONG:
321 emblem_name = EMBLEM_ENCRYPT_STRONG;
322 break;
323
324 default:
325 g_warn_if_reached ();
326 break;
327 }
328
329 else if (e_attachment_get_signed (attachment))
330 switch (e_attachment_get_signed (attachment)) {
331 case CAMEL_CIPHER_VALIDITY_SIGN_GOOD:
332 emblem_name = EMBLEM_SIGN_GOOD;
333 break;
334
335 case CAMEL_CIPHER_VALIDITY_SIGN_BAD:
336 emblem_name = EMBLEM_SIGN_BAD;
337 break;
338
339 case CAMEL_CIPHER_VALIDITY_SIGN_UNKNOWN:
340 case CAMEL_CIPHER_VALIDITY_SIGN_NEED_PUBLIC_KEY:
341 emblem_name = EMBLEM_SIGN_UNKNOWN;
342 break;
343
344 default:
345 g_warn_if_reached ();
346 break;
347 }
348
349 if (emblem_name != NULL) {
350 GIcon *emblemed_icon;
351 GEmblem *emblem;
352
353 emblemed_icon = g_themed_icon_new (emblem_name);
354 emblem = g_emblem_new (emblemed_icon);
355 g_object_unref (emblemed_icon);
356
357 emblemed_icon = g_emblemed_icon_new (icon, emblem);
358 g_object_unref (emblem);
359 g_object_unref (icon);
360
361 icon = emblemed_icon;
362 }
363
364 gtk_list_store_set (
365 GTK_LIST_STORE (model), &iter,
366 E_ATTACHMENT_STORE_COLUMN_ICON, icon,
367 -1);
368
369 /* Cache the icon to reuse for things like drag-n-drop. */
370 if (attachment->priv->icon != NULL)
371 g_object_unref (attachment->priv->icon);
372 attachment->priv->icon = icon;
373 g_object_notify (G_OBJECT (attachment), "icon");
374 }
375
376 static void
377 attachment_update_progress_columns (EAttachment *attachment)
378 {
379 GtkTreeRowReference *reference;
380 GtkTreeModel *model;
381 GtkTreePath *path;
382 GtkTreeIter iter;
383 gboolean loading;
384 gboolean saving;
385 gint percent;
386
387 reference = e_attachment_get_reference (attachment);
388 if (!gtk_tree_row_reference_valid (reference))
389 return;
390
391 model = gtk_tree_row_reference_get_model (reference);
392 path = gtk_tree_row_reference_get_path (reference);
393 gtk_tree_model_get_iter (model, &iter, path);
394 gtk_tree_path_free (path);
395
396 /* Don't show progress bars until we have progress to report. */
397 percent = e_attachment_get_percent (attachment);
398 loading = e_attachment_get_loading (attachment) && (percent > 0);
399 saving = e_attachment_get_saving (attachment) && (percent > 0);
400
401 gtk_list_store_set (
402 GTK_LIST_STORE (model), &iter,
403 E_ATTACHMENT_STORE_COLUMN_LOADING, loading,
404 E_ATTACHMENT_STORE_COLUMN_PERCENT, percent,
405 E_ATTACHMENT_STORE_COLUMN_SAVING, saving,
406 -1);
407 }
408
409 static void
410 attachment_set_loading (EAttachment *attachment,
411 gboolean loading)
412 {
413 GtkTreeRowReference *reference;
414
415 reference = e_attachment_get_reference (attachment);
416
417 attachment->priv->percent = 0;
418 attachment->priv->loading = loading;
419 attachment->priv->last_percent_notify = 0;
420
421 g_object_freeze_notify (G_OBJECT (attachment));
422 g_object_notify (G_OBJECT (attachment), "percent");
423 g_object_notify (G_OBJECT (attachment), "loading");
424 g_object_thaw_notify (G_OBJECT (attachment));
425
426 if (gtk_tree_row_reference_valid (reference)) {
427 GtkTreeModel *model;
428 model = gtk_tree_row_reference_get_model (reference);
429 g_object_notify (G_OBJECT (model), "num-loading");
430 }
431 }
432
433 static void
434 attachment_set_saving (EAttachment *attachment,
435 gboolean saving)
436 {
437 attachment->priv->percent = 0;
438 attachment->priv->saving = saving;
439 attachment->priv->last_percent_notify = 0;
440
441 g_object_freeze_notify (G_OBJECT (attachment));
442 g_object_notify (G_OBJECT (attachment), "percent");
443 g_object_notify (G_OBJECT (attachment), "saving");
444 g_object_thaw_notify (G_OBJECT (attachment));
445 }
446
447 static void
448 attachment_progress_cb (goffset current_num_bytes,
449 goffset total_num_bytes,
450 EAttachment *attachment)
451 {
452 gint new_percent;
453
454 /* Avoid dividing by zero. */
455 if (total_num_bytes == 0)
456 return;
457
458 /* do not notify too often, 5 times per second is sufficient */
459 if (g_get_monotonic_time () - attachment->priv->last_percent_notify < 200000)
460 return;
461
462 attachment->priv->last_percent_notify = g_get_monotonic_time ();
463
464 new_percent = (current_num_bytes * 100) / total_num_bytes;
465
466 if (new_percent != attachment->priv->percent) {
467 attachment->priv->percent = new_percent;
468 g_object_notify (G_OBJECT (attachment), "percent");
469 }
470 }
471
472 static gboolean
473 attachment_cancelled_timeout_cb (EAttachment *attachment)
474 {
475 attachment->priv->emblem_timeout_id = 0;
476 g_cancellable_reset (attachment->priv->cancellable);
477
478 attachment_update_icon_column (attachment);
479
480 return FALSE;
481 }
482
483 static void
484 attachment_cancelled_cb (EAttachment *attachment)
485 {
486 /* Reset the GCancellable after one second. This causes a
487 * cancel emblem to be briefly shown on the attachment icon
488 * as visual feedback that an operation was cancelled. */
489
490 if (attachment->priv->emblem_timeout_id > 0)
491 g_source_remove (attachment->priv->emblem_timeout_id);
492
493 attachment->priv->emblem_timeout_id = g_timeout_add_seconds (
494 1, (GSourceFunc) attachment_cancelled_timeout_cb, attachment);
495
496 attachment_update_icon_column (attachment);
497 }
498
499 static void
500 attachment_set_property (GObject *object,
501 guint property_id,
502 const GValue *value,
503 GParamSpec *pspec)
504 {
505 switch (property_id) {
506 case PROP_CAN_SHOW:
507 e_attachment_set_can_show (
508 E_ATTACHMENT (object),
509 g_value_get_boolean (value));
510 return;
511
512 case PROP_DISPOSITION:
513 e_attachment_set_disposition (
514 E_ATTACHMENT (object),
515 g_value_get_string (value));
516 return;
517
518 case PROP_ENCRYPTED:
519 e_attachment_set_encrypted (
520 E_ATTACHMENT (object),
521 g_value_get_int (value));
522 return;
523
524 case PROP_FILE:
525 e_attachment_set_file (
526 E_ATTACHMENT (object),
527 g_value_get_object (value));
528 return;
529
530 case PROP_SHOWN:
531 e_attachment_set_shown (
532 E_ATTACHMENT (object),
533 g_value_get_boolean (value));
534 return;
535
536 case PROP_MIME_PART:
537 e_attachment_set_mime_part (
538 E_ATTACHMENT (object),
539 g_value_get_boxed (value));
540 return;
541
542 case PROP_REFERENCE:
543 e_attachment_set_reference (
544 E_ATTACHMENT (object),
545 g_value_get_boxed (value));
546 return;
547
548 case PROP_SIGNED:
549 e_attachment_set_signed (
550 E_ATTACHMENT (object),
551 g_value_get_int (value));
552 return;
553 }
554
555 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
556 }
557
558 static void
559 attachment_get_property (GObject *object,
560 guint property_id,
561 GValue *value,
562 GParamSpec *pspec)
563 {
564 switch (property_id) {
565 case PROP_CAN_SHOW:
566 g_value_set_boolean (
567 value, e_attachment_get_can_show (
568 E_ATTACHMENT (object)));
569 return;
570
571 case PROP_DISPOSITION:
572 g_value_set_string (
573 value, e_attachment_get_disposition (
574 E_ATTACHMENT (object)));
575 return;
576
577 case PROP_ENCRYPTED:
578 g_value_set_int (
579 value, e_attachment_get_encrypted (
580 E_ATTACHMENT (object)));
581 return;
582
583 case PROP_FILE:
584 g_value_set_object (
585 value, e_attachment_get_file (
586 E_ATTACHMENT (object)));
587 return;
588
589 case PROP_FILE_INFO:
590 g_value_set_object (
591 value, e_attachment_get_file_info (
592 E_ATTACHMENT (object)));
593 return;
594
595 case PROP_ICON:
596 g_value_set_object (
597 value, e_attachment_get_icon (
598 E_ATTACHMENT (object)));
599 return;
600
601 case PROP_SHOWN:
602 g_value_set_boolean (
603 value, e_attachment_get_shown (
604 E_ATTACHMENT (object)));
605 return;
606
607 case PROP_LOADING:
608 g_value_set_boolean (
609 value, e_attachment_get_loading (
610 E_ATTACHMENT (object)));
611 return;
612
613 case PROP_MIME_PART:
614 g_value_set_boxed (
615 value, e_attachment_get_mime_part (
616 E_ATTACHMENT (object)));
617 return;
618
619 case PROP_PERCENT:
620 g_value_set_int (
621 value, e_attachment_get_percent (
622 E_ATTACHMENT (object)));
623 return;
624
625 case PROP_REFERENCE:
626 g_value_set_boxed (
627 value, e_attachment_get_reference (
628 E_ATTACHMENT (object)));
629 return;
630
631 case PROP_SAVING:
632 g_value_set_boolean (
633 value, e_attachment_get_saving (
634 E_ATTACHMENT (object)));
635 return;
636
637 case PROP_SIGNED:
638 g_value_set_int (
639 value, e_attachment_get_signed (
640 E_ATTACHMENT (object)));
641 return;
642 }
643
644 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
645 }
646
647 static void
648 attachment_dispose (GObject *object)
649 {
650 EAttachmentPrivate *priv;
651
652 priv = E_ATTACHMENT_GET_PRIVATE (object);
653
654 if (priv->file != NULL) {
655 g_object_unref (priv->file);
656 priv->file = NULL;
657 }
658
659 if (priv->icon != NULL) {
660 g_object_unref (priv->icon);
661 priv->icon = NULL;
662 }
663
664 if (priv->file_info != NULL) {
665 g_object_unref (priv->file_info);
666 priv->file_info = NULL;
667 }
668
669 if (priv->cancellable != NULL) {
670 g_object_unref (priv->cancellable);
671 priv->cancellable = NULL;
672 }
673
674 if (priv->mime_part != NULL) {
675 g_object_unref (priv->mime_part);
676 priv->mime_part = NULL;
677 }
678
679 if (priv->emblem_timeout_id > 0) {
680 g_source_remove (priv->emblem_timeout_id);
681 priv->emblem_timeout_id = 0;
682 }
683
684 /* This accepts NULL arguments. */
685 gtk_tree_row_reference_free (priv->reference);
686 priv->reference = NULL;
687
688 /* Chain up to parent's dispose() method. */
689 G_OBJECT_CLASS (e_attachment_parent_class)->dispose (object);
690 }
691
692 static void
693 attachment_finalize (GObject *object)
694 {
695 EAttachmentPrivate *priv;
696
697 priv = E_ATTACHMENT_GET_PRIVATE (object);
698
699 g_free (priv->disposition);
700
701 /* Chain up to parent's finalize() method. */
702 G_OBJECT_CLASS (e_attachment_parent_class)->finalize (object);
703 }
704
705 static void
706 e_attachment_class_init (EAttachmentClass *class)
707 {
708 GObjectClass *object_class;
709
710 g_type_class_add_private (class, sizeof (EAttachmentPrivate));
711
712 object_class = G_OBJECT_CLASS (class);
713 object_class->set_property = attachment_set_property;
714 object_class->get_property = attachment_get_property;
715 object_class->dispose = attachment_dispose;
716 object_class->finalize = attachment_finalize;
717
718 g_object_class_install_property (
719 object_class,
720 PROP_CAN_SHOW,
721 g_param_spec_boolean (
722 "can-show",
723 "Can Show",
724 NULL,
725 FALSE,
726 G_PARAM_READWRITE |
727 G_PARAM_CONSTRUCT));
728
729 g_object_class_install_property (
730 object_class,
731 PROP_DISPOSITION,
732 g_param_spec_string (
733 "disposition",
734 "Disposition",
735 NULL,
736 "attachment",
737 G_PARAM_READWRITE |
738 G_PARAM_CONSTRUCT));
739
740 /* FIXME Define a GEnumClass for this. */
741 g_object_class_install_property (
742 object_class,
743 PROP_ENCRYPTED,
744 g_param_spec_int (
745 "encrypted",
746 "Encrypted",
747 NULL,
748 CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE,
749 CAMEL_CIPHER_VALIDITY_ENCRYPT_STRONG,
750 CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE,
751 G_PARAM_READWRITE |
752 G_PARAM_CONSTRUCT));
753
754 g_object_class_install_property (
755 object_class,
756 PROP_FILE,
757 g_param_spec_object (
758 "file",
759 "File",
760 NULL,
761 G_TYPE_FILE,
762 G_PARAM_READWRITE |
763 G_PARAM_CONSTRUCT));
764
765 g_object_class_install_property (
766 object_class,
767 PROP_FILE_INFO,
768 g_param_spec_object (
769 "file-info",
770 "File Info",
771 NULL,
772 G_TYPE_FILE_INFO,
773 G_PARAM_READABLE));
774
775 g_object_class_install_property (
776 object_class,
777 PROP_ICON,
778 g_param_spec_object (
779 "icon",
780 "Icon",
781 NULL,
782 G_TYPE_ICON,
783 G_PARAM_READABLE));
784
785 g_object_class_install_property (
786 object_class,
787 PROP_LOADING,
788 g_param_spec_boolean (
789 "loading",
790 "Loading",
791 NULL,
792 FALSE,
793 G_PARAM_READABLE));
794
795 g_object_class_install_property (
796 object_class,
797 PROP_MIME_PART,
798 g_param_spec_object (
799 "mime-part",
800 "MIME Part",
801 NULL,
802 CAMEL_TYPE_MIME_PART,
803 G_PARAM_READWRITE));
804
805 g_object_class_install_property (
806 object_class,
807 PROP_PERCENT,
808 g_param_spec_int (
809 "percent",
810 "Percent",
811 NULL,
812 0,
813 100,
814 0,
815 G_PARAM_READABLE));
816
817 g_object_class_install_property (
818 object_class,
819 PROP_REFERENCE,
820 g_param_spec_boxed (
821 "reference",
822 "Reference",
823 NULL,
824 GTK_TYPE_TREE_ROW_REFERENCE,
825 G_PARAM_READWRITE));
826
827 g_object_class_install_property (
828 object_class,
829 PROP_SAVING,
830 g_param_spec_boolean (
831 "saving",
832 "Saving",
833 NULL,
834 FALSE,
835 G_PARAM_READABLE));
836
837 g_object_class_install_property (
838 object_class,
839 PROP_SHOWN,
840 g_param_spec_boolean (
841 "shown",
842 "Shown",
843 NULL,
844 FALSE,
845 G_PARAM_READWRITE |
846 G_PARAM_CONSTRUCT));
847
848 /* FIXME Define a GEnumClass for this. */
849 g_object_class_install_property (
850 object_class,
851 PROP_SIGNED,
852 g_param_spec_int (
853 "signed",
854 "Signed",
855 NULL,
856 CAMEL_CIPHER_VALIDITY_SIGN_NONE,
857 CAMEL_CIPHER_VALIDITY_SIGN_NEED_PUBLIC_KEY,
858 CAMEL_CIPHER_VALIDITY_SIGN_NONE,
859 G_PARAM_READWRITE |
860 G_PARAM_CONSTRUCT));
861 }
862
863 static void
864 e_attachment_init (EAttachment *attachment)
865 {
866 attachment->priv = E_ATTACHMENT_GET_PRIVATE (attachment);
867 attachment->priv->cancellable = g_cancellable_new ();
868 attachment->priv->encrypted = CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE;
869 attachment->priv->signed_ = CAMEL_CIPHER_VALIDITY_SIGN_NONE;
870
871 g_signal_connect (
872 attachment, "notify::encrypted",
873 G_CALLBACK (attachment_update_icon_column), NULL);
874
875 g_signal_connect (
876 attachment, "notify::file-info",
877 G_CALLBACK (attachment_update_file_info_columns), NULL);
878
879 g_signal_connect (
880 attachment, "notify::file-info",
881 G_CALLBACK (attachment_update_icon_column), NULL);
882
883 g_signal_connect (
884 attachment, "notify::loading",
885 G_CALLBACK (attachment_update_icon_column), NULL);
886
887 g_signal_connect (
888 attachment, "notify::loading",
889 G_CALLBACK (attachment_update_progress_columns), NULL);
890
891 g_signal_connect (
892 attachment, "notify::percent",
893 G_CALLBACK (attachment_update_progress_columns), NULL);
894
895 g_signal_connect (
896 attachment, "notify::reference",
897 G_CALLBACK (attachment_update_file_info_columns), NULL);
898
899 g_signal_connect (
900 attachment, "notify::reference",
901 G_CALLBACK (attachment_update_icon_column), NULL);
902
903 g_signal_connect (
904 attachment, "notify::reference",
905 G_CALLBACK (attachment_update_progress_columns), NULL);
906
907 g_signal_connect (
908 attachment, "notify::saving",
909 G_CALLBACK (attachment_update_icon_column), NULL);
910
911 g_signal_connect (
912 attachment, "notify::saving",
913 G_CALLBACK (attachment_update_progress_columns), NULL);
914
915 g_signal_connect (
916 attachment, "notify::signed",
917 G_CALLBACK (attachment_update_icon_column), NULL);
918
919 g_signal_connect_swapped (
920 attachment->priv->cancellable, "cancelled",
921 G_CALLBACK (attachment_cancelled_cb), attachment);
922 }
923
924 EAttachment *
925 e_attachment_new (void)
926 {
927 return g_object_new (E_TYPE_ATTACHMENT, NULL);
928 }
929
930 EAttachment *
931 e_attachment_new_for_path (const gchar *path)
932 {
933 EAttachment *attachment;
934 GFile *file;
935
936 g_return_val_if_fail (path != NULL, NULL);
937
938 file = g_file_new_for_path (path);
939 attachment = g_object_new (E_TYPE_ATTACHMENT, "file", file, NULL);
940 g_object_unref (file);
941
942 return attachment;
943 }
944
945 EAttachment *
946 e_attachment_new_for_uri (const gchar *uri)
947 {
948 EAttachment *attachment;
949 GFile *file;
950
951 g_return_val_if_fail (uri != NULL, NULL);
952
953 file = g_file_new_for_uri (uri);
954 attachment = g_object_new (E_TYPE_ATTACHMENT, "file", file, NULL);
955 g_object_unref (file);
956
957 return attachment;
958 }
959
960 EAttachment *
961 e_attachment_new_for_message (CamelMimeMessage *message)
962 {
963 CamelDataWrapper *wrapper;
964 CamelMimePart *mime_part;
965 EAttachment *attachment;
966 GString *description;
967 const gchar *subject;
968
969 g_return_val_if_fail (CAMEL_IS_MIME_MESSAGE (message), NULL);
970
971 mime_part = camel_mime_part_new ();
972 camel_mime_part_set_disposition (mime_part, "inline");
973 subject = camel_mime_message_get_subject (message);
974
975 /* To Translators: This text is set as a description of an attached
976 * message when, for example, attaching it to a composer. When the
977 * message to be attached has also filled Subject, then this text is
978 * of form "Attached message - Subject", otherwise it's left as is. */
979 description = g_string_new (_("Attached message"));
980 if (subject != NULL)
981 g_string_append_printf (description, " - %s", subject);
982 camel_mime_part_set_description (mime_part, description->str);
983 g_string_free (description, TRUE);
984
985 wrapper = CAMEL_DATA_WRAPPER (message);
986 camel_medium_set_content (CAMEL_MEDIUM (mime_part), wrapper);
987 camel_mime_part_set_content_type (mime_part, "message/rfc822");
988
989 attachment = e_attachment_new ();
990 e_attachment_set_mime_part (attachment, mime_part);
991 g_object_unref (mime_part);
992
993 return attachment;
994 }
995
996 void
997 e_attachment_add_to_multipart (EAttachment *attachment,
998 CamelMultipart *multipart,
999 const gchar *default_charset)
1000 {
1001 CamelContentType *content_type;
1002 CamelDataWrapper *wrapper;
1003 CamelMimePart *mime_part;
1004
1005 /* XXX EMsgComposer might be a better place for this function. */
1006
1007 g_return_if_fail (E_IS_ATTACHMENT (attachment));
1008 g_return_if_fail (CAMEL_IS_MULTIPART (multipart));
1009
1010 /* Still loading? Too bad. */
1011 mime_part = e_attachment_get_mime_part (attachment);
1012 if (mime_part == NULL)
1013 return;
1014
1015 content_type = camel_mime_part_get_content_type (mime_part);
1016 wrapper = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
1017
1018 if (CAMEL_IS_MULTIPART (wrapper))
1019 goto exit;
1020
1021 /* For text content, determine the best encoding and character set. */
1022 if (camel_content_type_is (content_type, "text", "*")) {
1023 CamelTransferEncoding encoding;
1024 CamelStream *filtered_stream;
1025 CamelMimeFilter *filter;
1026 CamelStream *stream;
1027 const gchar *charset;
1028
1029 charset = camel_content_type_param (content_type, "charset");
1030
1031 /* Determine the best encoding by writing the MIME
1032 * part to a NULL stream with a "bestenc" filter. */
1033 stream = camel_stream_null_new ();
1034 filtered_stream = camel_stream_filter_new (stream);
1035 filter = camel_mime_filter_bestenc_new (
1036 CAMEL_BESTENC_GET_ENCODING);
1037 camel_stream_filter_add (
1038 CAMEL_STREAM_FILTER (filtered_stream),
1039 CAMEL_MIME_FILTER (filter));
1040 camel_data_wrapper_decode_to_stream_sync (
1041 wrapper, filtered_stream, NULL, NULL);
1042 g_object_unref (filtered_stream);
1043 g_object_unref (stream);
1044
1045 /* Retrieve the best encoding from the filter. */
1046 encoding = camel_mime_filter_bestenc_get_best_encoding (
1047 CAMEL_MIME_FILTER_BESTENC (filter),
1048 CAMEL_BESTENC_8BIT);
1049 camel_mime_part_set_encoding (mime_part, encoding);
1050 g_object_unref (filter);
1051
1052 if (encoding == CAMEL_TRANSFER_ENCODING_7BIT) {
1053 /* The text fits within us-ascii, so this is safe.
1054 * FIXME Check that this isn't iso-2022-jp? */
1055 default_charset = "us-ascii";
1056
1057 } else if (charset == NULL && default_charset == NULL) {
1058 default_charset = attachment_get_default_charset ();
1059 /* FIXME Check that this fits within the
1060 * default_charset and if not, find one
1061 * that does and/or allow the user to
1062 * specify. */
1063 }
1064
1065 if (charset == NULL) {
1066 gchar *type;
1067
1068 camel_content_type_set_param (
1069 content_type, "charset", default_charset);
1070 type = camel_content_type_format (content_type);
1071 camel_mime_part_set_content_type (mime_part, type);
1072 g_free (type);
1073 }
1074
1075 /* Otherwise, unless it's a message/rfc822, Base64 encode it. */
1076 } else if (!CAMEL_IS_MIME_MESSAGE (wrapper))
1077 camel_mime_part_set_encoding (
1078 mime_part, CAMEL_TRANSFER_ENCODING_BASE64);
1079
1080 exit:
1081 camel_multipart_add_part (multipart, mime_part);
1082 }
1083
1084 void
1085 e_attachment_cancel (EAttachment *attachment)
1086 {
1087 g_return_if_fail (E_IS_ATTACHMENT (attachment));
1088
1089 g_cancellable_cancel (attachment->priv->cancellable);
1090 }
1091
1092 gboolean
1093 e_attachment_get_can_show (EAttachment *attachment)
1094 {
1095 g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
1096
1097 return attachment->priv->can_show;
1098 }
1099
1100 void
1101 e_attachment_set_can_show (EAttachment *attachment,
1102 gboolean can_show)
1103 {
1104 g_return_if_fail (E_IS_ATTACHMENT (attachment));
1105
1106 attachment->priv->can_show = can_show;
1107
1108 g_object_notify (G_OBJECT (attachment), "can-show");
1109 }
1110
1111 const gchar *
1112 e_attachment_get_disposition (EAttachment *attachment)
1113 {
1114 g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
1115
1116 return attachment->priv->disposition;
1117 }
1118
1119 void
1120 e_attachment_set_disposition (EAttachment *attachment,
1121 const gchar *disposition)
1122 {
1123 g_return_if_fail (E_IS_ATTACHMENT (attachment));
1124
1125 g_free (attachment->priv->disposition);
1126 attachment->priv->disposition = g_strdup (disposition);
1127
1128 g_object_notify (G_OBJECT (attachment), "disposition");
1129 }
1130
1131 GFile *
1132 e_attachment_get_file (EAttachment *attachment)
1133 {
1134 g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
1135
1136 return attachment->priv->file;
1137 }
1138
1139 void
1140 e_attachment_set_file (EAttachment *attachment,
1141 GFile *file)
1142 {
1143 g_return_if_fail (E_IS_ATTACHMENT (attachment));
1144
1145 if (file != NULL) {
1146 g_return_if_fail (G_IS_FILE (file));
1147 g_object_ref (file);
1148 }
1149
1150 if (attachment->priv->file != NULL)
1151 g_object_unref (attachment->priv->file);
1152
1153 attachment->priv->file = file;
1154
1155 g_object_notify (G_OBJECT (attachment), "file");
1156 }
1157
1158 GFileInfo *
1159 e_attachment_get_file_info (EAttachment *attachment)
1160 {
1161 g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
1162
1163 return attachment->priv->file_info;
1164 }
1165
1166 void
1167 e_attachment_set_file_info (EAttachment *attachment,
1168 GFileInfo *file_info)
1169 {
1170 GtkTreeRowReference *reference;
1171 GIcon *icon;
1172
1173 reference = e_attachment_get_reference (attachment);
1174
1175 if (file_info != NULL)
1176 g_object_ref (file_info);
1177
1178 if (attachment->priv->file_info != NULL)
1179 g_object_unref (attachment->priv->file_info);
1180
1181 attachment->priv->file_info = file_info;
1182
1183 /* If the GFileInfo contains a GThemedIcon, append a
1184 * fallback icon name to ensure we display something. */
1185 icon = g_file_info_get_icon (file_info);
1186 if (G_IS_THEMED_ICON (icon))
1187 g_themed_icon_append_name (
1188 G_THEMED_ICON (icon), DEFAULT_ICON_NAME);
1189
1190 g_object_notify (G_OBJECT (attachment), "file-info");
1191
1192 /* Tell the EAttachmentStore its total size changed. */
1193 if (gtk_tree_row_reference_valid (reference)) {
1194 GtkTreeModel *model;
1195 model = gtk_tree_row_reference_get_model (reference);
1196 g_object_notify (G_OBJECT (model), "total-size");
1197 }
1198 }
1199
1200 /**
1201 * e_attachment_get_mime_type:
1202 *
1203 * Returns mime_type part of the file_info as a newly allocated string,
1204 * which should be freed with g_free().
1205 * Returns NULL, if mime_type not found or set on the attachment.
1206 **/
1207 gchar *
1208 e_attachment_get_mime_type (EAttachment *attachment)
1209 {
1210 GFileInfo *file_info;
1211 const gchar *content_type;
1212 gchar *mime_type;
1213
1214 g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
1215
1216 file_info = e_attachment_get_file_info (attachment);
1217 if (file_info == NULL)
1218 return NULL;
1219
1220 content_type = g_file_info_get_content_type (file_info);
1221 if (content_type == NULL)
1222 return NULL;
1223
1224 mime_type = g_content_type_get_mime_type (content_type);
1225 if (!mime_type)
1226 return NULL;
1227
1228 camel_strdown (mime_type);
1229
1230 return mime_type;
1231 }
1232
1233 GIcon *
1234 e_attachment_get_icon (EAttachment *attachment)
1235 {
1236 g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
1237
1238 return attachment->priv->icon;
1239 }
1240
1241 gboolean
1242 e_attachment_get_loading (EAttachment *attachment)
1243 {
1244 g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
1245
1246 return attachment->priv->loading;
1247 }
1248
1249 CamelMimePart *
1250 e_attachment_get_mime_part (EAttachment *attachment)
1251 {
1252 g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
1253
1254 return attachment->priv->mime_part;
1255 }
1256
1257 void
1258 e_attachment_set_mime_part (EAttachment *attachment,
1259 CamelMimePart *mime_part)
1260 {
1261 g_return_if_fail (E_IS_ATTACHMENT (attachment));
1262
1263 if (mime_part != NULL) {
1264 g_return_if_fail (CAMEL_IS_MIME_PART (mime_part));
1265 g_object_ref (mime_part);
1266 }
1267
1268 if (attachment->priv->mime_part != NULL)
1269 g_object_unref (attachment->priv->mime_part);
1270
1271 attachment->priv->mime_part = mime_part;
1272
1273 g_object_notify (G_OBJECT (attachment), "mime-part");
1274 }
1275
1276 gint
1277 e_attachment_get_percent (EAttachment *attachment)
1278 {
1279 g_return_val_if_fail (E_IS_ATTACHMENT (attachment), 0);
1280
1281 return attachment->priv->percent;
1282 }
1283
1284 GtkTreeRowReference *
1285 e_attachment_get_reference (EAttachment *attachment)
1286 {
1287 g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
1288
1289 return attachment->priv->reference;
1290 }
1291
1292 void
1293 e_attachment_set_reference (EAttachment *attachment,
1294 GtkTreeRowReference *reference)
1295 {
1296 g_return_if_fail (E_IS_ATTACHMENT (attachment));
1297
1298 if (reference != NULL)
1299 reference = gtk_tree_row_reference_copy (reference);
1300
1301 gtk_tree_row_reference_free (attachment->priv->reference);
1302 attachment->priv->reference = reference;
1303
1304 g_object_notify (G_OBJECT (attachment), "reference");
1305 }
1306
1307 gboolean
1308 e_attachment_get_saving (EAttachment *attachment)
1309 {
1310 g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
1311
1312 return attachment->priv->saving;
1313 }
1314
1315 gboolean
1316 e_attachment_get_shown (EAttachment *attachment)
1317 {
1318 g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
1319
1320 return attachment->priv->shown;
1321 }
1322
1323 void
1324 e_attachment_set_shown (EAttachment *attachment,
1325 gboolean shown)
1326 {
1327 g_return_if_fail (E_IS_ATTACHMENT (attachment));
1328
1329 attachment->priv->shown = shown;
1330
1331 g_object_notify (G_OBJECT (attachment), "shown");
1332 }
1333
1334 camel_cipher_validity_encrypt_t
1335 e_attachment_get_encrypted (EAttachment *attachment)
1336 {
1337 g_return_val_if_fail (
1338 E_IS_ATTACHMENT (attachment),
1339 CAMEL_CIPHER_VALIDITY_ENCRYPT_NONE);
1340
1341 return attachment->priv->encrypted;
1342 }
1343
1344 void
1345 e_attachment_set_encrypted (EAttachment *attachment,
1346 camel_cipher_validity_encrypt_t encrypted)
1347 {
1348 g_return_if_fail (E_IS_ATTACHMENT (attachment));
1349
1350 attachment->priv->encrypted = encrypted;
1351
1352 g_object_notify (G_OBJECT (attachment), "encrypted");
1353 }
1354
1355 camel_cipher_validity_sign_t
1356 e_attachment_get_signed (EAttachment *attachment)
1357 {
1358 g_return_val_if_fail (
1359 E_IS_ATTACHMENT (attachment),
1360 CAMEL_CIPHER_VALIDITY_SIGN_NONE);
1361
1362 return attachment->priv->signed_;
1363 }
1364
1365 void
1366 e_attachment_set_signed (EAttachment *attachment,
1367 camel_cipher_validity_sign_t signed_)
1368 {
1369 g_return_if_fail (E_IS_ATTACHMENT (attachment));
1370
1371 attachment->priv->signed_ = signed_;
1372
1373 g_object_notify (G_OBJECT (attachment), "signed");
1374 }
1375
1376 const gchar *
1377 e_attachment_get_description (EAttachment *attachment)
1378 {
1379 GFileInfo *file_info;
1380 const gchar *attribute;
1381
1382 g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
1383
1384 attribute = G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION;
1385 file_info = e_attachment_get_file_info (attachment);
1386
1387 if (file_info == NULL)
1388 return NULL;
1389
1390 return g_file_info_get_attribute_string (file_info, attribute);
1391 }
1392
1393 const gchar *
1394 e_attachment_get_thumbnail_path (EAttachment *attachment)
1395 {
1396 GFileInfo *file_info;
1397 const gchar *attribute;
1398
1399 g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
1400
1401 attribute = G_FILE_ATTRIBUTE_THUMBNAIL_PATH;
1402 file_info = e_attachment_get_file_info (attachment);
1403
1404 if (file_info == NULL)
1405 return NULL;
1406
1407 return g_file_info_get_attribute_byte_string (file_info, attribute);
1408 }
1409
1410 gboolean
1411 e_attachment_is_rfc822 (EAttachment *attachment)
1412 {
1413 gchar *mime_type;
1414 gboolean is_rfc822;
1415
1416 g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
1417
1418 mime_type = e_attachment_get_mime_type (attachment);
1419 is_rfc822 = mime_type && g_ascii_strcasecmp (mime_type, "message/rfc822") == 0;
1420 g_free (mime_type);
1421
1422 return is_rfc822;
1423 }
1424
1425 GList *
1426 e_attachment_list_apps (EAttachment *attachment)
1427 {
1428 GList *app_info_list;
1429 GList *guessed_infos;
1430 GFileInfo *file_info;
1431 const gchar *content_type;
1432 const gchar *display_name;
1433 gboolean type_is_unknown;
1434 gchar *allocated;
1435
1436 g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
1437
1438 file_info = e_attachment_get_file_info (attachment);
1439 if (file_info == NULL)
1440 return NULL;
1441
1442 content_type = g_file_info_get_content_type (file_info);
1443 display_name = g_file_info_get_display_name (file_info);
1444 g_return_val_if_fail (content_type != NULL, NULL);
1445
1446 app_info_list = g_app_info_get_all_for_type (content_type);
1447 type_is_unknown = g_content_type_is_unknown (content_type);
1448
1449 if (app_info_list != NULL && !type_is_unknown)
1450 goto exit;
1451
1452 if (display_name == NULL)
1453 goto exit;
1454
1455 allocated = g_content_type_guess (display_name, NULL, 0, NULL);
1456 guessed_infos = g_app_info_get_all_for_type (allocated);
1457 app_info_list = g_list_concat (guessed_infos, app_info_list);
1458 g_free (allocated);
1459
1460 exit:
1461 return app_info_list;
1462 }
1463
1464 /************************* e_attachment_load_async() *************************/
1465
1466 typedef struct _LoadContext LoadContext;
1467
1468 struct _LoadContext {
1469 EAttachment *attachment;
1470 CamelMimePart *mime_part;
1471 GSimpleAsyncResult *simple;
1472
1473 GInputStream *input_stream;
1474 GOutputStream *output_stream;
1475 GFileInfo *file_info;
1476 goffset total_num_bytes;
1477 gssize bytes_read;
1478 gchar buffer[4096];
1479 };
1480
1481 /* Forward Declaration */
1482 static void
1483 attachment_load_stream_read_cb (GInputStream *input_stream,
1484 GAsyncResult *result,
1485 LoadContext *load_context);
1486
1487 static LoadContext *
1488 attachment_load_context_new (EAttachment *attachment,
1489 GAsyncReadyCallback callback,
1490 gpointer user_data)
1491 {
1492 LoadContext *load_context;
1493 GSimpleAsyncResult *simple;
1494
1495 simple = g_simple_async_result_new (
1496 G_OBJECT (attachment), callback,
1497 user_data, e_attachment_load_async);
1498
1499 load_context = g_slice_new0 (LoadContext);
1500 load_context->attachment = g_object_ref (attachment);
1501 load_context->simple = simple;
1502
1503 attachment_set_loading (load_context->attachment, TRUE);
1504
1505 return load_context;
1506 }
1507
1508 static void
1509 attachment_load_context_free (LoadContext *load_context)
1510 {
1511 g_object_unref (load_context->attachment);
1512
1513 if (load_context->mime_part != NULL)
1514 g_object_unref (load_context->mime_part);
1515
1516 if (load_context->simple)
1517 g_object_unref (load_context->simple);
1518
1519 if (load_context->input_stream != NULL)
1520 g_object_unref (load_context->input_stream);
1521
1522 if (load_context->output_stream != NULL)
1523 g_object_unref (load_context->output_stream);
1524
1525 if (load_context->file_info != NULL)
1526 g_object_unref (load_context->file_info);
1527
1528 g_slice_free (LoadContext, load_context);
1529 }
1530
1531 static gboolean
1532 attachment_load_check_for_error (LoadContext *load_context,
1533 GError *error)
1534 {
1535 GSimpleAsyncResult *simple;
1536
1537 if (error == NULL)
1538 return FALSE;
1539
1540 simple = load_context->simple;
1541 g_simple_async_result_take_error (simple, error);
1542 g_simple_async_result_complete (simple);
1543
1544 attachment_load_context_free (load_context);
1545
1546 return TRUE;
1547 }
1548
1549 static void
1550 attachment_load_finish (LoadContext *load_context)
1551 {
1552 GFileInfo *file_info;
1553 EAttachment *attachment;
1554 GMemoryOutputStream *output_stream;
1555 GSimpleAsyncResult *simple;
1556 CamelDataWrapper *wrapper;
1557 CamelMimePart *mime_part;
1558 CamelStream *stream;
1559 const gchar *attribute;
1560 const gchar *content_type;
1561 const gchar *display_name;
1562 const gchar *description;
1563 const gchar *disposition;
1564 gchar *mime_type;
1565 gpointer data;
1566 gsize size;
1567
1568 simple = load_context->simple;
1569
1570 file_info = load_context->file_info;
1571 attachment = load_context->attachment;
1572 output_stream = G_MEMORY_OUTPUT_STREAM (load_context->output_stream);
1573
1574 if (e_attachment_is_rfc822 (attachment))
1575 wrapper = (CamelDataWrapper *) camel_mime_message_new ();
1576 else
1577 wrapper = camel_data_wrapper_new ();
1578
1579 content_type = g_file_info_get_content_type (file_info);
1580 mime_type = g_content_type_get_mime_type (content_type);
1581
1582 data = g_memory_output_stream_get_data (output_stream);
1583 size = g_memory_output_stream_get_data_size (output_stream);
1584
1585 stream = camel_stream_mem_new_with_buffer (data, size);
1586 camel_data_wrapper_construct_from_stream_sync (
1587 wrapper, stream, NULL, NULL);
1588 camel_data_wrapper_set_mime_type (wrapper, mime_type);
1589 camel_stream_close (stream, NULL, NULL);
1590 g_object_unref (stream);
1591
1592 mime_part = camel_mime_part_new ();
1593 camel_medium_set_content (CAMEL_MEDIUM (mime_part), wrapper);
1594
1595 g_object_unref (wrapper);
1596 g_free (mime_type);
1597
1598 display_name = g_file_info_get_display_name (file_info);
1599 if (display_name != NULL)
1600 camel_mime_part_set_filename (mime_part, display_name);
1601
1602 attribute = G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION;
1603 description = g_file_info_get_attribute_string (file_info, attribute);
1604 if (description != NULL)
1605 camel_mime_part_set_description (mime_part, description);
1606
1607 disposition = e_attachment_get_disposition (attachment);
1608 if (disposition != NULL)
1609 camel_mime_part_set_disposition (mime_part, disposition);
1610
1611 /* Correctly report the size of zero length special files. */
1612 if (g_file_info_get_size (file_info) == 0)
1613 g_file_info_set_size (file_info, size);
1614
1615 load_context->mime_part = mime_part;
1616
1617 g_simple_async_result_set_op_res_gpointer (
1618 simple, load_context, (GDestroyNotify) attachment_load_context_free);
1619
1620 g_simple_async_result_complete (simple);
1621
1622 /* make sure it's freed on operation end */
1623 load_context->simple = NULL;
1624 g_object_unref (simple);
1625 }
1626
1627 static void
1628 attachment_load_write_cb (GOutputStream *output_stream,
1629 GAsyncResult *result,
1630 LoadContext *load_context)
1631 {
1632 EAttachment *attachment;
1633 GCancellable *cancellable;
1634 GInputStream *input_stream;
1635 gssize bytes_written;
1636 GError *error = NULL;
1637
1638 bytes_written = g_output_stream_write_finish (
1639 output_stream, result, &error);
1640
1641 if (attachment_load_check_for_error (load_context, error))
1642 return;
1643
1644 attachment = load_context->attachment;
1645 cancellable = attachment->priv->cancellable;
1646 input_stream = load_context->input_stream;
1647
1648 attachment_progress_cb (
1649 g_seekable_tell (G_SEEKABLE (output_stream)),
1650 load_context->total_num_bytes, attachment);
1651
1652 if (bytes_written < load_context->bytes_read) {
1653 g_memmove (
1654 load_context->buffer,
1655 load_context->buffer + bytes_written,
1656 load_context->bytes_read - bytes_written);
1657 load_context->bytes_read -= bytes_written;
1658
1659 g_output_stream_write_async (
1660 output_stream,
1661 load_context->buffer,
1662 load_context->bytes_read,
1663 G_PRIORITY_DEFAULT, cancellable,
1664 (GAsyncReadyCallback) attachment_load_write_cb,
1665 load_context);
1666 } else
1667 g_input_stream_read_async (
1668 input_stream,
1669 load_context->buffer,
1670 sizeof (load_context->buffer),
1671 G_PRIORITY_DEFAULT, cancellable,
1672 (GAsyncReadyCallback) attachment_load_stream_read_cb,
1673 load_context);
1674 }
1675
1676 static void
1677 attachment_load_stream_read_cb (GInputStream *input_stream,
1678 GAsyncResult *result,
1679 LoadContext *load_context)
1680 {
1681 EAttachment *attachment;
1682 GCancellable *cancellable;
1683 GOutputStream *output_stream;
1684 gssize bytes_read;
1685 GError *error = NULL;
1686
1687 bytes_read = g_input_stream_read_finish (
1688 input_stream, result, &error);
1689
1690 if (attachment_load_check_for_error (load_context, error))
1691 return;
1692
1693 if (bytes_read == 0) {
1694 attachment_load_finish (load_context);
1695 return;
1696 }
1697
1698 attachment = load_context->attachment;
1699 cancellable = attachment->priv->cancellable;
1700 output_stream = load_context->output_stream;
1701 load_context->bytes_read = bytes_read;
1702
1703 g_output_stream_write_async (
1704 output_stream,
1705 load_context->buffer,
1706 load_context->bytes_read,
1707 G_PRIORITY_DEFAULT, cancellable,
1708 (GAsyncReadyCallback) attachment_load_write_cb,
1709 load_context);
1710 }
1711
1712 static void
1713 attachment_load_file_read_cb (GFile *file,
1714 GAsyncResult *result,
1715 LoadContext *load_context)
1716 {
1717 EAttachment *attachment;
1718 GCancellable *cancellable;
1719 GFileInputStream *input_stream;
1720 GOutputStream *output_stream;
1721 GError *error = NULL;
1722
1723 /* Input stream might be NULL, so don't use cast macro. */
1724 input_stream = g_file_read_finish (file, result, &error);
1725 load_context->input_stream = (GInputStream *) input_stream;
1726
1727 if (attachment_load_check_for_error (load_context, error))
1728 return;
1729
1730 /* Load the contents into a GMemoryOutputStream. */
1731 output_stream = g_memory_output_stream_new (
1732 NULL, 0, g_realloc, g_free);
1733
1734 attachment = load_context->attachment;
1735 cancellable = attachment->priv->cancellable;
1736 load_context->output_stream = output_stream;
1737
1738 g_input_stream_read_async (
1739 load_context->input_stream,
1740 load_context->buffer,
1741 sizeof (load_context->buffer),
1742 G_PRIORITY_DEFAULT, cancellable,
1743 (GAsyncReadyCallback) attachment_load_stream_read_cb,
1744 load_context);
1745 }
1746
1747 static void
1748 attachment_load_query_info_cb (GFile *file,
1749 GAsyncResult *result,
1750 LoadContext *load_context)
1751 {
1752 EAttachment *attachment;
1753 GCancellable *cancellable;
1754 GFileInfo *file_info;
1755 GError *error = NULL;
1756
1757 attachment = load_context->attachment;
1758 cancellable = attachment->priv->cancellable;
1759
1760 file_info = g_file_query_info_finish (file, result, &error);
1761 if (attachment_load_check_for_error (load_context, error))
1762 return;
1763
1764 e_attachment_set_file_info (attachment, file_info);
1765 load_context->file_info = file_info;
1766
1767 load_context->total_num_bytes = g_file_info_get_size (file_info);
1768
1769 g_file_read_async (
1770 file, G_PRIORITY_DEFAULT,
1771 cancellable, (GAsyncReadyCallback)
1772 attachment_load_file_read_cb, load_context);
1773 }
1774
1775 #define ATTACHMENT_LOAD_CONTEXT "attachment-load-context-data"
1776
1777 static void
1778 attachment_load_from_mime_part_thread (GSimpleAsyncResult *simple,
1779 GObject *object,
1780 GCancellable *cancellable)
1781 {
1782 LoadContext *load_context;
1783 GFileInfo *file_info;
1784 EAttachment *attachment;
1785 CamelContentType *content_type;
1786 CamelMimePart *mime_part;
1787 const gchar *attribute;
1788 const gchar *string;
1789 gchar *allocated, *decoded_string = NULL;
1790 CamelStream *null;
1791 CamelDataWrapper *dw;
1792
1793 load_context = g_object_get_data (G_OBJECT (simple), ATTACHMENT_LOAD_CONTEXT);
1794 g_return_if_fail (load_context != NULL);
1795 g_object_set_data (G_OBJECT (simple), ATTACHMENT_LOAD_CONTEXT, NULL);
1796
1797 attachment = load_context->attachment;
1798 mime_part = e_attachment_get_mime_part (attachment);
1799
1800 file_info = g_file_info_new ();
1801 load_context->file_info = file_info;
1802
1803 content_type = camel_mime_part_get_content_type (mime_part);
1804 allocated = camel_content_type_simple (content_type);
1805 if (allocated != NULL) {
1806 GIcon *icon;
1807 gchar *cp;
1808
1809 /* GIO expects lowercase MIME types. */
1810 for (cp = allocated; *cp != '\0'; cp++)
1811 *cp = g_ascii_tolower (*cp);
1812
1813 /* Swap the MIME type for a content type. */
1814 cp = g_content_type_from_mime_type (allocated);
1815 g_free (allocated);
1816 allocated = cp;
1817
1818 /* Use the MIME part's filename if we have to. */
1819 if (g_content_type_is_unknown (allocated)) {
1820 string = camel_mime_part_get_filename (mime_part);
1821 if (string != NULL) {
1822 g_free (allocated);
1823 allocated = g_content_type_guess (
1824 string, NULL, 0, NULL);
1825 }
1826 }
1827
1828 g_file_info_set_content_type (file_info, allocated);
1829
1830 icon = g_content_type_get_icon (allocated);
1831 if (icon != NULL) {
1832 g_file_info_set_icon (file_info, icon);
1833 g_object_unref (icon);
1834 }
1835 }
1836 g_free (allocated);
1837
1838 /* Strip any path components from the filename. */
1839 string = camel_mime_part_get_filename (mime_part);
1840 if (string == NULL) {
1841 /* Translators: Default attachment filename. */
1842 string = _("attachment.dat");
1843
1844 if (camel_content_type_is (content_type, "message", "rfc822")) {
1845 CamelMimeMessage *msg = NULL;
1846 const gchar *subject = NULL;
1847
1848 if (CAMEL_IS_MIME_MESSAGE (mime_part)) {
1849 msg = CAMEL_MIME_MESSAGE (mime_part);
1850 } else {
1851 CamelDataWrapper *content;
1852
1853 content = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
1854 if (CAMEL_IS_MIME_MESSAGE (content))
1855 msg = CAMEL_MIME_MESSAGE (content);
1856 }
1857
1858 if (msg)
1859 subject = camel_mime_message_get_subject (msg);
1860
1861 if (subject && *subject)
1862 string = subject;
1863 }
1864 } else {
1865 decoded_string = camel_header_decode_string (string, "UTF-8");
1866 if (decoded_string && *decoded_string && !g_str_equal (decoded_string, string)) {
1867 string = decoded_string;
1868 } else {
1869 g_free (decoded_string);
1870 decoded_string = NULL;
1871 }
1872 }
1873 allocated = g_path_get_basename (string);
1874 g_file_info_set_display_name (file_info, allocated);
1875 g_free (decoded_string);
1876 g_free (allocated);
1877
1878 attribute = G_FILE_ATTRIBUTE_STANDARD_DESCRIPTION;
1879 string = camel_mime_part_get_description (mime_part);
1880 if (string != NULL)
1881 g_file_info_set_attribute_string (
1882 file_info, attribute, string);
1883
1884 dw = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
1885 null = camel_stream_null_new ();
1886 /* this actually downloads the part and makes it available later */
1887 camel_data_wrapper_decode_to_stream_sync (dw, null, attachment->priv->cancellable, NULL);
1888 g_file_info_set_size (file_info, CAMEL_STREAM_NULL (null)->written);
1889 g_object_unref (null);
1890
1891 load_context->mime_part = g_object_ref (mime_part);
1892
1893 /* make sure it's freed on operation end */
1894 g_object_unref (load_context->simple);
1895 load_context->simple = NULL;
1896
1897 g_simple_async_result_set_op_res_gpointer (
1898 simple, load_context,
1899 (GDestroyNotify) attachment_load_context_free);
1900 }
1901
1902 void
1903 e_attachment_load_async (EAttachment *attachment,
1904 GAsyncReadyCallback callback,
1905 gpointer user_data)
1906 {
1907 LoadContext *load_context;
1908 GCancellable *cancellable;
1909 CamelMimePart *mime_part;
1910 GFile *file;
1911
1912 g_return_if_fail (E_IS_ATTACHMENT (attachment));
1913
1914 if (e_attachment_get_loading (attachment)) {
1915 g_simple_async_report_error_in_idle (
1916 G_OBJECT (attachment), callback, user_data,
1917 G_IO_ERROR, G_IO_ERROR_BUSY,
1918 _("A load operation is already in progress"));
1919 return;
1920 }
1921
1922 if (e_attachment_get_saving (attachment)) {
1923 g_simple_async_report_error_in_idle (
1924 G_OBJECT (attachment), callback, user_data,
1925 G_IO_ERROR, G_IO_ERROR_BUSY,
1926 _("A save operation is already in progress"));
1927 return;
1928 }
1929
1930 file = e_attachment_get_file (attachment);
1931 mime_part = e_attachment_get_mime_part (attachment);
1932 g_return_if_fail (file != NULL || mime_part != NULL);
1933
1934 load_context = attachment_load_context_new (
1935 attachment, callback, user_data);
1936
1937 cancellable = attachment->priv->cancellable;
1938 g_cancellable_reset (cancellable);
1939
1940 if (file != NULL) {
1941 g_file_query_info_async (
1942 file, ATTACHMENT_QUERY,
1943 G_FILE_QUERY_INFO_NONE,G_PRIORITY_DEFAULT,
1944 cancellable, (GAsyncReadyCallback)
1945 attachment_load_query_info_cb, load_context);
1946
1947 } else if (mime_part != NULL) {
1948 g_object_set_data (G_OBJECT (load_context->simple), ATTACHMENT_LOAD_CONTEXT, load_context);
1949
1950 g_simple_async_result_run_in_thread (
1951 load_context->simple,
1952 attachment_load_from_mime_part_thread,
1953 G_PRIORITY_DEFAULT,
1954 cancellable);
1955 }
1956 }
1957
1958 gboolean
1959 e_attachment_load_finish (EAttachment *attachment,
1960 GAsyncResult *result,
1961 GError **error)
1962 {
1963 GSimpleAsyncResult *simple;
1964 const LoadContext *load_context;
1965
1966 g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
1967 g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE);
1968
1969 simple = G_SIMPLE_ASYNC_RESULT (result);
1970 load_context = g_simple_async_result_get_op_res_gpointer (simple);
1971 if (load_context && load_context->mime_part) {
1972 const gchar *string;
1973
1974 string = camel_mime_part_get_disposition (load_context->mime_part);
1975 e_attachment_set_disposition (attachment, string);
1976
1977 e_attachment_set_file_info (attachment, load_context->file_info);
1978 e_attachment_set_mime_part (attachment, load_context->mime_part);
1979 }
1980
1981 g_simple_async_result_propagate_error (simple, error);
1982
1983 attachment_set_loading (attachment, FALSE);
1984
1985 return (load_context != NULL);
1986 }
1987
1988 void
1989 e_attachment_load_handle_error (EAttachment *attachment,
1990 GAsyncResult *result,
1991 GtkWindow *parent)
1992 {
1993 GtkWidget *dialog;
1994 GFileInfo *file_info;
1995 GtkTreeRowReference *reference;
1996 const gchar *display_name;
1997 const gchar *primary_text;
1998 GError *error = NULL;
1999
2000 g_return_if_fail (E_IS_ATTACHMENT (attachment));
2001 g_return_if_fail (G_IS_ASYNC_RESULT (result));
2002 g_return_if_fail (!parent || GTK_IS_WINDOW (parent));
2003
2004 if (e_attachment_load_finish (attachment, result, &error))
2005 return;
2006
2007 /* XXX Calling EAttachmentStore functions from here violates
2008 * the abstraction, but for now it's not hurting anything. */
2009 reference = e_attachment_get_reference (attachment);
2010 if (gtk_tree_row_reference_valid (reference)) {
2011 GtkTreeModel *model;
2012
2013 model = gtk_tree_row_reference_get_model (reference);
2014
2015 e_attachment_store_remove_attachment (
2016 E_ATTACHMENT_STORE (model), attachment);
2017 }
2018
2019 /* Ignore cancellations. */
2020 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
2021 g_error_free (error);
2022 return;
2023 }
2024
2025 file_info = e_attachment_get_file_info (attachment);
2026
2027 if (file_info != NULL)
2028 display_name = g_file_info_get_display_name (file_info);
2029 else
2030 display_name = NULL;
2031
2032 if (display_name != NULL)
2033 primary_text = g_strdup_printf (
2034 _("Could not load '%s'"), display_name);
2035 else
2036 primary_text = g_strdup_printf (
2037 _("Could not load the attachment"));
2038
2039 dialog = gtk_message_dialog_new_with_markup (
2040 parent, GTK_DIALOG_DESTROY_WITH_PARENT,
2041 GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
2042 "<big><b>%s</b></big>", primary_text);
2043
2044 gtk_message_dialog_format_secondary_text (
2045 GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
2046
2047 gtk_dialog_run (GTK_DIALOG (dialog));
2048
2049 gtk_widget_destroy (dialog);
2050 g_error_free (error);
2051 }
2052
2053 gboolean
2054 e_attachment_load (EAttachment *attachment,
2055 GError **error)
2056 {
2057 EAsyncClosure *closure;
2058 GAsyncResult *result;
2059 gboolean success;
2060
2061 g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
2062
2063 closure = e_async_closure_new ();
2064
2065 e_attachment_load_async (attachment, e_async_closure_callback, closure);
2066
2067 result = e_async_closure_wait (closure);
2068
2069 success = e_attachment_load_finish (attachment, result, error);
2070
2071 e_async_closure_free (closure);
2072
2073 return success;
2074 }
2075
2076 /************************* e_attachment_open_async() *************************/
2077
2078 typedef struct _OpenContext OpenContext;
2079
2080 struct _OpenContext {
2081 EAttachment *attachment;
2082 GSimpleAsyncResult *simple;
2083
2084 GAppInfo *app_info;
2085 };
2086
2087 static OpenContext *
2088 attachment_open_context_new (EAttachment *attachment,
2089 GAsyncReadyCallback callback,
2090 gpointer user_data)
2091 {
2092 OpenContext *open_context;
2093 GSimpleAsyncResult *simple;
2094
2095 simple = g_simple_async_result_new (
2096 G_OBJECT (attachment), callback,
2097 user_data, e_attachment_open_async);
2098
2099 open_context = g_slice_new0 (OpenContext);
2100 open_context->attachment = g_object_ref (attachment);
2101 open_context->simple = simple;
2102
2103 return open_context;
2104 }
2105
2106 static void
2107 attachment_open_context_free (OpenContext *open_context)
2108 {
2109 g_object_unref (open_context->attachment);
2110 g_object_unref (open_context->simple);
2111
2112 if (open_context->app_info != NULL)
2113 g_object_unref (open_context->app_info);
2114
2115 g_slice_free (OpenContext, open_context);
2116 }
2117
2118 static gboolean
2119 attachment_open_check_for_error (OpenContext *open_context,
2120 GError *error)
2121 {
2122 GSimpleAsyncResult *simple;
2123
2124 if (error == NULL)
2125 return FALSE;
2126
2127 simple = open_context->simple;
2128 g_simple_async_result_take_error (simple, error);
2129 g_simple_async_result_complete (simple);
2130
2131 attachment_open_context_free (open_context);
2132
2133 return TRUE;
2134 }
2135
2136 static void
2137 attachment_open_file (GFile *file,
2138 OpenContext *open_context)
2139 {
2140 GdkAppLaunchContext *context;
2141 GSimpleAsyncResult *simple;
2142 gboolean success;
2143 GError *error = NULL;
2144
2145 simple = open_context->simple;
2146
2147 context = gdk_app_launch_context_new ();
2148
2149 if (open_context->app_info != NULL) {
2150 GList *file_list;
2151
2152 file_list = g_list_prepend (NULL, file);
2153 success = g_app_info_launch (
2154 open_context->app_info, file_list,
2155 G_APP_LAUNCH_CONTEXT (context), &error);
2156 g_list_free (file_list);
2157 } else {
2158 gchar *uri;
2159
2160 uri = g_file_get_uri (file);
2161 success = g_app_info_launch_default_for_uri (
2162 uri, G_APP_LAUNCH_CONTEXT (context), &error);
2163 g_free (uri);
2164 }
2165
2166 g_object_unref (context);
2167
2168 g_simple_async_result_set_op_res_gboolean (simple, success);
2169
2170 if (error != NULL)
2171 g_simple_async_result_take_error (simple, error);
2172
2173 g_simple_async_result_complete (simple);
2174 attachment_open_context_free (open_context);
2175 }
2176
2177 static void
2178 attachment_open_save_finished_cb (EAttachment *attachment,
2179 GAsyncResult *result,
2180 OpenContext *open_context)
2181 {
2182 GFile *file;
2183 gchar *path;
2184 GError *error = NULL;
2185
2186 file = e_attachment_save_finish (attachment, result, &error);
2187
2188 if (attachment_open_check_for_error (open_context, error))
2189 return;
2190
2191 /* Make the temporary file read-only.
2192 *
2193 * This step is non-critical, so if an error occurs just
2194 * emit a warning and move on.
2195 *
2196 * XXX I haven't figured out how to do this through GIO.
2197 * Attempting to set the "access::can-write" attribute via
2198 * g_file_set_attribute() returned G_IO_ERROR_NOT_SUPPORTED
2199 * and the only other possibility I see is "unix::mode",
2200 * which is obviously not portable.
2201 */
2202 path = g_file_get_path (file);
2203 #ifndef G_OS_WIN32
2204 if (g_chmod (path, S_IRUSR | S_IRGRP | S_IROTH) < 0)
2205 g_warning ("%s", g_strerror (errno));
2206 #endif
2207 g_free (path);
2208
2209 attachment_open_file (file, open_context);
2210 g_object_unref (file);
2211 }
2212
2213 static void
2214 attachment_open_save_temporary (OpenContext *open_context)
2215 {
2216 GFile *temp_directory;
2217 gchar *template;
2218 gchar *path;
2219 GError *error = NULL;
2220
2221 errno = 0;
2222
2223 /* Save the file to a temporary directory.
2224 * We use a directory so the files can retain their basenames.
2225 * XXX This could trigger a blocking temp directory cleanup. */
2226 template = g_strdup_printf (PACKAGE "-%s-XXXXXX", g_get_user_name ());
2227 path = e_mkdtemp (template);
2228 g_free (template);
2229
2230 /* XXX Let's hope errno got set properly. */
2231 if (path == NULL)
2232 g_set_error (
2233 &error, G_FILE_ERROR,
2234 g_file_error_from_errno (errno),
2235 "%s", g_strerror (errno));
2236
2237 /* We already know if there's an error, but this does the cleanup. */
2238 if (attachment_open_check_for_error (open_context, error))
2239 return;
2240
2241 temp_directory = g_file_new_for_path (path);
2242
2243 e_attachment_save_async (
2244 open_context->attachment,
2245 temp_directory, (GAsyncReadyCallback)
2246 attachment_open_save_finished_cb, open_context);
2247
2248 g_object_unref (temp_directory);
2249 g_free (path);
2250 }
2251
2252 void
2253 e_attachment_open_async (EAttachment *attachment,
2254 GAppInfo *app_info,
2255 GAsyncReadyCallback callback,
2256 gpointer user_data)
2257 {
2258 OpenContext *open_context;
2259 CamelMimePart *mime_part;
2260 GFile *file;
2261
2262 g_return_if_fail (E_IS_ATTACHMENT (attachment));
2263
2264 file = e_attachment_get_file (attachment);
2265 mime_part = e_attachment_get_mime_part (attachment);
2266 g_return_if_fail (file != NULL || mime_part != NULL);
2267
2268 open_context = attachment_open_context_new (
2269 attachment, callback, user_data);
2270
2271 if (G_IS_APP_INFO (app_info))
2272 open_context->app_info = g_object_ref (app_info);
2273
2274 /* If the attachment already references a GFile, we can launch
2275 * the application directly. Otherwise we have to save the MIME
2276 * part to a temporary file and launch the application from that. */
2277 if (file != NULL) {
2278 attachment_open_file (file, open_context);
2279
2280 } else if (mime_part != NULL)
2281 attachment_open_save_temporary (open_context);
2282 }
2283
2284 gboolean
2285 e_attachment_open_finish (EAttachment *attachment,
2286 GAsyncResult *result,
2287 GError **error)
2288 {
2289 GSimpleAsyncResult *simple;
2290 gboolean success;
2291
2292 g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
2293 g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE);
2294
2295 simple = G_SIMPLE_ASYNC_RESULT (result);
2296 success = g_simple_async_result_get_op_res_gboolean (simple);
2297 g_simple_async_result_propagate_error (simple, error);
2298
2299 return success;
2300 }
2301
2302 void
2303 e_attachment_open_handle_error (EAttachment *attachment,
2304 GAsyncResult *result,
2305 GtkWindow *parent)
2306 {
2307 GtkWidget *dialog;
2308 GFileInfo *file_info;
2309 const gchar *display_name;
2310 const gchar *primary_text;
2311 GError *error = NULL;
2312
2313 g_return_if_fail (E_IS_ATTACHMENT (attachment));
2314 g_return_if_fail (G_IS_ASYNC_RESULT (result));
2315 g_return_if_fail (GTK_IS_WINDOW (parent));
2316
2317 if (e_attachment_open_finish (attachment, result, &error))
2318 return;
2319
2320 /* Ignore cancellations. */
2321 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
2322 return;
2323
2324 file_info = e_attachment_get_file_info (attachment);
2325
2326 if (file_info != NULL)
2327 display_name = g_file_info_get_display_name (file_info);
2328 else
2329 display_name = NULL;
2330
2331 if (display_name != NULL)
2332 primary_text = g_strdup_printf (
2333 _("Could not open '%s'"), display_name);
2334 else
2335 primary_text = g_strdup_printf (
2336 _("Could not open the attachment"));
2337
2338 dialog = gtk_message_dialog_new_with_markup (
2339 parent, GTK_DIALOG_DESTROY_WITH_PARENT,
2340 GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
2341 "<big><b>%s</b></big>", primary_text);
2342
2343 gtk_message_dialog_format_secondary_text (
2344 GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
2345
2346 gtk_dialog_run (GTK_DIALOG (dialog));
2347
2348 gtk_widget_destroy (dialog);
2349 g_error_free (error);
2350 }
2351
2352 gboolean
2353 e_attachment_open (EAttachment *attachment,
2354 GAppInfo *app_info,
2355 GError **error)
2356 {
2357 EAsyncClosure *closure;
2358 GAsyncResult *result;
2359 gboolean success;
2360
2361 g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
2362
2363 closure = e_async_closure_new ();
2364
2365 e_attachment_open_async (attachment, app_info, e_async_closure_callback, closure);
2366
2367 result = e_async_closure_wait (closure);
2368
2369 success = e_attachment_open_finish (attachment, result, error);
2370
2371 e_async_closure_free (closure);
2372
2373 return success;
2374 }
2375
2376 /************************* e_attachment_save_async() *************************/
2377
2378 typedef struct _SaveContext SaveContext;
2379
2380 struct _SaveContext {
2381 EAttachment *attachment;
2382 GSimpleAsyncResult *simple;
2383
2384 GFile *directory;
2385 GFile *destination;
2386 GInputStream *input_stream;
2387 GOutputStream *output_stream;
2388 goffset total_num_bytes;
2389 gssize bytes_read;
2390 gchar buffer[4096];
2391 gint count;
2392 };
2393
2394 /* Forward Declaration */
2395 static void
2396 attachment_save_read_cb (GInputStream *input_stream,
2397 GAsyncResult *result,
2398 SaveContext *save_context);
2399
2400 static SaveContext *
2401 attachment_save_context_new (EAttachment *attachment,
2402 GAsyncReadyCallback callback,
2403 gpointer user_data)
2404 {
2405 SaveContext *save_context;
2406 GSimpleAsyncResult *simple;
2407
2408 simple = g_simple_async_result_new (
2409 G_OBJECT (attachment), callback,
2410 user_data, e_attachment_save_async);
2411
2412 save_context = g_slice_new0 (SaveContext);
2413 save_context->attachment = g_object_ref (attachment);
2414 save_context->simple = simple;
2415
2416 attachment_set_saving (save_context->attachment, TRUE);
2417
2418 return save_context;
2419 }
2420
2421 static void
2422 attachment_save_context_free (SaveContext *save_context)
2423 {
2424 g_object_unref (save_context->attachment);
2425 g_object_unref (save_context->simple);
2426
2427 if (save_context->directory != NULL)
2428 g_object_unref (save_context->directory);
2429
2430 if (save_context->destination != NULL)
2431 g_object_unref (save_context->destination);
2432
2433 if (save_context->input_stream != NULL)
2434 g_object_unref (save_context->input_stream);
2435
2436 if (save_context->output_stream != NULL)
2437 g_object_unref (save_context->output_stream);
2438
2439 g_slice_free (SaveContext, save_context);
2440 }
2441
2442 static gboolean
2443 attachment_save_check_for_error (SaveContext *save_context,
2444 GError *error)
2445 {
2446 GSimpleAsyncResult *simple;
2447
2448 if (error == NULL)
2449 return FALSE;
2450
2451 simple = save_context->simple;
2452 g_simple_async_result_take_error (simple, error);
2453 g_simple_async_result_complete (simple);
2454
2455 attachment_save_context_free (save_context);
2456
2457 return TRUE;
2458 }
2459
2460 static GFile *
2461 attachment_save_new_candidate (SaveContext *save_context)
2462 {
2463 GFile *candidate;
2464 GFileInfo *file_info;
2465 EAttachment *attachment;
2466 const gchar *display_name = NULL;
2467 gchar *basename;
2468
2469 attachment = save_context->attachment;
2470 file_info = e_attachment_get_file_info (attachment);
2471
2472 if (file_info != NULL)
2473 display_name = g_file_info_get_display_name (file_info);
2474 if (display_name == NULL)
2475 /* Translators: Default attachment filename. */
2476 display_name = _("attachment.dat");
2477
2478 if (save_context->count == 0)
2479 basename = g_strdup (display_name);
2480 else {
2481 GString *string;
2482 const gchar *ext;
2483 gsize length;
2484
2485 string = g_string_sized_new (strlen (display_name));
2486 ext = g_utf8_strchr (display_name, -1, '.');
2487
2488 if (ext != NULL)
2489 length = ext - display_name;
2490 else
2491 length = strlen (display_name);
2492
2493 g_string_append_len (string, display_name, length);
2494 g_string_append_printf (string, " (%d)", save_context->count);
2495 g_string_append (string, (ext != NULL) ? ext : "");
2496
2497 basename = g_string_free (string, FALSE);
2498 }
2499
2500 save_context->count++;
2501
2502 candidate = g_file_get_child (save_context->directory, basename);
2503
2504 g_free (basename);
2505
2506 return candidate;
2507 }
2508
2509 static void
2510 attachment_save_write_cb (GOutputStream *output_stream,
2511 GAsyncResult *result,
2512 SaveContext *save_context)
2513 {
2514 EAttachment *attachment;
2515 GCancellable *cancellable;
2516 GInputStream *input_stream;
2517 gssize bytes_written;
2518 GError *error = NULL;
2519
2520 bytes_written = g_output_stream_write_finish (
2521 output_stream, result, &error);
2522
2523 if (attachment_save_check_for_error (save_context, error))
2524 return;
2525
2526 attachment = save_context->attachment;
2527 cancellable = attachment->priv->cancellable;
2528 input_stream = save_context->input_stream;
2529
2530 if (bytes_written < save_context->bytes_read) {
2531 g_memmove (
2532 save_context->buffer,
2533 save_context->buffer + bytes_written,
2534 save_context->bytes_read - bytes_written);
2535 save_context->bytes_read -= bytes_written;
2536
2537 g_output_stream_write_async (
2538 output_stream,
2539 save_context->buffer,
2540 save_context->bytes_read,
2541 G_PRIORITY_DEFAULT, cancellable,
2542 (GAsyncReadyCallback) attachment_save_write_cb,
2543 save_context);
2544 } else
2545 g_input_stream_read_async (
2546 input_stream,
2547 save_context->buffer,
2548 sizeof (save_context->buffer),
2549 G_PRIORITY_DEFAULT, cancellable,
2550 (GAsyncReadyCallback) attachment_save_read_cb,
2551 save_context);
2552 }
2553
2554 static void
2555 attachment_save_read_cb (GInputStream *input_stream,
2556 GAsyncResult *result,
2557 SaveContext *save_context)
2558 {
2559 EAttachment *attachment;
2560 GCancellable *cancellable;
2561 GOutputStream *output_stream;
2562 gssize bytes_read;
2563 GError *error = NULL;
2564
2565 bytes_read = g_input_stream_read_finish (
2566 input_stream, result, &error);
2567
2568 if (attachment_save_check_for_error (save_context, error))
2569 return;
2570
2571 if (bytes_read == 0) {
2572 GSimpleAsyncResult *simple;
2573 GFile *destination;
2574
2575 /* Steal the destination. */
2576 destination = save_context->destination;
2577 save_context->destination = NULL;
2578
2579 simple = save_context->simple;
2580 g_simple_async_result_set_op_res_gpointer (
2581 simple, destination, (GDestroyNotify) g_object_unref);
2582 g_simple_async_result_complete (simple);
2583
2584 attachment_save_context_free (save_context);
2585
2586 return;
2587 }
2588
2589 attachment = save_context->attachment;
2590 cancellable = attachment->priv->cancellable;
2591 output_stream = save_context->output_stream;
2592 save_context->bytes_read = bytes_read;
2593
2594 attachment_progress_cb (
2595 g_seekable_tell (G_SEEKABLE (input_stream)),
2596 save_context->total_num_bytes, attachment);
2597
2598 g_output_stream_write_async (
2599 output_stream,
2600 save_context->buffer,
2601 save_context->bytes_read,
2602 G_PRIORITY_DEFAULT, cancellable,
2603 (GAsyncReadyCallback) attachment_save_write_cb,
2604 save_context);
2605 }
2606
2607 static void
2608 attachment_save_got_output_stream (SaveContext *save_context)
2609 {
2610 GCancellable *cancellable;
2611 GInputStream *input_stream;
2612 CamelDataWrapper *wrapper;
2613 CamelMimePart *mime_part;
2614 CamelStream *stream;
2615 EAttachment *attachment;
2616 GByteArray *buffer;
2617
2618 attachment = save_context->attachment;
2619 cancellable = attachment->priv->cancellable;
2620 mime_part = e_attachment_get_mime_part (attachment);
2621
2622 /* Decode the MIME part to an in-memory buffer. We have to do
2623 * this because CamelStream is synchronous-only, and using threads
2624 * is dangerous because CamelDataWrapper is not reentrant. */
2625 buffer = g_byte_array_new ();
2626 stream = camel_stream_mem_new ();
2627 camel_stream_mem_set_byte_array (CAMEL_STREAM_MEM (stream), buffer);
2628 wrapper = camel_medium_get_content (CAMEL_MEDIUM (mime_part));
2629 camel_data_wrapper_decode_to_stream_sync (wrapper, stream, NULL, NULL);
2630 g_object_unref (stream);
2631
2632 /* Load the buffer into a GMemoryInputStream.
2633 * But watch out for zero length MIME parts. */
2634 input_stream = g_memory_input_stream_new ();
2635 if (buffer->len > 0)
2636 g_memory_input_stream_add_data (
2637 G_MEMORY_INPUT_STREAM (input_stream),
2638 buffer->data, (gssize) buffer->len,
2639 (GDestroyNotify) g_free);
2640 save_context->input_stream = input_stream;
2641 save_context->total_num_bytes = (goffset) buffer->len;
2642 g_byte_array_free (buffer, FALSE);
2643
2644 g_input_stream_read_async (
2645 input_stream,
2646 save_context->buffer,
2647 sizeof (save_context->buffer),
2648 G_PRIORITY_DEFAULT, cancellable,
2649 (GAsyncReadyCallback) attachment_save_read_cb,
2650 save_context);
2651 }
2652
2653 static void
2654 attachment_save_create_cb (GFile *destination,
2655 GAsyncResult *result,
2656 SaveContext *save_context)
2657 {
2658 EAttachment *attachment;
2659 GCancellable *cancellable;
2660 GFileOutputStream *output_stream;
2661 GError *error = NULL;
2662
2663 /* Output stream might be NULL, so don't use cast macro. */
2664 output_stream = g_file_create_finish (destination, result, &error);
2665 save_context->output_stream = (GOutputStream *) output_stream;
2666
2667 attachment = save_context->attachment;
2668 cancellable = attachment->priv->cancellable;
2669
2670 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) {
2671 destination = attachment_save_new_candidate (save_context);
2672
2673 g_file_create_async (
2674 destination, G_FILE_CREATE_NONE,
2675 G_PRIORITY_DEFAULT, cancellable,
2676 (GAsyncReadyCallback) attachment_save_create_cb,
2677 save_context);
2678
2679 g_object_unref (destination);
2680 g_error_free (error);
2681 return;
2682 }
2683
2684 if (attachment_save_check_for_error (save_context, error))
2685 return;
2686
2687 save_context->destination = g_object_ref (destination);
2688 attachment_save_got_output_stream (save_context);
2689 }
2690
2691 static void
2692 attachment_save_replace_cb (GFile *destination,
2693 GAsyncResult *result,
2694 SaveContext *save_context)
2695 {
2696 GFileOutputStream *output_stream;
2697 GError *error = NULL;
2698
2699 /* Output stream might be NULL, so don't use cast macro. */
2700 output_stream = g_file_replace_finish (destination, result, &error);
2701 save_context->output_stream = (GOutputStream *) output_stream;
2702
2703 if (attachment_save_check_for_error (save_context, error))
2704 return;
2705
2706 save_context->destination = g_object_ref (destination);
2707 attachment_save_got_output_stream (save_context);
2708 }
2709
2710 static void
2711 attachment_save_query_info_cb (GFile *destination,
2712 GAsyncResult *result,
2713 SaveContext *save_context)
2714 {
2715 EAttachment *attachment;
2716 GCancellable *cancellable;
2717 GFileInfo *file_info;
2718 GFileType file_type;
2719 GError *error = NULL;
2720
2721 attachment = save_context->attachment;
2722 cancellable = attachment->priv->cancellable;
2723
2724 file_info = g_file_query_info_finish (destination, result, &error);
2725
2726 /* G_IO_ERROR_NOT_FOUND just means we're creating a new file. */
2727 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
2728 g_error_free (error);
2729 goto replace;
2730 }
2731
2732 if (attachment_save_check_for_error (save_context, error))
2733 return;
2734
2735 file_type = g_file_info_get_file_type (file_info);
2736 g_object_unref (file_info);
2737
2738 if (file_type == G_FILE_TYPE_DIRECTORY) {
2739 save_context->directory = g_object_ref (destination);
2740 destination = attachment_save_new_candidate (save_context);
2741
2742 g_file_create_async (
2743 destination, G_FILE_CREATE_NONE,
2744 G_PRIORITY_DEFAULT, cancellable,
2745 (GAsyncReadyCallback) attachment_save_create_cb,
2746 save_context);
2747
2748 g_object_unref (destination);
2749
2750 return;
2751 }
2752
2753 replace:
2754 g_file_replace_async (
2755 destination, NULL, FALSE,
2756 G_FILE_CREATE_REPLACE_DESTINATION,
2757 G_PRIORITY_DEFAULT, cancellable,
2758 (GAsyncReadyCallback) attachment_save_replace_cb,
2759 save_context);
2760 }
2761
2762 void
2763 e_attachment_save_async (EAttachment *attachment,
2764 GFile *destination,
2765 GAsyncReadyCallback callback,
2766 gpointer user_data)
2767 {
2768 SaveContext *save_context;
2769 GCancellable *cancellable;
2770
2771 g_return_if_fail (E_IS_ATTACHMENT (attachment));
2772 g_return_if_fail (G_IS_FILE (destination));
2773
2774 if (e_attachment_get_loading (attachment)) {
2775 g_simple_async_report_error_in_idle (
2776 G_OBJECT (attachment), callback, user_data,
2777 G_IO_ERROR, G_IO_ERROR_BUSY,
2778 _("A load operation is already in progress"));
2779 return;
2780 }
2781
2782 if (e_attachment_get_saving (attachment)) {
2783 g_simple_async_report_error_in_idle (
2784 G_OBJECT (attachment), callback, user_data,
2785 G_IO_ERROR, G_IO_ERROR_BUSY,
2786 _("A save operation is already in progress"));
2787 return;
2788 }
2789
2790 if (e_attachment_get_mime_part (attachment) == NULL) {
2791 g_simple_async_report_error_in_idle (
2792 G_OBJECT (attachment), callback, user_data,
2793 G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
2794 _("Attachment contents not loaded"));
2795 return;
2796 }
2797
2798 save_context = attachment_save_context_new (
2799 attachment, callback, user_data);
2800
2801 cancellable = attachment->priv->cancellable;
2802 g_cancellable_reset (cancellable);
2803
2804 /* First we need to know if destination is a directory. */
2805 g_file_query_info_async (
2806 destination, G_FILE_ATTRIBUTE_STANDARD_TYPE,
2807 G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT,
2808 cancellable, (GAsyncReadyCallback)
2809 attachment_save_query_info_cb, save_context);
2810 }
2811
2812 GFile *
2813 e_attachment_save_finish (EAttachment *attachment,
2814 GAsyncResult *result,
2815 GError **error)
2816 {
2817 GSimpleAsyncResult *simple;
2818 GFile *destination;
2819
2820 g_return_val_if_fail (E_IS_ATTACHMENT (attachment), NULL);
2821 g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), NULL);
2822
2823 simple = G_SIMPLE_ASYNC_RESULT (result);
2824 destination = g_simple_async_result_get_op_res_gpointer (simple);
2825 if (destination != NULL)
2826 g_object_ref (destination);
2827 g_simple_async_result_propagate_error (simple, error);
2828
2829 attachment_set_saving (attachment, FALSE);
2830
2831 return destination;
2832 }
2833
2834 void
2835 e_attachment_save_handle_error (EAttachment *attachment,
2836 GAsyncResult *result,
2837 GtkWindow *parent)
2838 {
2839 GFile *file;
2840 GFileInfo *file_info;
2841 GtkWidget *dialog;
2842 const gchar *display_name;
2843 const gchar *primary_text;
2844 GError *error = NULL;
2845
2846 g_return_if_fail (E_IS_ATTACHMENT (attachment));
2847 g_return_if_fail (G_IS_ASYNC_RESULT (result));
2848 g_return_if_fail (GTK_IS_WINDOW (parent));
2849
2850 file = e_attachment_save_finish (attachment, result, &error);
2851
2852 if (file != NULL) {
2853 g_object_unref (file);
2854 return;
2855 }
2856
2857 /* Ignore cancellations. */
2858 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
2859 return;
2860
2861 file_info = e_attachment_get_file_info (attachment);
2862
2863 if (file_info != NULL)
2864 display_name = g_file_info_get_display_name (file_info);
2865 else
2866 display_name = NULL;
2867
2868 if (display_name != NULL)
2869 primary_text = g_strdup_printf (
2870 _("Could not save '%s'"), display_name);
2871 else
2872 primary_text = g_strdup_printf (
2873 _("Could not save the attachment"));
2874
2875 dialog = gtk_message_dialog_new_with_markup (
2876 parent, GTK_DIALOG_DESTROY_WITH_PARENT,
2877 GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
2878 "<big><b>%s</b></big>", primary_text);
2879
2880 gtk_message_dialog_format_secondary_text (
2881 GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
2882
2883 gtk_dialog_run (GTK_DIALOG (dialog));
2884
2885 gtk_widget_destroy (dialog);
2886 g_error_free (error);
2887 }
2888
2889 gboolean
2890 e_attachment_save (EAttachment *attachment,
2891 GFile *in_destination,
2892 GFile **out_destination,
2893 GError **error)
2894 {
2895 EAsyncClosure *closure;
2896 GAsyncResult *result;
2897
2898 g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE);
2899 g_return_val_if_fail (out_destination != NULL, FALSE);
2900
2901 closure = e_async_closure_new ();
2902
2903 e_attachment_save_async (attachment, in_destination, e_async_closure_callback, closure);
2904
2905 result = e_async_closure_wait (closure);
2906
2907 *out_destination = e_attachment_save_finish (attachment, result, error);
2908
2909 e_async_closure_free (closure);
2910
2911 return *out_destination != NULL;
2912 }